From bf63ccdc2f616255b0166ae3057ca323122cd30d Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 21 Jul 2022 23:59:19 +0100 Subject: [PATCH 01/16] move dcr related capability to the dcr package --- dcr.go | 58 + dexclient.go | 2 +- go.mod | 7 + multiwallet.go | 173 ++- multiwallet_utils.go | 9 +- politeia_sync.go | 9 +- rescan.go | 53 +- sync.go | 493 -------- syncnotification.go | 1096 ++++++++--------- txandblocknotifications.go | 159 --- types.go | 8 +- utils.go | 33 +- vsp.go | 192 --- wallets.go | 46 +- .../dcr/account_mixer.go | 74 +- accounts.go => wallets/dcr/accounts.go | 24 +- address.go => wallets/dcr/address.go | 17 +- consensus.go => wallets/dcr/consensus.go | 6 +- decodetx.go => wallets/dcr/decodetx.go | 2 +- wallets/dcr/errors.go | 61 + wallets/dcr/log.go | 176 +++ message.go => wallets/dcr/message.go | 16 +- wallets/dcr/multiwallet_config.go | 152 +++ wallets/dcr/sync.go | 489 ++++++++ wallets/dcr/syncnotification.go | 682 ++++++++++ ticket.go => wallets/dcr/ticket.go | 334 ++--- .../dcr/transactions.go | 82 +- wallets/dcr/txandblocknotifications.go | 159 +++ txauthor.go => wallets/dcr/txauthor.go | 50 +- txindex.go => wallets/dcr/txindex.go | 20 +- txparser.go => wallets/dcr/txparser.go | 6 +- wallets/dcr/types.go | 530 ++++++++ wallets/dcr/utils.go | 502 ++++++++ utxo.go => wallets/dcr/utxo.go | 4 +- wallets/dcr/vsp.go | 192 +++ wallet.go => wallets/dcr/wallet.go | 88 +- .../dcr/wallet_config.go | 2 +- {walletdata => wallets/dcr/walletdata}/db.go | 0 .../dcr/walletdata}/filter.go | 0 .../dcr/walletdata}/read.go | 0 .../dcr/walletdata}/save.go | 0 wordlist.go => wallets/dcr/wordlist.go | 2 +- 42 files changed, 4074 insertions(+), 1934 deletions(-) create mode 100644 dcr.go delete mode 100644 sync.go delete mode 100644 txandblocknotifications.go delete mode 100644 vsp.go rename account_mixer.go => wallets/dcr/account_mixer.go (79%) rename accounts.go => wallets/dcr/accounts.go (90%) rename address.go => wallets/dcr/address.go (86%) rename consensus.go => wallets/dcr/consensus.go (98%) rename decodetx.go => wallets/dcr/decodetx.go (99%) create mode 100644 wallets/dcr/errors.go create mode 100644 wallets/dcr/log.go rename message.go => wallets/dcr/message.go (73%) create mode 100644 wallets/dcr/multiwallet_config.go create mode 100644 wallets/dcr/sync.go create mode 100644 wallets/dcr/syncnotification.go rename ticket.go => wallets/dcr/ticket.go (70%) rename transactions.go => wallets/dcr/transactions.go (81%) create mode 100644 wallets/dcr/txandblocknotifications.go rename txauthor.go => wallets/dcr/txauthor.go (93%) rename txindex.go => wallets/dcr/txindex.go (79%) rename txparser.go => wallets/dcr/txparser.go (94%) create mode 100644 wallets/dcr/types.go create mode 100644 wallets/dcr/utils.go rename utxo.go => wallets/dcr/utxo.go (98%) create mode 100644 wallets/dcr/vsp.go rename wallet.go => wallets/dcr/wallet.go (76%) rename wallet_config.go => wallets/dcr/wallet_config.go (99%) rename {walletdata => wallets/dcr/walletdata}/db.go (100%) rename {walletdata => wallets/dcr/walletdata}/filter.go (100%) rename {walletdata => wallets/dcr/walletdata}/read.go (100%) rename {walletdata => wallets/dcr/walletdata}/save.go (100%) rename wordlist.go => wallets/dcr/wordlist.go (99%) diff --git a/dcr.go b/dcr.go new file mode 100644 index 000000000..3ea1b5ba1 --- /dev/null +++ b/dcr.go @@ -0,0 +1,58 @@ +package dcrlibwallet + +import ( + // "context" + // "fmt" + "os" + "path/filepath" + + "decred.org/dcrwallet/v2/errors" + + "github.com/asdine/storm" + // "github.com/asdine/storm/q" + + bolt "go.etcd.io/bbolt" + + "github.com/planetdecred/dcrlibwallet/wallets/dcr" +) + +func initializeDCRWallet(rootDir, dbDriver, netType string) (*storm.DB, string, error) { + var mwDB *storm.DB + + rootDir = filepath.Join(rootDir, netType, "dcr") + err := os.MkdirAll(rootDir, os.ModePerm) + if err != nil { + return mwDB, "", errors.Errorf("failed to create dcr rootDir: %v", err) + } + + err = initLogRotator(filepath.Join(rootDir, logFileName)) + if err != nil { + return mwDB, "", errors.Errorf("failed to init dcr logRotator: %v", err.Error()) + } + + mwDB, err = storm.Open(filepath.Join(rootDir, walletsDbName)) + if err != nil { + log.Errorf("Error opening dcr wallets database: %s", err.Error()) + if err == bolt.ErrTimeout { + // timeout error occurs if storm fails to acquire a lock on the database file + return mwDB, "", errors.E(ErrWalletDatabaseInUse) + } + return mwDB, "", errors.Errorf("error opening dcr wallets database: %s", err.Error()) + } + + // init database for saving/reading wallet objects + err = mwDB.Init(&dcr.Wallet{}) + if err != nil { + log.Errorf("Error initializing wallets database: %s", err.Error()) + return mwDB, "", err + } + + // init database for saving/reading proposal objects + err = mwDB.Init(&dcr.Proposal{}) + if err != nil { + log.Errorf("Error initializing wallets database: %s", err.Error()) + return mwDB, "", err + } + + return mwDB, rootDir, nil +} diff --git a/dexclient.go b/dexclient.go index 087679138..5d9079e90 100644 --- a/dexclient.go +++ b/dexclient.go @@ -105,7 +105,7 @@ func (mw *MultiWallet) prepareDexSupportForDcrWalletLibrary() error { return nil, fmt.Errorf("account error: %v", err) } - walletDesc := fmt.Sprintf("%q in %s", wallet.Name, wallet.dataDir) + walletDesc := fmt.Sprintf("%q in %s", wallet.Name, wallet.DataDir) return dexdcr.NewSpvWallet(wallet.Internal(), walletDesc, chainParams, logger.SubLogger("DLWL")), nil } diff --git a/go.mod b/go.mod index 8dcbb46bc..b352b444d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,12 @@ require ( decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 github.com/DataDog/zstd v1.4.8 // indirect github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 + github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e // indirect + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect + github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // indirect + github.com/btcsuite/btcwallet v0.12.0 // indirect + github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect + github.com/btcsuite/btcwallet/wtxmgr v1.3.0 // indirect github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a // indirect github.com/dchest/siphash v1.2.3 // indirect github.com/decred/base58 v1.0.4 // indirect @@ -27,6 +33,7 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/jrick/logrotate v1.0.0 github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 + github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 // indirect github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d diff --git a/multiwallet.go b/multiwallet.go index efbaa26fd..f9695bd5f 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -17,8 +17,10 @@ import ( "github.com/asdine/storm/q" "github.com/decred/dcrd/chaincfg/v3" "github.com/planetdecred/dcrlibwallet/utils" - "github.com/planetdecred/dcrlibwallet/walletdata" - bolt "go.etcd.io/bbolt" + "github.com/planetdecred/dcrlibwallet/wallets/dcr/walletdata" + + "github.com/planetdecred/dcrlibwallet/wallets/dcr" + "golang.org/x/crypto/bcrypt" ) @@ -28,15 +30,16 @@ type MultiWallet struct { db *storm.DB chainParams *chaincfg.Params - wallets map[int]*Wallet - badWallets map[int]*Wallet - syncData *syncData + wallets map[int]*dcr.Wallet + badWallets map[int]*dcr.Wallet - notificationListenersMu sync.RWMutex + // syncData *dcr.SyncData + + // notificationListenersMu sync.RWMutex txAndBlockNotificationListeners map[string]TxAndBlockNotificationListener blocksRescanProgressListener BlocksRescanProgressListener - accountMixerNotificationListener map[string]AccountMixerNotificationListener + // accountMixerNotificationListener map[string]AccountMixerNotificationListener shuttingDown chan bool cancelFuncs []context.CancelFunc @@ -56,55 +59,29 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall return nil, err } - rootDir = filepath.Join(rootDir, netType) - err = os.MkdirAll(rootDir, os.ModePerm) + dcrDB, dcrRootDir, err := initializeDCRWallet(rootDir, dbDriver, netType) if err != nil { - return nil, errors.Errorf("failed to create rootDir: %v", err) - } - - err = initLogRotator(filepath.Join(rootDir, logFileName)) - if err != nil { - return nil, errors.Errorf("failed to init logRotator: %v", err.Error()) - } - - mwDB, err := storm.Open(filepath.Join(rootDir, walletsDbName)) - if err != nil { - log.Errorf("Error opening wallets database: %s", err.Error()) - if err == bolt.ErrTimeout { - // timeout error occurs if storm fails to acquire a lock on the database file - return nil, errors.E(ErrWalletDatabaseInUse) - } - return nil, errors.Errorf("error opening wallets database: %s", err.Error()) - } - - // init database for saving/reading wallet objects - err = mwDB.Init(&Wallet{}) - if err != nil { - log.Errorf("Error initializing wallets database: %s", err.Error()) - return nil, err - } - - // init database for saving/reading proposal objects - err = mwDB.Init(&Proposal{}) - if err != nil { - log.Errorf("Error initializing wallets database: %s", err.Error()) - return nil, err + log.Errorf("error initializing DCRWallet: %s", err.Error()) + return nil, errors.Errorf("error initializing DCRWallet: %s", err.Error()) } mw := &MultiWallet{ dbDriver: dbDriver, - rootDir: rootDir, - db: mwDB, + rootDir: dcrRootDir, + db: dcrDB, chainParams: chainParams, - wallets: make(map[int]*Wallet), - badWallets: make(map[int]*Wallet), - syncData: &syncData{ - syncProgressListeners: make(map[string]SyncProgressListener), - }, + wallets: make(map[int]*dcr.Wallet), + badWallets: make(map[int]*dcr.Wallet), + // syncData: &dcr.SyncData{ + // SyncProgressListeners: make(map[string]dcr.SyncProgressListener), + // }, txAndBlockNotificationListeners: make(map[string]TxAndBlockNotificationListener), - accountMixerNotificationListener: make(map[string]AccountMixerNotificationListener), } + // syncData: &dcr.SyncData{ + // SyncProgressListeners: make(map[string]dcr.SyncProgressListener), + // }, + mw.Politeia, err = newPoliteia(mw, politeiaHost) if err != nil { return nil, err @@ -112,7 +89,7 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall // read saved wallets info from db and initialize wallets query := mw.db.Select(q.True()).OrderBy("ID") - var wallets []*Wallet + var wallets []*dcr.Wallet err = query.Find(&wallets) if err != nil && err != storm.ErrNotFound { return nil, err @@ -120,8 +97,8 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall // prepare the wallets loaded from db for use for _, wallet := range wallets { - err = wallet.prepare(rootDir, chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) - if err == nil && !WalletExistsAt(wallet.dataDir) { + err = wallet.Prepare(rootDir, chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) + if err == nil && !WalletExistsAt(wallet.DataDir) { err = fmt.Errorf("missing wallet database file") } if err != nil { @@ -153,7 +130,9 @@ func (mw *MultiWallet) Shutdown() { mw.shuttingDown <- true mw.CancelRescan() - mw.CancelSync() + for _, wallet := range mw.wallets { + wallet.CancelSync(mw.wallets) + } for _, wallet := range mw.wallets { wallet.Shutdown() @@ -266,9 +245,9 @@ func (mw *MultiWallet) StartupSecurityType() int32 { } func (mw *MultiWallet) OpenWallets(startupPassphrase []byte) error { - if mw.IsSyncing() { - return errors.New(ErrSyncAlreadyInProgress) - } + // if mw.IsSyncing() { + // return errors.New(ErrSyncAlreadyInProgress) + // } err := mw.VerifyStartupPassphrase(startupPassphrase) if err != nil { @@ -276,7 +255,7 @@ func (mw *MultiWallet) OpenWallets(startupPassphrase []byte) error { } for _, wallet := range mw.wallets { - err = wallet.openWallet() + err = wallet.OpenWallet() if err != nil { return err } @@ -299,24 +278,24 @@ func (mw *MultiWallet) AllWalletsAreWatchOnly() (bool, error) { return true, nil } -func (mw *MultiWallet) CreateWatchOnlyWallet(walletName, extendedPublicKey string) (*Wallet, error) { - wallet := &Wallet{ +func (mw *MultiWallet) CreateWatchOnlyWallet(walletName, extendedPublicKey string) (*dcr.Wallet, error) { + wallet := &dcr.Wallet{ Name: walletName, IsRestored: true, HasDiscoveredAccounts: true, } return mw.saveNewWallet(wallet, func() error { - err := wallet.prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) + err := wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) if err != nil { return err } - return wallet.createWatchingOnlyWallet(extendedPublicKey) + return wallet.CreateWatchingOnlyWallet(extendedPublicKey) }) } -func (mw *MultiWallet) CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32) (*Wallet, error) { +func (mw *MultiWallet) CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32) (*dcr.Wallet, error) { seed, err := GenerateSeed() if err != nil { return nil, err @@ -326,7 +305,7 @@ func (mw *MultiWallet) CreateNewWallet(walletName, privatePassphrase string, pri if err != nil { return nil, err } - wallet := &Wallet{ + wallet := &dcr.Wallet{ Name: walletName, CreatedAt: time.Now(), EncryptedSeed: encryptedSeed, @@ -335,18 +314,18 @@ func (mw *MultiWallet) CreateNewWallet(walletName, privatePassphrase string, pri } return mw.saveNewWallet(wallet, func() error { - err := wallet.prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) + err := wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) if err != nil { return err } - return wallet.createWallet(privatePassphrase, seed) + return wallet.CreateWallet(privatePassphrase, seed) }) } -func (mw *MultiWallet) RestoreWallet(walletName, seedMnemonic, privatePassphrase string, privatePassphraseType int32) (*Wallet, error) { +func (mw *MultiWallet) RestoreWallet(walletName, seedMnemonic, privatePassphrase string, privatePassphraseType int32) (*dcr.Wallet, error) { - wallet := &Wallet{ + wallet := &dcr.Wallet{ Name: walletName, PrivatePassphraseType: privatePassphraseType, IsRestored: true, @@ -354,16 +333,16 @@ func (mw *MultiWallet) RestoreWallet(walletName, seedMnemonic, privatePassphrase } return mw.saveNewWallet(wallet, func() error { - err := wallet.prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) + err := wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) if err != nil { return err } - return wallet.createWallet(privatePassphrase, seedMnemonic) + return wallet.CreateWallet(privatePassphrase, seedMnemonic) }) } -func (mw *MultiWallet) LinkExistingWallet(walletName, walletDataDir, originalPubPass string, privatePassphraseType int32) (*Wallet, error) { +func (mw *MultiWallet) LinkExistingWallet(walletName, walletDataDir, originalPubPass string, privatePassphraseType int32) (*dcr.Wallet, error) { // check if `walletDataDir` contains wallet.db if !WalletExistsAt(walletDataDir) { return nil, errors.New(ErrNotExist) @@ -376,7 +355,7 @@ func (mw *MultiWallet) LinkExistingWallet(walletName, walletDataDir, originalPub return nil, err } - wallet := &Wallet{ + wallet := &dcr.Wallet{ Name: walletName, PrivatePassphraseType: privatePassphraseType, IsRestored: true, @@ -386,36 +365,36 @@ func (mw *MultiWallet) LinkExistingWallet(walletName, walletDataDir, originalPub return mw.saveNewWallet(wallet, func() error { // move wallet.db and tx.db files to newly created dir for the wallet currentWalletDbFilePath := filepath.Join(walletDataDir, walletDbName) - newWalletDbFilePath := filepath.Join(wallet.dataDir, walletDbName) + newWalletDbFilePath := filepath.Join(wallet.DataDir, walletDbName) if err := moveFile(currentWalletDbFilePath, newWalletDbFilePath); err != nil { return err } currentTxDbFilePath := filepath.Join(walletDataDir, walletdata.OldDbName) - newTxDbFilePath := filepath.Join(wallet.dataDir, walletdata.DbName) + newTxDbFilePath := filepath.Join(wallet.DataDir, walletdata.DbName) if err := moveFile(currentTxDbFilePath, newTxDbFilePath); err != nil { return err } // prepare the wallet for use and open it err := (func() error { - err := wallet.prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) + err := wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) if err != nil { return err } if originalPubPass == "" || originalPubPass == w.InsecurePubPassphrase { - return wallet.openWallet() + return wallet.OpenWallet() } - err = mw.loadWalletTemporarily(ctx, wallet.dataDir, originalPubPass, func(tempWallet *w.Wallet) error { + err = mw.loadWalletTemporarily(ctx, wallet.DataDir, originalPubPass, func(tempWallet *w.Wallet) error { return tempWallet.ChangePublicPassphrase(ctx, []byte(originalPubPass), []byte(w.InsecurePubPassphrase)) }) if err != nil { return err } - return wallet.openWallet() + return wallet.OpenWallet() })() // restore db files to their original location if there was an error @@ -441,7 +420,7 @@ func (mw *MultiWallet) LinkExistingWallet(walletName, walletDataDir, originalPub // // IFF all the above operations succeed, the wallet info will be persisted to db // and the wallet will be added to `mw.wallets`. -func (mw *MultiWallet) saveNewWallet(wallet *Wallet, setupWallet func() error) (*Wallet, error) { +func (mw *MultiWallet) saveNewWallet(wallet *dcr.Wallet, setupWallet func() error) (*dcr.Wallet, error) { exists, err := mw.WalletNameExists(wallet.Name) if err != nil { return nil, err @@ -449,10 +428,10 @@ func (mw *MultiWallet) saveNewWallet(wallet *Wallet, setupWallet func() error) ( return nil, errors.New(ErrExist) } - if mw.IsConnectedToDecredNetwork() { - mw.CancelSync() - defer mw.SpvSync() - } + // if mw.IsConnectedToDecredNetwork() { + // mw.CancelSync() + // defer mw.SpvSync() + // } // Perform database save operations in batch transaction // for automatic rollback if error occurs at any point. err = mw.batchDbTransaction(func(db storm.Node) error { @@ -481,7 +460,7 @@ func (mw *MultiWallet) saveNewWallet(wallet *Wallet, setupWallet func() error) ( if wallet.Name == "" { wallet.Name = "wallet-" + strconv.Itoa(wallet.ID) // wallet-# } - wallet.dataDir = walletDataDir + wallet.DataDir = walletDataDir wallet.DbDriver = mw.dbDriver err = db.Save(wallet) // update database with complete wallet information @@ -528,16 +507,16 @@ func (mw *MultiWallet) DeleteWallet(walletID int, privPass []byte) error { return errors.New(ErrNotExist) } - if mw.IsConnectedToDecredNetwork() { - mw.CancelSync() - defer func() { - if mw.OpenedWalletsCount() > 0 { - mw.SpvSync() - } - }() - } + // if mw.IsConnectedToDecredNetwork() { + // mw.CancelSync() + // defer func() { + // if mw.OpenedWalletsCount() > 0 { + // mw.SpvSync() + // } + // }() + // } - err := wallet.deleteWallet(privPass) + err := wallet.DeleteWallet(privPass) if err != nil { return translateError(err) } @@ -552,7 +531,7 @@ func (mw *MultiWallet) DeleteWallet(walletID int, privPass []byte) error { return nil } -func (mw *MultiWallet) BadWallets() map[int]*Wallet { +func (mw *MultiWallet) BadWallets() map[int]*dcr.Wallet { return mw.badWallets } @@ -569,13 +548,13 @@ func (mw *MultiWallet) DeleteBadWallet(walletID int) error { return translateError(err) } - os.RemoveAll(wallet.dataDir) + os.RemoveAll(wallet.DataDir) delete(mw.badWallets, walletID) return nil } -func (mw *MultiWallet) WalletWithID(walletID int) *Wallet { +func (mw *MultiWallet) WalletWithID(walletID int) *dcr.Wallet { if wallet, ok := mw.wallets[walletID]; ok { return wallet } @@ -640,7 +619,7 @@ func (mw *MultiWallet) OpenedWalletsCount() int32 { func (mw *MultiWallet) SyncedWalletsCount() int32 { var syncedWallets int32 for _, wallet := range mw.wallets { - if wallet.WalletOpened() && wallet.synced { + if wallet.WalletOpened() && wallet.Synced { syncedWallets++ } } @@ -653,7 +632,7 @@ func (mw *MultiWallet) WalletNameExists(walletName string) (bool, error) { return false, errors.E(ErrReservedWalletName) } - err := mw.db.One("Name", walletName, &Wallet{}) + err := mw.db.One("Name", walletName, &dcr.Wallet{}) if err == nil { return true, nil } else if err != storm.ErrNotFound { @@ -696,7 +675,7 @@ func (mw *MultiWallet) ChangePrivatePassphraseForWallet(walletID int, oldPrivate } } - err := wallet.changePrivatePassphrase(oldPrivatePassphrase, newPrivatePassphrase) + err := wallet.ChangePrivatePassphrase(oldPrivatePassphrase, newPrivatePassphrase) if err != nil { return translateError(err) } @@ -707,7 +686,7 @@ func (mw *MultiWallet) ChangePrivatePassphraseForWallet(walletID int, oldPrivate if err != nil { log.Errorf("error saving wallet-[%d] to database after passphrase change: %v", wallet.ID, err) - err2 := wallet.changePrivatePassphrase(newPrivatePassphrase, oldPrivatePassphrase) + err2 := wallet.ChangePrivatePassphrase(newPrivatePassphrase, oldPrivatePassphrase) if err2 != nil { log.Errorf("error undoing wallet passphrase change: %v", err2) log.Errorf("error wallet passphrase was changed but passphrase type and newly encrypted seed could not be saved: %v", err) diff --git a/multiwallet_utils.go b/multiwallet_utils.go index 9208c2be4..788f281e0 100644 --- a/multiwallet_utils.go +++ b/multiwallet_utils.go @@ -16,6 +16,9 @@ import ( "github.com/kevinburke/nacl" "github.com/kevinburke/nacl/secretbox" "golang.org/x/crypto/scrypt" + + "github.com/planetdecred/dcrlibwallet/wallets/dcr" + ) const ( @@ -143,7 +146,7 @@ func (mw *MultiWallet) WalletWithXPub(xpub string) (int, error) { return -1, err } for _, account := range accounts.Accounts { - if account.AccountNumber == ImportedAccountNumber { + if account.AccountNumber == dcr.ImportedAccountNumber { continue } acctXPub, err := w.Internal().AccountXpub(ctx, account.AccountNumber) @@ -166,7 +169,7 @@ func (mw *MultiWallet) WalletWithSeed(seedMnemonic string) (int, error) { return -1, errors.New(ErrEmptySeed) } - newSeedLegacyXPUb, newSeedSLIP0044XPUb, err := deriveBIP44AccountXPubs(seedMnemonic, DefaultAccountNum, mw.chainParams) + newSeedLegacyXPUb, newSeedSLIP0044XPUb, err := deriveBIP44AccountXPubs(seedMnemonic, dcr.DefaultAccountNum, mw.chainParams) if err != nil { return -1, err } @@ -180,7 +183,7 @@ func (mw *MultiWallet) WalletWithSeed(seedMnemonic string) (int, error) { // incorrect result from the check below. But this would return true // if the watch-only wallet was created using the xpub of the default // account of the provided seed. - usesSameSeed, err := wallet.AccountXPubMatches(DefaultAccountNum, newSeedLegacyXPUb, newSeedSLIP0044XPUb) + usesSameSeed, err := wallet.AccountXPubMatches(dcr.DefaultAccountNum, newSeedLegacyXPUb, newSeedSLIP0044XPUb) if err != nil { return -1, err } diff --git a/politeia_sync.go b/politeia_sync.go index 8832111c1..eeefd20f8 100644 --- a/politeia_sync.go +++ b/politeia_sync.go @@ -12,6 +12,9 @@ import ( "github.com/asdine/storm" tkv1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1" www "github.com/decred/politeia/politeiawww/api/www/v1" + + "github.com/planetdecred/dcrlibwallet/wallets/dcr" + ) const ( @@ -411,7 +414,7 @@ func (p *Politeia) ProposalVoteDetailsRaw(walletID int, token string) (*Proposal return nil, err } - ticketHashes, addresses, err := wal.Internal().CommittedTickets(wal.shutdownContext(), hashes) + ticketHashes, addresses, err := wal.Internal().CommittedTickets(wal.ShutdownContext(), hashes) if err != nil { return nil, err } @@ -437,7 +440,7 @@ func (p *Politeia) ProposalVoteDetailsRaw(walletID int, token string) (*Proposal } // filter out tickets controlled by imported accounts - if ainfo.AccountNumber == ImportedAccountNumber { + if ainfo.AccountNumber == dcr.ImportedAccountNumber { continue } @@ -522,7 +525,7 @@ func (p *Politeia) CastVotes(walletID int, eligibleTickets []*ProposalVote, toke msg := token + ticket.Hash + voteBitHex - signature, err := wal.signMessage(ticket.Address, msg) + signature, err := wal.SignMessageDirect(ticket.Address, msg) if err != nil { return err } diff --git a/rescan.go b/rescan.go index d6babc19f..8989d7127 100644 --- a/rescan.go +++ b/rescan.go @@ -25,24 +25,25 @@ func (mw *MultiWallet) RescanBlocksFromHeight(walletID int, startHeight int32) e return errors.E(ErrNotConnected) } - if mw.IsRescanning() || !mw.IsSynced() { - return errors.E(ErrInvalid) - } + // if mw.IsRescanning() || !mw.IsSynced() { + // return errors.E(ErrInvalid) + // } go func() { defer func() { - mw.syncData.mu.Lock() - mw.syncData.rescanning = false - mw.syncData.cancelRescan = nil - mw.syncData.mu.Unlock() + // mw.syncData.mu.Lock() + // mw.syncData.rescanning = false + // mw.syncData.cancelRescan = nil + // mw.syncData.mu.Unlock() }() - ctx, cancel := wallet.shutdownContextWithCancel() + ctx, _ := wallet.ShutdownContextWithCancel() + // ctx, cancel := wallet.ShutdownContextWithCancel() //undo this lateer - mw.syncData.mu.Lock() - mw.syncData.rescanning = true - mw.syncData.cancelRescan = cancel - mw.syncData.mu.Unlock() + // mw.syncData.mu.Lock() + // mw.syncData.rescanning = true + // mw.syncData.cancelRescan = cancel + // mw.syncData.mu.Unlock() if mw.blocksRescanProgressListener != nil { mw.blocksRescanProgressListener.OnBlocksRescanStarted(walletID) @@ -104,9 +105,9 @@ func (mw *MultiWallet) RescanBlocksFromHeight(walletID int, startHeight int32) e var err error if startHeight == 0 { - err = wallet.reindexTransactions() + err = wallet.ReindexTransactions() } else { - err = wallet.walletDataDB.SaveLastIndexPoint(startHeight) + err = wallet.WalletDataDB.SaveLastIndexPoint(startHeight) if err != nil { if mw.blocksRescanProgressListener != nil { mw.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, err) @@ -125,20 +126,22 @@ func (mw *MultiWallet) RescanBlocksFromHeight(walletID int, startHeight int32) e } func (mw *MultiWallet) CancelRescan() { - mw.syncData.mu.Lock() - defer mw.syncData.mu.Unlock() - if mw.syncData.cancelRescan != nil { - mw.syncData.cancelRescan() - mw.syncData.cancelRescan = nil - - log.Info("Rescan canceled.") - } + // mw.syncData.mu.Lock() + // defer mw.syncData.mu.Unlock() + // if mw.syncData.cancelRescan != nil { + // mw.syncData.cancelRescan() + // mw.syncData.cancelRescan = nil + + // log.Info("Rescan canceled.") + // } } func (mw *MultiWallet) IsRescanning() bool { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - return mw.syncData.rescanning + // mw.syncData.mu.RLock() + // defer mw.syncData.mu.RUnlock() + // return mw.syncData.rescanning + + return true } func (mw *MultiWallet) SetBlocksRescanProgressListener(blocksRescanProgressListener BlocksRescanProgressListener) { diff --git a/sync.go b/sync.go deleted file mode 100644 index 18cfcd09a..000000000 --- a/sync.go +++ /dev/null @@ -1,493 +0,0 @@ -package dcrlibwallet - -import ( - "context" - "encoding/json" - "fmt" - "net" - "sort" - "strings" - "sync" - - "decred.org/dcrwallet/v2/errors" - "decred.org/dcrwallet/v2/p2p" - w "decred.org/dcrwallet/v2/wallet" - "github.com/decred/dcrd/addrmgr/v2" - "github.com/planetdecred/dcrlibwallet/spv" -) - -// reading/writing of properties of this struct are protected by mutex.x -type syncData struct { - mu sync.RWMutex - - syncProgressListeners map[string]SyncProgressListener - showLogs bool - - synced bool - syncing bool - cancelSync context.CancelFunc - cancelRescan context.CancelFunc - syncCanceled chan struct{} - - // Flag to notify syncCanceled callback if the sync was canceled so as to be restarted. - restartSyncRequested bool - - rescanning bool - connectedPeers int32 - - *activeSyncData -} - -// reading/writing of properties of this struct are protected by syncData.mu. -type activeSyncData struct { - syncer *spv.Syncer - - syncStage int32 - - cfiltersFetchProgress CFiltersFetchProgressReport - headersFetchProgress HeadersFetchProgressReport - addressDiscoveryProgress AddressDiscoveryProgressReport - headersRescanProgress HeadersRescanProgressReport - - addressDiscoveryCompletedOrCanceled chan bool - - rescanStartTime int64 - - totalInactiveSeconds int64 -} - -const ( - InvalidSyncStage = -1 - CFiltersFetchSyncStage = 0 - HeadersFetchSyncStage = 1 - AddressDiscoverySyncStage = 2 - HeadersRescanSyncStage = 3 -) - -func (mw *MultiWallet) initActiveSyncData() { - - cfiltersFetchProgress := CFiltersFetchProgressReport{ - GeneralSyncProgress: &GeneralSyncProgress{}, - beginFetchCFiltersTimeStamp: 0, - startCFiltersHeight: -1, - cfiltersFetchTimeSpent: 0, - totalFetchedCFiltersCount: 0, - } - - headersFetchProgress := HeadersFetchProgressReport{ - GeneralSyncProgress: &GeneralSyncProgress{}, - beginFetchTimeStamp: -1, - headersFetchTimeSpent: -1, - totalFetchedHeadersCount: 0, - } - - addressDiscoveryProgress := AddressDiscoveryProgressReport{ - GeneralSyncProgress: &GeneralSyncProgress{}, - addressDiscoveryStartTime: -1, - totalDiscoveryTimeSpent: -1, - } - - headersRescanProgress := HeadersRescanProgressReport{} - headersRescanProgress.GeneralSyncProgress = &GeneralSyncProgress{} - - mw.syncData.mu.Lock() - mw.syncData.activeSyncData = &activeSyncData{ - syncStage: InvalidSyncStage, - - cfiltersFetchProgress: cfiltersFetchProgress, - headersFetchProgress: headersFetchProgress, - addressDiscoveryProgress: addressDiscoveryProgress, - headersRescanProgress: headersRescanProgress, - } - mw.syncData.mu.Unlock() -} - -func (mw *MultiWallet) IsSyncProgressListenerRegisteredFor(uniqueIdentifier string) bool { - mw.syncData.mu.RLock() - _, exists := mw.syncData.syncProgressListeners[uniqueIdentifier] - mw.syncData.mu.RUnlock() - return exists -} - -func (mw *MultiWallet) AddSyncProgressListener(syncProgressListener SyncProgressListener, uniqueIdentifier string) error { - if mw.IsSyncProgressListenerRegisteredFor(uniqueIdentifier) { - return errors.New(ErrListenerAlreadyExist) - } - - mw.syncData.mu.Lock() - mw.syncData.syncProgressListeners[uniqueIdentifier] = syncProgressListener - mw.syncData.mu.Unlock() - - // If sync is already on, notify this newly added listener of the current progress report. - return mw.PublishLastSyncProgress(uniqueIdentifier) -} - -func (mw *MultiWallet) RemoveSyncProgressListener(uniqueIdentifier string) { - mw.syncData.mu.Lock() - delete(mw.syncData.syncProgressListeners, uniqueIdentifier) - mw.syncData.mu.Unlock() -} - -func (mw *MultiWallet) syncProgressListeners() []SyncProgressListener { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - - listeners := make([]SyncProgressListener, 0, len(mw.syncData.syncProgressListeners)) - for _, listener := range mw.syncData.syncProgressListeners { - listeners = append(listeners, listener) - } - - return listeners -} - -func (mw *MultiWallet) PublishLastSyncProgress(uniqueIdentifier string) error { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - - syncProgressListener, exists := mw.syncData.syncProgressListeners[uniqueIdentifier] - if !exists { - return errors.New(ErrInvalid) - } - - if mw.syncData.syncing && mw.syncData.activeSyncData != nil { - switch mw.syncData.activeSyncData.syncStage { - case HeadersFetchSyncStage: - syncProgressListener.OnHeadersFetchProgress(&mw.syncData.headersFetchProgress) - case AddressDiscoverySyncStage: - syncProgressListener.OnAddressDiscoveryProgress(&mw.syncData.addressDiscoveryProgress) - case HeadersRescanSyncStage: - syncProgressListener.OnHeadersRescanProgress(&mw.syncData.headersRescanProgress) - } - } - - return nil -} - -func (mw *MultiWallet) EnableSyncLogs() { - mw.syncData.mu.Lock() - mw.syncData.showLogs = true - mw.syncData.mu.Unlock() -} - -func (mw *MultiWallet) SyncInactiveForPeriod(totalInactiveSeconds int64) { - mw.syncData.mu.Lock() - defer mw.syncData.mu.Unlock() - - if !mw.syncData.syncing || mw.syncData.activeSyncData == nil { - log.Debug("Not accounting for inactive time, wallet is not syncing.") - return - } - - mw.syncData.totalInactiveSeconds += totalInactiveSeconds - if mw.syncData.connectedPeers == 0 { - // assume it would take another 60 seconds to reconnect to peers - mw.syncData.totalInactiveSeconds += 60 - } -} - -func (mw *MultiWallet) SpvSync() error { - // prevent an attempt to sync when the previous syncing has not been canceled - if mw.IsSyncing() || mw.IsSynced() { - return errors.New(ErrSyncAlreadyInProgress) - } - - addr := &net.TCPAddr{IP: net.ParseIP("::1"), Port: 0} - addrManager := addrmgr.New(mw.rootDir, net.LookupIP) // TODO: be mindful of tor - lp := p2p.NewLocalPeer(mw.chainParams, addr, addrManager) - - var validPeerAddresses []string - peerAddresses := mw.ReadStringConfigValueForKey(SpvPersistentPeerAddressesConfigKey) - if peerAddresses != "" { - addresses := strings.Split(peerAddresses, ";") - for _, address := range addresses { - peerAddress, err := NormalizeAddress(address, mw.chainParams.DefaultPort) - if err != nil { - log.Errorf("SPV peer address(%s) is invalid: %v", peerAddress, err) - } else { - validPeerAddresses = append(validPeerAddresses, peerAddress) - } - } - - if len(validPeerAddresses) == 0 { - return errors.New(ErrInvalidPeers) - } - } - - // init activeSyncData to be used to hold data used - // to calculate sync estimates only during sync - mw.initActiveSyncData() - - wallets := make(map[int]*w.Wallet) - for id, wallet := range mw.wallets { - wallets[id] = wallet.Internal() - wallet.waitingForHeaders = true - wallet.syncing = true - } - - syncer := spv.NewSyncer(wallets, lp) - syncer.SetNotifications(mw.spvSyncNotificationCallbacks()) - if len(validPeerAddresses) > 0 { - syncer.SetPersistentPeers(validPeerAddresses) - } - - ctx, cancel := mw.contextWithShutdownCancel() - - var restartSyncRequested bool - - mw.syncData.mu.Lock() - restartSyncRequested = mw.syncData.restartSyncRequested - mw.syncData.restartSyncRequested = false - mw.syncData.syncing = true - mw.syncData.cancelSync = cancel - mw.syncData.syncCanceled = make(chan struct{}) - mw.syncData.syncer = syncer - mw.syncData.mu.Unlock() - - for _, listener := range mw.syncProgressListeners() { - listener.OnSyncStarted(restartSyncRequested) - } - - // syncer.Run uses a wait group to block the thread until the sync context - // expires or is canceled or some other error occurs such as - // losing connection to all persistent peers. - go func() { - syncError := syncer.Run(ctx) - //sync has ended or errored - if syncError != nil { - if syncError == context.DeadlineExceeded { - mw.notifySyncError(errors.Errorf("SPV synchronization deadline exceeded: %v", syncError)) - } else if syncError == context.Canceled { - close(mw.syncData.syncCanceled) - mw.notifySyncCanceled() - } else { - mw.notifySyncError(syncError) - } - } - - //reset sync variables - mw.resetSyncData() - }() - return nil -} - -func (mw *MultiWallet) RestartSpvSync() error { - mw.syncData.mu.Lock() - mw.syncData.restartSyncRequested = true - mw.syncData.mu.Unlock() - - mw.CancelSync() // necessary to unset the network backend. - return mw.SpvSync() -} - -func (mw *MultiWallet) CancelSync() { - mw.syncData.mu.RLock() - cancelSync := mw.syncData.cancelSync - mw.syncData.mu.RUnlock() - - if cancelSync != nil { - log.Info("Canceling sync. May take a while for sync to fully cancel.") - - // Stop running cspp mixers - for _, wallet := range mw.wallets { - if wallet.IsAccountMixerActive() { - log.Infof("[%d] Stopping cspp mixer", wallet.ID) - err := mw.StopAccountMixer(wallet.ID) - if err != nil { - log.Errorf("[%d] Error stopping cspp mixer: %v", wallet.ID, err) - } - } - } - - // Cancel the context used for syncer.Run in spvSync(). - // This may not immediately cause the sync process to terminate, - // but when it eventually terminates, syncer.Run will return `err == context.Canceled`. - cancelSync() - - // When sync terminates and syncer.Run returns `err == context.Canceled`, - // we will get notified on this channel. - <-mw.syncData.syncCanceled - - log.Info("Sync fully canceled.") - } -} - -func (wallet *Wallet) IsWaiting() bool { - return wallet.waitingForHeaders -} - -func (wallet *Wallet) IsSynced() bool { - return wallet.synced -} - -func (wallet *Wallet) IsSyncing() bool { - return wallet.syncing -} - -func (mw *MultiWallet) IsConnectedToDecredNetwork() bool { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - return mw.syncData.syncing || mw.syncData.synced -} - -func (mw *MultiWallet) IsSynced() bool { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - return mw.syncData.synced -} - -func (mw *MultiWallet) IsSyncing() bool { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - return mw.syncData.syncing -} - -func (mw *MultiWallet) CurrentSyncStage() int32 { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - - if mw.syncData != nil && mw.syncData.syncing { - return mw.syncData.syncStage - } - return InvalidSyncStage -} - -func (mw *MultiWallet) GeneralSyncProgress() *GeneralSyncProgress { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - - if mw.syncData != nil && mw.syncData.syncing { - switch mw.syncData.syncStage { - case HeadersFetchSyncStage: - return mw.syncData.headersFetchProgress.GeneralSyncProgress - case AddressDiscoverySyncStage: - return mw.syncData.addressDiscoveryProgress.GeneralSyncProgress - case HeadersRescanSyncStage: - return mw.syncData.headersRescanProgress.GeneralSyncProgress - case CFiltersFetchSyncStage: - return mw.syncData.cfiltersFetchProgress.GeneralSyncProgress - } - } - - return nil -} - -func (mw *MultiWallet) ConnectedPeers() int32 { - mw.syncData.mu.RLock() - defer mw.syncData.mu.RUnlock() - return mw.syncData.connectedPeers -} - -func (mw *MultiWallet) PeerInfoRaw() ([]PeerInfo, error) { - if !mw.IsConnectedToDecredNetwork() { - return nil, errors.New(ErrNotConnected) - } - - syncer := mw.syncData.syncer - - infos := make([]PeerInfo, 0, len(syncer.GetRemotePeers())) - for _, rp := range syncer.GetRemotePeers() { - info := PeerInfo{ - ID: int32(rp.ID()), - Addr: rp.RemoteAddr().String(), - AddrLocal: rp.LocalAddr().String(), - Services: fmt.Sprintf("%08d", uint64(rp.Services())), - Version: rp.Pver(), - SubVer: rp.UA(), - StartingHeight: int64(rp.InitialHeight()), - BanScore: int32(rp.BanScore()), - } - - infos = append(infos, info) - } - - sort.Slice(infos, func(i, j int) bool { - return infos[i].ID < infos[j].ID - }) - - return infos, nil -} - -func (mw *MultiWallet) PeerInfo() (string, error) { - infos, err := mw.PeerInfoRaw() - if err != nil { - return "", err - } - - result, _ := json.Marshal(infos) - return string(result), nil -} - -func (mw *MultiWallet) GetBestBlock() *BlockInfo { - var bestBlock int32 = -1 - var blockInfo *BlockInfo - for _, wallet := range mw.wallets { - if !wallet.WalletOpened() { - continue - } - - walletBestBLock := wallet.GetBestBlock() - if walletBestBLock > bestBlock || bestBlock == -1 { - bestBlock = walletBestBLock - blockInfo = &BlockInfo{Height: bestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} - } - } - - return blockInfo -} - -func (mw *MultiWallet) GetLowestBlock() *BlockInfo { - var lowestBlock int32 = -1 - var blockInfo *BlockInfo - for _, wallet := range mw.wallets { - if !wallet.WalletOpened() { - continue - } - walletBestBLock := wallet.GetBestBlock() - if walletBestBLock < lowestBlock || lowestBlock == -1 { - lowestBlock = walletBestBLock - blockInfo = &BlockInfo{Height: lowestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} - } - } - - return blockInfo -} - -func (wallet *Wallet) GetBestBlock() int32 { - if wallet.Internal() == nil { - // This method is sometimes called after a wallet is deleted and causes crash. - log.Error("Attempting to read best block height without a loaded wallet.") - return 0 - } - - _, height := wallet.Internal().MainChainTip(wallet.shutdownContext()) - return height -} - -func (wallet *Wallet) GetBestBlockTimeStamp() int64 { - if wallet.Internal() == nil { - // This method is sometimes called after a wallet is deleted and causes crash. - log.Error("Attempting to read best block timestamp without a loaded wallet.") - return 0 - } - - ctx := wallet.shutdownContext() - _, height := wallet.Internal().MainChainTip(ctx) - identifier := w.NewBlockIdentifierFromHeight(height) - info, err := wallet.Internal().BlockInfo(ctx, identifier) - if err != nil { - log.Error(err) - return 0 - } - return info.Timestamp -} - -func (mw *MultiWallet) GetLowestBlockTimestamp() int64 { - var timestamp int64 = -1 - for _, wallet := range mw.wallets { - bestBlockTimestamp := wallet.GetBestBlockTimeStamp() - if bestBlockTimestamp < timestamp || timestamp == -1 { - timestamp = bestBlockTimestamp - } - } - return timestamp -} diff --git a/syncnotification.go b/syncnotification.go index 4e4444376..65edd71ba 100644 --- a/syncnotification.go +++ b/syncnotification.go @@ -5,7 +5,7 @@ import ( "time" "github.com/planetdecred/dcrlibwallet/spv" - "golang.org/x/sync/errgroup" + // "golang.org/x/sync/errgroup" ) func (mw *MultiWallet) spvSyncNotificationCallbacks() *spv.Notifications { @@ -32,550 +32,550 @@ func (mw *MultiWallet) spvSyncNotificationCallbacks() *spv.Notifications { } func (mw *MultiWallet) handlePeerCountUpdate(peerCount int32) { - mw.syncData.mu.Lock() - mw.syncData.connectedPeers = peerCount - shouldLog := mw.syncData.showLogs && mw.syncData.syncing - mw.syncData.mu.Unlock() - - for _, syncProgressListener := range mw.syncProgressListeners() { - syncProgressListener.OnPeerConnectedOrDisconnected(peerCount) - } - - if shouldLog { - if peerCount == 1 { - log.Infof("Connected to %d peer on %s.", peerCount, mw.chainParams.Name) - } else { - log.Infof("Connected to %d peers on %s.", peerCount, mw.chainParams.Name) - } - } + // mw.syncData.mu.Lock() + // mw.syncData.connectedPeers = peerCount + // shouldLog := mw.syncData.showLogs && mw.syncData.syncing + // mw.syncData.mu.Unlock() + + // for _, syncProgressListener := range mw.syncProgressListeners() { + // syncProgressListener.OnPeerConnectedOrDisconnected(peerCount) + // } + + // if shouldLog { + // if peerCount == 1 { + // log.Infof("Connected to %d peer on %s.", peerCount, mw.chainParams.Name) + // } else { + // log.Infof("Connected to %d peers on %s.", peerCount, mw.chainParams.Name) + // } + // } } // Fetch CFilters Callbacks func (mw *MultiWallet) fetchCFiltersStarted(walletID int) { - mw.syncData.mu.Lock() - mw.syncData.activeSyncData.syncStage = CFiltersFetchSyncStage - mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp = time.Now().Unix() - mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount = 0 - showLogs := mw.syncData.showLogs - mw.syncData.mu.Unlock() - - if showLogs { - log.Infof("Step 1 of 3 - fetching %d block headers.") - } + // mw.syncData.mu.Lock() + // mw.syncData.activeSyncData.syncStage = CFiltersFetchSyncStage + // mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp = time.Now().Unix() + // mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount = 0 + // showLogs := mw.syncData.showLogs + // mw.syncData.mu.Unlock() + + // if showLogs { + // log.Infof("Step 1 of 3 - fetching %d block headers.") + // } } func (mw *MultiWallet) fetchCFiltersProgress(walletID int, startCFiltersHeight, endCFiltersHeight int32) { // lock the mutex before reading and writing to mw.syncData.* - mw.syncData.mu.Lock() - - if mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight == -1 { - mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight = startCFiltersHeight - } - - wallet := mw.WalletWithID(walletID) - mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount += endCFiltersHeight - startCFiltersHeight - - totalCFiltersToFetch := wallet.GetBestBlock() - mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight - // cfiltersLeftToFetch := totalCFiltersToFetch - mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount - - cfiltersFetchProgress := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(totalCFiltersToFetch) - - // If there was some period of inactivity, - // assume that this process started at some point in the future, - // thereby accounting for the total reported time of inactivity. - mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp += mw.syncData.activeSyncData.totalInactiveSeconds - mw.syncData.activeSyncData.totalInactiveSeconds = 0 - - timeTakenSoFar := time.Now().Unix() - mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp - if timeTakenSoFar < 1 { - timeTakenSoFar = 1 - } - estimatedTotalCFiltersFetchTime := float64(timeTakenSoFar) / cfiltersFetchProgress - - // Use CFilters fetch rate to estimate headers fetch time. - cfiltersFetchRate := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(timeTakenSoFar) - estimatedHeadersLeftToFetch := mw.estimateBlockHeadersCountAfter(wallet.GetBestBlockTimeStamp()) - estimatedTotalHeadersFetchTime := float64(estimatedHeadersLeftToFetch) / cfiltersFetchRate - // increase estimated value by FetchPercentage - estimatedTotalHeadersFetchTime /= FetchPercentage - - estimatedDiscoveryTime := estimatedTotalHeadersFetchTime * DiscoveryPercentage - estimatedRescanTime := estimatedTotalHeadersFetchTime * RescanPercentage - estimatedTotalSyncTime := estimatedTotalCFiltersFetchTime + estimatedTotalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime - - totalSyncProgress := float64(timeTakenSoFar) / estimatedTotalSyncTime - totalTimeRemainingSeconds := int64(math.Round(estimatedTotalSyncTime)) - timeTakenSoFar - - // update headers fetching progress report including total progress percentage and total time remaining - mw.syncData.activeSyncData.cfiltersFetchProgress.TotalCFiltersToFetch = totalCFiltersToFetch - mw.syncData.activeSyncData.cfiltersFetchProgress.CurrentCFilterHeight = startCFiltersHeight - mw.syncData.activeSyncData.cfiltersFetchProgress.CFiltersFetchProgress = roundUp(cfiltersFetchProgress * 100.0) - mw.syncData.activeSyncData.cfiltersFetchProgress.TotalSyncProgress = roundUp(totalSyncProgress * 100.0) - mw.syncData.activeSyncData.cfiltersFetchProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds - - mw.syncData.mu.Unlock() - - // notify progress listener of estimated progress report - mw.publishFetchCFiltersProgress() - - cfiltersFetchTimeRemaining := estimatedTotalCFiltersFetchTime - float64(timeTakenSoFar) - debugInfo := &DebugInfo{ - timeTakenSoFar, - totalTimeRemainingSeconds, - timeTakenSoFar, - int64(math.Round(cfiltersFetchTimeRemaining)), - } - mw.publishDebugInfo(debugInfo) + // mw.syncData.mu.Lock() + + // if mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight == -1 { + // mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight = startCFiltersHeight + // } + + // wallet := mw.WalletWithID(walletID) + // mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount += endCFiltersHeight - startCFiltersHeight + + // totalCFiltersToFetch := wallet.GetBestBlock() - mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight + // // cfiltersLeftToFetch := totalCFiltersToFetch - mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount + + // cfiltersFetchProgress := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(totalCFiltersToFetch) + + // // If there was some period of inactivity, + // // assume that this process started at some point in the future, + // // thereby accounting for the total reported time of inactivity. + // mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp += mw.syncData.activeSyncData.totalInactiveSeconds + // mw.syncData.activeSyncData.totalInactiveSeconds = 0 + + // timeTakenSoFar := time.Now().Unix() - mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp + // if timeTakenSoFar < 1 { + // timeTakenSoFar = 1 + // } + // estimatedTotalCFiltersFetchTime := float64(timeTakenSoFar) / cfiltersFetchProgress + + // // Use CFilters fetch rate to estimate headers fetch time. + // cfiltersFetchRate := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(timeTakenSoFar) + // estimatedHeadersLeftToFetch := mw.estimateBlockHeadersCountAfter(wallet.GetBestBlockTimeStamp()) + // estimatedTotalHeadersFetchTime := float64(estimatedHeadersLeftToFetch) / cfiltersFetchRate + // // increase estimated value by FetchPercentage + // estimatedTotalHeadersFetchTime /= FetchPercentage + + // estimatedDiscoveryTime := estimatedTotalHeadersFetchTime * DiscoveryPercentage + // estimatedRescanTime := estimatedTotalHeadersFetchTime * RescanPercentage + // estimatedTotalSyncTime := estimatedTotalCFiltersFetchTime + estimatedTotalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime + + // totalSyncProgress := float64(timeTakenSoFar) / estimatedTotalSyncTime + // totalTimeRemainingSeconds := int64(math.Round(estimatedTotalSyncTime)) - timeTakenSoFar + + // // update headers fetching progress report including total progress percentage and total time remaining + // mw.syncData.activeSyncData.cfiltersFetchProgress.TotalCFiltersToFetch = totalCFiltersToFetch + // mw.syncData.activeSyncData.cfiltersFetchProgress.CurrentCFilterHeight = startCFiltersHeight + // mw.syncData.activeSyncData.cfiltersFetchProgress.CFiltersFetchProgress = roundUp(cfiltersFetchProgress * 100.0) + // mw.syncData.activeSyncData.cfiltersFetchProgress.TotalSyncProgress = roundUp(totalSyncProgress * 100.0) + // mw.syncData.activeSyncData.cfiltersFetchProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds + + // mw.syncData.mu.Unlock() + + // // notify progress listener of estimated progress report + // mw.publishFetchCFiltersProgress() + + // cfiltersFetchTimeRemaining := estimatedTotalCFiltersFetchTime - float64(timeTakenSoFar) + // debugInfo := &DebugInfo{ + // timeTakenSoFar, + // totalTimeRemainingSeconds, + // timeTakenSoFar, + // int64(math.Round(cfiltersFetchTimeRemaining)), + // } + // mw.publishDebugInfo(debugInfo) } func (mw *MultiWallet) publishFetchCFiltersProgress() { - for _, syncProgressListener := range mw.syncProgressListeners() { - syncProgressListener.OnCFiltersFetchProgress(&mw.syncData.cfiltersFetchProgress) - } + // for _, syncProgressListener := range mw.syncProgressListeners() { + // syncProgressListener.OnCFiltersFetchProgress(&mw.syncData.cfiltersFetchProgress) + // } } func (mw *MultiWallet) fetchCFiltersEnded(walletID int) { - mw.syncData.mu.Lock() - defer mw.syncData.mu.Unlock() + // mw.syncData.mu.Lock() + // defer mw.syncData.mu.Unlock() - mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent = time.Now().Unix() - mw.syncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp + // mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent = time.Now().Unix() - mw.syncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp - // If there is some period of inactivity reported at this stage, - // subtract it from the total stage time. - mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent -= mw.syncData.totalInactiveSeconds - mw.syncData.activeSyncData.totalInactiveSeconds = 0 + // // If there is some period of inactivity reported at this stage, + // // subtract it from the total stage time. + // mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent -= mw.syncData.totalInactiveSeconds + // mw.syncData.activeSyncData.totalInactiveSeconds = 0 } // Fetch Headers Callbacks func (mw *MultiWallet) fetchHeadersStarted(peerInitialHeight int32) { - if !mw.IsSyncing() { - return - } - - mw.syncData.mu.RLock() - headersFetchingStarted := mw.syncData.headersFetchProgress.beginFetchTimeStamp != -1 - showLogs := mw.syncData.showLogs - mw.syncData.mu.RUnlock() - - if headersFetchingStarted { - // This function gets called for each newly connected peer so - // ignore if headers fetching was already started. - return - } - - for _, wallet := range mw.wallets { - wallet.waitingForHeaders = true - } - - lowestBlockHeight := mw.GetLowestBlock().Height - - mw.syncData.mu.Lock() - mw.syncData.activeSyncData.syncStage = HeadersFetchSyncStage - mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp = time.Now().Unix() - mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight = lowestBlockHeight - mw.syncData.headersFetchProgress.totalFetchedHeadersCount = 0 - mw.syncData.activeSyncData.totalInactiveSeconds = 0 - mw.syncData.mu.Unlock() - - if showLogs { - log.Infof("Step 1 of 3 - fetching %d block headers.", peerInitialHeight-lowestBlockHeight) - } + // if !mw.IsSyncing() { + // return + // } + + // mw.syncData.mu.RLock() + // headersFetchingStarted := mw.syncData.headersFetchProgress.beginFetchTimeStamp != -1 + // showLogs := mw.syncData.showLogs + // mw.syncData.mu.RUnlock() + + // if headersFetchingStarted { + // // This function gets called for each newly connected peer so + // // ignore if headers fetching was already started. + // return + // } + + // for _, wallet := range mw.wallets { + // wallet.WaitingForHeaders = true + // } + + // lowestBlockHeight := mw.GetLowestBlock().Height + + // mw.syncData.mu.Lock() + // mw.syncData.activeSyncData.syncStage = HeadersFetchSyncStage + // mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp = time.Now().Unix() + // mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight = lowestBlockHeight + // mw.syncData.headersFetchProgress.totalFetchedHeadersCount = 0 + // mw.syncData.activeSyncData.totalInactiveSeconds = 0 + // mw.syncData.mu.Unlock() + + // if showLogs { + // log.Infof("Step 1 of 3 - fetching %d block headers.", peerInitialHeight-lowestBlockHeight) + // } } func (mw *MultiWallet) fetchHeadersProgress(lastFetchedHeaderHeight int32, lastFetchedHeaderTime int64) { - if !mw.IsSyncing() { - return - } - - mw.syncData.mu.RLock() - headersFetchingCompleted := mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent != -1 - mw.syncData.mu.RUnlock() - - if headersFetchingCompleted { - // This function gets called for each newly connected peer so ignore - // this call if the headers fetching phase was previously completed. - return - } - - for _, wallet := range mw.wallets { - if wallet.waitingForHeaders { - wallet.waitingForHeaders = wallet.GetBestBlock() > lastFetchedHeaderHeight - } - } - - // lock the mutex before reading and writing to mw.syncData.* - mw.syncData.mu.Lock() - - if lastFetchedHeaderHeight > mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight { - mw.syncData.activeSyncData.headersFetchProgress.totalFetchedHeadersCount = lastFetchedHeaderHeight - mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight - } - - headersLeftToFetch := mw.estimateBlockHeadersCountAfter(lastFetchedHeaderTime) - totalHeadersToFetch := lastFetchedHeaderHeight + headersLeftToFetch - headersFetchProgress := float64(mw.syncData.activeSyncData.headersFetchProgress.totalFetchedHeadersCount) / float64(totalHeadersToFetch) - - // If there was some period of inactivity, - // assume that this process started at some point in the future, - // thereby accounting for the total reported time of inactivity. - mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp += mw.syncData.activeSyncData.totalInactiveSeconds - mw.syncData.activeSyncData.totalInactiveSeconds = 0 - - fetchTimeTakenSoFar := time.Now().Unix() - mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp - if fetchTimeTakenSoFar < 1 { - fetchTimeTakenSoFar = 1 - } - estimatedTotalHeadersFetchTime := float64(fetchTimeTakenSoFar) / headersFetchProgress - - // For some reason, the actual total headers fetch time is more than the predicted/estimated time. - // Account for this difference by multiplying the estimatedTotalHeadersFetchTime by an incrementing factor. - // The incrementing factor is inversely proportional to the headers fetch progress, - // ranging from 0.5 to 0 as headers fetching progress increases from 0 to 1. - // todo, the above noted (mal)calculation may explain this difference. - // TODO: is this adjustment still needed since the calculation has been corrected. - adjustmentFactor := 0.5 * (1 - headersFetchProgress) - estimatedTotalHeadersFetchTime += estimatedTotalHeadersFetchTime * adjustmentFactor - - estimatedDiscoveryTime := estimatedTotalHeadersFetchTime * DiscoveryPercentage - estimatedRescanTime := estimatedTotalHeadersFetchTime * RescanPercentage - estimatedTotalSyncTime := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent) + - estimatedTotalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime - - totalSyncProgress := float64(fetchTimeTakenSoFar) / estimatedTotalSyncTime - totalTimeRemainingSeconds := int64(math.Round(estimatedTotalSyncTime)) - fetchTimeTakenSoFar - - // update headers fetching progress report including total progress percentage and total time remaining - mw.syncData.activeSyncData.headersFetchProgress.TotalHeadersToFetch = totalHeadersToFetch - mw.syncData.activeSyncData.headersFetchProgress.CurrentHeaderHeight = lastFetchedHeaderHeight - mw.syncData.activeSyncData.headersFetchProgress.CurrentHeaderTimestamp = lastFetchedHeaderTime - mw.syncData.activeSyncData.headersFetchProgress.HeadersFetchProgress = roundUp(headersFetchProgress * 100.0) - mw.syncData.activeSyncData.headersFetchProgress.TotalSyncProgress = roundUp(totalSyncProgress * 100.0) - mw.syncData.activeSyncData.headersFetchProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds - - // unlock the mutex before issuing notification callbacks to prevent potential deadlock - // if any invoked callback takes a considerable amount of time to execute. - mw.syncData.mu.Unlock() - - // notify progress listener of estimated progress report - mw.publishFetchHeadersProgress() - - // todo: also log report if showLog == true - timeTakenSoFar := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + fetchTimeTakenSoFar - headersFetchTimeRemaining := estimatedTotalHeadersFetchTime - float64(fetchTimeTakenSoFar) - debugInfo := &DebugInfo{ - timeTakenSoFar, - totalTimeRemainingSeconds, - fetchTimeTakenSoFar, - int64(math.Round(headersFetchTimeRemaining)), - } - mw.publishDebugInfo(debugInfo) + // if !mw.IsSyncing() { + // return + // } + + // mw.syncData.mu.RLock() + // headersFetchingCompleted := mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent != -1 + // mw.syncData.mu.RUnlock() + + // if headersFetchingCompleted { + // // This function gets called for each newly connected peer so ignore + // // this call if the headers fetching phase was previously completed. + // return + // } + + // for _, wallet := range mw.wallets { + // if wallet.WaitingForHeaders { + // wallet.WaitingForHeaders = wallet.GetBestBlock() > lastFetchedHeaderHeight + // } + // } + + // // lock the mutex before reading and writing to mw.syncData.* + // mw.syncData.mu.Lock() + + // if lastFetchedHeaderHeight > mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight { + // mw.syncData.activeSyncData.headersFetchProgress.totalFetchedHeadersCount = lastFetchedHeaderHeight - mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight + // } + + // headersLeftToFetch := mw.estimateBlockHeadersCountAfter(lastFetchedHeaderTime) + // totalHeadersToFetch := lastFetchedHeaderHeight + headersLeftToFetch + // headersFetchProgress := float64(mw.syncData.activeSyncData.headersFetchProgress.totalFetchedHeadersCount) / float64(totalHeadersToFetch) + + // // If there was some period of inactivity, + // // assume that this process started at some point in the future, + // // thereby accounting for the total reported time of inactivity. + // mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp += mw.syncData.activeSyncData.totalInactiveSeconds + // mw.syncData.activeSyncData.totalInactiveSeconds = 0 + + // fetchTimeTakenSoFar := time.Now().Unix() - mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp + // if fetchTimeTakenSoFar < 1 { + // fetchTimeTakenSoFar = 1 + // } + // estimatedTotalHeadersFetchTime := float64(fetchTimeTakenSoFar) / headersFetchProgress + + // // For some reason, the actual total headers fetch time is more than the predicted/estimated time. + // // Account for this difference by multiplying the estimatedTotalHeadersFetchTime by an incrementing factor. + // // The incrementing factor is inversely proportional to the headers fetch progress, + // // ranging from 0.5 to 0 as headers fetching progress increases from 0 to 1. + // // todo, the above noted (mal)calculation may explain this difference. + // // TODO: is this adjustment still needed since the calculation has been corrected. + // adjustmentFactor := 0.5 * (1 - headersFetchProgress) + // estimatedTotalHeadersFetchTime += estimatedTotalHeadersFetchTime * adjustmentFactor + + // estimatedDiscoveryTime := estimatedTotalHeadersFetchTime * DiscoveryPercentage + // estimatedRescanTime := estimatedTotalHeadersFetchTime * RescanPercentage + // estimatedTotalSyncTime := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent) + + // estimatedTotalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime + + // totalSyncProgress := float64(fetchTimeTakenSoFar) / estimatedTotalSyncTime + // totalTimeRemainingSeconds := int64(math.Round(estimatedTotalSyncTime)) - fetchTimeTakenSoFar + + // // update headers fetching progress report including total progress percentage and total time remaining + // mw.syncData.activeSyncData.headersFetchProgress.TotalHeadersToFetch = totalHeadersToFetch + // mw.syncData.activeSyncData.headersFetchProgress.CurrentHeaderHeight = lastFetchedHeaderHeight + // mw.syncData.activeSyncData.headersFetchProgress.CurrentHeaderTimestamp = lastFetchedHeaderTime + // mw.syncData.activeSyncData.headersFetchProgress.HeadersFetchProgress = roundUp(headersFetchProgress * 100.0) + // mw.syncData.activeSyncData.headersFetchProgress.TotalSyncProgress = roundUp(totalSyncProgress * 100.0) + // mw.syncData.activeSyncData.headersFetchProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds + + // // unlock the mutex before issuing notification callbacks to prevent potential deadlock + // // if any invoked callback takes a considerable amount of time to execute. + // mw.syncData.mu.Unlock() + + // // notify progress listener of estimated progress report + // mw.publishFetchHeadersProgress() + + // // todo: also log report if showLog == true + // timeTakenSoFar := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + fetchTimeTakenSoFar + // headersFetchTimeRemaining := estimatedTotalHeadersFetchTime - float64(fetchTimeTakenSoFar) + // debugInfo := &DebugInfo{ + // timeTakenSoFar, + // totalTimeRemainingSeconds, + // fetchTimeTakenSoFar, + // int64(math.Round(headersFetchTimeRemaining)), + // } + // mw.publishDebugInfo(debugInfo) } func (mw *MultiWallet) publishFetchHeadersProgress() { - for _, syncProgressListener := range mw.syncProgressListeners() { - syncProgressListener.OnHeadersFetchProgress(&mw.syncData.headersFetchProgress) - } + // for _, syncProgressListener := range mw.syncProgressListeners() { + // syncProgressListener.OnHeadersFetchProgress(&mw.syncData.headersFetchProgress) + // } } func (mw *MultiWallet) fetchHeadersFinished() { - mw.syncData.mu.Lock() - defer mw.syncData.mu.Unlock() - - if !mw.syncData.syncing { - // ignore if sync is not in progress - return - } - - mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight = -1 - mw.syncData.headersFetchProgress.totalFetchedHeadersCount = 0 - mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent = time.Now().Unix() - mw.syncData.headersFetchProgress.beginFetchTimeStamp - - // If there is some period of inactivity reported at this stage, - // subtract it from the total stage time. - mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent -= mw.syncData.totalInactiveSeconds - mw.syncData.activeSyncData.totalInactiveSeconds = 0 - - if mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent < 150 { - // This ensures that minimum ETA used for stage 2 (address discovery) is 120 seconds (80% of 150 seconds). - mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent = 150 - } - - if mw.syncData.showLogs && mw.syncData.syncing { - log.Info("Fetch headers completed.") - } + // mw.syncData.mu.Lock() + // defer mw.syncData.mu.Unlock() + + // if !mw.syncData.syncing { + // // ignore if sync is not in progress + // return + // } + + // mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight = -1 + // mw.syncData.headersFetchProgress.totalFetchedHeadersCount = 0 + // mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent = time.Now().Unix() - mw.syncData.headersFetchProgress.beginFetchTimeStamp + + // // If there is some period of inactivity reported at this stage, + // // subtract it from the total stage time. + // mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent -= mw.syncData.totalInactiveSeconds + // mw.syncData.activeSyncData.totalInactiveSeconds = 0 + + // if mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent < 150 { + // // This ensures that minimum ETA used for stage 2 (address discovery) is 120 seconds (80% of 150 seconds). + // mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent = 150 + // } + + // if mw.syncData.showLogs && mw.syncData.syncing { + // log.Info("Fetch headers completed.") + // } } // Address/Account Discovery Callbacks func (mw *MultiWallet) discoverAddressesStarted(walletID int) { - if !mw.IsSyncing() { - return - } - - mw.syncData.mu.RLock() - addressDiscoveryAlreadyStarted := mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime != -1 - totalHeadersFetchTime := float64(mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent) - mw.syncData.mu.RUnlock() - - if addressDiscoveryAlreadyStarted { - return - } - - mw.syncData.mu.Lock() - mw.syncData.activeSyncData.syncStage = AddressDiscoverySyncStage - mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime = time.Now().Unix() - mw.syncData.activeSyncData.addressDiscoveryProgress.WalletID = walletID - mw.syncData.addressDiscoveryCompletedOrCanceled = make(chan bool) - mw.syncData.mu.Unlock() - - go mw.updateAddressDiscoveryProgress(totalHeadersFetchTime) - - if mw.syncData.showLogs { - log.Info("Step 2 of 3 - discovering used addresses.") - } + // if !mw.IsSyncing() { + // return + // } + + // mw.syncData.mu.RLock() + // addressDiscoveryAlreadyStarted := mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime != -1 + // totalHeadersFetchTime := float64(mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent) + // mw.syncData.mu.RUnlock() + + // if addressDiscoveryAlreadyStarted { + // return + // } + + // mw.syncData.mu.Lock() + // mw.syncData.activeSyncData.syncStage = AddressDiscoverySyncStage + // mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime = time.Now().Unix() + // mw.syncData.activeSyncData.addressDiscoveryProgress.WalletID = walletID + // mw.syncData.addressDiscoveryCompletedOrCanceled = make(chan bool) + // mw.syncData.mu.Unlock() + + // go mw.updateAddressDiscoveryProgress(totalHeadersFetchTime) + + // if mw.syncData.showLogs { + // log.Info("Step 2 of 3 - discovering used addresses.") + // } } func (mw *MultiWallet) updateAddressDiscoveryProgress(totalHeadersFetchTime float64) { // use ticker to calculate and broadcast address discovery progress every second - everySecondTicker := time.NewTicker(1 * time.Second) - - // these values will be used every second to calculate the total sync progress - estimatedDiscoveryTime := totalHeadersFetchTime * DiscoveryPercentage - estimatedRescanTime := totalHeadersFetchTime * RescanPercentage - - // track last logged time remaining and total percent to avoid re-logging same message - var lastTimeRemaining int64 - var lastTotalPercent int32 = -1 - - for { - if !mw.IsSyncing() { - return - } - - // If there was some period of inactivity, - // assume that this process started at some point in the future, - // thereby accounting for the total reported time of inactivity. - mw.syncData.mu.Lock() - mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime += mw.syncData.totalInactiveSeconds - mw.syncData.totalInactiveSeconds = 0 - addressDiscoveryStartTime := mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime - totalCfiltersFetchTime := float64(mw.syncData.cfiltersFetchProgress.cfiltersFetchTimeSpent) - showLogs := mw.syncData.showLogs - mw.syncData.mu.Unlock() - - select { - case <-mw.syncData.addressDiscoveryCompletedOrCanceled: - // stop calculating and broadcasting address discovery progress - everySecondTicker.Stop() - if showLogs { - log.Info("Address discovery complete.") - } - return - - case <-everySecondTicker.C: - // calculate address discovery progress - elapsedDiscoveryTime := float64(time.Now().Unix() - addressDiscoveryStartTime) - discoveryProgress := (elapsedDiscoveryTime / estimatedDiscoveryTime) * 100 - - var totalSyncTime float64 - if elapsedDiscoveryTime > estimatedDiscoveryTime { - totalSyncTime = totalCfiltersFetchTime + totalHeadersFetchTime + elapsedDiscoveryTime + estimatedRescanTime - } else { - totalSyncTime = totalCfiltersFetchTime + totalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime - } - - totalElapsedTime := totalCfiltersFetchTime + totalHeadersFetchTime + elapsedDiscoveryTime - totalProgress := (totalElapsedTime / totalSyncTime) * 100 - - remainingAccountDiscoveryTime := math.Round(estimatedDiscoveryTime - elapsedDiscoveryTime) - if remainingAccountDiscoveryTime < 0 { - remainingAccountDiscoveryTime = 0 - } - - totalProgressPercent := int32(math.Round(totalProgress)) - totalTimeRemainingSeconds := int64(math.Round(remainingAccountDiscoveryTime + estimatedRescanTime)) - - // update address discovery progress, total progress and total time remaining - mw.syncData.mu.Lock() - mw.syncData.addressDiscoveryProgress.AddressDiscoveryProgress = int32(math.Round(discoveryProgress)) - mw.syncData.addressDiscoveryProgress.TotalSyncProgress = totalProgressPercent - mw.syncData.addressDiscoveryProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds - mw.syncData.mu.Unlock() - - mw.publishAddressDiscoveryProgress() - - debugInfo := &DebugInfo{ - int64(math.Round(totalElapsedTime)), - totalTimeRemainingSeconds, - int64(math.Round(elapsedDiscoveryTime)), - int64(math.Round(remainingAccountDiscoveryTime)), - } - mw.publishDebugInfo(debugInfo) - - if showLogs { - // avoid logging same message multiple times - if totalProgressPercent != lastTotalPercent || totalTimeRemainingSeconds != lastTimeRemaining { - log.Infof("Syncing %d%%, %s remaining, discovering used addresses.", - totalProgressPercent, CalculateTotalTimeRemaining(totalTimeRemainingSeconds)) - - lastTotalPercent = totalProgressPercent - lastTimeRemaining = totalTimeRemainingSeconds - } - } - } - } + // everySecondTicker := time.NewTicker(1 * time.Second) + + // // these values will be used every second to calculate the total sync progress + // estimatedDiscoveryTime := totalHeadersFetchTime * DiscoveryPercentage + // estimatedRescanTime := totalHeadersFetchTime * RescanPercentage + + // // track last logged time remaining and total percent to avoid re-logging same message + // var lastTimeRemaining int64 + // var lastTotalPercent int32 = -1 + + // for { + // if !mw.IsSyncing() { + // return + // } + + // // If there was some period of inactivity, + // // assume that this process started at some point in the future, + // // thereby accounting for the total reported time of inactivity. + // mw.syncData.mu.Lock() + // mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime += mw.syncData.totalInactiveSeconds + // mw.syncData.totalInactiveSeconds = 0 + // addressDiscoveryStartTime := mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime + // totalCfiltersFetchTime := float64(mw.syncData.cfiltersFetchProgress.cfiltersFetchTimeSpent) + // showLogs := mw.syncData.showLogs + // mw.syncData.mu.Unlock() + + // select { + // case <-mw.syncData.addressDiscoveryCompletedOrCanceled: + // // stop calculating and broadcasting address discovery progress + // everySecondTicker.Stop() + // if showLogs { + // log.Info("Address discovery complete.") + // } + // return + + // case <-everySecondTicker.C: + // // calculate address discovery progress + // elapsedDiscoveryTime := float64(time.Now().Unix() - addressDiscoveryStartTime) + // discoveryProgress := (elapsedDiscoveryTime / estimatedDiscoveryTime) * 100 + + // var totalSyncTime float64 + // if elapsedDiscoveryTime > estimatedDiscoveryTime { + // totalSyncTime = totalCfiltersFetchTime + totalHeadersFetchTime + elapsedDiscoveryTime + estimatedRescanTime + // } else { + // totalSyncTime = totalCfiltersFetchTime + totalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime + // } + + // totalElapsedTime := totalCfiltersFetchTime + totalHeadersFetchTime + elapsedDiscoveryTime + // totalProgress := (totalElapsedTime / totalSyncTime) * 100 + + // remainingAccountDiscoveryTime := math.Round(estimatedDiscoveryTime - elapsedDiscoveryTime) + // if remainingAccountDiscoveryTime < 0 { + // remainingAccountDiscoveryTime = 0 + // } + + // totalProgressPercent := int32(math.Round(totalProgress)) + // totalTimeRemainingSeconds := int64(math.Round(remainingAccountDiscoveryTime + estimatedRescanTime)) + + // // update address discovery progress, total progress and total time remaining + // mw.syncData.mu.Lock() + // mw.syncData.addressDiscoveryProgress.AddressDiscoveryProgress = int32(math.Round(discoveryProgress)) + // mw.syncData.addressDiscoveryProgress.TotalSyncProgress = totalProgressPercent + // mw.syncData.addressDiscoveryProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds + // mw.syncData.mu.Unlock() + + // mw.publishAddressDiscoveryProgress() + + // debugInfo := &DebugInfo{ + // int64(math.Round(totalElapsedTime)), + // totalTimeRemainingSeconds, + // int64(math.Round(elapsedDiscoveryTime)), + // int64(math.Round(remainingAccountDiscoveryTime)), + // } + // mw.publishDebugInfo(debugInfo) + + // if showLogs { + // // avoid logging same message multiple times + // if totalProgressPercent != lastTotalPercent || totalTimeRemainingSeconds != lastTimeRemaining { + // log.Infof("Syncing %d%%, %s remaining, discovering used addresses.", + // totalProgressPercent, CalculateTotalTimeRemaining(totalTimeRemainingSeconds)) + + // lastTotalPercent = totalProgressPercent + // lastTimeRemaining = totalTimeRemainingSeconds + // } + // } + // } + // } } func (mw *MultiWallet) publishAddressDiscoveryProgress() { - for _, syncProgressListener := range mw.syncProgressListeners() { - syncProgressListener.OnAddressDiscoveryProgress(&mw.syncData.activeSyncData.addressDiscoveryProgress) - } + // for _, syncProgressListener := range mw.syncProgressListeners() { + // syncProgressListener.OnAddressDiscoveryProgress(&mw.syncData.activeSyncData.addressDiscoveryProgress) + // } } func (mw *MultiWallet) discoverAddressesFinished(walletID int) { - if !mw.IsSyncing() { - return - } + // if !mw.IsSyncing() { + // return + // } mw.stopUpdatingAddressDiscoveryProgress() } func (mw *MultiWallet) stopUpdatingAddressDiscoveryProgress() { - mw.syncData.mu.Lock() - if mw.syncData.activeSyncData != nil && mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled != nil { - close(mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled) - mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled = nil - mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent = time.Now().Unix() - mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime - } - mw.syncData.mu.Unlock() + // mw.syncData.mu.Lock() + // if mw.syncData.activeSyncData != nil && mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled != nil { + // close(mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled) + // mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled = nil + // mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent = time.Now().Unix() - mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime + // } + // mw.syncData.mu.Unlock() } // Blocks Scan Callbacks func (mw *MultiWallet) rescanStarted(walletID int) { - mw.stopUpdatingAddressDiscoveryProgress() + // mw.stopUpdatingAddressDiscoveryProgress() - mw.syncData.mu.Lock() - defer mw.syncData.mu.Unlock() + // mw.syncData.mu.Lock() + // defer mw.syncData.mu.Unlock() - if !mw.syncData.syncing { - // ignore if sync is not in progress - return - } + // if !mw.syncData.syncing { + // // ignore if sync is not in progress + // return + // } - mw.syncData.activeSyncData.syncStage = HeadersRescanSyncStage - mw.syncData.activeSyncData.rescanStartTime = time.Now().Unix() + // mw.syncData.activeSyncData.syncStage = HeadersRescanSyncStage + // mw.syncData.activeSyncData.rescanStartTime = time.Now().Unix() - // retain last total progress report from address discovery phase - mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = mw.syncData.activeSyncData.addressDiscoveryProgress.TotalTimeRemainingSeconds - mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = mw.syncData.activeSyncData.addressDiscoveryProgress.TotalSyncProgress - mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID + // // retain last total progress report from address discovery phase + // mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = mw.syncData.activeSyncData.addressDiscoveryProgress.TotalTimeRemainingSeconds + // mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = mw.syncData.activeSyncData.addressDiscoveryProgress.TotalSyncProgress + // mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID - if mw.syncData.showLogs && mw.syncData.syncing { - log.Info("Step 3 of 3 - Scanning block headers.") - } + // if mw.syncData.showLogs && mw.syncData.syncing { + // log.Info("Step 3 of 3 - Scanning block headers.") + // } } func (mw *MultiWallet) rescanProgress(walletID int, rescannedThrough int32) { - if !mw.IsSyncing() { - // ignore if sync is not in progress - return - } - - wallet := mw.wallets[walletID] - totalHeadersToScan := wallet.GetBestBlock() - - rescanRate := float64(rescannedThrough) / float64(totalHeadersToScan) - - mw.syncData.mu.Lock() - - // If there was some period of inactivity, - // assume that this process started at some point in the future, - // thereby accounting for the total reported time of inactivity. - mw.syncData.activeSyncData.rescanStartTime += mw.syncData.activeSyncData.totalInactiveSeconds - mw.syncData.activeSyncData.totalInactiveSeconds = 0 - - elapsedRescanTime := time.Now().Unix() - mw.syncData.activeSyncData.rescanStartTime - estimatedTotalRescanTime := int64(math.Round(float64(elapsedRescanTime) / rescanRate)) - totalTimeRemainingSeconds := estimatedTotalRescanTime - elapsedRescanTime - totalElapsedTime := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent + - mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent + elapsedRescanTime - - mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID - mw.syncData.activeSyncData.headersRescanProgress.TotalHeadersToScan = totalHeadersToScan - mw.syncData.activeSyncData.headersRescanProgress.RescanProgress = int32(math.Round(rescanRate * 100)) - mw.syncData.activeSyncData.headersRescanProgress.CurrentRescanHeight = rescannedThrough - mw.syncData.activeSyncData.headersRescanProgress.RescanTimeRemaining = totalTimeRemainingSeconds - - // do not update total time taken and total progress percent if elapsedRescanTime is 0 - // because the estimatedTotalRescanTime will be inaccurate (also 0) - // which will make the estimatedTotalSyncTime equal to totalElapsedTime - // giving the wrong impression that the process is complete - if elapsedRescanTime > 0 { - estimatedTotalSyncTime := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent + - mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent + estimatedTotalRescanTime - totalProgress := (float64(totalElapsedTime) / float64(estimatedTotalSyncTime)) * 100 - - mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds - mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = int32(math.Round(totalProgress)) - } - - mw.syncData.mu.Unlock() - - mw.publishHeadersRescanProgress() - - debugInfo := &DebugInfo{ - totalElapsedTime, - totalTimeRemainingSeconds, - elapsedRescanTime, - totalTimeRemainingSeconds, - } - mw.publishDebugInfo(debugInfo) - - mw.syncData.mu.RLock() - if mw.syncData.showLogs { - log.Infof("Syncing %d%%, %s remaining, scanning %d of %d block headers.", - mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress, - CalculateTotalTimeRemaining(mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds), - mw.syncData.activeSyncData.headersRescanProgress.CurrentRescanHeight, - mw.syncData.activeSyncData.headersRescanProgress.TotalHeadersToScan, - ) - } - mw.syncData.mu.RUnlock() + // if !mw.IsSyncing() { + // // ignore if sync is not in progress + // return + // } + + // wallet := mw.wallets[walletID] + // totalHeadersToScan := wallet.GetBestBlock() + + // rescanRate := float64(rescannedThrough) / float64(totalHeadersToScan) + + // mw.syncData.mu.Lock() + + // // If there was some period of inactivity, + // // assume that this process started at some point in the future, + // // thereby accounting for the total reported time of inactivity. + // mw.syncData.activeSyncData.rescanStartTime += mw.syncData.activeSyncData.totalInactiveSeconds + // mw.syncData.activeSyncData.totalInactiveSeconds = 0 + + // elapsedRescanTime := time.Now().Unix() - mw.syncData.activeSyncData.rescanStartTime + // estimatedTotalRescanTime := int64(math.Round(float64(elapsedRescanTime) / rescanRate)) + // totalTimeRemainingSeconds := estimatedTotalRescanTime - elapsedRescanTime + // totalElapsedTime := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent + + // mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent + elapsedRescanTime + + // mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID + // mw.syncData.activeSyncData.headersRescanProgress.TotalHeadersToScan = totalHeadersToScan + // mw.syncData.activeSyncData.headersRescanProgress.RescanProgress = int32(math.Round(rescanRate * 100)) + // mw.syncData.activeSyncData.headersRescanProgress.CurrentRescanHeight = rescannedThrough + // mw.syncData.activeSyncData.headersRescanProgress.RescanTimeRemaining = totalTimeRemainingSeconds + + // // do not update total time taken and total progress percent if elapsedRescanTime is 0 + // // because the estimatedTotalRescanTime will be inaccurate (also 0) + // // which will make the estimatedTotalSyncTime equal to totalElapsedTime + // // giving the wrong impression that the process is complete + // if elapsedRescanTime > 0 { + // estimatedTotalSyncTime := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent + + // mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent + estimatedTotalRescanTime + // totalProgress := (float64(totalElapsedTime) / float64(estimatedTotalSyncTime)) * 100 + + // mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds + // mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = int32(math.Round(totalProgress)) + // } + + // mw.syncData.mu.Unlock() + + // mw.publishHeadersRescanProgress() + + // debugInfo := &DebugInfo{ + // totalElapsedTime, + // totalTimeRemainingSeconds, + // elapsedRescanTime, + // totalTimeRemainingSeconds, + // } + // mw.publishDebugInfo(debugInfo) + + // mw.syncData.mu.RLock() + // if mw.syncData.showLogs { + // log.Infof("Syncing %d%%, %s remaining, scanning %d of %d block headers.", + // mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress, + // CalculateTotalTimeRemaining(mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds), + // mw.syncData.activeSyncData.headersRescanProgress.CurrentRescanHeight, + // mw.syncData.activeSyncData.headersRescanProgress.TotalHeadersToScan, + // ) + // } + // mw.syncData.mu.RUnlock() } func (mw *MultiWallet) publishHeadersRescanProgress() { - for _, syncProgressListener := range mw.syncProgressListeners() { - syncProgressListener.OnHeadersRescanProgress(&mw.syncData.activeSyncData.headersRescanProgress) - } + // for _, syncProgressListener := range mw.syncProgressListeners() { + // syncProgressListener.OnHeadersRescanProgress(&mw.syncData.activeSyncData.headersRescanProgress) + // } } func (mw *MultiWallet) rescanFinished(walletID int) { - if !mw.IsSyncing() { - // ignore if sync is not in progress - return - } - - mw.syncData.mu.Lock() - mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID - mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = 0 - mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = 100 - - // Reset these value so that address discovery would - // not be skipped for the next wallet. - mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime = -1 - mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent = -1 - mw.syncData.mu.Unlock() - - mw.publishHeadersRescanProgress() + // if !mw.IsSyncing() { + // // ignore if sync is not in progress + // return + // } + + // mw.syncData.mu.Lock() + // mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID + // mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = 0 + // mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = 100 + + // // Reset these value so that address discovery would + // // not be skipped for the next wallet. + // mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime = -1 + // mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent = -1 + // mw.syncData.mu.Unlock() + + // mw.publishHeadersRescanProgress() } func (mw *MultiWallet) publishDebugInfo(debugInfo *DebugInfo) { - for _, syncProgressListener := range mw.syncProgressListeners() { - syncProgressListener.Debug(debugInfo) - } + // for _, syncProgressListener := range mw.syncProgressListeners() { + // syncProgressListener.Debug(debugInfo) + // } } /** Helper functions start here */ @@ -592,95 +592,95 @@ func (mw *MultiWallet) estimateBlockHeadersCountAfter(lastHeaderTime int64) int3 } func (mw *MultiWallet) notifySyncError(err error) { - for _, syncProgressListener := range mw.syncProgressListeners() { - syncProgressListener.OnSyncEndedWithError(err) - } + // for _, syncProgressListener := range mw.syncProgressListeners() { + // syncProgressListener.OnSyncEndedWithError(err) + // } } func (mw *MultiWallet) notifySyncCanceled() { - mw.syncData.mu.RLock() - restartSyncRequested := mw.syncData.restartSyncRequested - mw.syncData.mu.RUnlock() + // mw.syncData.mu.RLock() + // restartSyncRequested := mw.syncData.restartSyncRequested + // mw.syncData.mu.RUnlock() - for _, syncProgressListener := range mw.syncProgressListeners() { - syncProgressListener.OnSyncCanceled(restartSyncRequested) - } + // for _, syncProgressListener := range mw.syncProgressListeners() { + // syncProgressListener.OnSyncCanceled(restartSyncRequested) + // } } func (mw *MultiWallet) resetSyncData() { // It's possible that sync ends or errors while address discovery is ongoing. // If this happens, it's important to stop the address discovery process before // resetting sync data. - mw.stopUpdatingAddressDiscoveryProgress() - - mw.syncData.mu.Lock() - mw.syncData.syncing = false - mw.syncData.synced = false - mw.syncData.cancelSync = nil - mw.syncData.syncCanceled = nil - mw.syncData.activeSyncData = nil - mw.syncData.mu.Unlock() - - for _, wallet := range mw.wallets { - wallet.waitingForHeaders = true - wallet.LockWallet() // lock wallet if previously unlocked to perform account discovery. - } + // mw.stopUpdatingAddressDiscoveryProgress() + + // mw.syncData.mu.Lock() + // mw.syncData.syncing = false + // mw.syncData.synced = false + // mw.syncData.cancelSync = nil + // mw.syncData.syncCanceled = nil + // mw.syncData.activeSyncData = nil + // mw.syncData.mu.Unlock() + + // for _, wallet := range mw.wallets { + // wallet.WaitingForHeaders = true + // wallet.LockWallet() // lock wallet if previously unlocked to perform account discovery. + // } } func (mw *MultiWallet) synced(walletID int, synced bool) { - indexTransactions := func() { - // begin indexing transactions after sync is completed, - // syncProgressListeners.OnSynced() will be invoked after transactions are indexed - var txIndexing errgroup.Group - for _, wallet := range mw.wallets { - txIndexing.Go(wallet.IndexTransactions) - } - - go func() { - err := txIndexing.Wait() - if err != nil { - log.Errorf("Tx Index Error: %v", err) - } - - for _, syncProgressListener := range mw.syncProgressListeners() { - if synced { - syncProgressListener.OnSyncCompleted() - } else { - syncProgressListener.OnSyncCanceled(false) - } - } - }() - } - - mw.syncData.mu.RLock() - allWalletsSynced := mw.syncData.synced - mw.syncData.mu.RUnlock() - - if allWalletsSynced && synced { - indexTransactions() - return - } - - wallet := mw.wallets[walletID] - wallet.synced = synced - wallet.syncing = false - mw.listenForTransactions(wallet.ID) - - if !wallet.Internal().Locked() { - wallet.LockWallet() // lock wallet if previously unlocked to perform account discovery. - err := mw.markWalletAsDiscoveredAccounts(walletID) - if err != nil { - log.Error(err) - } - } - - if mw.OpenedWalletsCount() == mw.SyncedWalletsCount() { - mw.syncData.mu.Lock() - mw.syncData.syncing = false - mw.syncData.synced = true - mw.syncData.mu.Unlock() - - indexTransactions() - } + // indexTransactions := func() { + // // begin indexing transactions after sync is completed, + // // syncProgressListeners.OnSynced() will be invoked after transactions are indexed + // var txIndexing errgroup.Group + // for _, wallet := range mw.wallets { + // txIndexing.Go(wallet.IndexTransactions) + // } + + // go func() { + // err := txIndexing.Wait() + // if err != nil { + // log.Errorf("Tx Index Error: %v", err) + // } + + // for _, syncProgressListener := range mw.syncProgressListeners() { + // if synced { + // syncProgressListener.OnSyncCompleted() + // } else { + // syncProgressListener.OnSyncCanceled(false) + // } + // } + // }() + // } + + // mw.syncData.mu.RLock() + // allWalletsSynced := mw.syncData.synced + // mw.syncData.mu.RUnlock() + + // if allWalletsSynced && synced { + // indexTransactions() + // return + // } + + // wallet := mw.wallets[walletID] + // wallet.Synced = synced + // wallet.Syncing = false + // // mw.listenForTransactions(wallet.ID) + + // if !wallet.Internal().Locked() { + // wallet.LockWallet() // lock wallet if previously unlocked to perform account discovery. + // err := mw.markWalletAsDiscoveredAccounts(walletID) + // if err != nil { + // log.Error(err) + // } + // } + + // if mw.OpenedWalletsCount() == mw.SyncedWalletsCount() { + // mw.syncData.mu.Lock() + // mw.syncData.syncing = false + // mw.syncData.synced = true + // mw.syncData.mu.Unlock() + + // indexTransactions() + // } } diff --git a/txandblocknotifications.go b/txandblocknotifications.go deleted file mode 100644 index dd55166a9..000000000 --- a/txandblocknotifications.go +++ /dev/null @@ -1,159 +0,0 @@ -package dcrlibwallet - -import ( - "encoding/json" - - "decred.org/dcrwallet/v2/errors" -) - -func (mw *MultiWallet) listenForTransactions(walletID int) { - go func() { - - wallet := mw.wallets[walletID] - n := wallet.Internal().NtfnServer.TransactionNotifications() - - for { - select { - case v := <-n.C: - if v == nil { - return - } - for _, transaction := range v.UnminedTransactions { - tempTransaction, err := wallet.decodeTransactionWithTxSummary(&transaction, nil) - if err != nil { - log.Errorf("[%d] Error ntfn parse tx: %v", wallet.ID, err) - return - } - - overwritten, err := wallet.walletDataDB.SaveOrUpdate(&Transaction{}, tempTransaction) - if err != nil { - log.Errorf("[%d] New Tx save err: %v", wallet.ID, err) - return - } - - if !overwritten { - log.Infof("[%d] New Transaction %s", wallet.ID, tempTransaction.Hash) - - result, err := json.Marshal(tempTransaction) - if err != nil { - log.Error(err) - } else { - mw.mempoolTransactionNotification(string(result)) - } - } - } - - for _, block := range v.AttachedBlocks { - blockHash := block.Header.BlockHash() - for _, transaction := range block.Transactions { - tempTransaction, err := wallet.decodeTransactionWithTxSummary(&transaction, &blockHash) - if err != nil { - log.Errorf("[%d] Error ntfn parse tx: %v", wallet.ID, err) - return - } - - _, err = wallet.walletDataDB.SaveOrUpdate(&Transaction{}, tempTransaction) - if err != nil { - log.Errorf("[%d] Incoming block replace tx error :%v", wallet.ID, err) - return - } - mw.publishTransactionConfirmed(wallet.ID, transaction.Hash.String(), int32(block.Header.Height)) - } - - mw.publishBlockAttached(wallet.ID, int32(block.Header.Height)) - } - - if len(v.AttachedBlocks) > 0 { - mw.checkWalletMixers() - } - - case <-mw.syncData.syncCanceled: - n.Done() - } - } - }() -} - -// AddTxAndBlockNotificationListener registers a set of functions to be invoked -// when a transaction or block update is processed by the wallet. If async is -// true, the provided callback methods will be called from separate goroutines, -// allowing notification senders to continue their operation without waiting -// for the listener to complete processing the notification. This asyncrhonous -// handling is especially important for cases where the wallet process that -// sends the notification temporarily prevents access to other wallet features -// until all notification handlers finish processing the notification. If a -// notification handler were to try to access such features, it would result -// in a deadlock. -func (mw *MultiWallet) AddTxAndBlockNotificationListener(txAndBlockNotificationListener TxAndBlockNotificationListener, async bool, uniqueIdentifier string) error { - mw.notificationListenersMu.Lock() - defer mw.notificationListenersMu.Unlock() - - _, ok := mw.txAndBlockNotificationListeners[uniqueIdentifier] - if ok { - return errors.New(ErrListenerAlreadyExist) - } - - if async { - mw.txAndBlockNotificationListeners[uniqueIdentifier] = &asyncTxAndBlockNotificationListener{ - l: txAndBlockNotificationListener, - } - } else { - mw.txAndBlockNotificationListeners[uniqueIdentifier] = txAndBlockNotificationListener - } - - return nil -} - -func (mw *MultiWallet) RemoveTxAndBlockNotificationListener(uniqueIdentifier string) { - mw.notificationListenersMu.Lock() - defer mw.notificationListenersMu.Unlock() - - delete(mw.txAndBlockNotificationListeners, uniqueIdentifier) -} - -func (mw *MultiWallet) checkWalletMixers() { - for _, wallet := range mw.wallets { - if wallet.IsAccountMixerActive() { - unmixedAccount := wallet.ReadInt32ConfigValueForKey(AccountMixerUnmixedAccount, -1) - hasMixableOutput, err := wallet.accountHasMixableOutput(unmixedAccount) - if err != nil { - log.Errorf("Error checking for mixable outputs: %v", err) - } - - if !hasMixableOutput { - log.Infof("[%d] unmixed account does not have a mixable output, stopping account mixer", wallet.ID) - err = mw.StopAccountMixer(wallet.ID) - if err != nil { - log.Errorf("Error stopping account mixer: %v", err) - } - } - } - } -} - -func (mw *MultiWallet) mempoolTransactionNotification(transaction string) { - mw.notificationListenersMu.RLock() - defer mw.notificationListenersMu.RUnlock() - - for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { - txAndBlockNotifcationListener.OnTransaction(transaction) - } -} - -func (mw *MultiWallet) publishTransactionConfirmed(walletID int, transactionHash string, blockHeight int32) { - mw.notificationListenersMu.RLock() - defer mw.notificationListenersMu.RUnlock() - - for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { - txAndBlockNotifcationListener.OnTransactionConfirmed(walletID, transactionHash, blockHeight) - } -} - -func (mw *MultiWallet) publishBlockAttached(walletID int, blockHeight int32) { - mw.notificationListenersMu.RLock() - defer mw.notificationListenersMu.RUnlock() - - for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { - txAndBlockNotifcationListener.OnBlockAttached(walletID, blockHeight) - } -} diff --git a/types.go b/types.go index 899ff1015..bfaecbb9a 100644 --- a/types.go +++ b/types.go @@ -36,10 +36,10 @@ type CSPPConfig struct { ChangeAccount uint32 } -type WalletsIterator struct { - currentIndex int - wallets []*Wallet -} +// type WalletsIterator struct { +// currentIndex int +// wallets []*Wallet +// } type BlockInfo struct { Height int32 diff --git a/utils.go b/utils.go index 9e4068919..68387a7e2 100644 --- a/utils.go +++ b/utils.go @@ -26,6 +26,7 @@ import ( "github.com/decred/dcrd/hdkeychain/v3" "github.com/decred/dcrd/wire" "github.com/planetdecred/dcrlibwallet/internal/loader" + ) const ( @@ -63,15 +64,6 @@ func (mw *MultiWallet) RequiredConfirmations() int32 { return DefaultRequiredConfirmations } -func (wallet *Wallet) RequiredConfirmations() int32 { - var spendUnconfirmed bool - wallet.readUserConfigValue(true, SpendUnconfirmedConfigKey, &spendUnconfirmed) - if spendUnconfirmed { - return 0 - } - return DefaultRequiredConfirmations -} - func (mw *MultiWallet) listenForShutdown() { mw.cancelFuncs = make([]context.CancelFunc, 0) @@ -84,17 +76,6 @@ func (mw *MultiWallet) listenForShutdown() { }() } -func (wallet *Wallet) shutdownContextWithCancel() (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(context.Background()) - wallet.cancelFuncs = append(wallet.cancelFuncs, cancel) - return ctx, cancel -} - -func (wallet *Wallet) shutdownContext() (ctx context.Context) { - ctx, _ = wallet.shutdownContextWithCancel() - return -} - func (mw *MultiWallet) contextWithShutdownCancel() (context.Context, context.CancelFunc) { ctx, cancel := context.WithCancel(context.Background()) mw.cancelFuncs = append(mw.cancelFuncs, cancel) @@ -207,12 +188,12 @@ func ShannonEntropy(text string) (entropy float64) { func TransactionDirectionName(direction int32) string { switch direction { - case TxDirectionSent: - return "Sent" - case TxDirectionReceived: - return "Received" - case TxDirectionTransferred: - return "Yourself" + // case TxDirectionSent: + // return "Sent" + // case TxDirectionReceived: + // return "Received" + // case TxDirectionTransferred: + // return "Yourself" default: return "invalid" } diff --git a/vsp.go b/vsp.go deleted file mode 100644 index 95eb37224..000000000 --- a/vsp.go +++ /dev/null @@ -1,192 +0,0 @@ -package dcrlibwallet - -import ( - "context" - "crypto/ed25519" - "encoding/base64" - "fmt" - "strings" - - "decred.org/dcrwallet/v2/errors" - "github.com/planetdecred/dcrlibwallet/internal/vsp" -) - -// VSPClient loads or creates a VSP client instance for the specified host. -func (wallet *Wallet) VSPClient(host string, pubKey []byte) (*vsp.Client, error) { - wallet.vspClientsMu.Lock() - defer wallet.vspClientsMu.Unlock() - client, ok := wallet.vspClients[host] - if ok { - return client, nil - } - - cfg := vsp.Config{ - URL: host, - PubKey: base64.StdEncoding.EncodeToString(pubKey), - Dialer: nil, // optional, but consider providing a value - Wallet: wallet.Internal(), - } - client, err := vsp.New(cfg) - if err != nil { - return nil, err - } - wallet.vspClients[host] = client - return client, nil -} - -// KnownVSPs returns a list of known VSPs. This list may be updated by calling -// ReloadVSPList. This method is safe for concurrent access. -func (mw *MultiWallet) KnownVSPs() []*VSP { - mw.vspMu.RLock() - defer mw.vspMu.RUnlock() - return mw.vsps // TODO: Return a copy. -} - -// SaveVSP marks a VSP as known and will be susbequently included as part of -// known VSPs. -func (mw *MultiWallet) SaveVSP(host string) (err error) { - // check if host already exists - vspDbData := mw.getVSPDBData() - for _, savedHost := range vspDbData.SavedHosts { - if savedHost == host { - return fmt.Errorf("duplicate host %s", host) - } - } - - // validate host network - info, err := vspInfo(host) - if err != nil { - return err - } - - // TODO: defaultVSPs() uses strings.Contains(network, vspInfo.Network). - if info.Network != mw.NetType() { - return fmt.Errorf("invalid net %s", info.Network) - } - - vspDbData.SavedHosts = append(vspDbData.SavedHosts, host) - mw.updateVSPDBData(vspDbData) - - mw.vspMu.Lock() - mw.vsps = append(mw.vsps, &VSP{Host: host, VspInfoResponse: info}) - mw.vspMu.Unlock() - - return -} - -// LastUsedVSP returns the host of the last used VSP, as saved by the -// SaveLastUsedVSP() method. -func (mw *MultiWallet) LastUsedVSP() string { - return mw.getVSPDBData().LastUsedVSP -} - -// SaveLastUsedVSP saves the host of the last used VSP. -func (mw *MultiWallet) SaveLastUsedVSP(host string) { - vspDbData := mw.getVSPDBData() - vspDbData.LastUsedVSP = host - mw.updateVSPDBData(vspDbData) -} - -type vspDbData struct { - SavedHosts []string - LastUsedVSP string -} - -func (mw *MultiWallet) getVSPDBData() *vspDbData { - vspDbData := new(vspDbData) - mw.ReadUserConfigValue(KnownVSPsConfigKey, vspDbData) - return vspDbData -} - -func (mw *MultiWallet) updateVSPDBData(data *vspDbData) { - mw.SaveUserConfigValue(KnownVSPsConfigKey, data) -} - -// ReloadVSPList reloads the list of known VSPs. -// This method makes multiple network calls; should be called in a goroutine -// to prevent blocking the UI thread. -func (mw *MultiWallet) ReloadVSPList(ctx context.Context) { - log.Debugf("Reloading list of known VSPs") - defer log.Debugf("Reloaded list of known VSPs") - - vspDbData := mw.getVSPDBData() - vspList := make(map[string]*VspInfoResponse) - for _, host := range vspDbData.SavedHosts { - vspInfo, err := vspInfo(host) - if err != nil { - // User saved this VSP. Log an error message. - log.Errorf("get vsp info error for %s: %v", host, err) - } else { - vspList[host] = vspInfo - } - if ctx.Err() != nil { - return // context canceled, abort - } - } - - otherVSPHosts, err := defaultVSPs(mw.NetType()) - if err != nil { - log.Debugf("get default vsp list error: %v", err) - } - for _, host := range otherVSPHosts { - if _, wasAdded := vspList[host]; wasAdded { - continue - } - vspInfo, err := vspInfo(host) - if err != nil { - log.Debugf("vsp info error for %s: %v\n", host, err) // debug only, user didn't request this VSP - } else { - vspList[host] = vspInfo - } - if ctx.Err() != nil { - return // context canceled, abort - } - } - - mw.vspMu.Lock() - mw.vsps = make([]*VSP, 0, len(vspList)) - for host, info := range vspList { - mw.vsps = append(mw.vsps, &VSP{Host: host, VspInfoResponse: info}) - } - mw.vspMu.Unlock() -} - -func vspInfo(vspHost string) (*VspInfoResponse, error) { - vspInfoResponse := new(VspInfoResponse) - resp, respBytes, err := HttpGet(vspHost+"/api/v3/vspinfo", vspInfoResponse) - if err != nil { - return nil, err - } - - // Validate server response. - sigStr := resp.Header.Get("VSP-Server-Signature") - sig, err := base64.StdEncoding.DecodeString(sigStr) - if err != nil { - return nil, fmt.Errorf("error validating VSP signature: %v", err) - } - if !ed25519.Verify(vspInfoResponse.PubKey, respBytes, sig) { - return nil, errors.New("bad signature from VSP") - } - - return vspInfoResponse, nil -} - -// defaultVSPs returns a list of known VSPs. -func defaultVSPs(network string) ([]string, error) { - var vspInfoResponse map[string]*VspInfoResponse - _, _, err := HttpGet("https://api.decred.org/?c=vsp", &vspInfoResponse) - if err != nil { - return nil, err - } - - // The above API does not return the pubKeys for the - // VSPs. Only return the host since we'll still need - // to make another API call to get the VSP pubKeys. - vsps := make([]string, 0) - for url, vspInfo := range vspInfoResponse { - if strings.Contains(network, vspInfo.Network) { - vsps = append(vsps, "https://"+url) - } - } - return vsps, nil -} diff --git a/wallets.go b/wallets.go index c5f97cc7b..7bb87906a 100644 --- a/wallets.go +++ b/wallets.go @@ -1,29 +1,29 @@ package dcrlibwallet -func (mw *MultiWallet) AllWallets() (wallets []*Wallet) { - for _, wallet := range mw.wallets { - wallets = append(wallets, wallet) - } - return wallets -} +// func (mw *MultiWallet) AllWallets() (wallets []*Wallet) { +// for _, wallet := range mw.wallets { +// wallets = append(wallets, wallet) +// } +// return wallets +// } -func (mw *MultiWallet) WalletsIterator() *WalletsIterator { - return &WalletsIterator{ - currentIndex: 0, - wallets: mw.AllWallets(), - } -} +// func (mw *MultiWallet) WalletsIterator() *WalletsIterator { +// return &WalletsIterator{ +// currentIndex: 0, +// wallets: mw.AllWallets(), +// } +// } -func (walletsIterator *WalletsIterator) Next() *Wallet { - if walletsIterator.currentIndex < len(walletsIterator.wallets) { - wallet := walletsIterator.wallets[walletsIterator.currentIndex] - walletsIterator.currentIndex++ - return wallet - } +// func (walletsIterator *WalletsIterator) Next() *Wallet { +// if walletsIterator.currentIndex < len(walletsIterator.wallets) { +// wallet := walletsIterator.wallets[walletsIterator.currentIndex] +// walletsIterator.currentIndex++ +// return wallet +// } - return nil -} +// return nil +// } -func (walletsIterator *WalletsIterator) Reset() { - walletsIterator.currentIndex = 0 -} +// func (walletsIterator *WalletsIterator) Reset() { +// walletsIterator.currentIndex = 0 +// } diff --git a/account_mixer.go b/wallets/dcr/account_mixer.go similarity index 79% rename from account_mixer.go rename to wallets/dcr/account_mixer.go index 957fc5807..620648171 100644 --- a/account_mixer.go +++ b/wallets/dcr/account_mixer.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "context" @@ -23,23 +23,23 @@ const ( MixedAccountBranch = int32(udb.ExternalBranch) ) -func (mw *MultiWallet) AddAccountMixerNotificationListener(accountMixerNotificationListener AccountMixerNotificationListener, uniqueIdentifier string) error { - mw.notificationListenersMu.Lock() - defer mw.notificationListenersMu.Unlock() +func (wallet *Wallet) AddAccountMixerNotificationListener(accountMixerNotificationListener AccountMixerNotificationListener, uniqueIdentifier string) error { + wallet.notificationListenersMu.Lock() + defer wallet.notificationListenersMu.Unlock() - if _, ok := mw.accountMixerNotificationListener[uniqueIdentifier]; ok { + if _, ok := wallet.accountMixerNotificationListener[uniqueIdentifier]; ok { return errors.New(ErrListenerAlreadyExist) } - mw.accountMixerNotificationListener[uniqueIdentifier] = accountMixerNotificationListener + wallet.accountMixerNotificationListener[uniqueIdentifier] = accountMixerNotificationListener return nil } -func (mw *MultiWallet) RemoveAccountMixerNotificationListener(uniqueIdentifier string) { - mw.notificationListenersMu.Lock() - defer mw.notificationListenersMu.Unlock() +func (wallet *Wallet) RemoveAccountMixerNotificationListener(uniqueIdentifier string) { + wallet.notificationListenersMu.Lock() + defer wallet.notificationListenersMu.Unlock() - delete(mw.accountMixerNotificationListener, uniqueIdentifier) + delete(wallet.accountMixerNotificationListener, uniqueIdentifier) } // CreateMixerAccounts creates the two accounts needed for the account mixer. This function @@ -133,8 +133,7 @@ func (wallet *Wallet) ClearMixerConfig() { wallet.SetBoolConfigValueForKey(AccountMixerConfigSet, false) } -func (mw *MultiWallet) ReadyToMix(walletID int) (bool, error) { - wallet := mw.WalletWithID(walletID) +func (wallet *Wallet) ReadyToMix(walletID int) (bool, error) { if wallet == nil { return false, errors.New(ErrNotExist) } @@ -150,12 +149,11 @@ func (mw *MultiWallet) ReadyToMix(walletID int) (bool, error) { } // StartAccountMixer starts the automatic account mixer -func (mw *MultiWallet) StartAccountMixer(walletID int, walletPassphrase string) error { - if !mw.IsConnectedToDecredNetwork() { +func (wallet *Wallet) StartAccountMixer(walletID int, walletPassphrase string) error { + if !wallet.IsConnectedToDecredNetwork() { return errors.New(ErrNotConnected) } - wallet := mw.WalletWithID(walletID) if wallet == nil { return errors.New(ErrNotExist) } @@ -192,20 +190,20 @@ func (mw *MultiWallet) StartAccountMixer(walletID int, walletPassphrase string) go func() { log.Info("Running account mixer") - if mw.accountMixerNotificationListener != nil { - mw.publishAccountMixerStarted(walletID) + if wallet.accountMixerNotificationListener != nil { + wallet.publishAccountMixerStarted(walletID) } - ctx, cancel := mw.contextWithShutdownCancel() - wallet.cancelAccountMixer = cancel + ctx, cancel := wallet.contextWithShutdownCancel() + wallet.CancelAccountMixer = cancel err = tb.Run(ctx, []byte(walletPassphrase)) if err != nil { log.Errorf("AccountMixer instance errored: %v", err) } - wallet.cancelAccountMixer = nil - if mw.accountMixerNotificationListener != nil { - mw.publishAccountMixerEnded(walletID) + wallet.CancelAccountMixer = nil + if wallet.accountMixerNotificationListener != nil { + wallet.publishAccountMixerEnded(walletID) } }() @@ -256,19 +254,17 @@ func (wallet *Wallet) readCSPPConfig() *CSPPConfig { } // StopAccountMixer stops the active account mixer -func (mw *MultiWallet) StopAccountMixer(walletID int) error { - - wallet := mw.WalletWithID(walletID) +func (wallet *Wallet) StopAccountMixer(walletID int) error { if wallet == nil { return errors.New(ErrNotExist) } - if wallet.cancelAccountMixer == nil { + if wallet.CancelAccountMixer == nil { return errors.New(ErrInvalid) } - wallet.cancelAccountMixer() - wallet.cancelAccountMixer = nil + wallet.CancelAccountMixer() + wallet.CancelAccountMixer = nil return nil } @@ -281,7 +277,7 @@ func (wallet *Wallet) accountHasMixableOutput(accountNumber int32) (bool, error) // fetch all utxos in account to extract details for the utxos selected by user // use targetAmount = 0 to fetch ALL utxos in account - inputDetail, err := wallet.Internal().SelectInputs(wallet.shutdownContext(), dcrutil.Amount(0), policy) + inputDetail, err := wallet.Internal().SelectInputs(wallet.ShutdownContext(), dcrutil.Amount(0), policy) if err != nil { return false, nil } @@ -300,7 +296,7 @@ func (wallet *Wallet) accountHasMixableOutput(accountNumber int32) (bool, error) return hasMixableOutput, nil } - lockedOutpoints, err := wallet.Internal().LockedOutpoints(wallet.shutdownContext(), accountName) + lockedOutpoints, err := wallet.Internal().LockedOutpoints(wallet.ShutdownContext(), accountName) if err != nil { return hasMixableOutput, nil } @@ -312,23 +308,23 @@ func (wallet *Wallet) accountHasMixableOutput(accountNumber int32) (bool, error) // IsAccountMixerActive returns true if account mixer is active func (wallet *Wallet) IsAccountMixerActive() bool { - return wallet.cancelAccountMixer != nil + return wallet.CancelAccountMixer != nil } -func (mw *MultiWallet) publishAccountMixerStarted(walletID int) { - mw.notificationListenersMu.RLock() - defer mw.notificationListenersMu.RUnlock() +func (wallet *Wallet) publishAccountMixerStarted(walletID int) { + wallet.notificationListenersMu.RLock() + defer wallet.notificationListenersMu.RUnlock() - for _, accountMixerNotificationListener := range mw.accountMixerNotificationListener { + for _, accountMixerNotificationListener := range wallet.accountMixerNotificationListener { accountMixerNotificationListener.OnAccountMixerStarted(walletID) } } -func (mw *MultiWallet) publishAccountMixerEnded(walletID int) { - mw.notificationListenersMu.RLock() - defer mw.notificationListenersMu.RUnlock() +func (wallet *Wallet) publishAccountMixerEnded(walletID int) { + wallet.notificationListenersMu.RLock() + defer wallet.notificationListenersMu.RUnlock() - for _, accountMixerNotificationListener := range mw.accountMixerNotificationListener { + for _, accountMixerNotificationListener := range wallet.accountMixerNotificationListener { accountMixerNotificationListener.OnAccountMixerEnded(walletID) } } diff --git a/accounts.go b/wallets/dcr/accounts.go similarity index 90% rename from accounts.go rename to wallets/dcr/accounts.go index 4fe3e2624..05ad1d851 100644 --- a/accounts.go +++ b/wallets/dcr/accounts.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "encoding/json" @@ -31,7 +31,7 @@ func (wallet *Wallet) GetAccounts() (string, error) { } func (wallet *Wallet) GetAccountsRaw() (*Accounts, error) { - resp, err := wallet.Internal().Accounts(wallet.shutdownContext()) + resp, err := wallet.Internal().Accounts(wallet.ShutdownContext()) if err != nil { return nil, err } @@ -105,7 +105,7 @@ func (wallet *Wallet) GetAccount(accountNumber int32) (*Account, error) { } func (wallet *Wallet) GetAccountBalance(accountNumber int32) (*Balance, error) { - balance, err := wallet.Internal().AccountBalance(wallet.shutdownContext(), uint32(accountNumber), wallet.RequiredConfirmations()) + balance, err := wallet.Internal().AccountBalance(wallet.ShutdownContext(), uint32(accountNumber), wallet.RequiredConfirmations()) if err != nil { return nil, err } @@ -122,7 +122,7 @@ func (wallet *Wallet) GetAccountBalance(accountNumber int32) (*Balance, error) { } func (wallet *Wallet) SpendableForAccount(account int32) (int64, error) { - bals, err := wallet.Internal().AccountBalance(wallet.shutdownContext(), uint32(account), wallet.RequiredConfirmations()) + bals, err := wallet.Internal().AccountBalance(wallet.ShutdownContext(), uint32(account), wallet.RequiredConfirmations()) if err != nil { log.Error(err) return 0, translateError(err) @@ -138,7 +138,7 @@ func (wallet *Wallet) UnspentOutputs(account int32) ([]*UnspentOutput, error) { // fetch all utxos in account to extract details for the utxos selected by user // use targetAmount = 0 to fetch ALL utxos in account - inputDetail, err := wallet.Internal().SelectInputs(wallet.shutdownContext(), dcrutil.Amount(0), policy) + inputDetail, err := wallet.Internal().SelectInputs(wallet.ShutdownContext(), dcrutil.Amount(0), policy) if err != nil { return nil, err @@ -147,7 +147,7 @@ func (wallet *Wallet) UnspentOutputs(account int32) ([]*UnspentOutput, error) { unspentOutputs := make([]*UnspentOutput, len(inputDetail.Inputs)) for i, input := range inputDetail.Inputs { - outputInfo, err := wallet.Internal().OutputInfo(wallet.shutdownContext(), &input.PreviousOutPoint) + outputInfo, err := wallet.Internal().OutputInfo(wallet.ShutdownContext(), &input.PreviousOutPoint) if err != nil { return nil, err } @@ -197,7 +197,7 @@ func (wallet *Wallet) NextAccount(accountName string) (int32, error) { return -1, errors.New(ErrWalletLocked) } - ctx := wallet.shutdownContext() + ctx := wallet.ShutdownContext() accountNumber, err := wallet.Internal().NextAccount(ctx, accountName) if err != nil { @@ -208,7 +208,7 @@ func (wallet *Wallet) NextAccount(accountName string) (int32, error) { } func (wallet *Wallet) RenameAccount(accountNumber int32, newName string) error { - err := wallet.Internal().RenameAccount(wallet.shutdownContext(), uint32(accountNumber), newName) + err := wallet.Internal().RenameAccount(wallet.ShutdownContext(), uint32(accountNumber), newName) if err != nil { return translateError(err) } @@ -225,21 +225,21 @@ func (wallet *Wallet) AccountName(accountNumber int32) (string, error) { } func (wallet *Wallet) AccountNameRaw(accountNumber uint32) (string, error) { - return wallet.Internal().AccountName(wallet.shutdownContext(), accountNumber) + return wallet.Internal().AccountName(wallet.ShutdownContext(), accountNumber) } func (wallet *Wallet) AccountNumber(accountName string) (int32, error) { - accountNumber, err := wallet.Internal().AccountNumber(wallet.shutdownContext(), accountName) + accountNumber, err := wallet.Internal().AccountNumber(wallet.ShutdownContext(), accountName) return int32(accountNumber), translateError(err) } func (wallet *Wallet) HasAccount(accountName string) bool { - _, err := wallet.Internal().AccountNumber(wallet.shutdownContext(), accountName) + _, err := wallet.Internal().AccountNumber(wallet.ShutdownContext(), accountName) return err == nil } func (wallet *Wallet) HDPathForAccount(accountNumber int32) (string, error) { - cointype, err := wallet.Internal().CoinType(wallet.shutdownContext()) + cointype, err := wallet.Internal().CoinType(wallet.ShutdownContext()) if err != nil { return "", translateError(err) } diff --git a/address.go b/wallets/dcr/address.go similarity index 86% rename from address.go rename to wallets/dcr/address.go index c42b6cf8b..9a69620c6 100644 --- a/address.go +++ b/wallets/dcr/address.go @@ -1,10 +1,11 @@ -package dcrlibwallet +package dcr import ( "fmt" "decred.org/dcrwallet/v2/errors" w "decred.org/dcrwallet/v2/wallet" + "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/txscript/v4/stdaddr" ) @@ -17,8 +18,8 @@ type AddressInfo struct { AccountName string } -func (mw *MultiWallet) IsAddressValid(address string) bool { - _, err := stdaddr.DecodeAddress(address, mw.chainParams) +func (wallet *Wallet) IsAddressValid(address string, chainParams *chaincfg.Params) bool { + _, err := stdaddr.DecodeAddress(address, chainParams) return err == nil } @@ -28,7 +29,7 @@ func (wallet *Wallet) HaveAddress(address string) bool { return false } - have, err := wallet.Internal().HaveAddress(wallet.shutdownContext(), addr) + have, err := wallet.Internal().HaveAddress(wallet.ShutdownContext(), addr) if err != nil { return false } @@ -42,7 +43,7 @@ func (wallet *Wallet) AccountOfAddress(address string) (string, error) { return "", translateError(err) } - a, err := wallet.Internal().KnownAddress(wallet.shutdownContext(), addr) + a, err := wallet.Internal().KnownAddress(wallet.ShutdownContext(), addr) if err != nil { return "", translateError(err) } @@ -60,7 +61,7 @@ func (wallet *Wallet) AddressInfo(address string) (*AddressInfo, error) { Address: address, } - known, _ := wallet.Internal().KnownAddress(wallet.shutdownContext(), addr) + known, _ := wallet.Internal().KnownAddress(wallet.ShutdownContext(), addr) if known != nil { addressInfo.IsMine = true addressInfo.AccountName = known.AccountName() @@ -104,7 +105,7 @@ func (wallet *Wallet) NextAddress(account int32) (string, error) { // the newly incremented index) is returned below by CurrentAddress. // NOTE: This workaround will be unnecessary once this anomaly is corrected // upstream. - _, err := wallet.Internal().NewExternalAddress(wallet.shutdownContext(), uint32(account), w.WithGapPolicyWrap()) + _, err := wallet.Internal().NewExternalAddress(wallet.ShutdownContext(), uint32(account), w.WithGapPolicyWrap()) if err != nil { log.Errorf("NewExternalAddress error: %w", err) return "", err @@ -119,7 +120,7 @@ func (wallet *Wallet) AddressPubKey(address string) (string, error) { return "", err } - known, err := wallet.Internal().KnownAddress(wallet.shutdownContext(), addr) + known, err := wallet.Internal().KnownAddress(wallet.ShutdownContext(), addr) if err != nil { return "", err } diff --git a/consensus.go b/wallets/dcr/consensus.go similarity index 98% rename from consensus.go rename to wallets/dcr/consensus.go index baae75db5..9a353614b 100644 --- a/consensus.go +++ b/wallets/dcr/consensus.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "fmt" @@ -101,7 +101,7 @@ func (wallet *Wallet) SetVoteChoice(agendaID, choiceID, hash string, passphrase } defer wallet.LockWallet() - ctx := wallet.shutdownContext() + ctx := wallet.ShutdownContext() // get choices choices, _, err := wallet.Internal().AgendaChoices(ctx, ticketHash) // returns saved prefs for current agendas @@ -208,7 +208,7 @@ func (wallet *Wallet) AllVoteAgendas(hash string, newestFirst bool) ([]*Agenda, ticketHash = hash } - ctx := wallet.shutdownContext() + ctx := wallet.ShutdownContext() choices, _, err := wallet.Internal().AgendaChoices(ctx, ticketHash) // returns saved prefs for current agendas if err != nil { return nil, err diff --git a/decodetx.go b/wallets/dcr/decodetx.go similarity index 99% rename from decodetx.go rename to wallets/dcr/decodetx.go index effea74c9..20b4b60c3 100644 --- a/decodetx.go +++ b/wallets/dcr/decodetx.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "fmt" diff --git a/wallets/dcr/errors.go b/wallets/dcr/errors.go new file mode 100644 index 000000000..7bec27cfb --- /dev/null +++ b/wallets/dcr/errors.go @@ -0,0 +1,61 @@ +package dcr + +import ( + "decred.org/dcrwallet/v2/errors" + "github.com/asdine/storm" +) + +const ( + // Error Codes + ErrInsufficientBalance = "insufficient_balance" + ErrInvalid = "invalid" + ErrWalletLocked = "wallet_locked" + ErrWalletDatabaseInUse = "wallet_db_in_use" + ErrWalletNotLoaded = "wallet_not_loaded" + ErrWalletNotFound = "wallet_not_found" + ErrWalletNameExist = "wallet_name_exists" + ErrReservedWalletName = "wallet_name_reserved" + ErrWalletIsRestored = "wallet_is_restored" + ErrWalletIsWatchOnly = "watch_only_wallet" + ErrUnusableSeed = "unusable_seed" + ErrPassphraseRequired = "passphrase_required" + ErrInvalidPassphrase = "invalid_passphrase" + ErrNotConnected = "not_connected" + ErrExist = "exists" + ErrNotExist = "not_exists" + ErrEmptySeed = "empty_seed" + ErrInvalidAddress = "invalid_address" + ErrInvalidAuth = "invalid_auth" + ErrUnavailable = "unavailable" + ErrContextCanceled = "context_canceled" + ErrFailedPrecondition = "failed_precondition" + ErrSyncAlreadyInProgress = "sync_already_in_progress" + ErrNoPeers = "no_peers" + ErrInvalidPeers = "invalid_peers" + ErrListenerAlreadyExist = "listener_already_exist" + ErrLoggerAlreadyRegistered = "logger_already_registered" + ErrLogRotatorAlreadyInitialized = "log_rotator_already_initialized" + ErrAddressDiscoveryNotDone = "address_discovery_not_done" + ErrChangingPassphrase = "err_changing_passphrase" + ErrSavingWallet = "err_saving_wallet" + ErrIndexOutOfRange = "err_index_out_of_range" + ErrNoMixableOutput = "err_no_mixable_output" + ErrInvalidVoteBit = "err_invalid_vote_bit" +) + +// todo, should update this method to translate more error kinds. +func translateError(err error) error { + if err, ok := err.(*errors.Error); ok { + switch err.Kind { + case errors.InsufficientBalance: + return errors.New(ErrInsufficientBalance) + case errors.NotExist, storm.ErrNotFound: + return errors.New(ErrNotExist) + case errors.Passphrase: + return errors.New(ErrInvalidPassphrase) + case errors.NoPeers: + return errors.New(ErrNoPeers) + } + } + return err +} diff --git a/wallets/dcr/log.go b/wallets/dcr/log.go new file mode 100644 index 000000000..5b13d7839 --- /dev/null +++ b/wallets/dcr/log.go @@ -0,0 +1,176 @@ +// Copyright (c) 2013-2017 The btcsuite developers +// Copyright (c) 2015-2018 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package dcr + +import ( + "os" + + "decred.org/dcrwallet/v2/errors" + "decred.org/dcrwallet/v2/p2p" + "decred.org/dcrwallet/v2/ticketbuyer" + "decred.org/dcrwallet/v2/wallet" + "decred.org/dcrwallet/v2/wallet/udb" + "github.com/decred/dcrd/addrmgr/v2" + "github.com/decred/dcrd/connmgr/v3" + "github.com/decred/slog" + "github.com/jrick/logrotate/rotator" + "github.com/planetdecred/dcrlibwallet/internal/loader" + "github.com/planetdecred/dcrlibwallet/internal/vsp" + "github.com/planetdecred/dcrlibwallet/spv" +) + +// logWriter implements an io.Writer that outputs to both standard output and +// the write-end pipe of an initialized log rotator. +type logWriter struct{} + +func (logWriter) Write(p []byte) (n int, err error) { + os.Stdout.Write(p) + logRotator.Write(p) + return len(p), nil +} + +// Loggers per subsystem. A single backend logger is created and all subsytem +// loggers created from it will write to the backend. When adding new +// subsystems, add the subsystem logger variable here and to the +// subsystemLoggers map. +// +// Loggers can not be used before the log rotator has been initialized with a +// log file. This must be performed early during application startup by calling +// initLogRotator. +var ( + // backendLog is the logging backend used to create all subsystem loggers. + // The backend must not be used before the log rotator has been initialized, + // or data races and/or nil pointer dereferences will occur. + backendLog = slog.NewBackend(logWriter{}) + + // logRotator is one of the logging outputs. It should be closed on + // application shutdown. + logRotator *rotator.Rotator + + log = backendLog.Logger("DLWL") + loaderLog = backendLog.Logger("LODR") + walletLog = backendLog.Logger("WLLT") + tkbyLog = backendLog.Logger("TKBY") + syncLog = backendLog.Logger("SYNC") + grpcLog = backendLog.Logger("GRPC") + legacyRPCLog = backendLog.Logger("RPCS") + cmgrLog = backendLog.Logger("CMGR") + amgrLog = backendLog.Logger("AMGR") + vspcLog = backendLog.Logger("VSPC") +) + +// Initialize package-global logger variables. +func init() { + loader.UseLogger(loaderLog) + wallet.UseLogger(walletLog) + udb.UseLogger(walletLog) + ticketbuyer.UseLogger(tkbyLog) + spv.UseLogger(syncLog) + p2p.UseLogger(syncLog) + connmgr.UseLogger(cmgrLog) + addrmgr.UseLogger(amgrLog) + vsp.UseLogger(vspcLog) +} + +// subsystemLoggers maps each subsystem identifier to its associated logger. +var subsystemLoggers = map[string]slog.Logger{ + "DLWL": log, + "LODR": loaderLog, + "WLLT": walletLog, + "TKBY": tkbyLog, + "SYNC": syncLog, + "GRPC": grpcLog, + "RPCS": legacyRPCLog, + "CMGR": cmgrLog, + "AMGR": amgrLog, + "VSPC": vspcLog, +} + +// initLogRotator initializes the logging rotater to write logs to logFile and +// create roll files in the same directory. It must be called before the +// package-global log rotater variables are used. +func initLogRotator(logFile string) error { + r, err := rotator.New(logFile, 10*1024, false, 3) + if err != nil { + return errors.Errorf("failed to create file rotator: %v", err) + } + + logRotator = r + return nil +} + +// UseLoggers sets the subsystem logs to use the provided loggers. +func UseLoggers(main, loaderLog, walletLog, tkbyLog, + syncLog, cmgrLog, amgrLog slog.Logger) { + log = main + loader.UseLogger(loaderLog) + wallet.UseLogger(walletLog) + udb.UseLogger(walletLog) + ticketbuyer.UseLogger(tkbyLog) + spv.UseLogger(syncLog) + p2p.UseLogger(syncLog) + connmgr.UseLogger(cmgrLog) + addrmgr.UseLogger(amgrLog) +} + +// UseLogger sets the subsystem logs to use the provided logger. +func UseLogger(logger slog.Logger) { + UseLoggers(logger, logger, logger, logger, logger, logger, logger) +} + +// RegisterLogger should be called before logRotator is initialized. +func RegisterLogger(tag string) (slog.Logger, error) { + if logRotator != nil { + return nil, errors.E(ErrLogRotatorAlreadyInitialized) + } + + if _, exists := subsystemLoggers[tag]; exists { + return nil, errors.E(ErrLoggerAlreadyRegistered) + } + + logger := backendLog.Logger(tag) + subsystemLoggers[tag] = logger + + return logger, nil +} + +func SetLogLevels(logLevel string) { + _, ok := slog.LevelFromString(logLevel) + if !ok { + return + } + + // Configure all sub-systems with the new logging level. Dynamically + // create loggers as needed. + for subsystemID := range subsystemLoggers { + setLogLevel(subsystemID, logLevel) + } +} + +// setLogLevel sets the logging level for provided subsystem. Invalid +// subsystems are ignored. Uninitialized subsystems are dynamically created as +// needed. +func setLogLevel(subsystemID string, logLevel string) { + // Ignore invalid subsystems. + logger, ok := subsystemLoggers[subsystemID] + if !ok { + return + } + + // Defaults to info if the log level is invalid. + level, _ := slog.LevelFromString(logLevel) + logger.SetLevel(level) +} + +// Log writes a message to the log using LevelInfo. +func Log(m string) { + log.Info(m) +} + +// LogT writes a tagged message to the log using LevelInfo. +func LogT(tag, m string) { + log.Infof("%s: %s", tag, m) +} diff --git a/message.go b/wallets/dcr/message.go similarity index 73% rename from message.go rename to wallets/dcr/message.go index 989a1c045..5d21da557 100644 --- a/message.go +++ b/wallets/dcr/message.go @@ -1,9 +1,11 @@ -package dcrlibwallet +package dcr import ( "decred.org/dcrwallet/v2/errors" w "decred.org/dcrwallet/v2/wallet" "github.com/decred/dcrd/txscript/v4/stdaddr" + "github.com/decred/dcrd/chaincfg/v3" + ) func (wallet *Wallet) SignMessage(passphrase []byte, address string, message string) ([]byte, error) { @@ -13,10 +15,10 @@ func (wallet *Wallet) SignMessage(passphrase []byte, address string, message str } defer wallet.LockWallet() - return wallet.signMessage(address, message) + return wallet.SignMessageDirect(address, message) } -func (wallet *Wallet) signMessage(address string, message string) ([]byte, error) { +func (wallet *Wallet) SignMessageDirect(address string, message string) ([]byte, error) { addr, err := stdaddr.DecodeAddress(address, wallet.chainParams) if err != nil { return nil, translateError(err) @@ -31,7 +33,7 @@ func (wallet *Wallet) signMessage(address string, message string) ([]byte, error return nil, errors.New(ErrInvalidAddress) } - sig, err := wallet.Internal().SignMessage(wallet.shutdownContext(), message, addr) + sig, err := wallet.Internal().SignMessage(wallet.ShutdownContext(), message, addr) if err != nil { return nil, translateError(err) } @@ -39,10 +41,10 @@ func (wallet *Wallet) signMessage(address string, message string) ([]byte, error return sig, nil } -func (mw *MultiWallet) VerifyMessage(address string, message string, signatureBase64 string) (bool, error) { +func (wallet *Wallet) VerifyMessage(address string, message string, signatureBase64 string, chainParams *chaincfg.Params) (bool, error) { var valid bool - addr, err := stdaddr.DecodeAddress(address, mw.chainParams) + addr, err := stdaddr.DecodeAddress(address, chainParams) if err != nil { return false, translateError(err) } @@ -61,7 +63,7 @@ func (mw *MultiWallet) VerifyMessage(address string, message string, signatureBa return false, errors.New(ErrInvalidAddress) } - valid, err = w.VerifyMessage(message, addr, signature, mw.chainParams) + valid, err = w.VerifyMessage(message, addr, signature, chainParams) if err != nil { return false, translateError(err) } diff --git a/wallets/dcr/multiwallet_config.go b/wallets/dcr/multiwallet_config.go new file mode 100644 index 000000000..b4b33a166 --- /dev/null +++ b/wallets/dcr/multiwallet_config.go @@ -0,0 +1,152 @@ +package dcr + +import ( + // "github.com/asdine/storm" +) + +const ( + userConfigBucketName = "user_config" + + LogLevelConfigKey = "log_level" + + SpendUnconfirmedConfigKey = "spend_unconfirmed" + CurrencyConversionConfigKey = "currency_conversion_option" + + IsStartupSecuritySetConfigKey = "startup_security_set" + StartupSecurityTypeConfigKey = "startup_security_type" + UseBiometricConfigKey = "use_biometric" + + IncomingTxNotificationsConfigKey = "tx_notification_enabled" + BeepNewBlocksConfigKey = "beep_new_blocks" + + SyncOnCellularConfigKey = "always_sync" + NetworkModeConfigKey = "network_mode" + SpvPersistentPeerAddressesConfigKey = "spv_peer_addresses" + UserAgentConfigKey = "user_agent" + + PoliteiaNotificationConfigKey = "politeia_notification" + + LastTxHashConfigKey = "last_tx_hash" + + KnownVSPsConfigKey = "known_vsps" + + TicketBuyerVSPHostConfigKey = "tb_vsp_host" + TicketBuyerWalletConfigKey = "tb_wallet_id" + TicketBuyerAccountConfigKey = "tb_account_number" + TicketBuyerATMConfigKey = "tb_amount_to_maintain" + + PassphraseTypePin int32 = 0 + PassphraseTypePass int32 = 1 +) + +type configSaveFn = func(key string, value interface{}) error +type configReadFn = func(multiwallet bool, key string, valueOut interface{}) error + +// func (mw *MultiWallet) walletConfigSetFn(walletID int) configSaveFn { +// return func(key string, value interface{}) error { +// walletUniqueKey := WalletUniqueConfigKey(walletID, key) +// return mw.db.Set(userConfigBucketName, walletUniqueKey, value) +// } +// } + +// func (mw *MultiWallet) walletConfigReadFn(walletID int) configReadFn { +// return func(multiwallet bool, key string, valueOut interface{}) error { +// if !multiwallet { +// key = WalletUniqueConfigKey(walletID, key) +// } +// return mw.db.Get(userConfigBucketName, key, valueOut) +// } +// } + +// func (mw *MultiWallet) SaveUserConfigValue(key string, value interface{}) { +// err := mw.db.Set(userConfigBucketName, key, value) +// if err != nil { +// log.Errorf("error setting config value for key: %s, error: %v", key, err) +// } +// } + +// func (mw *MultiWallet) ReadUserConfigValue(key string, valueOut interface{}) error { +// err := mw.db.Get(userConfigBucketName, key, valueOut) +// if err != nil && err != storm.ErrNotFound { +// log.Errorf("error reading config value for key: %s, error: %v", key, err) +// } +// return err +// } + +// func (mw *MultiWallet) DeleteUserConfigValueForKey(key string) { +// err := mw.db.Delete(userConfigBucketName, key) +// if err != nil { +// log.Errorf("error deleting config value for key: %s, error: %v", key, err) +// } +// } + +// func (mw *MultiWallet) ClearConfig() { +// err := mw.db.Drop(userConfigBucketName) +// if err != nil { +// log.Errorf("error deleting config bucket: %v", err) +// } +// } + +// func (mw *MultiWallet) SetBoolConfigValueForKey(key string, value bool) { +// mw.SaveUserConfigValue(key, value) +// } + +// func (mw *MultiWallet) SetDoubleConfigValueForKey(key string, value float64) { +// mw.SaveUserConfigValue(key, value) +// } + +// func (mw *MultiWallet) SetIntConfigValueForKey(key string, value int) { +// mw.SaveUserConfigValue(key, value) +// } + +// func (mw *MultiWallet) SetInt32ConfigValueForKey(key string, value int32) { +// mw.SaveUserConfigValue(key, value) +// } + +// func (mw *MultiWallet) SetLongConfigValueForKey(key string, value int64) { +// mw.SaveUserConfigValue(key, value) +// } + +// func (mw *MultiWallet) SetStringConfigValueForKey(key, value string) { +// mw.SaveUserConfigValue(key, value) +// } + +// func (mw *MultiWallet) ReadBoolConfigValueForKey(key string, defaultValue bool) (valueOut bool) { +// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { +// valueOut = defaultValue +// } +// return +// } + +// func (mw *MultiWallet) ReadDoubleConfigValueForKey(key string, defaultValue float64) (valueOut float64) { +// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { +// valueOut = defaultValue +// } +// return +// } + +// func (mw *MultiWallet) ReadIntConfigValueForKey(key string, defaultValue int) (valueOut int) { +// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { +// valueOut = defaultValue +// } +// return +// } + +// func (mw *MultiWallet) ReadInt32ConfigValueForKey(key string, defaultValue int32) (valueOut int32) { +// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { +// valueOut = defaultValue +// } +// return +// } + +// func (mw *MultiWallet) ReadLongConfigValueForKey(key string, defaultValue int64) (valueOut int64) { +// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { +// valueOut = defaultValue +// } +// return +// } + +// func (mw *MultiWallet) ReadStringConfigValueForKey(key string) (valueOut string) { +// mw.ReadUserConfigValue(key, &valueOut) +// return +// } diff --git a/wallets/dcr/sync.go b/wallets/dcr/sync.go new file mode 100644 index 000000000..fa299fe8b --- /dev/null +++ b/wallets/dcr/sync.go @@ -0,0 +1,489 @@ +package dcr + +import ( + "context" + "encoding/json" + "fmt" + "net" + "sort" + "strings" + "sync" + + "decred.org/dcrwallet/v2/errors" + "decred.org/dcrwallet/v2/p2p" + w "decred.org/dcrwallet/v2/wallet" + "github.com/decred/dcrd/addrmgr/v2" + "github.com/planetdecred/dcrlibwallet/spv" +) + +// reading/writing of properties of this struct are protected by mutex.x +type SyncData struct { + mu sync.RWMutex + + SyncProgressListeners map[string]SyncProgressListener + showLogs bool + + synced bool + syncing bool + cancelSync context.CancelFunc + cancelRescan context.CancelFunc + syncCanceled chan struct{} + + // Flag to notify syncCanceled callback if the sync was canceled so as to be restarted. + restartSyncRequested bool + + rescanning bool + connectedPeers int32 + + *activeSyncData +} + +// reading/writing of properties of this struct are protected by syncData.mu. +type activeSyncData struct { + syncer *spv.Syncer + + syncStage int32 + + cfiltersFetchProgress CFiltersFetchProgressReport + headersFetchProgress HeadersFetchProgressReport + addressDiscoveryProgress AddressDiscoveryProgressReport + headersRescanProgress HeadersRescanProgressReport + + addressDiscoveryCompletedOrCanceled chan bool + + rescanStartTime int64 + + totalInactiveSeconds int64 +} + +const ( + InvalidSyncStage = -1 + CFiltersFetchSyncStage = 0 + HeadersFetchSyncStage = 1 + AddressDiscoverySyncStage = 2 + HeadersRescanSyncStage = 3 +) + +func (wallet *Wallet) initActiveSyncData() { + + cfiltersFetchProgress := CFiltersFetchProgressReport{ + GeneralSyncProgress: &GeneralSyncProgress{}, + beginFetchCFiltersTimeStamp: 0, + startCFiltersHeight: -1, + cfiltersFetchTimeSpent: 0, + totalFetchedCFiltersCount: 0, + } + + headersFetchProgress := HeadersFetchProgressReport{ + GeneralSyncProgress: &GeneralSyncProgress{}, + beginFetchTimeStamp: -1, + headersFetchTimeSpent: -1, + totalFetchedHeadersCount: 0, + } + + addressDiscoveryProgress := AddressDiscoveryProgressReport{ + GeneralSyncProgress: &GeneralSyncProgress{}, + addressDiscoveryStartTime: -1, + totalDiscoveryTimeSpent: -1, + } + + headersRescanProgress := HeadersRescanProgressReport{} + headersRescanProgress.GeneralSyncProgress = &GeneralSyncProgress{} + + wallet.syncData.mu.Lock() + wallet.syncData.activeSyncData = &activeSyncData{ + syncStage: InvalidSyncStage, + + cfiltersFetchProgress: cfiltersFetchProgress, + headersFetchProgress: headersFetchProgress, + addressDiscoveryProgress: addressDiscoveryProgress, + headersRescanProgress: headersRescanProgress, + } + wallet.syncData.mu.Unlock() +} + +func (wallet *Wallet) IsSyncProgressListenerRegisteredFor(uniqueIdentifier string) bool { + wallet.syncData.mu.RLock() + _, exists := wallet.syncData.SyncProgressListeners[uniqueIdentifier] + wallet.syncData.mu.RUnlock() + return exists +} + +func (wallet *Wallet) AddSyncProgressListener(syncProgressListener SyncProgressListener, uniqueIdentifier string) error { + if wallet.IsSyncProgressListenerRegisteredFor(uniqueIdentifier) { + return errors.New(ErrListenerAlreadyExist) + } + + wallet.syncData.mu.Lock() + wallet.syncData.SyncProgressListeners[uniqueIdentifier] = syncProgressListener + wallet.syncData.mu.Unlock() + + // If sync is already on, notify this newly added listener of the current progress report. + return wallet.PublishLastSyncProgress(uniqueIdentifier) +} + +func (wallet *Wallet) RemoveSyncProgressListener(uniqueIdentifier string) { + wallet.syncData.mu.Lock() + delete(wallet.syncData.SyncProgressListeners, uniqueIdentifier) + wallet.syncData.mu.Unlock() +} + +func (wallet *Wallet) syncProgressListeners() []SyncProgressListener { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + + listeners := make([]SyncProgressListener, 0, len(wallet.syncData.SyncProgressListeners)) + for _, listener := range wallet.syncData.SyncProgressListeners { + listeners = append(listeners, listener) + } + + return listeners +} + +func (wallet *Wallet) PublishLastSyncProgress(uniqueIdentifier string) error { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + + syncProgressListener, exists := wallet.syncData.SyncProgressListeners[uniqueIdentifier] + if !exists { + return errors.New(ErrInvalid) + } + + if wallet.syncData.syncing && wallet.syncData.activeSyncData != nil { + switch wallet.syncData.activeSyncData.syncStage { + case HeadersFetchSyncStage: + syncProgressListener.OnHeadersFetchProgress(&wallet.syncData.headersFetchProgress) + case AddressDiscoverySyncStage: + syncProgressListener.OnAddressDiscoveryProgress(&wallet.syncData.addressDiscoveryProgress) + case HeadersRescanSyncStage: + syncProgressListener.OnHeadersRescanProgress(&wallet.syncData.headersRescanProgress) + } + } + + return nil +} + +func (wallet *Wallet) EnableSyncLogs() { + wallet.syncData.mu.Lock() + wallet.syncData.showLogs = true + wallet.syncData.mu.Unlock() +} + +func (wallet *Wallet) SyncInactiveForPeriod(totalInactiveSeconds int64) { + wallet.syncData.mu.Lock() + defer wallet.syncData.mu.Unlock() + + if !wallet.syncData.syncing || wallet.syncData.activeSyncData == nil { + log.Debug("Not accounting for inactive time, wallet is not syncing.") + return + } + + wallet.syncData.totalInactiveSeconds += totalInactiveSeconds + if wallet.syncData.connectedPeers == 0 { + // assume it would take another 60 seconds to reconnect to peers + wallet.syncData.totalInactiveSeconds += 60 + } +} + +func (wallet *Wallet) SpvSync() error { + // prevent an attempt to sync when the previous syncing has not been canceled + if wallet.IsSyncing() || wallet.IsSynced() { + return errors.New(ErrSyncAlreadyInProgress) + } + + addr := &net.TCPAddr{IP: net.ParseIP("::1"), Port: 0} + addrManager := addrmgr.New(wallet.rootDir, net.LookupIP) // TODO: be mindful of tor + lp := p2p.NewLocalPeer(wallet.chainParams, addr, addrManager) + + var validPeerAddresses []string + peerAddresses := wallet.ReadStringConfigValueForKey(SpvPersistentPeerAddressesConfigKey, "") + if peerAddresses != "" { + addresses := strings.Split(peerAddresses, ";") + for _, address := range addresses { + peerAddress, err := NormalizeAddress(address, wallet.chainParams.DefaultPort) + if err != nil { + log.Errorf("SPV peer address(%s) is invalid: %v", peerAddress, err) + } else { + validPeerAddresses = append(validPeerAddresses, peerAddress) + } + } + + if len(validPeerAddresses) == 0 { + return errors.New(ErrInvalidPeers) + } + } + + // init activeSyncData to be used to hold data used + // to calculate sync estimates only during sync + wallet.initActiveSyncData() + + wallets := make(map[int]*w.Wallet) + wallets[wallet.ID] = wallet.Internal() + wallet.WaitingForHeaders = true + wallet.Syncing = true + + syncer := spv.NewSyncer(wallets, lp) + syncer.SetNotifications(wallet.spvSyncNotificationCallbacks()) + if len(validPeerAddresses) > 0 { + syncer.SetPersistentPeers(validPeerAddresses) + } + + ctx, cancel := wallet.contextWithShutdownCancel() + + var restartSyncRequested bool + + wallet.syncData.mu.Lock() + restartSyncRequested = wallet.syncData.restartSyncRequested + wallet.syncData.restartSyncRequested = false + wallet.syncData.syncing = true + wallet.syncData.cancelSync = cancel + wallet.syncData.syncCanceled = make(chan struct{}) + wallet.syncData.syncer = syncer + wallet.syncData.mu.Unlock() + + for _, listener := range wallet.syncProgressListeners() { + listener.OnSyncStarted(restartSyncRequested) + } + + // syncer.Run uses a wait group to block the thread until the sync context + // expires or is canceled or some other error occurs such as + // losing connection to all persistent peers. + go func() { + syncError := syncer.Run(ctx) + //sync has ended or errored + if syncError != nil { + if syncError == context.DeadlineExceeded { + wallet.notifySyncError(errors.Errorf("SPV synchronization deadline exceeded: %v", syncError)) + } else if syncError == context.Canceled { + close(wallet.syncData.syncCanceled) + wallet.notifySyncCanceled() + } else { + wallet.notifySyncError(syncError) + } + } + + //reset sync variables + wallet.resetSyncData() + }() + return nil +} + +func (wallet *Wallet) RestartSpvSync() error { + wallet.syncData.mu.Lock() + wallet.syncData.restartSyncRequested = true + wallet.syncData.mu.Unlock() + + wallet.CancelSync() // necessary to unset the network backend. + return wallet.SpvSync() +} + +func (wallet *Wallet) CancelSync() { + wallet.syncData.mu.RLock() + cancelSync := wallet.syncData.cancelSync + wallet.syncData.mu.RUnlock() + + if cancelSync != nil { + log.Info("Canceling sync. May take a while for sync to fully cancel.") + + // Stop running cspp mixers + if wallet.IsAccountMixerActive() { + log.Infof("[%d] Stopping cspp mixer", wallet.ID) + err := wallet.StopAccountMixer(wallet.ID) + if err != nil { + log.Errorf("[%d] Error stopping cspp mixer: %v", wallet.ID, err) + } + } + + // Cancel the context used for syncer.Run in spvSync(). + // This may not immediately cause the sync process to terminate, + // but when it eventually terminates, syncer.Run will return `err == context.Canceled`. + cancelSync() + + // When sync terminates and syncer.Run returns `err == context.Canceled`, + // we will get notified on this channel. + <-wallet.syncData.syncCanceled + + log.Info("Sync fully canceled.") + } +} + +func (wallet *Wallet) IsWaiting() bool { + return wallet.WaitingForHeaders +} + +func (wallet *Wallet) IsSynced() bool { + return wallet.Synced +} + +func (wallet *Wallet) IsSyncing() bool { + return wallet.Syncing +} + +func (wallet *Wallet) IsConnectedToDecredNetwork() bool { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + return wallet.syncData.syncing || wallet.syncData.synced +} + +// func (wallet *Wallet) IsSynced() bool { +// wallet.syncData.mu.RLock() +// defer wallet.syncData.mu.RUnlock() +// return wallet.syncData.synced +// } + +// func (wallet *Wallet) IsSyncing() bool { +// wallet.syncData.mu.RLock() +// defer wallet.syncData.mu.RUnlock() +// return wallet.syncData.syncing +// } + +func (wallet *Wallet) CurrentSyncStage() int32 { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + + if wallet.syncData != nil && wallet.syncData.syncing { + return wallet.syncData.syncStage + } + return InvalidSyncStage +} + +func (wallet *Wallet) GeneralSyncProgress() *GeneralSyncProgress { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + + if wallet.syncData != nil && wallet.syncData.syncing { + switch wallet.syncData.syncStage { + case HeadersFetchSyncStage: + return wallet.syncData.headersFetchProgress.GeneralSyncProgress + case AddressDiscoverySyncStage: + return wallet.syncData.addressDiscoveryProgress.GeneralSyncProgress + case HeadersRescanSyncStage: + return wallet.syncData.headersRescanProgress.GeneralSyncProgress + case CFiltersFetchSyncStage: + return wallet.syncData.cfiltersFetchProgress.GeneralSyncProgress + } + } + + return nil +} + +func (wallet *Wallet) ConnectedPeers() int32 { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + return wallet.syncData.connectedPeers +} + +func (wallet *Wallet) PeerInfoRaw() ([]PeerInfo, error) { + if !wallet.IsConnectedToDecredNetwork() { + return nil, errors.New(ErrNotConnected) + } + + syncer := wallet.syncData.syncer + + infos := make([]PeerInfo, 0, len(syncer.GetRemotePeers())) + for _, rp := range syncer.GetRemotePeers() { + info := PeerInfo{ + ID: int32(rp.ID()), + Addr: rp.RemoteAddr().String(), + AddrLocal: rp.LocalAddr().String(), + Services: fmt.Sprintf("%08d", uint64(rp.Services())), + Version: rp.Pver(), + SubVer: rp.UA(), + StartingHeight: int64(rp.InitialHeight()), + BanScore: int32(rp.BanScore()), + } + + infos = append(infos, info) + } + + sort.Slice(infos, func(i, j int) bool { + return infos[i].ID < infos[j].ID + }) + + return infos, nil +} + +func (wallet *Wallet) PeerInfo() (string, error) { + infos, err := wallet.PeerInfoRaw() + if err != nil { + return "", err + } + + result, _ := json.Marshal(infos) + return string(result), nil +} + +// func (wallet *Wallet) GetBestBlock() *BlockInfo { +// var bestBlock int32 = -1 +// var blockInfo *BlockInfo +// for _, wallet := range wallet.wallets { +// if !wallet.WalletOpened() { +// continue +// } + +// walletBestBLock := wallet.GetBestBlock() +// if walletBestBLock > bestBlock || bestBlock == -1 { +// bestBlock = walletBestBLock +// blockInfo = &BlockInfo{Height: bestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} +// } +// } + +// return blockInfo +// } + +func (wallet *Wallet) GetLowestBlock() *BlockInfo { + var lowestBlock int32 = -1 + var blockInfo *BlockInfo + // for _, wallet := range wallet.wallets { + // if !wallet.WalletOpened() { + // continue + // } + walletBestBLock := wallet.GetBestBlock() + if walletBestBLock < lowestBlock || lowestBlock == -1 { + lowestBlock = walletBestBLock + blockInfo = &BlockInfo{Height: lowestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} + } + // } + + return blockInfo +} + +func (wallet *Wallet) GetBestBlock() int32 { + if wallet.Internal() == nil { + // This method is sometimes called after a wallet is deleted and causes crash. + log.Error("Attempting to read best block height without a loaded wallet.") + return 0 + } + + _, height := wallet.Internal().MainChainTip(wallet.ShutdownContext()) + return height +} + +func (wallet *Wallet) GetBestBlockTimeStamp() int64 { + if wallet.Internal() == nil { + // This method is sometimes called after a wallet is deleted and causes crash. + log.Error("Attempting to read best block timestamp without a loaded wallet.") + return 0 + } + + ctx := wallet.ShutdownContext() + _, height := wallet.Internal().MainChainTip(ctx) + identifier := w.NewBlockIdentifierFromHeight(height) + info, err := wallet.Internal().BlockInfo(ctx, identifier) + if err != nil { + log.Error(err) + return 0 + } + return info.Timestamp +} + +// func (wallet *Wallet) GetLowestBlockTimestamp() int64 { +// var timestamp int64 = -1 +// for _, wallet := range wallet.wallets { +// bestBlockTimestamp := wallet.GetBestBlockTimeStamp() +// if bestBlockTimestamp < timestamp || timestamp == -1 { +// timestamp = bestBlockTimestamp +// } +// } +// return timestamp +// } diff --git a/wallets/dcr/syncnotification.go b/wallets/dcr/syncnotification.go new file mode 100644 index 000000000..877741d8f --- /dev/null +++ b/wallets/dcr/syncnotification.go @@ -0,0 +1,682 @@ +package dcr + +import ( + "math" + "time" + + "github.com/planetdecred/dcrlibwallet/spv" + // "golang.org/x/sync/errgroup" +) + +func (w *Wallet) spvSyncNotificationCallbacks() *spv.Notifications { + return &spv.Notifications{ + PeerConnected: func(peerCount int32, addr string) { + w.handlePeerCountUpdate(peerCount) + }, + PeerDisconnected: func(peerCount int32, addr string) { + w.handlePeerCountUpdate(peerCount) + }, + Synced: w.synced, + FetchHeadersStarted: w.fetchHeadersStarted, + FetchHeadersProgress: w.fetchHeadersProgress, + FetchHeadersFinished: w.fetchHeadersFinished, + FetchMissingCFiltersStarted: w.fetchCFiltersStarted, + FetchMissingCFiltersProgress: w.fetchCFiltersProgress, + FetchMissingCFiltersFinished: w.fetchCFiltersEnded, + DiscoverAddressesStarted: w.discoverAddressesStarted, + DiscoverAddressesFinished: w.discoverAddressesFinished, + RescanStarted: w.rescanStarted, + RescanProgress: w.rescanProgress, + RescanFinished: w.rescanFinished, + } +} + +func (w *Wallet) handlePeerCountUpdate(peerCount int32) { + w.syncData.mu.Lock() + w.syncData.connectedPeers = peerCount + shouldLog := w.syncData.showLogs && w.syncData.syncing + w.syncData.mu.Unlock() + + for _, syncProgressListener := range w.syncProgressListeners() { + syncProgressListener.OnPeerConnectedOrDisconnected(peerCount) + } + + if shouldLog { + if peerCount == 1 { + log.Infof("Connected to %d peer on %s.", peerCount, w.chainParams.Name) + } else { + log.Infof("Connected to %d peers on %s.", peerCount, w.chainParams.Name) + } + } +} + +// Fetch CFilters Callbacks + +func (w *Wallet) fetchCFiltersStarted(walletID int) { + w.syncData.mu.Lock() + w.syncData.activeSyncData.syncStage = CFiltersFetchSyncStage + w.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp = time.Now().Unix() + w.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount = 0 + showLogs := w.syncData.showLogs + w.syncData.mu.Unlock() + + if showLogs { + log.Infof("Step 1 of 3 - fetching %d block headers.") + } +} + +func (w *Wallet) fetchCFiltersProgress(walletID int, startCFiltersHeight, endCFiltersHeight int32) { + + // lock the mutex before reading and writing to w.syncData.* + w.syncData.mu.Lock() + + if w.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight == -1 { + w.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight = startCFiltersHeight + } + + // wallet := w.WalletWithID(walletID) + w.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount += endCFiltersHeight - startCFiltersHeight + + totalCFiltersToFetch := w.GetBestBlock() - w.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight + // cfiltersLeftToFetch := totalCFiltersToFetch - w.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount + + cfiltersFetchProgress := float64(w.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(totalCFiltersToFetch) + + // If there was some period of inactivity, + // assume that this process started at some point in the future, + // thereby accounting for the total reported time of inactivity. + w.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp += w.syncData.activeSyncData.totalInactiveSeconds + w.syncData.activeSyncData.totalInactiveSeconds = 0 + + timeTakenSoFar := time.Now().Unix() - w.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp + if timeTakenSoFar < 1 { + timeTakenSoFar = 1 + } + estimatedTotalCFiltersFetchTime := float64(timeTakenSoFar) / cfiltersFetchProgress + + // Use CFilters fetch rate to estimate headers fetch time. + cfiltersFetchRate := float64(w.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(timeTakenSoFar) + estimatedHeadersLeftToFetch := w.estimateBlockHeadersCountAfter(w.GetBestBlockTimeStamp()) + estimatedTotalHeadersFetchTime := float64(estimatedHeadersLeftToFetch) / cfiltersFetchRate + // increase estimated value by FetchPercentage + estimatedTotalHeadersFetchTime /= FetchPercentage + + estimatedDiscoveryTime := estimatedTotalHeadersFetchTime * DiscoveryPercentage + estimatedRescanTime := estimatedTotalHeadersFetchTime * RescanPercentage + estimatedTotalSyncTime := estimatedTotalCFiltersFetchTime + estimatedTotalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime + + totalSyncProgress := float64(timeTakenSoFar) / estimatedTotalSyncTime + totalTimeRemainingSeconds := int64(math.Round(estimatedTotalSyncTime)) - timeTakenSoFar + + // update headers fetching progress report including total progress percentage and total time remaining + w.syncData.activeSyncData.cfiltersFetchProgress.TotalCFiltersToFetch = totalCFiltersToFetch + w.syncData.activeSyncData.cfiltersFetchProgress.CurrentCFilterHeight = startCFiltersHeight + w.syncData.activeSyncData.cfiltersFetchProgress.CFiltersFetchProgress = roundUp(cfiltersFetchProgress * 100.0) + w.syncData.activeSyncData.cfiltersFetchProgress.TotalSyncProgress = roundUp(totalSyncProgress * 100.0) + w.syncData.activeSyncData.cfiltersFetchProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds + + w.syncData.mu.Unlock() + + // notify progress listener of estimated progress report + w.publishFetchCFiltersProgress() + + cfiltersFetchTimeRemaining := estimatedTotalCFiltersFetchTime - float64(timeTakenSoFar) + debugInfo := &DebugInfo{ + timeTakenSoFar, + totalTimeRemainingSeconds, + timeTakenSoFar, + int64(math.Round(cfiltersFetchTimeRemaining)), + } + w.publishDebugInfo(debugInfo) +} + +func (w *Wallet) publishFetchCFiltersProgress() { + for _, syncProgressListener := range w.syncProgressListeners() { + syncProgressListener.OnCFiltersFetchProgress(&w.syncData.cfiltersFetchProgress) + } +} + +func (w *Wallet) fetchCFiltersEnded(walletID int) { + w.syncData.mu.Lock() + defer w.syncData.mu.Unlock() + + w.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent = time.Now().Unix() - w.syncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp + + // If there is some period of inactivity reported at this stage, + // subtract it from the total stage time. + w.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent -= w.syncData.totalInactiveSeconds + w.syncData.activeSyncData.totalInactiveSeconds = 0 +} + +// Fetch Headers Callbacks + +func (w *Wallet) fetchHeadersStarted(peerInitialHeight int32) { + if !w.IsSyncing() { + return + } + + w.syncData.mu.RLock() + headersFetchingStarted := w.syncData.headersFetchProgress.beginFetchTimeStamp != -1 + showLogs := w.syncData.showLogs + w.syncData.mu.RUnlock() + + if headersFetchingStarted { + // This function gets called for each newly connected peer so + // ignore if headers fetching was already started. + return + } + + w.WaitingForHeaders = true + + lowestBlockHeight := w.GetLowestBlock().Height + + w.syncData.mu.Lock() + w.syncData.activeSyncData.syncStage = HeadersFetchSyncStage + w.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp = time.Now().Unix() + w.syncData.activeSyncData.headersFetchProgress.startHeaderHeight = lowestBlockHeight + w.syncData.headersFetchProgress.totalFetchedHeadersCount = 0 + w.syncData.activeSyncData.totalInactiveSeconds = 0 + w.syncData.mu.Unlock() + + if showLogs { + log.Infof("Step 1 of 3 - fetching %d block headers.", peerInitialHeight-lowestBlockHeight) + } +} + +func (w *Wallet) fetchHeadersProgress(lastFetchedHeaderHeight int32, lastFetchedHeaderTime int64) { + if !w.IsSyncing() { + return + } + + w.syncData.mu.RLock() + headersFetchingCompleted := w.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent != -1 + w.syncData.mu.RUnlock() + + if headersFetchingCompleted { + // This function gets called for each newly connected peer so ignore + // this call if the headers fetching phase was previously completed. + return + } + + // for _, wallet := range w.wallets { + if w.WaitingForHeaders { + w.WaitingForHeaders = w.GetBestBlock() > lastFetchedHeaderHeight + } + // } + + // lock the mutex before reading and writing to w.syncData.* + w.syncData.mu.Lock() + + if lastFetchedHeaderHeight > w.syncData.activeSyncData.headersFetchProgress.startHeaderHeight { + w.syncData.activeSyncData.headersFetchProgress.totalFetchedHeadersCount = lastFetchedHeaderHeight - w.syncData.activeSyncData.headersFetchProgress.startHeaderHeight + } + + headersLeftToFetch := w.estimateBlockHeadersCountAfter(lastFetchedHeaderTime) + totalHeadersToFetch := lastFetchedHeaderHeight + headersLeftToFetch + headersFetchProgress := float64(w.syncData.activeSyncData.headersFetchProgress.totalFetchedHeadersCount) / float64(totalHeadersToFetch) + + // If there was some period of inactivity, + // assume that this process started at some point in the future, + // thereby accounting for the total reported time of inactivity. + w.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp += w.syncData.activeSyncData.totalInactiveSeconds + w.syncData.activeSyncData.totalInactiveSeconds = 0 + + fetchTimeTakenSoFar := time.Now().Unix() - w.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp + if fetchTimeTakenSoFar < 1 { + fetchTimeTakenSoFar = 1 + } + estimatedTotalHeadersFetchTime := float64(fetchTimeTakenSoFar) / headersFetchProgress + + // For some reason, the actual total headers fetch time is more than the predicted/estimated time. + // Account for this difference by multiplying the estimatedTotalHeadersFetchTime by an incrementing factor. + // The incrementing factor is inversely proportional to the headers fetch progress, + // ranging from 0.5 to 0 as headers fetching progress increases from 0 to 1. + // todo, the above noted (mal)calculation may explain this difference. + // TODO: is this adjustment still needed since the calculation has been corrected. + adjustmentFactor := 0.5 * (1 - headersFetchProgress) + estimatedTotalHeadersFetchTime += estimatedTotalHeadersFetchTime * adjustmentFactor + + estimatedDiscoveryTime := estimatedTotalHeadersFetchTime * DiscoveryPercentage + estimatedRescanTime := estimatedTotalHeadersFetchTime * RescanPercentage + estimatedTotalSyncTime := float64(w.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent) + + estimatedTotalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime + + totalSyncProgress := float64(fetchTimeTakenSoFar) / estimatedTotalSyncTime + totalTimeRemainingSeconds := int64(math.Round(estimatedTotalSyncTime)) - fetchTimeTakenSoFar + + // update headers fetching progress report including total progress percentage and total time remaining + w.syncData.activeSyncData.headersFetchProgress.TotalHeadersToFetch = totalHeadersToFetch + w.syncData.activeSyncData.headersFetchProgress.CurrentHeaderHeight = lastFetchedHeaderHeight + w.syncData.activeSyncData.headersFetchProgress.CurrentHeaderTimestamp = lastFetchedHeaderTime + w.syncData.activeSyncData.headersFetchProgress.HeadersFetchProgress = roundUp(headersFetchProgress * 100.0) + w.syncData.activeSyncData.headersFetchProgress.TotalSyncProgress = roundUp(totalSyncProgress * 100.0) + w.syncData.activeSyncData.headersFetchProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds + + // unlock the mutex before issuing notification callbacks to prevent potential deadlock + // if any invoked callback takes a considerable amount of time to execute. + w.syncData.mu.Unlock() + + // notify progress listener of estimated progress report + w.publishFetchHeadersProgress() + + // todo: also log report if showLog == true + timeTakenSoFar := w.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + fetchTimeTakenSoFar + headersFetchTimeRemaining := estimatedTotalHeadersFetchTime - float64(fetchTimeTakenSoFar) + debugInfo := &DebugInfo{ + timeTakenSoFar, + totalTimeRemainingSeconds, + fetchTimeTakenSoFar, + int64(math.Round(headersFetchTimeRemaining)), + } + w.publishDebugInfo(debugInfo) +} + +func (w *Wallet) publishFetchHeadersProgress() { + for _, syncProgressListener := range w.syncProgressListeners() { + syncProgressListener.OnHeadersFetchProgress(&w.syncData.headersFetchProgress) + } +} + +func (w *Wallet) fetchHeadersFinished() { + w.syncData.mu.Lock() + defer w.syncData.mu.Unlock() + + if !w.syncData.syncing { + // ignore if sync is not in progress + return + } + + w.syncData.activeSyncData.headersFetchProgress.startHeaderHeight = -1 + w.syncData.headersFetchProgress.totalFetchedHeadersCount = 0 + w.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent = time.Now().Unix() - w.syncData.headersFetchProgress.beginFetchTimeStamp + + // If there is some period of inactivity reported at this stage, + // subtract it from the total stage time. + w.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent -= w.syncData.totalInactiveSeconds + w.syncData.activeSyncData.totalInactiveSeconds = 0 + + if w.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent < 150 { + // This ensures that minimum ETA used for stage 2 (address discovery) is 120 seconds (80% of 150 seconds). + w.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent = 150 + } + + if w.syncData.showLogs && w.syncData.syncing { + log.Info("Fetch headers completed.") + } +} + +// Address/Account Discovery Callbacks + +func (w *Wallet) discoverAddressesStarted(walletID int) { + if !w.IsSyncing() { + return + } + + w.syncData.mu.RLock() + addressDiscoveryAlreadyStarted := w.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime != -1 + totalHeadersFetchTime := float64(w.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent) + w.syncData.mu.RUnlock() + + if addressDiscoveryAlreadyStarted { + return + } + + w.syncData.mu.Lock() + w.syncData.activeSyncData.syncStage = AddressDiscoverySyncStage + w.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime = time.Now().Unix() + w.syncData.activeSyncData.addressDiscoveryProgress.WalletID = walletID + w.syncData.addressDiscoveryCompletedOrCanceled = make(chan bool) + w.syncData.mu.Unlock() + + go w.updateAddressDiscoveryProgress(totalHeadersFetchTime) + + if w.syncData.showLogs { + log.Info("Step 2 of 3 - discovering used addresses.") + } +} + +func (w *Wallet) updateAddressDiscoveryProgress(totalHeadersFetchTime float64) { + // use ticker to calculate and broadcast address discovery progress every second + everySecondTicker := time.NewTicker(1 * time.Second) + + // these values will be used every second to calculate the total sync progress + estimatedDiscoveryTime := totalHeadersFetchTime * DiscoveryPercentage + estimatedRescanTime := totalHeadersFetchTime * RescanPercentage + + // track last logged time remaining and total percent to avoid re-logging same message + var lastTimeRemaining int64 + var lastTotalPercent int32 = -1 + + for { + if !w.IsSyncing() { + return + } + + // If there was some period of inactivity, + // assume that this process started at some point in the future, + // thereby accounting for the total reported time of inactivity. + w.syncData.mu.Lock() + w.syncData.addressDiscoveryProgress.addressDiscoveryStartTime += w.syncData.totalInactiveSeconds + w.syncData.totalInactiveSeconds = 0 + addressDiscoveryStartTime := w.syncData.addressDiscoveryProgress.addressDiscoveryStartTime + totalCfiltersFetchTime := float64(w.syncData.cfiltersFetchProgress.cfiltersFetchTimeSpent) + showLogs := w.syncData.showLogs + w.syncData.mu.Unlock() + + select { + case <-w.syncData.addressDiscoveryCompletedOrCanceled: + // stop calculating and broadcasting address discovery progress + everySecondTicker.Stop() + if showLogs { + log.Info("Address discovery complete.") + } + return + + case <-everySecondTicker.C: + // calculate address discovery progress + elapsedDiscoveryTime := float64(time.Now().Unix() - addressDiscoveryStartTime) + discoveryProgress := (elapsedDiscoveryTime / estimatedDiscoveryTime) * 100 + + var totalSyncTime float64 + if elapsedDiscoveryTime > estimatedDiscoveryTime { + totalSyncTime = totalCfiltersFetchTime + totalHeadersFetchTime + elapsedDiscoveryTime + estimatedRescanTime + } else { + totalSyncTime = totalCfiltersFetchTime + totalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime + } + + totalElapsedTime := totalCfiltersFetchTime + totalHeadersFetchTime + elapsedDiscoveryTime + totalProgress := (totalElapsedTime / totalSyncTime) * 100 + + remainingAccountDiscoveryTime := math.Round(estimatedDiscoveryTime - elapsedDiscoveryTime) + if remainingAccountDiscoveryTime < 0 { + remainingAccountDiscoveryTime = 0 + } + + totalProgressPercent := int32(math.Round(totalProgress)) + totalTimeRemainingSeconds := int64(math.Round(remainingAccountDiscoveryTime + estimatedRescanTime)) + + // update address discovery progress, total progress and total time remaining + w.syncData.mu.Lock() + w.syncData.addressDiscoveryProgress.AddressDiscoveryProgress = int32(math.Round(discoveryProgress)) + w.syncData.addressDiscoveryProgress.TotalSyncProgress = totalProgressPercent + w.syncData.addressDiscoveryProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds + w.syncData.mu.Unlock() + + w.publishAddressDiscoveryProgress() + + debugInfo := &DebugInfo{ + int64(math.Round(totalElapsedTime)), + totalTimeRemainingSeconds, + int64(math.Round(elapsedDiscoveryTime)), + int64(math.Round(remainingAccountDiscoveryTime)), + } + w.publishDebugInfo(debugInfo) + + if showLogs { + // avoid logging same message multiple times + if totalProgressPercent != lastTotalPercent || totalTimeRemainingSeconds != lastTimeRemaining { + log.Infof("Syncing %d%%, %s remaining, discovering used addresses.", + totalProgressPercent, CalculateTotalTimeRemaining(totalTimeRemainingSeconds)) + + lastTotalPercent = totalProgressPercent + lastTimeRemaining = totalTimeRemainingSeconds + } + } + } + } +} + +func (w *Wallet) publishAddressDiscoveryProgress() { + for _, syncProgressListener := range w.syncProgressListeners() { + syncProgressListener.OnAddressDiscoveryProgress(&w.syncData.activeSyncData.addressDiscoveryProgress) + } +} + +func (w *Wallet) discoverAddressesFinished(walletID int) { + if !w.IsSyncing() { + return + } + + w.stopUpdatingAddressDiscoveryProgress() +} + +func (w *Wallet) stopUpdatingAddressDiscoveryProgress() { + w.syncData.mu.Lock() + if w.syncData.activeSyncData != nil && w.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled != nil { + close(w.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled) + w.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled = nil + w.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent = time.Now().Unix() - w.syncData.addressDiscoveryProgress.addressDiscoveryStartTime + } + w.syncData.mu.Unlock() +} + +// Blocks Scan Callbacks + +func (w *Wallet) rescanStarted(walletID int) { + w.stopUpdatingAddressDiscoveryProgress() + + w.syncData.mu.Lock() + defer w.syncData.mu.Unlock() + + if !w.syncData.syncing { + // ignore if sync is not in progress + return + } + + w.syncData.activeSyncData.syncStage = HeadersRescanSyncStage + w.syncData.activeSyncData.rescanStartTime = time.Now().Unix() + + // retain last total progress report from address discovery phase + w.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = w.syncData.activeSyncData.addressDiscoveryProgress.TotalTimeRemainingSeconds + w.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = w.syncData.activeSyncData.addressDiscoveryProgress.TotalSyncProgress + w.syncData.activeSyncData.headersRescanProgress.WalletID = walletID + + if w.syncData.showLogs && w.syncData.syncing { + log.Info("Step 3 of 3 - Scanning block headers.") + } +} + +func (w *Wallet) rescanProgress(walletID int, rescannedThrough int32) { + if !w.IsSyncing() { + // ignore if sync is not in progress + return + } + + totalHeadersToScan := w.GetBestBlock() + + rescanRate := float64(rescannedThrough) / float64(totalHeadersToScan) + + w.syncData.mu.Lock() + + // If there was some period of inactivity, + // assume that this process started at some point in the future, + // thereby accounting for the total reported time of inactivity. + w.syncData.activeSyncData.rescanStartTime += w.syncData.activeSyncData.totalInactiveSeconds + w.syncData.activeSyncData.totalInactiveSeconds = 0 + + elapsedRescanTime := time.Now().Unix() - w.syncData.activeSyncData.rescanStartTime + estimatedTotalRescanTime := int64(math.Round(float64(elapsedRescanTime) / rescanRate)) + totalTimeRemainingSeconds := estimatedTotalRescanTime - elapsedRescanTime + totalElapsedTime := w.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + w.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent + + w.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent + elapsedRescanTime + + w.syncData.activeSyncData.headersRescanProgress.WalletID = walletID + w.syncData.activeSyncData.headersRescanProgress.TotalHeadersToScan = totalHeadersToScan + w.syncData.activeSyncData.headersRescanProgress.RescanProgress = int32(math.Round(rescanRate * 100)) + w.syncData.activeSyncData.headersRescanProgress.CurrentRescanHeight = rescannedThrough + w.syncData.activeSyncData.headersRescanProgress.RescanTimeRemaining = totalTimeRemainingSeconds + + // do not update total time taken and total progress percent if elapsedRescanTime is 0 + // because the estimatedTotalRescanTime will be inaccurate (also 0) + // which will make the estimatedTotalSyncTime equal to totalElapsedTime + // giving the wrong impression that the process is complete + if elapsedRescanTime > 0 { + estimatedTotalSyncTime := w.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + w.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent + + w.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent + estimatedTotalRescanTime + totalProgress := (float64(totalElapsedTime) / float64(estimatedTotalSyncTime)) * 100 + + w.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds + w.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = int32(math.Round(totalProgress)) + } + + w.syncData.mu.Unlock() + + w.publishHeadersRescanProgress() + + debugInfo := &DebugInfo{ + totalElapsedTime, + totalTimeRemainingSeconds, + elapsedRescanTime, + totalTimeRemainingSeconds, + } + w.publishDebugInfo(debugInfo) + + w.syncData.mu.RLock() + if w.syncData.showLogs { + log.Infof("Syncing %d%%, %s remaining, scanning %d of %d block headers.", + w.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress, + CalculateTotalTimeRemaining(w.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds), + w.syncData.activeSyncData.headersRescanProgress.CurrentRescanHeight, + w.syncData.activeSyncData.headersRescanProgress.TotalHeadersToScan, + ) + } + w.syncData.mu.RUnlock() +} + +func (w *Wallet) publishHeadersRescanProgress() { + for _, syncProgressListener := range w.syncProgressListeners() { + syncProgressListener.OnHeadersRescanProgress(&w.syncData.activeSyncData.headersRescanProgress) + } +} + +func (w *Wallet) rescanFinished(walletID int) { + if !w.IsSyncing() { + // ignore if sync is not in progress + return + } + + w.syncData.mu.Lock() + w.syncData.activeSyncData.headersRescanProgress.WalletID = walletID + w.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = 0 + w.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = 100 + + // Reset these value so that address discovery would + // not be skipped for the next wallet. + w.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime = -1 + w.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent = -1 + w.syncData.mu.Unlock() + + w.publishHeadersRescanProgress() +} + +func (w *Wallet) publishDebugInfo(debugInfo *DebugInfo) { + for _, syncProgressListener := range w.syncProgressListeners() { + syncProgressListener.Debug(debugInfo) + } +} + +/** Helper functions start here */ + +func (w *Wallet) estimateBlockHeadersCountAfter(lastHeaderTime int64) int32 { + // Use the difference between current time (now) and last reported block time, + // to estimate total headers to fetch. + timeDifferenceInSeconds := float64(time.Now().Unix() - lastHeaderTime) + targetTimePerBlockInSeconds := w.chainParams.TargetTimePerBlock.Seconds() + estimatedHeadersDifference := timeDifferenceInSeconds / targetTimePerBlockInSeconds + + // return next integer value (upper limit) if estimatedHeadersDifference is a fraction + return int32(math.Ceil(estimatedHeadersDifference)) +} + +func (w *Wallet) notifySyncError(err error) { + for _, syncProgressListener := range w.syncProgressListeners() { + syncProgressListener.OnSyncEndedWithError(err) + } +} + +func (w *Wallet) notifySyncCanceled() { + w.syncData.mu.RLock() + restartSyncRequested := w.syncData.restartSyncRequested + w.syncData.mu.RUnlock() + + for _, syncProgressListener := range w.syncProgressListeners() { + syncProgressListener.OnSyncCanceled(restartSyncRequested) + } +} + +func (w *Wallet) resetSyncData() { + // It's possible that sync ends or errors while address discovery is ongoing. + // If this happens, it's important to stop the address discovery process before + // resetting sync data. + w.stopUpdatingAddressDiscoveryProgress() + + w.syncData.mu.Lock() + w.syncData.syncing = false + w.syncData.synced = false + w.syncData.cancelSync = nil + w.syncData.syncCanceled = nil + w.syncData.activeSyncData = nil + w.syncData.mu.Unlock() + + for _, wallet := range w.wallets { + wallet.WaitingForHeaders = true + wallet.LockWallet() // lock wallet if previously unlocked to perform account discovery. + } +} + +func (w *Wallet) synced(walletID int, synced bool) { + + indexTransactions := func() { + // begin indexing transactions after sync is completed, + // syncProgressListeners.OnSynced() will be invoked after transactions are indexed + var txIndexing errgroup.Group + for _, wallet := range w.wallets { + txIndexing.Go(wallet.IndexTransactions) + } + + go func() { + err := txIndexing.Wait() + if err != nil { + log.Errorf("Tx Index Error: %v", err) + } + + for _, syncProgressListener := range w.syncProgressListeners() { + if synced { + syncProgressListener.OnSyncCompleted() + } else { + syncProgressListener.OnSyncCanceled(false) + } + } + }() + } + + w.syncData.mu.RLock() + allWalletsSynced := w.syncData.synced + w.syncData.mu.RUnlock() + + if allWalletsSynced && synced { + indexTransactions() + return + } + + w.Synced = synced + w.Syncing = false + w.listenForTransactions(wallet.ID) + + if !w.Internal().Locked() { + w.LockWallet() // lock wallet if previously unlocked to perform account discovery. + err := w.markWalletAsDiscoveredAccounts(walletID) + if err != nil { + log.Error(err) + } + } + + if w.OpenedWalletsCount() == w.SyncedWalletsCount() { + w.syncData.mu.Lock() + w.syncData.syncing = false + w.syncData.synced = true + w.syncData.mu.Unlock() + + indexTransactions() + } +} diff --git a/ticket.go b/wallets/dcr/ticket.go similarity index 70% rename from ticket.go rename to wallets/dcr/ticket.go index e1b2de3a9..fcd7c959d 100644 --- a/ticket.go +++ b/wallets/dcr/ticket.go @@ -1,11 +1,11 @@ -package dcrlibwallet +package dcr import ( "context" "fmt" "runtime/trace" "sync" - "time" + // "time" "decred.org/dcrwallet/v2/errors" w "decred.org/dcrwallet/v2/wallet" @@ -13,7 +13,7 @@ import ( "github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/wire" "github.com/planetdecred/dcrlibwallet/internal/vsp" - "github.com/planetdecred/dcrlibwallet/utils" + // "github.com/planetdecred/dcrlibwallet/utils" ) func (wallet *Wallet) TotalStakingRewards() (int64, error) { @@ -30,27 +30,27 @@ func (wallet *Wallet) TotalStakingRewards() (int64, error) { return totalRewards, nil } -func (mw *MultiWallet) TotalStakingRewards() (int64, error) { - var totalRewards int64 - for _, wal := range mw.wallets { - walletTotalRewards, err := wal.TotalStakingRewards() - if err != nil { - return 0, err - } +// func (mw *MultiWallet) TotalStakingRewards() (int64, error) { +// var totalRewards int64 +// for _, wal := range mw.wallets { +// walletTotalRewards, err := wal.TotalStakingRewards() +// if err != nil { +// return 0, err +// } - totalRewards += walletTotalRewards - } +// totalRewards += walletTotalRewards +// } - return totalRewards, nil -} +// return totalRewards, nil +// } -func (mw *MultiWallet) TicketMaturity() int32 { - return int32(mw.chainParams.TicketMaturity) -} +// func (mw *MultiWallet) TicketMaturity() int32 { +// return int32(mw.chainParams.TicketMaturity) +// } -func (mw *MultiWallet) TicketExpiry() int32 { - return int32(mw.chainParams.TicketExpiry) -} +// func (mw *MultiWallet) TicketExpiry() int32 { +// return int32(mw.chainParams.TicketExpiry) +// } func (wallet *Wallet) StakingOverview() (stOverview *StakingOverview, err error) { stOverview = &StakingOverview{} @@ -91,34 +91,34 @@ func (wallet *Wallet) StakingOverview() (stOverview *StakingOverview, err error) return stOverview, nil } -func (mw *MultiWallet) StakingOverview() (stOverview *StakingOverview, err error) { - stOverview = &StakingOverview{} +// func (mw *MultiWallet) StakingOverview() (stOverview *StakingOverview, err error) { +// stOverview = &StakingOverview{} - for _, wallet := range mw.wallets { - st, err := wallet.StakingOverview() - if err != nil { - return nil, err - } +// for _, wallet := range mw.wallets { +// st, err := wallet.StakingOverview() +// if err != nil { +// return nil, err +// } - stOverview.Unmined += st.Unmined - stOverview.Immature += st.Immature - stOverview.Live += st.Live - stOverview.Voted += st.Voted - stOverview.Revoked += st.Revoked - stOverview.Expired += st.Expired - } +// stOverview.Unmined += st.Unmined +// stOverview.Immature += st.Immature +// stOverview.Live += st.Live +// stOverview.Voted += st.Voted +// stOverview.Revoked += st.Revoked +// stOverview.Expired += st.Expired +// } - stOverview.All = stOverview.Unmined + stOverview.Immature + stOverview.Live + stOverview.Voted + - stOverview.Revoked + stOverview.Expired +// stOverview.All = stOverview.Unmined + stOverview.Immature + stOverview.Live + stOverview.Voted + +// stOverview.Revoked + stOverview.Expired - return stOverview, nil -} +// return stOverview, nil +// } // TicketPrice returns the price of a ticket for the next block, also known as // the stake difficulty. May be incorrect if blockchain sync is ongoing or if // blockchain is not up-to-date. func (wallet *Wallet) TicketPrice() (*TicketPriceResponse, error) { - ctx := wallet.shutdownContext() + ctx := wallet.ShutdownContext() sdiff, err := wallet.Internal().NextStakeDifficulty(ctx) if err != nil { return nil, err @@ -132,21 +132,21 @@ func (wallet *Wallet) TicketPrice() (*TicketPriceResponse, error) { return resp, nil } -func (mw *MultiWallet) TicketPrice() (*TicketPriceResponse, error) { - bestBlock := mw.GetBestBlock() - for _, wal := range mw.wallets { - resp, err := wal.TicketPrice() - if err != nil { - return nil, err - } +// func (mw *MultiWallet) TicketPrice() (*TicketPriceResponse, error) { +// bestBlock := mw.GetBestBlock() +// for _, wal := range mw.wallets { +// resp, err := wal.TicketPrice() +// if err != nil { +// return nil, err +// } - if resp.Height == bestBlock.Height { - return resp, nil - } - } +// if resp.Height == bestBlock.Height { +// return resp, nil +// } +// } - return nil, errors.New(ErrWalletNotFound) -} +// return nil, errors.New(ErrWalletNotFound) +// } // PurchaseTickets purchases tickets from the wallet. // Returns a slice of hashes for tickets purchased. @@ -194,7 +194,7 @@ func (wallet *Wallet) PurchaseTickets(account, numTickets int32, vspHost string, request.MixedSplitAccount = csppCfg.TicketSplitAccount } - ctx := wallet.shutdownContext() + ctx := wallet.ShutdownContext() ticketsResponse, err := wallet.Internal().PurchaseTickets(ctx, networkBackend, request) if err != nil { return nil, err @@ -205,77 +205,77 @@ func (wallet *Wallet) PurchaseTickets(account, numTickets int32, vspHost string, // VSPTicketInfo returns vsp-related info for a given ticket. Returns an error // if the ticket is not yet assigned to a VSP. -func (mw *MultiWallet) VSPTicketInfo(walletID int, hash string) (*VSPTicketInfo, error) { - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return nil, fmt.Errorf("no wallet with ID %d", walletID) - } - - ticketHash, err := chainhash.NewHashFromStr(hash) - if err != nil { - return nil, err - } - - // Read the VSP info for this ticket from the wallet db. - ctx := wallet.shutdownContext() - walletTicketInfo, err := wallet.Internal().VSPTicketInfo(ctx, ticketHash) - if err != nil { - return nil, err - } - - ticketInfo := &VSPTicketInfo{ - VSP: walletTicketInfo.Host, - FeeTxHash: walletTicketInfo.FeeHash.String(), - FeeTxStatus: VSPFeeStatus(walletTicketInfo.FeeTxStatus), - } - - // Cannot submit a ticketstatus api request to the VSP if - // the wallet is locked. Return just the wallet info. - if wallet.IsLocked() { - return ticketInfo, nil - } - - vspClient, err := wallet.VSPClient(walletTicketInfo.Host, walletTicketInfo.PubKey) - if err != nil { - log.Warnf("unable to get vsp ticket info for %s: %v", hash, err) - return ticketInfo, nil - } - vspTicketStatus, err := vspClient.TicketStatus(ctx, ticketHash) - if err != nil { - log.Warnf("unable to get vsp ticket info for %s: %v", hash, err) - return ticketInfo, nil - } - - // Parse the fee status returned by the vsp. - var vspFeeStatus VSPFeeStatus - switch vspTicketStatus.FeeTxStatus { - case "received": // received but not broadcast - vspFeeStatus = VSPFeeProcessStarted - case "broadcast": // broadcast but not confirmed - vspFeeStatus = VSPFeeProcessPaid - case "confirmed": // broadcast and confirmed - vspFeeStatus = VSPFeeProcessConfirmed - case "error": - vspFeeStatus = VSPFeeProcessErrored - default: - vspFeeStatus = VSPFeeProcessErrored - log.Warnf("VSP responded with %v for %v", vspTicketStatus.FeeTxStatus, ticketHash) - } - - // Sanity check and log any observed discrepancies. - if ticketInfo.FeeTxHash != vspTicketStatus.FeeTxHash { - log.Warnf("wallet fee tx hash %s differs from vsp fee tx hash %s for ticket %s", - ticketInfo.FeeTxHash, vspTicketStatus.FeeTxHash, ticketHash) - ticketInfo.FeeTxHash = vspTicketStatus.FeeTxHash - } - if ticketInfo.FeeTxStatus != vspFeeStatus { - log.Warnf("wallet fee status %q differs from vsp fee status %q for ticket %s", - ticketInfo.FeeTxStatus, vspFeeStatus, ticketHash) - ticketInfo.FeeTxStatus = vspFeeStatus - } - - return ticketInfo, nil -} +// func (mw *MultiWallet) VSPTicketInfo(walletID int, hash string) (*VSPTicketInfo, error) { +// wallet := mw.WalletWithID(walletID) +// if wallet == nil { +// return nil, fmt.Errorf("no wallet with ID %d", walletID) +// } + +// ticketHash, err := chainhash.NewHashFromStr(hash) +// if err != nil { +// return nil, err +// } + +// // Read the VSP info for this ticket from the wallet db. +// ctx := wallet.shutdownContext() +// walletTicketInfo, err := wallet.Internal().VSPTicketInfo(ctx, ticketHash) +// if err != nil { +// return nil, err +// } + +// ticketInfo := &VSPTicketInfo{ +// VSP: walletTicketInfo.Host, +// FeeTxHash: walletTicketInfo.FeeHash.String(), +// FeeTxStatus: VSPFeeStatus(walletTicketInfo.FeeTxStatus), +// } + +// // Cannot submit a ticketstatus api request to the VSP if +// // the wallet is locked. Return just the wallet info. +// if wallet.IsLocked() { +// return ticketInfo, nil +// } + +// vspClient, err := wallet.VSPClient(walletTicketInfo.Host, walletTicketInfo.PubKey) +// if err != nil { +// log.Warnf("unable to get vsp ticket info for %s: %v", hash, err) +// return ticketInfo, nil +// } +// vspTicketStatus, err := vspClient.TicketStatus(ctx, ticketHash) +// if err != nil { +// log.Warnf("unable to get vsp ticket info for %s: %v", hash, err) +// return ticketInfo, nil +// } + +// // Parse the fee status returned by the vsp. +// var vspFeeStatus VSPFeeStatus +// switch vspTicketStatus.FeeTxStatus { +// case "received": // received but not broadcast +// vspFeeStatus = VSPFeeProcessStarted +// case "broadcast": // broadcast but not confirmed +// vspFeeStatus = VSPFeeProcessPaid +// case "confirmed": // broadcast and confirmed +// vspFeeStatus = VSPFeeProcessConfirmed +// case "error": +// vspFeeStatus = VSPFeeProcessErrored +// default: +// vspFeeStatus = VSPFeeProcessErrored +// log.Warnf("VSP responded with %v for %v", vspTicketStatus.FeeTxStatus, ticketHash) +// } + +// // Sanity check and log any observed discrepancies. +// if ticketInfo.FeeTxHash != vspTicketStatus.FeeTxHash { +// log.Warnf("wallet fee tx hash %s differs from vsp fee tx hash %s for ticket %s", +// ticketInfo.FeeTxHash, vspTicketStatus.FeeTxHash, ticketHash) +// ticketInfo.FeeTxHash = vspTicketStatus.FeeTxHash +// } +// if ticketInfo.FeeTxStatus != vspFeeStatus { +// log.Warnf("wallet fee status %q differs from vsp fee status %q for ticket %s", +// ticketInfo.FeeTxStatus, vspFeeStatus, ticketHash) +// ticketInfo.FeeTxStatus = vspFeeStatus +// } + +// return ticketInfo, nil +// } // StartTicketBuyer starts the automatic ticket buyer. The wallet // should already be configured with the required parameters using @@ -295,7 +295,7 @@ func (wallet *Wallet) StartTicketBuyer(passphrase []byte) error { return errors.New("Ticket buyer already running") } - ctx, cancel := wallet.shutdownContextWithCancel() + ctx, cancel := wallet.ShutdownContextWithCancel() wallet.cancelAutoTicketBuyer = cancel wallet.cancelAutoTicketBuyerMu.Unlock() @@ -545,23 +545,23 @@ func (wallet *Wallet) IsAutoTicketsPurchaseActive() bool { } // StopAutoTicketsPurchase stops the automatic ticket buyer. -func (mw *MultiWallet) StopAutoTicketsPurchase(walletID int) error { - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return errors.New(ErrNotExist) - } +// func (mw *MultiWallet) StopAutoTicketsPurchase(walletID int) error { +// wallet := mw.WalletWithID(walletID) +// if wallet == nil { +// return errors.New(ErrNotExist) +// } - wallet.cancelAutoTicketBuyerMu.Lock() - defer wallet.cancelAutoTicketBuyerMu.Unlock() +// wallet.cancelAutoTicketBuyerMu.Lock() +// defer wallet.cancelAutoTicketBuyerMu.Unlock() - if wallet.cancelAutoTicketBuyer == nil { - return errors.New(ErrInvalid) - } +// if wallet.cancelAutoTicketBuyer == nil { +// return errors.New(ErrInvalid) +// } - wallet.cancelAutoTicketBuyer() - wallet.cancelAutoTicketBuyer = nil - return nil -} +// wallet.cancelAutoTicketBuyer() +// wallet.cancelAutoTicketBuyer = nil +// return nil +// } // SetAutoTicketsBuyerConfig sets ticket buyer config for the wallet. func (wallet *Wallet) SetAutoTicketsBuyerConfig(vspHost string, purchaseAccount int32, amountToMaintain int64) { @@ -590,39 +590,39 @@ func (wallet *Wallet) TicketBuyerConfigIsSet() bool { } // ClearTicketBuyerConfig clears the wallet's ticket buyer config. -func (mw *MultiWallet) ClearTicketBuyerConfig(walletID int) error { - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return errors.New(ErrNotExist) - } +// func (mw *MultiWallet) ClearTicketBuyerConfig(walletID int) error { +// wallet := mw.WalletWithID(walletID) +// if wallet == nil { +// return errors.New(ErrNotExist) +// } - mw.SetLongConfigValueForKey(TicketBuyerATMConfigKey, -1) - mw.SetInt32ConfigValueForKey(TicketBuyerAccountConfigKey, -1) - mw.SetStringConfigValueForKey(TicketBuyerVSPHostConfigKey, "") +// mw.SetLongConfigValueForKey(TicketBuyerATMConfigKey, -1) +// mw.SetInt32ConfigValueForKey(TicketBuyerAccountConfigKey, -1) +// mw.SetStringConfigValueForKey(TicketBuyerVSPHostConfigKey, "") - return nil -} +// return nil +// } // NextTicketPriceRemaining returns the remaning time in seconds of a ticket for the next block, // if secs equal 0 is imminent -func (mw *MultiWallet) NextTicketPriceRemaining() (secs int64, err error) { - params, er := utils.ChainParams(mw.chainParams.Name) - if er != nil { - secs, err = -1, er - return - } - bestBestBlock := mw.GetBestBlock() - idxBlockInWindow := int(int64(bestBestBlock.Height)%params.StakeDiffWindowSize) + 1 - blockTime := params.TargetTimePerBlock.Nanoseconds() - windowSize := params.StakeDiffWindowSize - x := (windowSize - int64(idxBlockInWindow)) * blockTime - if x == 0 { - secs, err = 0, nil - return - } - secs, err = int64(time.Duration(x).Seconds()), nil - return -} +// func (mw *MultiWallet) NextTicketPriceRemaining() (secs int64, err error) { +// params, er := utils.ChainParams(mw.chainParams.Name) +// if er != nil { +// secs, err = -1, er +// return +// } +// bestBestBlock := mw.GetBestBlock() +// idxBlockInWindow := int(int64(bestBestBlock.Height)%params.StakeDiffWindowSize) + 1 +// blockTime := params.TargetTimePerBlock.Nanoseconds() +// windowSize := params.StakeDiffWindowSize +// x := (windowSize - int64(idxBlockInWindow)) * blockTime +// if x == 0 { +// secs, err = 0, nil +// return +// } +// secs, err = int64(time.Duration(x).Seconds()), nil +// return +// } // UnspentUnexpiredTickets returns all Unmined, Immature and Live tickets. func (wallet *Wallet) UnspentUnexpiredTickets() ([]Transaction, error) { diff --git a/transactions.go b/wallets/dcr/transactions.go similarity index 81% rename from transactions.go rename to wallets/dcr/transactions.go index 9bfb76844..9da4cf566 100644 --- a/transactions.go +++ b/wallets/dcr/transactions.go @@ -1,13 +1,13 @@ -package dcrlibwallet +package dcr import ( "encoding/json" - "sort" + // "sort" "github.com/asdine/storm" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/planetdecred/dcrlibwallet/txhelper" - "github.com/planetdecred/dcrlibwallet/walletdata" + "github.com/planetdecred/dcrlibwallet/wallets/dcr/walletdata" ) const ( @@ -55,7 +55,7 @@ func (wallet *Wallet) PublishUnminedTransactions() error { return err } - return wallet.Internal().PublishUnminedTransactions(wallet.shutdownContext(), n) + return wallet.Internal().PublishUnminedTransactions(wallet.ShutdownContext(), n) } func (wallet *Wallet) GetTransaction(txHash string) (string, error) { @@ -80,7 +80,7 @@ func (wallet *Wallet) GetTransactionRaw(txHash string) (*Transaction, error) { return nil, err } - txSummary, _, blockHash, err := wallet.Internal().TransactionSummary(wallet.shutdownContext(), hash) + txSummary, _, blockHash, err := wallet.Internal().TransactionSummary(wallet.ShutdownContext(), hash) if err != nil { log.Error(err) return nil, err @@ -104,57 +104,57 @@ func (wallet *Wallet) GetTransactions(offset, limit, txFilter int32, newestFirst } func (wallet *Wallet) GetTransactionsRaw(offset, limit, txFilter int32, newestFirst bool) (transactions []Transaction, err error) { - err = wallet.walletDataDB.Read(offset, limit, txFilter, newestFirst, wallet.RequiredConfirmations(), wallet.GetBestBlock(), &transactions) + err = wallet.WalletDataDB.Read(offset, limit, txFilter, newestFirst, wallet.RequiredConfirmations(), wallet.GetBestBlock(), &transactions) return } -func (mw *MultiWallet) GetTransactions(offset, limit, txFilter int32, newestFirst bool) (string, error) { +// func (mw *MultiWallet) GetTransactions(offset, limit, txFilter int32, newestFirst bool) (string, error) { - transactions, err := mw.GetTransactionsRaw(offset, limit, txFilter, newestFirst) - if err != nil { - return "", err - } +// transactions, err := mw.GetTransactionsRaw(offset, limit, txFilter, newestFirst) +// if err != nil { +// return "", err +// } - jsonEncodedTransactions, err := json.Marshal(&transactions) - if err != nil { - return "", err - } +// jsonEncodedTransactions, err := json.Marshal(&transactions) +// if err != nil { +// return "", err +// } - return string(jsonEncodedTransactions), nil -} +// return string(jsonEncodedTransactions), nil +// } -func (mw *MultiWallet) GetTransactionsRaw(offset, limit, txFilter int32, newestFirst bool) ([]Transaction, error) { - transactions := make([]Transaction, 0) - for _, wallet := range mw.wallets { - walletTransactions, err := wallet.GetTransactionsRaw(offset, limit, txFilter, newestFirst) - if err != nil { - return nil, err - } +// func (mw *MultiWallet) GetTransactionsRaw(offset, limit, txFilter int32, newestFirst bool) ([]Transaction, error) { +// transactions := make([]Transaction, 0) +// for _, wallet := range mw.wallets { +// walletTransactions, err := wallet.GetTransactionsRaw(offset, limit, txFilter, newestFirst) +// if err != nil { +// return nil, err +// } - transactions = append(transactions, walletTransactions...) - } +// transactions = append(transactions, walletTransactions...) +// } - // sort transaction by timestamp in descending order - sort.Slice(transactions[:], func(i, j int) bool { - if newestFirst { - return transactions[i].Timestamp > transactions[j].Timestamp - } - return transactions[i].Timestamp < transactions[j].Timestamp - }) +// // sort transaction by timestamp in descending order +// sort.Slice(transactions[:], func(i, j int) bool { +// if newestFirst { +// return transactions[i].Timestamp > transactions[j].Timestamp +// } +// return transactions[i].Timestamp < transactions[j].Timestamp +// }) - if len(transactions) > int(limit) && limit > 0 { - transactions = transactions[:limit] - } +// if len(transactions) > int(limit) && limit > 0 { +// transactions = transactions[:limit] +// } - return transactions, nil -} +// return transactions, nil +// } func (wallet *Wallet) CountTransactions(txFilter int32) (int, error) { - return wallet.walletDataDB.Count(txFilter, wallet.RequiredConfirmations(), wallet.GetBestBlock(), &Transaction{}) + return wallet.WalletDataDB.Count(txFilter, wallet.RequiredConfirmations(), wallet.GetBestBlock(), &Transaction{}) } func (wallet *Wallet) TicketHasVotedOrRevoked(ticketHash string) (bool, error) { - err := wallet.walletDataDB.FindOne("TicketSpentHash", ticketHash, &Transaction{}) + err := wallet.WalletDataDB.FindOne("TicketSpentHash", ticketHash, &Transaction{}) if err != nil { if err == storm.ErrNotFound { return false, nil @@ -167,7 +167,7 @@ func (wallet *Wallet) TicketHasVotedOrRevoked(ticketHash string) (bool, error) { func (wallet *Wallet) TicketSpender(ticketHash string) (*Transaction, error) { var spender Transaction - err := wallet.walletDataDB.FindOne("TicketSpentHash", ticketHash, &spender) + err := wallet.WalletDataDB.FindOne("TicketSpentHash", ticketHash, &spender) if err != nil { if err == storm.ErrNotFound { return nil, nil diff --git a/wallets/dcr/txandblocknotifications.go b/wallets/dcr/txandblocknotifications.go new file mode 100644 index 000000000..f127bdbe4 --- /dev/null +++ b/wallets/dcr/txandblocknotifications.go @@ -0,0 +1,159 @@ +package dcr + +import ( + // "encoding/json" + + // "decred.org/dcrwallet/v2/errors" +) + +// func (mw *MultiWallet) listenForTransactions(walletID int) { +// go func() { + +// wallet := mw.wallets[walletID] +// n := wallet.Internal().NtfnServer.TransactionNotifications() + +// for { +// select { +// case v := <-n.C: +// if v == nil { +// return +// } +// for _, transaction := range v.UnminedTransactions { +// tempTransaction, err := wallet.decodeTransactionWithTxSummary(&transaction, nil) +// if err != nil { +// log.Errorf("[%d] Error ntfn parse tx: %v", wallet.ID, err) +// return +// } + +// overwritten, err := wallet.walletDataDB.SaveOrUpdate(&Transaction{}, tempTransaction) +// if err != nil { +// log.Errorf("[%d] New Tx save err: %v", wallet.ID, err) +// return +// } + +// if !overwritten { +// log.Infof("[%d] New Transaction %s", wallet.ID, tempTransaction.Hash) + +// result, err := json.Marshal(tempTransaction) +// if err != nil { +// log.Error(err) +// } else { +// mw.mempoolTransactionNotification(string(result)) +// } +// } +// } + +// for _, block := range v.AttachedBlocks { +// blockHash := block.Header.BlockHash() +// for _, transaction := range block.Transactions { +// tempTransaction, err := wallet.decodeTransactionWithTxSummary(&transaction, &blockHash) +// if err != nil { +// log.Errorf("[%d] Error ntfn parse tx: %v", wallet.ID, err) +// return +// } + +// _, err = wallet.walletDataDB.SaveOrUpdate(&Transaction{}, tempTransaction) +// if err != nil { +// log.Errorf("[%d] Incoming block replace tx error :%v", wallet.ID, err) +// return +// } +// mw.publishTransactionConfirmed(wallet.ID, transaction.Hash.String(), int32(block.Header.Height)) +// } + +// mw.publishBlockAttached(wallet.ID, int32(block.Header.Height)) +// } + +// if len(v.AttachedBlocks) > 0 { +// mw.checkWalletMixers() +// } + +// case <-mw.syncData.syncCanceled: +// n.Done() +// } +// } +// }() +// } + +// AddTxAndBlockNotificationListener registers a set of functions to be invoked +// when a transaction or block update is processed by the wallet. If async is +// true, the provided callback methods will be called from separate goroutines, +// allowing notification senders to continue their operation without waiting +// for the listener to complete processing the notification. This asyncrhonous +// handling is especially important for cases where the wallet process that +// sends the notification temporarily prevents access to other wallet features +// until all notification handlers finish processing the notification. If a +// notification handler were to try to access such features, it would result +// in a deadlock. +// func (mw *MultiWallet) AddTxAndBlockNotificationListener(txAndBlockNotificationListener TxAndBlockNotificationListener, async bool, uniqueIdentifier string) error { +// mw.notificationListenersMu.Lock() +// defer mw.notificationListenersMu.Unlock() + +// _, ok := mw.txAndBlockNotificationListeners[uniqueIdentifier] +// if ok { +// return errors.New(ErrListenerAlreadyExist) +// } + +// if async { +// mw.txAndBlockNotificationListeners[uniqueIdentifier] = &asyncTxAndBlockNotificationListener{ +// l: txAndBlockNotificationListener, +// } +// } else { +// mw.txAndBlockNotificationListeners[uniqueIdentifier] = txAndBlockNotificationListener +// } + +// return nil +// } + +// func (mw *MultiWallet) RemoveTxAndBlockNotificationListener(uniqueIdentifier string) { +// mw.notificationListenersMu.Lock() +// defer mw.notificationListenersMu.Unlock() + +// delete(mw.txAndBlockNotificationListeners, uniqueIdentifier) +// } + +// func (mw *MultiWallet) checkWalletMixers() { +// for _, wallet := range mw.wallets { +// if wallet.IsAccountMixerActive() { +// unmixedAccount := wallet.ReadInt32ConfigValueForKey(AccountMixerUnmixedAccount, -1) +// hasMixableOutput, err := wallet.accountHasMixableOutput(unmixedAccount) +// if err != nil { +// log.Errorf("Error checking for mixable outputs: %v", err) +// } + +// if !hasMixableOutput { +// log.Infof("[%d] unmixed account does not have a mixable output, stopping account mixer", wallet.ID) +// err = mw.StopAccountMixer(wallet.ID) +// if err != nil { +// log.Errorf("Error stopping account mixer: %v", err) +// } +// } +// } +// } +// } + +// func (mw *MultiWallet) mempoolTransactionNotification(transaction string) { +// mw.notificationListenersMu.RLock() +// defer mw.notificationListenersMu.RUnlock() + +// for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { +// txAndBlockNotifcationListener.OnTransaction(transaction) +// } +// } + +// func (mw *MultiWallet) publishTransactionConfirmed(walletID int, transactionHash string, blockHeight int32) { +// mw.notificationListenersMu.RLock() +// defer mw.notificationListenersMu.RUnlock() + +// for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { +// txAndBlockNotifcationListener.OnTransactionConfirmed(walletID, transactionHash, blockHeight) +// } +// } + +// func (mw *MultiWallet) publishBlockAttached(walletID int, blockHeight int32) { +// mw.notificationListenersMu.RLock() +// defer mw.notificationListenersMu.RUnlock() + +// for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { +// txAndBlockNotifcationListener.OnBlockAttached(walletID, blockHeight) +// } +// } diff --git a/txauthor.go b/wallets/dcr/txauthor.go similarity index 93% rename from txauthor.go rename to wallets/dcr/txauthor.go index 878889983..73dcf2e5c 100644 --- a/txauthor.go +++ b/wallets/dcr/txauthor.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "bytes" @@ -32,24 +32,24 @@ type TxAuthor struct { needsConstruct bool } -func (mw *MultiWallet) NewUnsignedTx(walletID int, sourceAccountNumber int32) (*TxAuthor, error) { - sourceWallet := mw.WalletWithID(walletID) - if sourceWallet == nil { - return nil, fmt.Errorf(ErrWalletNotFound) - } - - _, err := sourceWallet.GetAccount(sourceAccountNumber) - if err != nil { - return nil, err - } - - return &TxAuthor{ - sourceWallet: sourceWallet, - sourceAccountNumber: uint32(sourceAccountNumber), - destinations: make([]TransactionDestination, 0), - needsConstruct: true, - }, nil -} +// func (mw *MultiWallet) NewUnsignedTx(walletID int, sourceAccountNumber int32) (*TxAuthor, error) { +// sourceWallet := mw.WalletWithID(walletID) +// if sourceWallet == nil { +// return nil, fmt.Errorf(ErrWalletNotFound) +// } + +// _, err := sourceWallet.GetAccount(sourceAccountNumber) +// if err != nil { +// return nil, err +// } + +// return &TxAuthor{ +// sourceWallet: sourceWallet, +// sourceAccountNumber: uint32(sourceAccountNumber), +// destinations: make([]TransactionDestination, 0), +// needsConstruct: true, +// }, nil +// } func (tx *TxAuthor) AddSendDestination(address string, atomAmount int64, sendMax bool) error { _, err := stdaddr.DecodeAddress(address, tx.sourceWallet.chainParams) @@ -194,7 +194,7 @@ func (tx *TxAuthor) UseInputs(utxoKeys []string) error { Hash: *txHash, Index: uint32(index), } - outputInfo, err := tx.sourceWallet.Internal().OutputInfo(tx.sourceWallet.shutdownContext(), op) + outputInfo, err := tx.sourceWallet.Internal().OutputInfo(tx.sourceWallet.ShutdownContext(), op) if err != nil { return fmt.Errorf("no valid utxo found for '%s' in the source account", utxoKey) } @@ -251,7 +251,7 @@ func (tx *TxAuthor) Broadcast(privatePassphrase []byte) ([]byte, error) { lock <- time.Time{} }() - ctx := tx.sourceWallet.shutdownContext() + ctx := tx.sourceWallet.ShutdownContext() err = tx.sourceWallet.Internal().Unlock(ctx, privatePassphrase, lock) if err != nil { log.Error(err) @@ -308,16 +308,16 @@ func (tx *TxAuthor) unsignedTransaction() (*txauthor.AuthoredTx, error) { } func (tx *TxAuthor) constructTransaction() (*txauthor.AuthoredTx, error) { - if len(tx.inputs) != 0 { - return tx.constructCustomTransaction() - } + // if len(tx.inputs) != 0 { + // return tx.constructCustomTransaction() + // } var err error var outputs = make([]*wire.TxOut, 0) var outputSelectionAlgorithm w.OutputSelectionAlgorithm = w.OutputSelectionAlgorithmDefault var changeSource txauthor.ChangeSource - ctx := tx.sourceWallet.shutdownContext() + ctx := tx.sourceWallet.ShutdownContext() for _, destination := range tx.destinations { if err := tx.validateSendAmount(destination.SendMax, destination.AtomAmount); err != nil { diff --git a/txindex.go b/wallets/dcr/txindex.go similarity index 79% rename from txindex.go rename to wallets/dcr/txindex.go index ffc29f3b4..a91f92952 100644 --- a/txindex.go +++ b/wallets/dcr/txindex.go @@ -1,13 +1,13 @@ -package dcrlibwallet +package dcr import ( w "decred.org/dcrwallet/v2/wallet" "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/planetdecred/dcrlibwallet/walletdata" + "github.com/planetdecred/dcrlibwallet/wallets/dcr/walletdata" ) func (wallet *Wallet) IndexTransactions() error { - ctx := wallet.shutdownContext() + ctx := wallet.ShutdownContext() var totalIndex int32 var txEndHeight uint32 @@ -27,7 +27,7 @@ func (wallet *Wallet) IndexTransactions() error { return false, err } - _, err = wallet.walletDataDB.SaveOrUpdate(&Transaction{}, tx) + _, err = wallet.WalletDataDB.SaveOrUpdate(&Transaction{}, tx) if err != nil { log.Errorf("[%d] Index tx replace tx err : %v", wallet.ID, err) return false, err @@ -38,7 +38,7 @@ func (wallet *Wallet) IndexTransactions() error { if block.Header != nil { txEndHeight = block.Header.Height - err := wallet.walletDataDB.SaveLastIndexPoint(int32(txEndHeight)) + err := wallet.WalletDataDB.SaveLastIndexPoint(int32(txEndHeight)) if err != nil { log.Errorf("[%d] Set tx index end block height error: ", wallet.ID, err) return false, err @@ -55,7 +55,7 @@ func (wallet *Wallet) IndexTransactions() error { } } - beginHeight, err := wallet.walletDataDB.ReadIndexingStartBlock() + beginHeight, err := wallet.WalletDataDB.ReadIndexingStartBlock() if err != nil { log.Errorf("[%d] Get tx indexing start point error: %v", wallet.ID, err) return err @@ -67,14 +67,14 @@ func (wallet *Wallet) IndexTransactions() error { endBlock := w.NewBlockIdentifierFromHeight(endHeight) defer func() { - count, err := wallet.walletDataDB.Count(walletdata.TxFilterAll, wallet.RequiredConfirmations(), endHeight, &Transaction{}) + count, err := wallet.WalletDataDB.Count(walletdata.TxFilterAll, wallet.RequiredConfirmations(), endHeight, &Transaction{}) if err != nil { log.Errorf("[%d] Post-indexing tx count error :%v", wallet.ID, err) } else if count > 0 { log.Infof("[%d] Transaction index finished at %d, %d transaction(s) indexed in total", wallet.ID, endHeight, count) } - err = wallet.walletDataDB.SaveLastIndexPoint(endHeight) + err = wallet.WalletDataDB.SaveLastIndexPoint(endHeight) if err != nil { log.Errorf("[%d] Set tx index end block height error: ", wallet.ID, err) } @@ -84,8 +84,8 @@ func (wallet *Wallet) IndexTransactions() error { return wallet.Internal().GetTransactions(ctx, rangeFn, startBlock, endBlock) } -func (wallet *Wallet) reindexTransactions() error { - err := wallet.walletDataDB.ClearSavedTransactions(&Transaction{}) +func (wallet *Wallet) ReindexTransactions() error { + err := wallet.WalletDataDB.ClearSavedTransactions(&Transaction{}) if err != nil { return err } diff --git a/txparser.go b/wallets/dcr/txparser.go similarity index 94% rename from txparser.go rename to wallets/dcr/txparser.go index 126c8c985..30826bb9c 100644 --- a/txparser.go +++ b/wallets/dcr/txparser.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "fmt" @@ -15,7 +15,7 @@ func (wallet *Wallet) decodeTransactionWithTxSummary(txSummary *w.TransactionSum var blockHeight int32 = BlockHeightInvalid if blockHash != nil { blockIdentifier := w.NewBlockIdentifierFromHash(blockHash) - blockInfo, err := wallet.Internal().BlockInfo(wallet.shutdownContext(), blockIdentifier) + blockInfo, err := wallet.Internal().BlockInfo(wallet.ShutdownContext(), blockIdentifier) if err != nil { log.Error(err) } else { @@ -104,7 +104,7 @@ func (wallet *Wallet) decodeTransactionWithTxSummary(txSummary *w.TransactionSum // update ticket with spender hash ticketPurchaseTx.TicketSpender = decodedTx.Hash - wallet.walletDataDB.SaveOrUpdate(&Transaction{}, ticketPurchaseTx) + wallet.WalletDataDB.SaveOrUpdate(&Transaction{}, ticketPurchaseTx) } return decodedTx, nil diff --git a/wallets/dcr/types.go b/wallets/dcr/types.go new file mode 100644 index 000000000..063ea9542 --- /dev/null +++ b/wallets/dcr/types.go @@ -0,0 +1,530 @@ +package dcr + +import ( + "context" + "fmt" + "net" + + "decred.org/dcrwallet/v2/wallet/udb" + + "github.com/decred/dcrd/chaincfg/v3" + "github.com/decred/dcrd/dcrutil/v4" + "github.com/planetdecred/dcrlibwallet/internal/vsp" +) + +// WalletConfig defines options for configuring wallet behaviour. +// This is a subset of the config used by dcrwallet. +type WalletConfig struct { + // General + GapLimit uint32 // Allowed unused address gap between used addresses of accounts + ManualTickets bool // Do not discover new tickets through network synchronization + AllowHighFees bool // Do not perform high fee checks + RelayFee dcrutil.Amount // Transaction fee per kilobyte + AccountGapLimit int // Allowed gap of unused accounts + DisableCoinTypeUpgrades bool // Never upgrade from legacy to SLIP0044 coin type keys + + // CSPP + MixSplitLimit int // Connection limit to CoinShuffle++ server per change amount +} + +type CSPPConfig struct { + CSPPServer string + DialCSPPServer func(ctx context.Context, network, addr string) (net.Conn, error) + MixedAccount uint32 + MixedAccountBranch uint32 + TicketSplitAccount uint32 + ChangeAccount uint32 +} + +type WalletsIterator struct { + currentIndex int + wallets []*Wallet +} + +type BlockInfo struct { + Height int32 + Timestamp int64 +} + +type Amount struct { + AtomValue int64 + DcrValue float64 +} + +type TxFeeAndSize struct { + Fee *Amount + Change *Amount + EstimatedSignedSize int +} + +type UnsignedTransaction struct { + UnsignedTransaction []byte + EstimatedSignedSize int + ChangeIndex int + TotalOutputAmount int64 + TotalPreviousOutputAmount int64 +} + +type Balance struct { + Total int64 + Spendable int64 + ImmatureReward int64 + ImmatureStakeGeneration int64 + LockedByTickets int64 + VotingAuthority int64 + UnConfirmed int64 +} + +type Account struct { + WalletID int + Number int32 + Name string + Balance *Balance + TotalBalance int64 + ExternalKeyCount int32 + InternalKeyCount int32 + ImportedKeyCount int32 +} + +type AccountsIterator struct { + currentIndex int + accounts []*Account +} + +type Accounts struct { + Count int + Acc []*Account + CurrentBlockHash []byte + CurrentBlockHeight int32 +} + +type PeerInfo struct { + ID int32 `json:"id"` + Addr string `json:"addr"` + AddrLocal string `json:"addr_local"` + Services string `json:"services"` + Version uint32 `json:"version"` + SubVer string `json:"sub_ver"` + StartingHeight int64 `json:"starting_height"` + BanScore int32 `json:"ban_score"` +} + +type AccountMixerNotificationListener interface { + OnAccountMixerStarted(walletID int) + OnAccountMixerEnded(walletID int) +} + +/** begin sync-related types */ + +type SyncProgressListener interface { + OnSyncStarted(wasRestarted bool) + OnPeerConnectedOrDisconnected(numberOfConnectedPeers int32) + OnCFiltersFetchProgress(cfiltersFetchProgress *CFiltersFetchProgressReport) + OnHeadersFetchProgress(headersFetchProgress *HeadersFetchProgressReport) + OnAddressDiscoveryProgress(addressDiscoveryProgress *AddressDiscoveryProgressReport) + OnHeadersRescanProgress(headersRescanProgress *HeadersRescanProgressReport) + OnSyncCompleted() + OnSyncCanceled(willRestart bool) + OnSyncEndedWithError(err error) + Debug(debugInfo *DebugInfo) +} + +type GeneralSyncProgress struct { + TotalSyncProgress int32 `json:"totalSyncProgress"` + TotalTimeRemainingSeconds int64 `json:"totalTimeRemainingSeconds"` +} + +type CFiltersFetchProgressReport struct { + *GeneralSyncProgress + beginFetchCFiltersTimeStamp int64 + startCFiltersHeight int32 + cfiltersFetchTimeSpent int64 + totalFetchedCFiltersCount int32 + TotalCFiltersToFetch int32 `json:"totalCFiltersToFetch"` + CurrentCFilterHeight int32 `json:"currentCFilterHeight"` + CFiltersFetchProgress int32 `json:"headersFetchProgress"` +} + +type HeadersFetchProgressReport struct { + *GeneralSyncProgress + headersFetchTimeSpent int64 + beginFetchTimeStamp int64 + startHeaderHeight int32 + totalFetchedHeadersCount int32 + TotalHeadersToFetch int32 `json:"totalHeadersToFetch"` + CurrentHeaderHeight int32 `json:"currentHeaderHeight"` + CurrentHeaderTimestamp int64 `json:"currentHeaderTimestamp"` + HeadersFetchProgress int32 `json:"headersFetchProgress"` +} + +type AddressDiscoveryProgressReport struct { + *GeneralSyncProgress + addressDiscoveryStartTime int64 + totalDiscoveryTimeSpent int64 + AddressDiscoveryProgress int32 `json:"addressDiscoveryProgress"` + WalletID int `json:"walletID"` +} + +type HeadersRescanProgressReport struct { + *GeneralSyncProgress + TotalHeadersToScan int32 `json:"totalHeadersToScan"` + CurrentRescanHeight int32 `json:"currentRescanHeight"` + RescanProgress int32 `json:"rescanProgress"` + RescanTimeRemaining int64 `json:"rescanTimeRemaining"` + WalletID int `json:"walletID"` +} + +type DebugInfo struct { + TotalTimeElapsed int64 + TotalTimeRemaining int64 + CurrentStageTimeElapsed int64 + CurrentStageTimeRemaining int64 +} + +/** end sync-related types */ + +/** begin tx-related types */ + +type TxAndBlockNotificationListener interface { + OnTransaction(transaction string) + OnBlockAttached(walletID int, blockHeight int32) + OnTransactionConfirmed(walletID int, hash string, blockHeight int32) +} + +// asyncTxAndBlockNotificationListener is a TxAndBlockNotificationListener that +// triggers notifcation callbacks asynchronously. +type asyncTxAndBlockNotificationListener struct { + l TxAndBlockNotificationListener +} + +// OnTransaction satisfies the TxAndBlockNotificationListener interface and +// starts a goroutine to actually handle the notification using the embedded +// listener. +func (asyncTxBlockListener *asyncTxAndBlockNotificationListener) OnTransaction(transaction string) { + go asyncTxBlockListener.l.OnTransaction(transaction) +} + +// OnBlockAttached satisfies the TxAndBlockNotificationListener interface and +// starts a goroutine to actually handle the notification using the embedded +// listener. +func (asyncTxBlockListener *asyncTxAndBlockNotificationListener) OnBlockAttached(walletID int, blockHeight int32) { + go asyncTxBlockListener.l.OnBlockAttached(walletID, blockHeight) +} + +// OnTransactionConfirmed satisfies the TxAndBlockNotificationListener interface +// and starts a goroutine to actually handle the notification using the embedded +// listener. +func (asyncTxBlockListener *asyncTxAndBlockNotificationListener) OnTransactionConfirmed(walletID int, hash string, blockHeight int32) { + go asyncTxBlockListener.l.OnTransactionConfirmed(walletID, hash, blockHeight) +} + +type BlocksRescanProgressListener interface { + OnBlocksRescanStarted(walletID int) + OnBlocksRescanProgress(*HeadersRescanProgressReport) + OnBlocksRescanEnded(walletID int, err error) +} + +// Transaction is used with storm for tx indexing operations. +// For faster queries, the `Hash`, `Type` and `Direction` fields are indexed. +type Transaction struct { + WalletID int `json:"walletID"` + Hash string `storm:"id,unique" json:"hash"` + Type string `storm:"index" json:"type"` + Hex string `json:"hex"` + Timestamp int64 `storm:"index" json:"timestamp"` + BlockHeight int32 `storm:"index" json:"block_height"` + TicketSpender string `storm:"index" json:"ticket_spender"` + + MixDenomination int64 `json:"mix_denom"` + MixCount int32 `json:"mix_count"` + + Version int32 `json:"version"` + LockTime int32 `json:"lock_time"` + Expiry int32 `json:"expiry"` + Fee int64 `json:"fee"` + FeeRate int64 `json:"fee_rate"` + Size int `json:"size"` + + Direction int32 `storm:"index" json:"direction"` + Amount int64 `json:"amount"` + Inputs []*TxInput `json:"inputs"` + Outputs []*TxOutput `json:"outputs"` + + // Vote Info + VoteVersion int32 `json:"vote_version"` + LastBlockValid bool `json:"last_block_valid"` + VoteBits string `json:"vote_bits"` + VoteReward int64 `json:"vote_reward"` + TicketSpentHash string `storm:"unique" json:"ticket_spent_hash"` + DaysToVoteOrRevoke int32 `json:"days_to_vote_revoke"` +} + +type TxInput struct { + PreviousTransactionHash string `json:"previous_transaction_hash"` + PreviousTransactionIndex int32 `json:"previous_transaction_index"` + PreviousOutpoint string `json:"previous_outpoint"` + Amount int64 `json:"amount"` + AccountNumber int32 `json:"account_number"` +} + +type TxOutput struct { + Index int32 `json:"index"` + Amount int64 `json:"amount"` + Version int32 `json:"version"` + ScriptType string `json:"script_type"` + Address string `json:"address"` + Internal bool `json:"internal"` + AccountNumber int32 `json:"account_number"` +} + +// TxInfoFromWallet contains tx data that relates to the querying wallet. +// This info is used with `DecodeTransaction` to compose the entire details of a transaction. +type TxInfoFromWallet struct { + WalletID int + Hex string + Timestamp int64 + BlockHeight int32 + Inputs []*WalletInput + Outputs []*WalletOutput +} + +type WalletInput struct { + Index int32 `json:"index"` + AmountIn int64 `json:"amount_in"` + *WalletAccount +} + +type WalletOutput struct { + Index int32 `json:"index"` + AmountOut int64 `json:"amount_out"` + Internal bool `json:"internal"` + Address string `json:"address"` + *WalletAccount +} + +type WalletAccount struct { + AccountNumber int32 `json:"account_number"` + AccountName string `json:"account_name"` +} + +type TransactionDestination struct { + Address string + AtomAmount int64 + SendMax bool +} + +type TransactionOverview struct { + All int + Sent int + Received int + Transferred int + Mixed int + Staking int + Coinbase int +} + +/** end tx-related types */ + +/** begin ticket-related types */ + +type TicketPriceResponse struct { + TicketPrice int64 + Height int32 +} + +type StakingOverview struct { + All int + Unmined int + Immature int + Live int + Voted int + Revoked int + Expired int +} + +// TicketBuyerConfig defines configuration parameters for running +// an automated ticket buyer. +type TicketBuyerConfig struct { + VspHost string + PurchaseAccount int32 + BalanceToMaintain int64 + + vspClient *vsp.Client +} + +// VSPFeeStatus represents the current fee status of a ticket. +type VSPFeeStatus uint8 + +const ( + // VSPFeeProcessStarted represents the state which process has being + // called but fee still not paid. + VSPFeeProcessStarted VSPFeeStatus = iota + // VSPFeeProcessPaid represents the state where the process has being + // paid, but not published. + VSPFeeProcessPaid + VSPFeeProcessErrored + // VSPFeeProcessConfirmed represents the state where the fee has been + // confirmed by the VSP. + VSPFeeProcessConfirmed +) + +// String returns a human-readable interpretation of the vsp fee status. +func (status VSPFeeStatus) String() string { + switch udb.FeeStatus(status) { + case udb.VSPFeeProcessStarted: + return "fee process started" + case udb.VSPFeeProcessPaid: + return "fee paid" + case udb.VSPFeeProcessErrored: + return "fee payment errored" + case udb.VSPFeeProcessConfirmed: + return "fee confirmed by vsp" + default: + return fmt.Sprintf("invalid fee status %d", status) + } +} + +// VSPTicketInfo is information about a ticket that is assigned to a VSP. +type VSPTicketInfo struct { + VSP string + FeeTxHash string + FeeTxStatus VSPFeeStatus + // ConfirmedByVSP is nil if the ticket status could not be obtained + // from the VSP, false if the VSP hasn't confirmed the fee and true + // if the VSP has fully registered the ticket. + ConfirmedByVSP *bool + // VoteChoices is only set if the ticket status was obtained from the + // VSP. + VoteChoices map[string]string +} + +/** end ticket-related types */ + +/** begin politeia types */ +type Proposal struct { + ID int `storm:"id,increment"` + Token string `json:"token" storm:"unique"` + Category int32 `json:"category" storm:"index"` + Name string `json:"name"` + State int32 `json:"state"` + Status int32 `json:"status"` + Timestamp int64 `json:"timestamp"` + UserID string `json:"userid"` + Username string `json:"username"` + NumComments int32 `json:"numcomments"` + Version string `json:"version"` + PublishedAt int64 `json:"publishedat"` + IndexFile string `json:"indexfile"` + IndexFileVersion string `json:"fileversion"` + VoteStatus int32 `json:"votestatus"` + VoteApproved bool `json:"voteapproved"` + YesVotes int32 `json:"yesvotes"` + NoVotes int32 `json:"novotes"` + EligibleTickets int32 `json:"eligibletickets"` + QuorumPercentage int32 `json:"quorumpercentage"` + PassPercentage int32 `json:"passpercentage"` +} + +type ProposalOverview struct { + All int32 + Discussion int32 + Voting int32 + Approved int32 + Rejected int32 + Abandoned int32 +} + +type ProposalVoteDetails struct { + EligibleTickets []*EligibleTicket + Votes []*ProposalVote + YesVotes int32 + NoVotes int32 +} + +type EligibleTicket struct { + Hash string + Address string +} + +type ProposalVote struct { + Ticket *EligibleTicket + Bit string +} + +type ProposalNotificationListener interface { + OnProposalsSynced() + OnNewProposal(proposal *Proposal) + OnProposalVoteStarted(proposal *Proposal) + OnProposalVoteFinished(proposal *Proposal) +} + +/** end politea proposal types */ + +type UnspentOutput struct { + TransactionHash []byte + OutputIndex uint32 + OutputKey string + ReceiveTime int64 + Amount int64 + FromCoinbase bool + Tree int32 + PkScript []byte + Addresses string // separated by commas + Confirmations int32 +} + +/** end politea proposal types */ + +/** begin vspd-related types */ +type VspInfoResponse struct { + APIVersions []int64 `json:"apiversions"` + Timestamp int64 `json:"timestamp"` + PubKey []byte `json:"pubkey"` + FeePercentage float64 `json:"feepercentage"` + VspClosed bool `json:"vspclosed"` + Network string `json:"network"` + VspdVersion string `json:"vspdversion"` + Voting int64 `json:"voting"` + Voted int64 `json:"voted"` + Revoked int64 `json:"revoked"` +} + +type VSP struct { + Host string + *VspInfoResponse +} + +/** end vspd-related types */ + +/** begin agenda types */ + +// Agenda contains information about a consensus deployment +type Agenda struct { + AgendaID string `json:"agenda_id"` + Description string `json:"description"` + Mask uint32 `json:"mask"` + Choices []chaincfg.Choice `json:"choices"` + VotingPreference string `json:"voting_preference"` + StartTime int64 `json:"start_time"` + ExpireTime int64 `json:"expire_time"` + Status string `json:"status"` +} + +// DcrdataAgenda models agenda information for the active network from the +// dcrdata api https://dcrdata.decred.org/api/agendas for mainnet or +// https://testnet.decred.org/api/agendas for testnet. +type DcrdataAgenda struct { + Name string `json:"name"` + Description string `json:"-"` + Status string `json:"status"` + VotingStarted int64 `json:"-"` + VotingDone int64 `json:"-"` + Activated int64 `json:"-"` + HardForked int64 `json:"-"` + StartTime string `json:"-"` + ExpireTime string `json:"-"` + VoteVersion uint32 `json:"-"` + Mask uint16 `json:"-"` +} + +/** end agenda types */ diff --git a/wallets/dcr/utils.go b/wallets/dcr/utils.go new file mode 100644 index 000000000..c23e66ab9 --- /dev/null +++ b/wallets/dcr/utils.go @@ -0,0 +1,502 @@ +package dcr + +import ( + "context" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "math" + "net" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + // "decred.org/dcrwallet/v2/errors" + "decred.org/dcrwallet/v2/wallet" + "decred.org/dcrwallet/v2/wallet/txrules" + "decred.org/dcrwallet/v2/walletseed" + "github.com/decred/dcrd/chaincfg/chainhash" + "github.com/decred/dcrd/chaincfg/v3" + "github.com/decred/dcrd/dcrutil/v4" + "github.com/decred/dcrd/hdkeychain/v3" + "github.com/decred/dcrd/wire" + "github.com/planetdecred/dcrlibwallet/internal/loader" +) + +const ( + walletDbName = "wallet.db" + + // FetchPercentage is used to increase the initial estimate gotten during cfilters stage + FetchPercentage = 0.38 + + // Use 10% of estimated total headers fetch time to estimate rescan time + RescanPercentage = 0.1 + + // Use 80% of estimated total headers fetch time to estimate address discovery time + DiscoveryPercentage = 0.8 + + MaxAmountAtom = dcrutil.MaxAmount + MaxAmountDcr = dcrutil.MaxAmount / dcrutil.AtomsPerCoin + + TestnetHDPath = "m / 44' / 1' / " + LegacyTestnetHDPath = "m / 44’ / 11’ / " + MainnetHDPath = "m / 44' / 42' / " + LegacyMainnetHDPath = "m / 44’ / 20’ / " + + DefaultRequiredConfirmations = 2 + + LongAbbreviationFormat = "long" + ShortAbbreviationFormat = "short" + ShortestAbbreviationFormat = "shortest" +) + +// func (mw *MultiWallet) RequiredConfirmations() int32 { +// spendUnconfirmed := mw.ReadBoolConfigValueForKey(SpendUnconfirmedConfigKey, false) +// if spendUnconfirmed { +// return 0 +// } +// return DefaultRequiredConfirmations +// } + +func (wallet *Wallet) RequiredConfirmations() int32 { + var spendUnconfirmed bool + wallet.readUserConfigValue(true, SpendUnconfirmedConfigKey, &spendUnconfirmed) + if spendUnconfirmed { + return 0 + } + return DefaultRequiredConfirmations +} + +// func (mw *MultiWallet) listenForShutdown() { + +// mw.cancelFuncs = make([]context.CancelFunc, 0) +// mw.shuttingDown = make(chan bool) +// go func() { +// <-mw.shuttingDown +// for _, cancel := range mw.cancelFuncs { +// cancel() +// } +// }() +// } + +func (wallet *Wallet) ShutdownContextWithCancel() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + wallet.cancelFuncs = append(wallet.cancelFuncs, cancel) + return ctx, cancel +} + +func (wallet *Wallet) ShutdownContext() (ctx context.Context) { + ctx, _ = wallet.ShutdownContextWithCancel() + return +} + +func (wallet *Wallet) contextWithShutdownCancel() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + wallet.cancelFuncs = append(wallet.cancelFuncs, cancel) + return ctx, cancel +} + +// func (mw *MultiWallet) ValidateExtPubKey(extendedPubKey string) error { +// _, err := hdkeychain.NewKeyFromString(extendedPubKey, mw.chainParams) +// if err != nil { +// if err == hdkeychain.ErrInvalidChild { +// return errors.New(ErrUnusableSeed) +// } + +// return errors.New(ErrInvalid) +// } + +// return nil +// } + +func NormalizeAddress(addr string, defaultPort string) (string, error) { + // If the first SplitHostPort errors because of a missing port and not + // for an invalid host, add the port. If the second SplitHostPort + // fails, then a port is not missing and the original error should be + // returned. + host, port, origErr := net.SplitHostPort(addr) + if origErr == nil { + return net.JoinHostPort(host, port), nil + } + addr = net.JoinHostPort(addr, defaultPort) + _, _, err := net.SplitHostPort(addr) + if err != nil { + return "", origErr + } + return addr, nil +} + +// For use with gomobile bind, +// doesn't support the alternative `GenerateSeed` function because it returns more than 2 types. +func GenerateSeed() (string, error) { + seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) + if err != nil { + return "", err + } + + return walletseed.EncodeMnemonic(seed), nil +} + +func VerifySeed(seedMnemonic string) bool { + _, err := walletseed.DecodeUserInput(seedMnemonic) + return err == nil +} + +// ExtractDateOrTime returns the date represented by the timestamp as a date string if the timestamp is over 24 hours ago. +// Otherwise, the time alone is returned as a string. +func ExtractDateOrTime(timestamp int64) string { + utcTime := time.Unix(timestamp, 0).UTC() + if time.Now().UTC().Sub(utcTime).Hours() > 24 { + return utcTime.Format("2006-01-02") + } else { + return utcTime.Format("15:04:05") + } +} + +func FormatUTCTime(timestamp int64) string { + return time.Unix(timestamp, 0).UTC().Format("2006-01-02 15:04:05") +} + +func AmountCoin(amount int64) float64 { + return dcrutil.Amount(amount).ToCoin() +} + +func AmountAtom(f float64) int64 { + amount, err := dcrutil.NewAmount(f) + if err != nil { + log.Error(err) + return -1 + } + return int64(amount) +} + +func EncodeHex(hexBytes []byte) string { + return hex.EncodeToString(hexBytes) +} + +func EncodeBase64(text []byte) string { + return base64.StdEncoding.EncodeToString(text) +} + +func DecodeBase64(base64Text string) ([]byte, error) { + b, err := base64.StdEncoding.DecodeString(base64Text) + if err != nil { + return nil, err + } + + return b, nil +} + +func ShannonEntropy(text string) (entropy float64) { + if text == "" { + return 0 + } + for i := 0; i < 256; i++ { + px := float64(strings.Count(text, string(byte(i)))) / float64(len(text)) + if px > 0 { + entropy += -px * math.Log2(px) + } + } + return entropy +} + +// func TransactionDirectionName(direction int32) string { +// switch direction { +// case TxDirectionSent: +// return "Sent" +// case TxDirectionReceived: +// return "Received" +// case TxDirectionTransferred: +// return "Yourself" +// default: +// return "invalid" +// } +// } + +func CalculateTotalTimeRemaining(timeRemainingInSeconds int64) string { + minutes := timeRemainingInSeconds / 60 + if minutes > 0 { + return fmt.Sprintf("%d min", minutes) + } + return fmt.Sprintf("%d sec", timeRemainingInSeconds) +} + +func CalculateDaysBehind(lastHeaderTime int64) string { + diff := time.Since(time.Unix(lastHeaderTime, 0)) + daysBehind := int(math.Round(diff.Hours() / 24)) + if daysBehind == 0 { + return "<1 day" + } else if daysBehind == 1 { + return "1 day" + } else { + return fmt.Sprintf("%d days", daysBehind) + } +} + +func StringsToHashes(h []string) ([]*chainhash.Hash, error) { + hashes := make([]*chainhash.Hash, 0, len(h)) + for _, v := range h { + hash, err := chainhash.NewHashFromStr(v) + if err != nil { + return nil, err + } + hashes = append(hashes, hash) + } + return hashes, nil +} + +func roundUp(n float64) int32 { + return int32(math.Round(n)) +} + +func WalletUniqueConfigKey(walletID int, key string) string { + return fmt.Sprintf("%d%s", walletID, key) +} + +func WalletExistsAt(directory string) bool { + walletDbFilePath := filepath.Join(directory, walletDbName) + exists, err := fileExists(walletDbFilePath) + if err != nil { + log.Errorf("wallet exists check error: %v", err) + } + return exists +} + +func fileExists(filePath string) (bool, error) { + _, err := os.Stat(filePath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func moveFile(sourcePath, destinationPath string) error { + if exists, _ := fileExists(sourcePath); exists { + return os.Rename(sourcePath, destinationPath) + } + return nil +} + +// done returns whether the context's Done channel was closed due to +// cancellation or exceeded deadline. +func done(ctx context.Context) bool { + select { + case <-ctx.Done(): + return true + default: + return false + } +} + +func backupFile(fileName string, suffix int) (newName string, err error) { + newName = fileName + ".bak" + strconv.Itoa(suffix) + exists, err := fileExists(newName) + if err != nil { + return "", err + } else if exists { + return backupFile(fileName, suffix+1) + } + + err = moveFile(fileName, newName) + if err != nil { + return "", err + } + + return newName, nil +} + +func initWalletLoader(chainParams *chaincfg.Params, walletDataDir, walletDbDriver string) *loader.Loader { + // TODO: Allow users provide values to override these defaults. + cfg := &WalletConfig{ + GapLimit: 20, + AllowHighFees: false, + RelayFee: txrules.DefaultRelayFeePerKb, + AccountGapLimit: wallet.DefaultAccountGapLimit, + DisableCoinTypeUpgrades: false, + ManualTickets: false, + MixSplitLimit: 10, + } + + stakeOptions := &loader.StakeOptions{ + VotingEnabled: false, + AddressReuse: false, + VotingAddress: nil, + } + walletLoader := loader.NewLoader(chainParams, walletDataDir, stakeOptions, + cfg.GapLimit, cfg.AllowHighFees, cfg.RelayFee, cfg.AccountGapLimit, + cfg.DisableCoinTypeUpgrades, cfg.ManualTickets, cfg.MixSplitLimit) + + if walletDbDriver != "" { + walletLoader.SetDatabaseDriver(walletDbDriver) + } + + return walletLoader +} + +// makePlural is used with the TimeElapsed function. makePlural checks if the arguments passed is > 1, +// if true, it adds "s" after the given time to make it plural +func makePlural(x float64) string { + if int(x) == 1 { + return "" + } + return "s" +} + +// TimeElapsed returns the formatted time diffrence between two times as a string. +// If the argument `fullTime` is set to true, then the full time available is returned e.g 3 hours, 2 minutes, 20 seconds ago, +// as opposed to 3 hours ago. +// If the argument `abbreviationFormat` is set to `long` the time format is e.g 2 minutes +// If the argument `abbreviationFormat` is set to `short` the time format is e.g 2 mins +// If the argument `abbreviationFormat` is set to `shortest` the time format is e.g 2 m +func TimeElapsed(now, then time.Time, abbreviationFormat string, fullTime bool) string { + var parts []string + var text string + + year2, month2, day2 := now.Date() + hour2, minute2, second2 := now.Clock() + + year1, month1, day1 := then.Date() + hour1, minute1, second1 := then.Clock() + + year := math.Abs(float64(year2 - year1)) + month := math.Abs(float64(month2 - month1)) + day := math.Abs(float64(day2 - day1)) + hour := math.Abs(float64(hour2 - hour1)) + minute := math.Abs(float64(minute2 - minute1)) + second := math.Abs(float64(second2 - second1)) + + week := math.Floor(day / 7) + + if year > 0 { + if abbreviationFormat == LongAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(year))+" year"+makePlural(year)) + } else if abbreviationFormat == ShortAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(year))+" yr"+makePlural(year)) + } else if abbreviationFormat == ShortestAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(year))+" y") + } + } + + if month > 0 { + if abbreviationFormat == LongAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(month))+" month"+makePlural(month)) + } else if abbreviationFormat == ShortAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(month))+" mon"+makePlural(month)) + } else if abbreviationFormat == ShortestAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(month))+" m") + } + } + + if week > 0 { + if abbreviationFormat == LongAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(week))+" week"+makePlural(week)) + } else if abbreviationFormat == ShortAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(week))+" wk"+makePlural(week)) + } else if abbreviationFormat == ShortestAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(week))+" w") + } + } + + if day > 0 { + if abbreviationFormat == LongAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(day))+" day"+makePlural(day)) + } else if abbreviationFormat == ShortAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(day))+" dy"+makePlural(day)) + } else if abbreviationFormat == ShortestAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(day))+" d") + } + } + + if hour > 0 { + if abbreviationFormat == LongAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(hour))+" hour"+makePlural(hour)) + } else if abbreviationFormat == ShortAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(hour))+" hr"+makePlural(hour)) + } else if abbreviationFormat == ShortestAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(hour))+" h") + } + } + + if minute > 0 { + if abbreviationFormat == LongAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(minute))+" minute"+makePlural(minute)) + } else if abbreviationFormat == ShortAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(minute))+" min"+makePlural(minute)) + } else if abbreviationFormat == ShortestAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(minute))+" mi") + } + } + + if second > 0 { + if abbreviationFormat == LongAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(second))+" second"+makePlural(second)) + } else if abbreviationFormat == ShortAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(second))+" sec"+makePlural(second)) + } else if abbreviationFormat == ShortestAbbreviationFormat { + parts = append(parts, strconv.Itoa(int(second))+" s") + } + } + + if now.After(then) { + text = " ago" + } else { + text = " after" + } + + if len(parts) == 0 { + return "just now" + } + + if fullTime { + return strings.Join(parts, ", ") + text + } + return parts[0] + text +} + +// voteVersion was borrowed from upstream, and needs to always be in +// sync with the upstream method. This is the LOC to the upstream version: +// https://github.com/decred/dcrwallet/blob/master/wallet/wallet.go#L266 +func voteVersion(params *chaincfg.Params) uint32 { + switch params.Net { + case wire.MainNet: + return 9 + case 0x48e7a065: // TestNet2 + return 6 + case wire.TestNet3: + return 10 + case wire.SimNet: + return 10 + default: + return 1 + } +} + +// HttpGet helps to convert json(Byte data) into a struct object. +func HttpGet(url string, respObj interface{}) (*http.Response, []byte, error) { + rq := new(http.Client) + resp, err := rq.Get((url)) + if err != nil { + return nil, nil, err + } + + respBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, nil, err + } + + if resp.StatusCode != http.StatusOK { + return resp, respBytes, fmt.Errorf("%d response from server: %v", resp.StatusCode, string(respBytes)) + } + + err = json.Unmarshal(respBytes, respObj) + return resp, respBytes, err +} diff --git a/utxo.go b/wallets/dcr/utxo.go similarity index 98% rename from utxo.go rename to wallets/dcr/utxo.go index 5e8775c49..d0de847e1 100644 --- a/utxo.go +++ b/wallets/dcr/utxo.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "fmt" @@ -67,7 +67,7 @@ func (tx *TxAuthor) constructCustomTransaction() (*txauthor.AuthoredTx, error) { // if no change destination is provided and // no recipient is set to receive max amount. nextInternalAddress := func() (string, error) { - ctx := tx.sourceWallet.shutdownContext() + ctx := tx.sourceWallet.ShutdownContext() addr, err := tx.sourceWallet.Internal().NewChangeAddress(ctx, tx.sourceAccountNumber) if err != nil { return "", err diff --git a/wallets/dcr/vsp.go b/wallets/dcr/vsp.go new file mode 100644 index 000000000..fe0054d4b --- /dev/null +++ b/wallets/dcr/vsp.go @@ -0,0 +1,192 @@ +package dcr + +import ( + // "context" + "crypto/ed25519" + "encoding/base64" + "fmt" + "strings" + + "decred.org/dcrwallet/v2/errors" + "github.com/planetdecred/dcrlibwallet/internal/vsp" +) + +// VSPClient loads or creates a VSP client instance for the specified host. +func (wallet *Wallet) VSPClient(host string, pubKey []byte) (*vsp.Client, error) { + wallet.vspClientsMu.Lock() + defer wallet.vspClientsMu.Unlock() + client, ok := wallet.vspClients[host] + if ok { + return client, nil + } + + cfg := vsp.Config{ + URL: host, + PubKey: base64.StdEncoding.EncodeToString(pubKey), + Dialer: nil, // optional, but consider providing a value + Wallet: wallet.Internal(), + } + client, err := vsp.New(cfg) + if err != nil { + return nil, err + } + wallet.vspClients[host] = client + return client, nil +} + +// KnownVSPs returns a list of known VSPs. This list may be updated by calling +// ReloadVSPList. This method is safe for concurrent access. +// func (mw *MultiWallet) KnownVSPs() []*VSP { +// mw.vspMu.RLock() +// defer mw.vspMu.RUnlock() +// return mw.vsps // TODO: Return a copy. +// } + +// SaveVSP marks a VSP as known and will be susbequently included as part of +// known VSPs. +// func (mw *MultiWallet) SaveVSP(host string) (err error) { +// // check if host already exists +// vspDbData := mw.getVSPDBData() +// for _, savedHost := range vspDbData.SavedHosts { +// if savedHost == host { +// return fmt.Errorf("duplicate host %s", host) +// } +// } + +// // validate host network +// info, err := vspInfo(host) +// if err != nil { +// return err +// } + +// // TODO: defaultVSPs() uses strings.Contains(network, vspInfo.Network). +// if info.Network != mw.NetType() { +// return fmt.Errorf("invalid net %s", info.Network) +// } + +// vspDbData.SavedHosts = append(vspDbData.SavedHosts, host) +// mw.updateVSPDBData(vspDbData) + +// mw.vspMu.Lock() +// mw.vsps = append(mw.vsps, &VSP{Host: host, VspInfoResponse: info}) +// mw.vspMu.Unlock() + +// return +// } + +// LastUsedVSP returns the host of the last used VSP, as saved by the +// SaveLastUsedVSP() method. +// func (mw *MultiWallet) LastUsedVSP() string { +// return mw.getVSPDBData().LastUsedVSP +// } + +// SaveLastUsedVSP saves the host of the last used VSP. +// func (mw *MultiWallet) SaveLastUsedVSP(host string) { +// vspDbData := mw.getVSPDBData() +// vspDbData.LastUsedVSP = host +// mw.updateVSPDBData(vspDbData) +// } + +type vspDbData struct { + SavedHosts []string + LastUsedVSP string +} + +// func (mw *MultiWallet) getVSPDBData() *vspDbData { +// vspDbData := new(vspDbData) +// mw.ReadUserConfigValue(KnownVSPsConfigKey, vspDbData) +// return vspDbData +// } + +// func (mw *MultiWallet) updateVSPDBData(data *vspDbData) { +// mw.SaveUserConfigValue(KnownVSPsConfigKey, data) +// } + +// ReloadVSPList reloads the list of known VSPs. +// This method makes multiple network calls; should be called in a goroutine +// to prevent blocking the UI thread. +// func (mw *MultiWallet) ReloadVSPList(ctx context.Context) { +// log.Debugf("Reloading list of known VSPs") +// defer log.Debugf("Reloaded list of known VSPs") + +// vspDbData := mw.getVSPDBData() +// vspList := make(map[string]*VspInfoResponse) +// for _, host := range vspDbData.SavedHosts { +// vspInfo, err := vspInfo(host) +// if err != nil { +// // User saved this VSP. Log an error message. +// log.Errorf("get vsp info error for %s: %v", host, err) +// } else { +// vspList[host] = vspInfo +// } +// if ctx.Err() != nil { +// return // context canceled, abort +// } +// } + +// otherVSPHosts, err := defaultVSPs(mw.NetType()) +// if err != nil { +// log.Debugf("get default vsp list error: %v", err) +// } +// for _, host := range otherVSPHosts { +// if _, wasAdded := vspList[host]; wasAdded { +// continue +// } +// vspInfo, err := vspInfo(host) +// if err != nil { +// log.Debugf("vsp info error for %s: %v\n", host, err) // debug only, user didn't request this VSP +// } else { +// vspList[host] = vspInfo +// } +// if ctx.Err() != nil { +// return // context canceled, abort +// } +// } + +// mw.vspMu.Lock() +// mw.vsps = make([]*VSP, 0, len(vspList)) +// for host, info := range vspList { +// mw.vsps = append(mw.vsps, &VSP{Host: host, VspInfoResponse: info}) +// } +// mw.vspMu.Unlock() +// } + +func vspInfo(vspHost string) (*VspInfoResponse, error) { + vspInfoResponse := new(VspInfoResponse) + resp, respBytes, err := HttpGet(vspHost+"/api/v3/vspinfo", vspInfoResponse) + if err != nil { + return nil, err + } + + // Validate server response. + sigStr := resp.Header.Get("VSP-Server-Signature") + sig, err := base64.StdEncoding.DecodeString(sigStr) + if err != nil { + return nil, fmt.Errorf("error validating VSP signature: %v", err) + } + if !ed25519.Verify(vspInfoResponse.PubKey, respBytes, sig) { + return nil, errors.New("bad signature from VSP") + } + + return vspInfoResponse, nil +} + +// defaultVSPs returns a list of known VSPs. +func defaultVSPs(network string) ([]string, error) { + var vspInfoResponse map[string]*VspInfoResponse + _, _, err := HttpGet("https://api.decred.org/?c=vsp", &vspInfoResponse) + if err != nil { + return nil, err + } + + // The above API does not return the pubKeys for the + // VSPs. Only return the host since we'll still need + // to make another API call to get the VSP pubKeys. + vsps := make([]string, 0) + for url, vspInfo := range vspInfoResponse { + if strings.Contains(network, vspInfo.Network) { + vsps = append(vsps, "https://"+url) + } + } + return vsps, nil +} diff --git a/wallet.go b/wallets/dcr/wallet.go similarity index 76% rename from wallet.go rename to wallets/dcr/wallet.go index 593b92a1e..1e4ddd582 100644 --- a/wallet.go +++ b/wallets/dcr/wallet.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "context" @@ -12,34 +12,38 @@ import ( "decred.org/dcrwallet/v2/errors" w "decred.org/dcrwallet/v2/wallet" "decred.org/dcrwallet/v2/walletseed" + "github.com/asdine/storm" "github.com/decred/dcrd/chaincfg/v3" "github.com/planetdecred/dcrlibwallet/internal/loader" "github.com/planetdecred/dcrlibwallet/internal/vsp" - "github.com/planetdecred/dcrlibwallet/walletdata" + "github.com/planetdecred/dcrlibwallet/wallets/dcr/walletdata" ) type Wallet struct { - ID int `storm:"id,increment"` - Name string `storm:"unique"` - CreatedAt time.Time `storm:"index"` - DbDriver string + ID int `storm:"id,increment"` + Name string `storm:"unique"` + CreatedAt time.Time `storm:"index"` + DbDriver string + rootDir string + db *storm.DB + EncryptedSeed []byte IsRestored bool HasDiscoveredAccounts bool PrivatePassphraseType int32 chainParams *chaincfg.Params - dataDir string + DataDir string loader *loader.Loader - walletDataDB *walletdata.DB + WalletDataDB *walletdata.DB - synced bool - syncing bool - waitingForHeaders bool + Synced bool + Syncing bool + WaitingForHeaders bool shuttingDown chan bool cancelFuncs []context.CancelFunc - cancelAccountMixer context.CancelFunc + CancelAccountMixer context.CancelFunc cancelAutoTicketBuyerMu sync.Mutex cancelAutoTicketBuyer context.CancelFunc @@ -57,34 +61,38 @@ type Wallet struct { // This function is ideally assigned when the `wallet.prepare` method is // called from a MultiWallet instance. readUserConfigValue configReadFn + + notificationListenersMu sync.RWMutex + syncData *SyncData + accountMixerNotificationListener map[string]AccountMixerNotificationListener } // prepare gets a wallet ready for use by opening the transactions index database // and initializing the wallet loader which can be used subsequently to create, // load and unload the wallet. -func (wallet *Wallet) prepare(rootDir string, chainParams *chaincfg.Params, +func (wallet *Wallet) Prepare(rootDir string, chainParams *chaincfg.Params, setUserConfigValueFn configSaveFn, readUserConfigValueFn configReadFn) (err error) { wallet.chainParams = chainParams - wallet.dataDir = filepath.Join(rootDir, strconv.Itoa(wallet.ID)) + wallet.DataDir = filepath.Join(rootDir, strconv.Itoa(wallet.ID)) wallet.vspClients = make(map[string]*vsp.Client) wallet.setUserConfigValue = setUserConfigValueFn wallet.readUserConfigValue = readUserConfigValueFn // open database for indexing transactions for faster loading - walletDataDBPath := filepath.Join(wallet.dataDir, walletdata.DbName) - oldTxDBPath := filepath.Join(wallet.dataDir, walletdata.OldDbName) + walletDataDBPath := filepath.Join(wallet.DataDir, walletdata.DbName) + oldTxDBPath := filepath.Join(wallet.DataDir, walletdata.OldDbName) if exists, _ := fileExists(oldTxDBPath); exists { moveFile(oldTxDBPath, walletDataDBPath) } - wallet.walletDataDB, err = walletdata.Initialize(walletDataDBPath, chainParams, &Transaction{}) + wallet.WalletDataDB, err = walletdata.Initialize(walletDataDBPath, chainParams, &Transaction{}) if err != nil { log.Error(err.Error()) return err } // init loader - wallet.loader = initWalletLoader(wallet.chainParams, wallet.dataDir, wallet.DbDriver) + wallet.loader = initWalletLoader(wallet.chainParams, wallet.DataDir, wallet.DbDriver) // init cancelFuncs slice to hold cancel functions for long running // operations and start go routine to listen for shutdown signal @@ -102,7 +110,7 @@ func (wallet *Wallet) prepare(rootDir string, chainParams *chaincfg.Params, func (wallet *Wallet) Shutdown() { // Trigger shuttingDown signal to cancel all contexts created with - // `wallet.shutdownContext()` or `wallet.shutdownContextWithCancel()`. + // `wallet.ShutdownContext()` or `wallet.shutdownContextWithCancel()`. wallet.shuttingDown <- true if _, loaded := wallet.loader.LoadedWallet(); loaded { @@ -114,8 +122,8 @@ func (wallet *Wallet) Shutdown() { } } - if wallet.walletDataDB != nil { - err := wallet.walletDataDB.Close() + if wallet.WalletDataDB != nil { + err := wallet.WalletDataDB.Close() if err != nil { log.Errorf("tx db closed with error: %v", err) } else { @@ -147,7 +155,7 @@ func (wallet *Wallet) WalletExists() (bool, error) { return wallet.loader.WalletExists() } -func (wallet *Wallet) createWallet(privatePassphrase, seedMnemonic string) error { +func (wallet *Wallet) CreateWallet(privatePassphrase, seedMnemonic string) error { log.Info("Creating Wallet") if len(seedMnemonic) == 0 { return errors.New(ErrEmptySeed) @@ -161,7 +169,7 @@ func (wallet *Wallet) createWallet(privatePassphrase, seedMnemonic string) error return err } - _, err = wallet.loader.CreateNewWallet(wallet.shutdownContext(), pubPass, privPass, seed) + _, err = wallet.loader.CreateNewWallet(wallet.ShutdownContext(), pubPass, privPass, seed) if err != nil { log.Error(err) return err @@ -171,10 +179,10 @@ func (wallet *Wallet) createWallet(privatePassphrase, seedMnemonic string) error return nil } -func (wallet *Wallet) createWatchingOnlyWallet(extendedPublicKey string) error { +func (wallet *Wallet) CreateWatchingOnlyWallet(extendedPublicKey string) error { pubPass := []byte(w.InsecurePubPassphrase) - _, err := wallet.loader.CreateWatchingOnlyWallet(wallet.shutdownContext(), extendedPublicKey, pubPass) + _, err := wallet.loader.CreateWatchingOnlyWallet(wallet.ShutdownContext(), extendedPublicKey, pubPass) if err != nil { log.Error(err) return err @@ -192,10 +200,10 @@ func (wallet *Wallet) IsWatchingOnlyWallet() bool { return false } -func (wallet *Wallet) openWallet() error { +func (wallet *Wallet) OpenWallet() error { pubPass := []byte(w.InsecurePubPassphrase) - _, err := wallet.loader.OpenExistingWallet(wallet.shutdownContext(), pubPass) + _, err := wallet.loader.OpenExistingWallet(wallet.ShutdownContext(), pubPass) if err != nil { log.Error(err) return translateError(err) @@ -214,7 +222,7 @@ func (wallet *Wallet) UnlockWallet(privPass []byte) error { return fmt.Errorf("wallet has not been loaded") } - ctx, _ := wallet.shutdownContextWithCancel() + ctx, _ := wallet.ShutdownContextWithCancel() err := loadedWallet.Unlock(ctx, privPass, nil) if err != nil { return translateError(err) @@ -238,7 +246,7 @@ func (wallet *Wallet) IsLocked() bool { return wallet.Internal().Locked() } -func (wallet *Wallet) changePrivatePassphrase(oldPass []byte, newPass []byte) error { +func (wallet *Wallet) ChangePrivatePassphrase(oldPass []byte, newPass []byte) error { defer func() { for i := range oldPass { oldPass[i] = 0 @@ -249,14 +257,14 @@ func (wallet *Wallet) changePrivatePassphrase(oldPass []byte, newPass []byte) er } }() - err := wallet.Internal().ChangePrivatePassphrase(wallet.shutdownContext(), oldPass, newPass) + err := wallet.Internal().ChangePrivatePassphrase(wallet.ShutdownContext(), oldPass, newPass) if err != nil { return translateError(err) } return nil } -func (wallet *Wallet) deleteWallet(privatePassphrase []byte) error { +func (wallet *Wallet) DeleteWallet(privatePassphrase []byte) error { defer func() { for i := range privatePassphrase { privatePassphrase[i] = 0 @@ -268,7 +276,7 @@ func (wallet *Wallet) deleteWallet(privatePassphrase []byte) error { } if !wallet.IsWatchingOnlyWallet() { - err := wallet.Internal().Unlock(wallet.shutdownContext(), privatePassphrase, nil) + err := wallet.Internal().Unlock(wallet.ShutdownContext(), privatePassphrase, nil) if err != nil { return translateError(err) } @@ -278,24 +286,24 @@ func (wallet *Wallet) deleteWallet(privatePassphrase []byte) error { wallet.Shutdown() log.Info("Deleting Wallet") - return os.RemoveAll(wallet.dataDir) + return os.RemoveAll(wallet.DataDir) } // DecryptSeed decrypts wallet.EncryptedSeed using privatePassphrase -func (wallet *Wallet) DecryptSeed(privatePassphrase []byte) (string, error) { - if wallet.EncryptedSeed == nil { - return "", errors.New(ErrInvalid) - } +// func (wallet *Wallet) DecryptSeed(privatePassphrase []byte) (string, error) { +// if wallet.EncryptedSeed == nil { +// return "", errors.New(ErrInvalid) +// } - return decryptWalletSeed(privatePassphrase, wallet.EncryptedSeed) -} +// return decryptWalletSeed(privatePassphrase, wallet.EncryptedSeed) +// } // AccountXPubMatches checks if the xpub of the provided account matches the // provided legacy or SLIP0044 xpub. While both the legacy and SLIP0044 xpubs // will be checked for watch-only wallets, other wallets will only check the // xpub that matches the coin type key used by the wallet. func (wallet *Wallet) AccountXPubMatches(account uint32, legacyXPub, slip044XPub string) (bool, error) { - ctx := wallet.shutdownContext() + ctx := wallet.ShutdownContext() acctXPubKey, err := wallet.Internal().AccountXpub(ctx, account) if err != nil { diff --git a/wallet_config.go b/wallets/dcr/wallet_config.go similarity index 99% rename from wallet_config.go rename to wallets/dcr/wallet_config.go index 8f6ba8ab8..29636c2cc 100644 --- a/wallet_config.go +++ b/wallets/dcr/wallet_config.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "decred.org/dcrwallet/v2/errors" diff --git a/walletdata/db.go b/wallets/dcr/walletdata/db.go similarity index 100% rename from walletdata/db.go rename to wallets/dcr/walletdata/db.go diff --git a/walletdata/filter.go b/wallets/dcr/walletdata/filter.go similarity index 100% rename from walletdata/filter.go rename to wallets/dcr/walletdata/filter.go diff --git a/walletdata/read.go b/wallets/dcr/walletdata/read.go similarity index 100% rename from walletdata/read.go rename to wallets/dcr/walletdata/read.go diff --git a/walletdata/save.go b/wallets/dcr/walletdata/save.go similarity index 100% rename from walletdata/save.go rename to wallets/dcr/walletdata/save.go diff --git a/wordlist.go b/wallets/dcr/wordlist.go similarity index 99% rename from wordlist.go rename to wallets/dcr/wordlist.go index 91a819dda..64d30862a 100644 --- a/wordlist.go +++ b/wallets/dcr/wordlist.go @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -package dcrlibwallet +package dcr import "strings" From 5a4ac85e61f090821c38725b8c2555f18f73fdb2 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 12:31:57 +0100 Subject: [PATCH 02/16] - move politeia to dcr package - uncomment some multiwallet method and convert then to wallet methods --- go.mod | 7 - multiwallet.go | 21 +- politeia.go | 242 +-------------- rescan.go | 149 --------- wallets/dcr/account_mixer.go | 2 +- .../dcr/politeia_client.go | 17 +- .../dcr/politeia_sync.go | 252 ++++++++++++++-- wallets/dcr/rescan.go | 142 +++++++++ wallets/dcr/sync.go | 8 +- wallets/dcr/syncnotification.go | 36 +-- wallets/dcr/txandblocknotifications.go | 282 +++++++++--------- wallets/dcr/types.go | 32 ++ wallets/dcr/utils.go | 62 ++-- wallets/dcr/wallet.go | 3 + wallets/dcr/wallet_utils.go | 20 ++ 15 files changed, 647 insertions(+), 628 deletions(-) delete mode 100644 rescan.go rename politeia_client.go => wallets/dcr/politeia_client.go (96%) rename politeia_sync.go => wallets/dcr/politeia_sync.go (68%) create mode 100644 wallets/dcr/rescan.go create mode 100644 wallets/dcr/wallet_utils.go diff --git a/go.mod b/go.mod index b352b444d..8dcbb46bc 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,6 @@ require ( decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 github.com/DataDog/zstd v1.4.8 // indirect github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 - github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e // indirect - github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // indirect - github.com/btcsuite/btcwallet v0.12.0 // indirect - github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect - github.com/btcsuite/btcwallet/wtxmgr v1.3.0 // indirect github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a // indirect github.com/dchest/siphash v1.2.3 // indirect github.com/decred/base58 v1.0.4 // indirect @@ -33,7 +27,6 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/jrick/logrotate v1.0.0 github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 - github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 // indirect github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d diff --git a/multiwallet.go b/multiwallet.go index f9695bd5f..ea2cb3141 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -38,13 +38,13 @@ type MultiWallet struct { // notificationListenersMu sync.RWMutex txAndBlockNotificationListeners map[string]TxAndBlockNotificationListener - blocksRescanProgressListener BlocksRescanProgressListener + blocksRescanProgressListener BlocksRescanProgressListener // accountMixerNotificationListener map[string]AccountMixerNotificationListener shuttingDown chan bool cancelFuncs []context.CancelFunc - Politeia *Politeia + Politeia *dcr.Politeia dexClient *DexClient vspMu sync.RWMutex @@ -75,14 +75,14 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall // syncData: &dcr.SyncData{ // SyncProgressListeners: make(map[string]dcr.SyncProgressListener), // }, - txAndBlockNotificationListeners: make(map[string]TxAndBlockNotificationListener), + txAndBlockNotificationListeners: make(map[string]TxAndBlockNotificationListener), } - // syncData: &dcr.SyncData{ - // SyncProgressListeners: make(map[string]dcr.SyncProgressListener), - // }, + // syncData: &dcr.SyncData{ + // SyncProgressListeners: make(map[string]dcr.SyncProgressListener), + // }, - mw.Politeia, err = newPoliteia(mw, politeiaHost) + mw.Politeia, err = newPoliteia(mw.wallets[0], politeiaHost) if err != nil { return nil, err } @@ -129,9 +129,12 @@ func (mw *MultiWallet) Shutdown() { // Trigger shuttingDown signal to cancel all contexts created with `shutdownContextWithCancel`. mw.shuttingDown <- true - mw.CancelRescan() for _, wallet := range mw.wallets { - wallet.CancelSync(mw.wallets) + wallet.CancelRescan() + } + + for _, wallet := range mw.wallets { + wallet.CancelSync() } for _, wallet := range mw.wallets { diff --git a/politeia.go b/politeia.go index 45b836082..6954faddd 100644 --- a/politeia.go +++ b/politeia.go @@ -1,27 +1,17 @@ package dcrlibwallet import ( - "context" - "encoding/json" - "fmt" - "sync" - - "decred.org/dcrwallet/v2/errors" - "github.com/asdine/storm" - "github.com/asdine/storm/q" + // "context" + // "encoding/json" + // "fmt" + // "sync" + + // "decred.org/dcrwallet/v2/errors" + // "github.com/asdine/storm" + // "github.com/asdine/storm/q" + "github.com/planetdecred/dcrlibwallet/wallets/dcr" ) -type Politeia struct { - mwRef *MultiWallet - host string - mu sync.RWMutex - ctx context.Context - cancelSync context.CancelFunc - client *politeiaClient - notificationListenersMu sync.RWMutex - notificationListeners map[string]ProposalNotificationListener -} - const ( ProposalCategoryAll int32 = iota + 1 ProposalCategoryPre @@ -31,215 +21,13 @@ const ( ProposalCategoryAbandoned ) -func newPoliteia(mwRef *MultiWallet, host string) (*Politeia, error) { - p := &Politeia{ - mwRef: mwRef, - host: host, - client: nil, - notificationListeners: make(map[string]ProposalNotificationListener), +func newPoliteia(walletRef *dcr.Wallet, host string) (*dcr.Politeia, error) { + p := &dcr.Politeia{ + WalletRef: walletRef, + Host: host, + Client: nil, + NotificationListeners: make(map[string]dcr.ProposalNotificationListener), } return p, nil } - -func (p *Politeia) saveLastSyncedTimestamp(lastSyncedTimestamp int64) { - p.mwRef.SetLongConfigValueForKey(PoliteiaLastSyncedTimestampConfigKey, lastSyncedTimestamp) -} - -func (p *Politeia) getLastSyncedTimestamp() int64 { - return p.mwRef.ReadLongConfigValueForKey(PoliteiaLastSyncedTimestampConfigKey, 0) -} - -func (p *Politeia) saveOrOverwiteProposal(proposal *Proposal) error { - var oldProposal Proposal - err := p.mwRef.db.One("Token", proposal.Token, &oldProposal) - if err != nil && err != storm.ErrNotFound { - return errors.Errorf("error checking if proposal was already indexed: %s", err.Error()) - } - - if oldProposal.Token != "" { - // delete old record before saving new (if it exists) - p.mwRef.db.DeleteStruct(oldProposal) - } - - return p.mwRef.db.Save(proposal) -} - -// GetProposalsRaw fetches and returns a proposals from the db -func (p *Politeia) GetProposalsRaw(category int32, offset, limit int32, newestFirst bool) ([]Proposal, error) { - return p.getProposalsRaw(category, offset, limit, newestFirst, false) -} - -func (p *Politeia) getProposalsRaw(category int32, offset, limit int32, newestFirst bool, skipAbandoned bool) ([]Proposal, error) { - - var query storm.Query - switch category { - case ProposalCategoryAll: - - if skipAbandoned { - query = p.mwRef.db.Select( - q.Not(q.Eq("Category", ProposalCategoryAbandoned)), - ) - } else { - query = p.mwRef.db.Select( - q.True(), - ) - } - default: - query = p.mwRef.db.Select( - q.Eq("Category", category), - ) - } - - if offset > 0 { - query = query.Skip(int(offset)) - } - - if limit > 0 { - query = query.Limit(int(limit)) - } - - if newestFirst { - query = query.OrderBy("PublishedAt").Reverse() - } else { - query = query.OrderBy("PublishedAt") - } - - var proposals []Proposal - err := query.Find(&proposals) - if err != nil && err != storm.ErrNotFound { - return nil, fmt.Errorf("error fetching proposals: %s", err.Error()) - } - - return proposals, nil -} - -// GetProposals returns the result of GetProposalsRaw as a JSON string -func (p *Politeia) GetProposals(category int32, offset, limit int32, newestFirst bool) (string, error) { - - result, err := p.GetProposalsRaw(category, offset, limit, newestFirst) - if err != nil { - return "", err - } - - if len(result) == 0 { - return "[]", nil - } - - response, err := json.Marshal(result) - if err != nil { - return "", fmt.Errorf("error marshalling result: %s", err.Error()) - } - - return string(response), nil -} - -// GetProposalRaw fetches and returns a single proposal specified by it's censorship record token -func (p *Politeia) GetProposalRaw(censorshipToken string) (*Proposal, error) { - var proposal Proposal - err := p.mwRef.db.One("Token", censorshipToken, &proposal) - if err != nil { - return nil, err - } - - return &proposal, nil -} - -// GetProposal returns the result of GetProposalRaw as a JSON string -func (p *Politeia) GetProposal(censorshipToken string) (string, error) { - return p.marshalResult(p.GetProposalRaw(censorshipToken)) -} - -// GetProposalByIDRaw fetches and returns a single proposal specified by it's ID -func (p *Politeia) GetProposalByIDRaw(proposalID int) (*Proposal, error) { - var proposal Proposal - err := p.mwRef.db.One("ID", proposalID, &proposal) - if err != nil { - return nil, err - } - - return &proposal, nil -} - -// GetProposalByID returns the result of GetProposalByIDRaw as a JSON string -func (p *Politeia) GetProposalByID(proposalID int) (string, error) { - return p.marshalResult(p.GetProposalByIDRaw(proposalID)) -} - -// Count returns the number of proposals of a specified category -func (p *Politeia) Count(category int32) (int32, error) { - var matcher q.Matcher - - if category == ProposalCategoryAll { - matcher = q.True() - } else { - matcher = q.Eq("Category", category) - } - - count, err := p.mwRef.db.Select(matcher).Count(&Proposal{}) - if err != nil { - return 0, err - } - - return int32(count), nil -} - -func (p *Politeia) Overview() (*ProposalOverview, error) { - - pre, err := p.Count(ProposalCategoryPre) - if err != nil { - return nil, err - } - - active, err := p.Count(ProposalCategoryActive) - if err != nil { - return nil, err - } - - approved, err := p.Count(ProposalCategoryApproved) - if err != nil { - return nil, err - } - - rejected, err := p.Count(ProposalCategoryRejected) - if err != nil { - return nil, err - } - - abandoned, err := p.Count(ProposalCategoryApproved) - if err != nil { - return nil, err - } - - return &ProposalOverview{ - All: pre + active + approved + rejected + abandoned, - Discussion: pre, - Voting: active, - Approved: approved, - Rejected: rejected, - Abandoned: abandoned, - }, nil -} - -func (p *Politeia) ClearSavedProposals() error { - err := p.mwRef.db.Drop(&Proposal{}) - if err != nil { - return translateError(err) - } - - return p.mwRef.db.Init(&Proposal{}) -} - -func (p *Politeia) marshalResult(result interface{}, err error) (string, error) { - - if err != nil { - return "", translateError(err) - } - - response, err := json.Marshal(result) - if err != nil { - return "", fmt.Errorf("error marshalling result: %s", err.Error()) - } - - return string(response), nil -} diff --git a/rescan.go b/rescan.go deleted file mode 100644 index 8989d7127..000000000 --- a/rescan.go +++ /dev/null @@ -1,149 +0,0 @@ -package dcrlibwallet - -import ( - "context" - "math" - "time" - - "decred.org/dcrwallet/v2/errors" - w "decred.org/dcrwallet/v2/wallet" -) - -func (mw *MultiWallet) RescanBlocks(walletID int) error { - return mw.RescanBlocksFromHeight(walletID, 0) -} - -func (mw *MultiWallet) RescanBlocksFromHeight(walletID int, startHeight int32) error { - - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return errors.E(ErrNotExist) - } - - netBackend, err := wallet.Internal().NetworkBackend() - if err != nil { - return errors.E(ErrNotConnected) - } - - // if mw.IsRescanning() || !mw.IsSynced() { - // return errors.E(ErrInvalid) - // } - - go func() { - defer func() { - // mw.syncData.mu.Lock() - // mw.syncData.rescanning = false - // mw.syncData.cancelRescan = nil - // mw.syncData.mu.Unlock() - }() - - ctx, _ := wallet.ShutdownContextWithCancel() - // ctx, cancel := wallet.ShutdownContextWithCancel() //undo this lateer - - // mw.syncData.mu.Lock() - // mw.syncData.rescanning = true - // mw.syncData.cancelRescan = cancel - // mw.syncData.mu.Unlock() - - if mw.blocksRescanProgressListener != nil { - mw.blocksRescanProgressListener.OnBlocksRescanStarted(walletID) - } - - progress := make(chan w.RescanProgress, 1) - go wallet.Internal().RescanProgressFromHeight(ctx, netBackend, startHeight, progress) - - rescanStartTime := time.Now().Unix() - - for p := range progress { - if p.Err != nil { - log.Error(p.Err) - if mw.blocksRescanProgressListener != nil { - mw.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, p.Err) - } - return - } - - rescanProgressReport := &HeadersRescanProgressReport{ - CurrentRescanHeight: p.ScannedThrough, - TotalHeadersToScan: wallet.GetBestBlock(), - WalletID: walletID, - } - - elapsedRescanTime := time.Now().Unix() - rescanStartTime - rescanRate := float64(p.ScannedThrough) / float64(rescanProgressReport.TotalHeadersToScan) - - rescanProgressReport.RescanProgress = int32(math.Round(rescanRate * 100)) - estimatedTotalRescanTime := int64(math.Round(float64(elapsedRescanTime) / rescanRate)) - rescanProgressReport.RescanTimeRemaining = estimatedTotalRescanTime - elapsedRescanTime - - rescanProgressReport.GeneralSyncProgress = &GeneralSyncProgress{ - TotalSyncProgress: rescanProgressReport.RescanProgress, - TotalTimeRemainingSeconds: rescanProgressReport.RescanTimeRemaining, - } - - if mw.blocksRescanProgressListener != nil { - mw.blocksRescanProgressListener.OnBlocksRescanProgress(rescanProgressReport) - } - - select { - case <-ctx.Done(): - log.Info("Rescan canceled through context") - - if mw.blocksRescanProgressListener != nil { - if ctx.Err() != nil && ctx.Err() != context.Canceled { - mw.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, ctx.Err()) - } else { - mw.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, nil) - } - } - - return - default: - continue - } - } - - var err error - if startHeight == 0 { - err = wallet.ReindexTransactions() - } else { - err = wallet.WalletDataDB.SaveLastIndexPoint(startHeight) - if err != nil { - if mw.blocksRescanProgressListener != nil { - mw.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, err) - } - return - } - - err = wallet.IndexTransactions() - } - if mw.blocksRescanProgressListener != nil { - mw.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, err) - } - }() - - return nil -} - -func (mw *MultiWallet) CancelRescan() { - // mw.syncData.mu.Lock() - // defer mw.syncData.mu.Unlock() - // if mw.syncData.cancelRescan != nil { - // mw.syncData.cancelRescan() - // mw.syncData.cancelRescan = nil - - // log.Info("Rescan canceled.") - // } -} - -func (mw *MultiWallet) IsRescanning() bool { - // mw.syncData.mu.RLock() - // defer mw.syncData.mu.RUnlock() - // return mw.syncData.rescanning - - return true -} - -func (mw *MultiWallet) SetBlocksRescanProgressListener(blocksRescanProgressListener BlocksRescanProgressListener) { - mw.blocksRescanProgressListener = blocksRescanProgressListener -} diff --git a/wallets/dcr/account_mixer.go b/wallets/dcr/account_mixer.go index 620648171..196c495e6 100644 --- a/wallets/dcr/account_mixer.go +++ b/wallets/dcr/account_mixer.go @@ -254,7 +254,7 @@ func (wallet *Wallet) readCSPPConfig() *CSPPConfig { } // StopAccountMixer stops the active account mixer -func (wallet *Wallet) StopAccountMixer(walletID int) error { +func (wallet *Wallet) StopAccountMixer() error { if wallet == nil { return errors.New(ErrNotExist) } diff --git a/politeia_client.go b/wallets/dcr/politeia_client.go similarity index 96% rename from politeia_client.go rename to wallets/dcr/politeia_client.go index cd5981fe9..fb33d9ab3 100644 --- a/politeia_client.go +++ b/wallets/dcr/politeia_client.go @@ -1,4 +1,4 @@ -package dcrlibwallet +package dcr import ( "bytes" @@ -16,15 +16,6 @@ import ( "github.com/decred/politeia/politeiawww/client" ) -type politeiaClient struct { - host string - httpClient *http.Client - - version *www.VersionReply - policy *www.PolicyReply - cookies []*http.Cookie -} - const ( PoliteiaMainnetHost = "https://proposals.decred.org/api" PoliteiaTestnetHost = "https://test-proposals.decred.org/api" @@ -58,9 +49,9 @@ func newPoliteiaClient(host string) *politeiaClient { func (p *Politeia) getClient() (*politeiaClient, error) { p.mu.Lock() defer p.mu.Unlock() - client := p.client + client := p.Client if client == nil { - client = newPoliteiaClient(p.host) + client = newPoliteiaClient(p.Host) version, err := client.serverVersion() if err != nil { return nil, err @@ -72,7 +63,7 @@ func (p *Politeia) getClient() (*politeiaClient, error) { return nil, err } - p.client = client + p.Client = client } return client, nil diff --git a/politeia_sync.go b/wallets/dcr/politeia_sync.go similarity index 68% rename from politeia_sync.go rename to wallets/dcr/politeia_sync.go index eeefd20f8..209103afb 100644 --- a/politeia_sync.go +++ b/wallets/dcr/politeia_sync.go @@ -1,20 +1,20 @@ -package dcrlibwallet +package dcr import ( + "decred.org/dcrwallet/v2/errors" "encoding/hex" "encoding/json" - "errors" + // "errors" "fmt" "reflect" "strconv" "time" "github.com/asdine/storm" + "github.com/asdine/storm/q" + tkv1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1" www "github.com/decred/politeia/politeiawww/api/www/v1" - - "github.com/planetdecred/dcrlibwallet/wallets/dcr" - ) const ( @@ -38,7 +38,7 @@ func (p *Politeia) Sync() error { log.Info("Politeia sync: started") - p.ctx, p.cancelSync = p.mwRef.contextWithShutdownCancel() + p.ctx, p.cancelSync = p.WalletRef.contextWithShutdownCancel() defer p.resetSyncData() p.mu.Unlock() @@ -99,7 +99,7 @@ func (p *Politeia) StopSync() { func (p *Politeia) checkForUpdates() error { offset := 0 p.mu.RLock() - limit := int32(p.client.policy.ProposalListPageSize) + limit := int32(p.Client.policy.ProposalListPageSize) p.mu.RUnlock() for { @@ -141,7 +141,7 @@ func (p *Politeia) handleNewProposals(proposals []Proposal) error { } p.mu.RLock() - tokenInventory, err := p.client.tokenInventory() + tokenInventory, err := p.Client.tokenInventory() p.mu.RUnlock() if err != nil { return err @@ -159,12 +159,12 @@ func (p *Politeia) handleProposalsUpdate(proposals []Proposal) error { p.mu.RLock() defer p.mu.RUnlock() - batchProposals, err := p.client.batchProposals(tokens) + batchProposals, err := p.Client.batchProposals(tokens) if err != nil { return err } - batchVotesSummaries, err := p.client.batchVoteSummary(tokens) + batchVotesSummaries, err := p.Client.batchVoteSummary(tokens) if err != nil { return err } @@ -223,7 +223,7 @@ func (p *Politeia) updateProposalDetails(oldProposal, updatedProposal Proposal) } } - err := p.mwRef.db.Update(&updatedProposal) + err := p.WalletRef.db.Update(&updatedProposal) if err != nil { return fmt.Errorf("error saving updated proposal: %s", err.Error()) } @@ -290,7 +290,7 @@ func (p *Politeia) fetchBatchProposals(category int32, tokens []string, broadcas return errors.New(ErrContextCanceled) } - limit := int(p.client.policy.ProposalListPageSize) + limit := int(p.Client.policy.ProposalListPageSize) if len(tokens) <= limit { limit = len(tokens) } @@ -300,7 +300,7 @@ func (p *Politeia) fetchBatchProposals(category int32, tokens []string, broadcas var tokenBatch []string tokenBatch, tokens = tokens[:limit], tokens[limit:] - proposals, err := p.client.batchProposals(tokenBatch) + proposals, err := p.Client.batchProposals(tokenBatch) if err != nil { return err } @@ -309,7 +309,7 @@ func (p *Politeia) fetchBatchProposals(category int32, tokens []string, broadcas return errors.New(ErrContextCanceled) } - votesSummaries, err := p.client.batchVoteSummary(tokenBatch) + votesSummaries, err := p.Client.batchVoteSummary(tokenBatch) if err != nil { return err } @@ -354,12 +354,12 @@ func (p *Politeia) FetchProposalDescription(token string) (string, error) { return "", err } - client, err := p.getClient() + Client, err := p.getClient() if err != nil { return "", err } - proposalDetailsReply, err := client.proposalDetails(token) + proposalDetailsReply, err := Client.proposalDetails(token) if err != nil { return "", err } @@ -389,22 +389,22 @@ func (p *Politeia) FetchProposalDescription(token string) (string, error) { } func (p *Politeia) ProposalVoteDetailsRaw(walletID int, token string) (*ProposalVoteDetails, error) { - wal := p.mwRef.WalletWithID(walletID) + wal := p.WalletRef if wal == nil { return nil, fmt.Errorf(ErrWalletNotFound) } - client, err := p.getClient() + Client, err := p.getClient() if err != nil { return nil, err } - detailsReply, err := client.voteDetails(token) + detailsReply, err := Client.voteDetails(token) if err != nil { return nil, err } - votesResults, err := client.voteResults(token) + votesResults, err := Client.voteResults(token) if err != nil { return nil, err } @@ -440,7 +440,7 @@ func (p *Politeia) ProposalVoteDetailsRaw(walletID int, token string) (*Proposal } // filter out tickets controlled by imported accounts - if ainfo.AccountNumber == dcr.ImportedAccountNumber { + if ainfo.AccountNumber == ImportedAccountNumber { continue } @@ -485,17 +485,17 @@ func (p *Politeia) ProposalVoteDetails(walletID int, token string) (string, erro } func (p *Politeia) CastVotes(walletID int, eligibleTickets []*ProposalVote, token, passphrase string) error { - wal := p.mwRef.WalletWithID(walletID) + wal := p.WalletRef if wal == nil { return fmt.Errorf(ErrWalletNotFound) } - client, err := p.getClient() + Client, err := p.getClient() if err != nil { return err } - detailsReply, err := client.voteDetails(token) + detailsReply, err := Client.voteDetails(token) if err != nil { return err } @@ -541,18 +541,18 @@ func (p *Politeia) CastVotes(walletID int, eligibleTickets []*ProposalVote, toke votes = append(votes, singleVote) } - return client.sendVotes(votes) + return Client.sendVotes(votes) } func (p *Politeia) AddNotificationListener(notificationListener ProposalNotificationListener, uniqueIdentifier string) error { p.notificationListenersMu.Lock() defer p.notificationListenersMu.Unlock() - if _, ok := p.notificationListeners[uniqueIdentifier]; ok { + if _, ok := p.NotificationListeners[uniqueIdentifier]; ok { return errors.New(ErrListenerAlreadyExist) } - p.notificationListeners[uniqueIdentifier] = notificationListener + p.NotificationListeners[uniqueIdentifier] = notificationListener return nil } @@ -560,14 +560,14 @@ func (p *Politeia) RemoveNotificationListener(uniqueIdentifier string) { p.notificationListenersMu.Lock() defer p.notificationListenersMu.Unlock() - delete(p.notificationListeners, uniqueIdentifier) + delete(p.NotificationListeners, uniqueIdentifier) } func (p *Politeia) publishSynced() { p.notificationListenersMu.Lock() defer p.notificationListenersMu.Unlock() - for _, notificationListener := range p.notificationListeners { + for _, notificationListener := range p.NotificationListeners { notificationListener.OnProposalsSynced() } } @@ -576,7 +576,7 @@ func (p *Politeia) publishNewProposal(proposal *Proposal) { p.notificationListenersMu.Lock() defer p.notificationListenersMu.Unlock() - for _, notificationListener := range p.notificationListeners { + for _, notificationListener := range p.NotificationListeners { notificationListener.OnNewProposal(proposal) } } @@ -585,7 +585,7 @@ func (p *Politeia) publishVoteStarted(proposal *Proposal) { p.notificationListenersMu.Lock() defer p.notificationListenersMu.Unlock() - for _, notificationListener := range p.notificationListeners { + for _, notificationListener := range p.NotificationListeners { notificationListener.OnProposalVoteStarted(proposal) } } @@ -594,7 +594,7 @@ func (p *Politeia) publishVoteFinished(proposal *Proposal) { p.notificationListenersMu.Lock() defer p.notificationListenersMu.Unlock() - for _, notificationListener := range p.notificationListeners { + for _, notificationListener := range p.NotificationListeners { notificationListener.OnProposalVoteFinished(proposal) } } @@ -634,3 +634,191 @@ func getUniqueTokens(tokenInventory, savedTokens []string) ([]string, []string) return diff, savedTokens } + +func (p *Politeia) saveLastSyncedTimestamp(lastSyncedTimestamp int64) { + p.WalletRef.SetLongConfigValueForKey(PoliteiaLastSyncedTimestampConfigKey, lastSyncedTimestamp) +} + +func (p *Politeia) getLastSyncedTimestamp() int64 { + return p.WalletRef.ReadLongConfigValueForKey(PoliteiaLastSyncedTimestampConfigKey, 0) +} + +func (p *Politeia) saveOrOverwiteProposal(proposal *Proposal) error { + var oldProposal Proposal + err := p.WalletRef.db.One("Token", proposal.Token, &oldProposal) + if err != nil && err != storm.ErrNotFound { + return errors.Errorf("error checking if proposal was already indexed: %s", err.Error()) + } + + if oldProposal.Token != "" { + // delete old record before saving new (if it exists) + p.WalletRef.db.DeleteStruct(oldProposal) + } + + return p.WalletRef.db.Save(proposal) +} + +// GetProposalsRaw fetches and returns a proposals from the db +func (p *Politeia) GetProposalsRaw(category int32, offset, limit int32, newestFirst bool) ([]Proposal, error) { + return p.getProposalsRaw(category, offset, limit, newestFirst, false) +} + +func (p *Politeia) getProposalsRaw(category int32, offset, limit int32, newestFirst bool, skipAbandoned bool) ([]Proposal, error) { + + var query storm.Query + switch category { + case ProposalCategoryAll: + + if skipAbandoned { + query = p.WalletRef.db.Select( + q.Not(q.Eq("Category", ProposalCategoryAbandoned)), + ) + } else { + query = p.WalletRef.db.Select( + q.True(), + ) + } + default: + query = p.WalletRef.db.Select( + q.Eq("Category", category), + ) + } + + if offset > 0 { + query = query.Skip(int(offset)) + } + + if limit > 0 { + query = query.Limit(int(limit)) + } + + if newestFirst { + query = query.OrderBy("PublishedAt").Reverse() + } else { + query = query.OrderBy("PublishedAt") + } + + var proposals []Proposal + err := query.Find(&proposals) + if err != nil && err != storm.ErrNotFound { + return nil, fmt.Errorf("error fetching proposals: %s", err.Error()) + } + + return proposals, nil +} + +// GetProposals returns the result of GetProposalsRaw as a JSON string +func (p *Politeia) GetProposals(category int32, offset, limit int32, newestFirst bool) (string, error) { + + result, err := p.GetProposalsRaw(category, offset, limit, newestFirst) + if err != nil { + return "", err + } + + if len(result) == 0 { + return "[]", nil + } + + response, err := json.Marshal(result) + if err != nil { + return "", fmt.Errorf("error marshalling result: %s", err.Error()) + } + + return string(response), nil +} + +// GetProposalRaw fetches and returns a single proposal specified by it's censorship record token +func (p *Politeia) GetProposalRaw(censorshipToken string) (*Proposal, error) { + var proposal Proposal + err := p.WalletRef.db.One("Token", censorshipToken, &proposal) + if err != nil { + return nil, err + } + + return &proposal, nil +} + +// GetProposal returns the result of GetProposalRaw as a JSON string +func (p *Politeia) GetProposal(censorshipToken string) (string, error) { + return marshalResult(p.GetProposalRaw(censorshipToken)) +} + +// GetProposalByIDRaw fetches and returns a single proposal specified by it's ID +func (p *Politeia) GetProposalByIDRaw(proposalID int) (*Proposal, error) { + var proposal Proposal + err := p.WalletRef.db.One("ID", proposalID, &proposal) + if err != nil { + return nil, err + } + + return &proposal, nil +} + +// GetProposalByID returns the result of GetProposalByIDRaw as a JSON string +func (p *Politeia) GetProposalByID(proposalID int) (string, error) { + return marshalResult(p.GetProposalByIDRaw(proposalID)) +} + +// Count returns the number of proposals of a specified category +func (p *Politeia) Count(category int32) (int32, error) { + var matcher q.Matcher + + if category == ProposalCategoryAll { + matcher = q.True() + } else { + matcher = q.Eq("Category", category) + } + + count, err := p.WalletRef.db.Select(matcher).Count(&Proposal{}) + if err != nil { + return 0, err + } + + return int32(count), nil +} + +func (p *Politeia) Overview() (*ProposalOverview, error) { + + pre, err := p.Count(ProposalCategoryPre) + if err != nil { + return nil, err + } + + active, err := p.Count(ProposalCategoryActive) + if err != nil { + return nil, err + } + + approved, err := p.Count(ProposalCategoryApproved) + if err != nil { + return nil, err + } + + rejected, err := p.Count(ProposalCategoryRejected) + if err != nil { + return nil, err + } + + abandoned, err := p.Count(ProposalCategoryApproved) + if err != nil { + return nil, err + } + + return &ProposalOverview{ + All: pre + active + approved + rejected + abandoned, + Discussion: pre, + Voting: active, + Approved: approved, + Rejected: rejected, + Abandoned: abandoned, + }, nil +} + +func (p *Politeia) ClearSavedProposals() error { + err := p.WalletRef.db.Drop(&Proposal{}) + if err != nil { + return translateError(err) + } + + return p.WalletRef.db.Init(&Proposal{}) +} diff --git a/wallets/dcr/rescan.go b/wallets/dcr/rescan.go new file mode 100644 index 000000000..426dbb887 --- /dev/null +++ b/wallets/dcr/rescan.go @@ -0,0 +1,142 @@ +package dcr + +import ( + "context" + "math" + "time" + + "decred.org/dcrwallet/v2/errors" + w "decred.org/dcrwallet/v2/wallet" +) + +func (wallet *Wallet) RescanBlocks(walletID int) error { + return wallet.RescanBlocksFromHeight(walletID, 0) +} + +func (wallet *Wallet) RescanBlocksFromHeight(walletID int, startHeight int32) error { + + netBackend, err := wallet.Internal().NetworkBackend() + if err != nil { + return errors.E(ErrNotConnected) + } + + if wallet.IsRescanning() || !wallet.IsSynced() { + return errors.E(ErrInvalid) + } + + go func() { + defer func() { + wallet.syncData.mu.Lock() + wallet.syncData.rescanning = false + wallet.syncData.cancelRescan = nil + wallet.syncData.mu.Unlock() + }() + + ctx, _ := wallet.ShutdownContextWithCancel() + ctx, cancel := wallet.ShutdownContextWithCancel() //undo this lateer + + wallet.syncData.mu.Lock() + wallet.syncData.rescanning = true + wallet.syncData.cancelRescan = cancel + wallet.syncData.mu.Unlock() + + if wallet.blocksRescanProgressListener != nil { + wallet.blocksRescanProgressListener.OnBlocksRescanStarted(walletID) + } + + progress := make(chan w.RescanProgress, 1) + go wallet.Internal().RescanProgressFromHeight(ctx, netBackend, startHeight, progress) + + rescanStartTime := time.Now().Unix() + + for p := range progress { + if p.Err != nil { + log.Error(p.Err) + if wallet.blocksRescanProgressListener != nil { + wallet.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, p.Err) + } + return + } + + rescanProgressReport := &HeadersRescanProgressReport{ + CurrentRescanHeight: p.ScannedThrough, + TotalHeadersToScan: wallet.GetBestBlock(), + WalletID: walletID, + } + + elapsedRescanTime := time.Now().Unix() - rescanStartTime + rescanRate := float64(p.ScannedThrough) / float64(rescanProgressReport.TotalHeadersToScan) + + rescanProgressReport.RescanProgress = int32(math.Round(rescanRate * 100)) + estimatedTotalRescanTime := int64(math.Round(float64(elapsedRescanTime) / rescanRate)) + rescanProgressReport.RescanTimeRemaining = estimatedTotalRescanTime - elapsedRescanTime + + rescanProgressReport.GeneralSyncProgress = &GeneralSyncProgress{ + TotalSyncProgress: rescanProgressReport.RescanProgress, + TotalTimeRemainingSeconds: rescanProgressReport.RescanTimeRemaining, + } + + if wallet.blocksRescanProgressListener != nil { + wallet.blocksRescanProgressListener.OnBlocksRescanProgress(rescanProgressReport) + } + + select { + case <-ctx.Done(): + log.Info("Rescan canceled through context") + + if wallet.blocksRescanProgressListener != nil { + if ctx.Err() != nil && ctx.Err() != context.Canceled { + wallet.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, ctx.Err()) + } else { + wallet.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, nil) + } + } + + return + default: + continue + } + } + + var err error + if startHeight == 0 { + err = wallet.ReindexTransactions() + } else { + err = wallet.WalletDataDB.SaveLastIndexPoint(startHeight) + if err != nil { + if wallet.blocksRescanProgressListener != nil { + wallet.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, err) + } + return + } + + err = wallet.IndexTransactions() + } + if wallet.blocksRescanProgressListener != nil { + wallet.blocksRescanProgressListener.OnBlocksRescanEnded(walletID, err) + } + }() + + return nil +} + +func (wallet *Wallet) CancelRescan() { + wallet.syncData.mu.Lock() + defer wallet.syncData.mu.Unlock() + if wallet.syncData.cancelRescan != nil { + wallet.syncData.cancelRescan() + wallet.syncData.cancelRescan = nil + + log.Info("Rescan canceled.") + } +} + +func (wallet *Wallet) IsRescanning() bool { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + return wallet.syncData.rescanning +} + +func (wallet *Wallet) SetBlocksRescanProgressListener(blocksRescanProgressListener BlocksRescanProgressListener) { + wallet.blocksRescanProgressListener = blocksRescanProgressListener +} diff --git a/wallets/dcr/sync.go b/wallets/dcr/sync.go index fa299fe8b..9b6c0a903 100644 --- a/wallets/dcr/sync.go +++ b/wallets/dcr/sync.go @@ -288,7 +288,7 @@ func (wallet *Wallet) CancelSync() { // Stop running cspp mixers if wallet.IsAccountMixerActive() { log.Infof("[%d] Stopping cspp mixer", wallet.ID) - err := wallet.StopAccountMixer(wallet.ID) + err := wallet.StopAccountMixer() if err != nil { log.Errorf("[%d] Error stopping cspp mixer: %v", wallet.ID, err) } @@ -435,9 +435,9 @@ func (wallet *Wallet) GetLowestBlock() *BlockInfo { var lowestBlock int32 = -1 var blockInfo *BlockInfo // for _, wallet := range wallet.wallets { - // if !wallet.WalletOpened() { - // continue - // } + if !wallet.WalletOpened() { + return nil + } walletBestBLock := wallet.GetBestBlock() if walletBestBLock < lowestBlock || lowestBlock == -1 { lowestBlock = walletBestBLock diff --git a/wallets/dcr/syncnotification.go b/wallets/dcr/syncnotification.go index 877741d8f..4bc96780d 100644 --- a/wallets/dcr/syncnotification.go +++ b/wallets/dcr/syncnotification.go @@ -5,7 +5,7 @@ import ( "time" "github.com/planetdecred/dcrlibwallet/spv" - // "golang.org/x/sync/errgroup" + "golang.org/x/sync/errgroup" ) func (w *Wallet) spvSyncNotificationCallbacks() *spv.Notifications { @@ -199,9 +199,9 @@ func (w *Wallet) fetchHeadersProgress(lastFetchedHeaderHeight int32, lastFetched } // for _, wallet := range w.wallets { - if w.WaitingForHeaders { - w.WaitingForHeaders = w.GetBestBlock() > lastFetchedHeaderHeight - } + if w.WaitingForHeaders { + w.WaitingForHeaders = w.GetBestBlock() > lastFetchedHeaderHeight + } // } // lock the mutex before reading and writing to w.syncData.* @@ -618,10 +618,8 @@ func (w *Wallet) resetSyncData() { w.syncData.activeSyncData = nil w.syncData.mu.Unlock() - for _, wallet := range w.wallets { - wallet.WaitingForHeaders = true - wallet.LockWallet() // lock wallet if previously unlocked to perform account discovery. - } + w.WaitingForHeaders = true + w.LockWallet() // lock wallet if previously unlocked to perform account discovery. } func (w *Wallet) synced(walletID int, synced bool) { @@ -630,9 +628,7 @@ func (w *Wallet) synced(walletID int, synced bool) { // begin indexing transactions after sync is completed, // syncProgressListeners.OnSynced() will be invoked after transactions are indexed var txIndexing errgroup.Group - for _, wallet := range w.wallets { - txIndexing.Go(wallet.IndexTransactions) - } + txIndexing.Go(w.IndexTransactions) go func() { err := txIndexing.Wait() @@ -661,22 +657,22 @@ func (w *Wallet) synced(walletID int, synced bool) { w.Synced = synced w.Syncing = false - w.listenForTransactions(wallet.ID) + w.listenForTransactions() if !w.Internal().Locked() { w.LockWallet() // lock wallet if previously unlocked to perform account discovery. - err := w.markWalletAsDiscoveredAccounts(walletID) + err := w.markWalletAsDiscoveredAccounts() if err != nil { log.Error(err) } } - if w.OpenedWalletsCount() == w.SyncedWalletsCount() { - w.syncData.mu.Lock() - w.syncData.syncing = false - w.syncData.synced = true - w.syncData.mu.Unlock() + // if w.OpenedWalletsCount() == w.SyncedWalletsCount() { + w.syncData.mu.Lock() + w.syncData.syncing = false + w.syncData.synced = true + w.syncData.mu.Unlock() - indexTransactions() - } + indexTransactions() + // } } diff --git a/wallets/dcr/txandblocknotifications.go b/wallets/dcr/txandblocknotifications.go index f127bdbe4..b11ef52b2 100644 --- a/wallets/dcr/txandblocknotifications.go +++ b/wallets/dcr/txandblocknotifications.go @@ -1,78 +1,76 @@ package dcr import ( - // "encoding/json" - - // "decred.org/dcrwallet/v2/errors" + "encoding/json" + "decred.org/dcrwallet/v2/errors" ) -// func (mw *MultiWallet) listenForTransactions(walletID int) { -// go func() { - -// wallet := mw.wallets[walletID] -// n := wallet.Internal().NtfnServer.TransactionNotifications() - -// for { -// select { -// case v := <-n.C: -// if v == nil { -// return -// } -// for _, transaction := range v.UnminedTransactions { -// tempTransaction, err := wallet.decodeTransactionWithTxSummary(&transaction, nil) -// if err != nil { -// log.Errorf("[%d] Error ntfn parse tx: %v", wallet.ID, err) -// return -// } - -// overwritten, err := wallet.walletDataDB.SaveOrUpdate(&Transaction{}, tempTransaction) -// if err != nil { -// log.Errorf("[%d] New Tx save err: %v", wallet.ID, err) -// return -// } - -// if !overwritten { -// log.Infof("[%d] New Transaction %s", wallet.ID, tempTransaction.Hash) - -// result, err := json.Marshal(tempTransaction) -// if err != nil { -// log.Error(err) -// } else { -// mw.mempoolTransactionNotification(string(result)) -// } -// } -// } - -// for _, block := range v.AttachedBlocks { -// blockHash := block.Header.BlockHash() -// for _, transaction := range block.Transactions { -// tempTransaction, err := wallet.decodeTransactionWithTxSummary(&transaction, &blockHash) -// if err != nil { -// log.Errorf("[%d] Error ntfn parse tx: %v", wallet.ID, err) -// return -// } - -// _, err = wallet.walletDataDB.SaveOrUpdate(&Transaction{}, tempTransaction) -// if err != nil { -// log.Errorf("[%d] Incoming block replace tx error :%v", wallet.ID, err) -// return -// } -// mw.publishTransactionConfirmed(wallet.ID, transaction.Hash.String(), int32(block.Header.Height)) -// } - -// mw.publishBlockAttached(wallet.ID, int32(block.Header.Height)) -// } - -// if len(v.AttachedBlocks) > 0 { -// mw.checkWalletMixers() -// } - -// case <-mw.syncData.syncCanceled: -// n.Done() -// } -// } -// }() -// } +func (wallet *Wallet) listenForTransactions() { + go func() { + + n := wallet.Internal().NtfnServer.TransactionNotifications() + + for { + select { + case v := <-n.C: + if v == nil { + return + } + for _, transaction := range v.UnminedTransactions { + tempTransaction, err := wallet.decodeTransactionWithTxSummary(&transaction, nil) + if err != nil { + log.Errorf("[%d] Error ntfn parse tx: %v", wallet.ID, err) + return + } + + overwritten, err := wallet.WalletDataDB.SaveOrUpdate(&Transaction{}, tempTransaction) + if err != nil { + log.Errorf("[%d] New Tx save err: %v", wallet.ID, err) + return + } + + if !overwritten { + log.Infof("[%d] New Transaction %s", wallet.ID, tempTransaction.Hash) + + result, err := json.Marshal(tempTransaction) + if err != nil { + log.Error(err) + } else { + wallet.mempoolTransactionNotification(string(result)) + } + } + } + + for _, block := range v.AttachedBlocks { + blockHash := block.Header.BlockHash() + for _, transaction := range block.Transactions { + tempTransaction, err := wallet.decodeTransactionWithTxSummary(&transaction, &blockHash) + if err != nil { + log.Errorf("[%d] Error ntfn parse tx: %v", wallet.ID, err) + return + } + + _, err = wallet.WalletDataDB.SaveOrUpdate(&Transaction{}, tempTransaction) + if err != nil { + log.Errorf("[%d] Incoming block replace tx error :%v", wallet.ID, err) + return + } + wallet.publishTransactionConfirmed(transaction.Hash.String(), int32(block.Header.Height)) + } + + wallet.publishBlockAttached(int32(block.Header.Height)) + } + + if len(v.AttachedBlocks) > 0 { + wallet.checkWalletMixers() + } + + case <-wallet.syncData.syncCanceled: + n.Done() + } + } + }() +} // AddTxAndBlockNotificationListener registers a set of functions to be invoked // when a transaction or block update is processed by the wallet. If async is @@ -84,76 +82,74 @@ import ( // until all notification handlers finish processing the notification. If a // notification handler were to try to access such features, it would result // in a deadlock. -// func (mw *MultiWallet) AddTxAndBlockNotificationListener(txAndBlockNotificationListener TxAndBlockNotificationListener, async bool, uniqueIdentifier string) error { -// mw.notificationListenersMu.Lock() -// defer mw.notificationListenersMu.Unlock() - -// _, ok := mw.txAndBlockNotificationListeners[uniqueIdentifier] -// if ok { -// return errors.New(ErrListenerAlreadyExist) -// } - -// if async { -// mw.txAndBlockNotificationListeners[uniqueIdentifier] = &asyncTxAndBlockNotificationListener{ -// l: txAndBlockNotificationListener, -// } -// } else { -// mw.txAndBlockNotificationListeners[uniqueIdentifier] = txAndBlockNotificationListener -// } - -// return nil -// } - -// func (mw *MultiWallet) RemoveTxAndBlockNotificationListener(uniqueIdentifier string) { -// mw.notificationListenersMu.Lock() -// defer mw.notificationListenersMu.Unlock() - -// delete(mw.txAndBlockNotificationListeners, uniqueIdentifier) -// } - -// func (mw *MultiWallet) checkWalletMixers() { -// for _, wallet := range mw.wallets { -// if wallet.IsAccountMixerActive() { -// unmixedAccount := wallet.ReadInt32ConfigValueForKey(AccountMixerUnmixedAccount, -1) -// hasMixableOutput, err := wallet.accountHasMixableOutput(unmixedAccount) -// if err != nil { -// log.Errorf("Error checking for mixable outputs: %v", err) -// } - -// if !hasMixableOutput { -// log.Infof("[%d] unmixed account does not have a mixable output, stopping account mixer", wallet.ID) -// err = mw.StopAccountMixer(wallet.ID) -// if err != nil { -// log.Errorf("Error stopping account mixer: %v", err) -// } -// } -// } -// } -// } - -// func (mw *MultiWallet) mempoolTransactionNotification(transaction string) { -// mw.notificationListenersMu.RLock() -// defer mw.notificationListenersMu.RUnlock() - -// for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { -// txAndBlockNotifcationListener.OnTransaction(transaction) -// } -// } - -// func (mw *MultiWallet) publishTransactionConfirmed(walletID int, transactionHash string, blockHeight int32) { -// mw.notificationListenersMu.RLock() -// defer mw.notificationListenersMu.RUnlock() - -// for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { -// txAndBlockNotifcationListener.OnTransactionConfirmed(walletID, transactionHash, blockHeight) -// } -// } - -// func (mw *MultiWallet) publishBlockAttached(walletID int, blockHeight int32) { -// mw.notificationListenersMu.RLock() -// defer mw.notificationListenersMu.RUnlock() - -// for _, txAndBlockNotifcationListener := range mw.txAndBlockNotificationListeners { -// txAndBlockNotifcationListener.OnBlockAttached(walletID, blockHeight) -// } -// } +func (wallet *Wallet) AddTxAndBlockNotificationListener(txAndBlockNotificationListener TxAndBlockNotificationListener, async bool, uniqueIdentifier string) error { + wallet.notificationListenersMu.Lock() + defer wallet.notificationListenersMu.Unlock() + + _, ok := wallet.txAndBlockNotificationListeners[uniqueIdentifier] + if ok { + return errors.New(ErrListenerAlreadyExist) + } + + if async { + wallet.txAndBlockNotificationListeners[uniqueIdentifier] = &asyncTxAndBlockNotificationListener{ + l: txAndBlockNotificationListener, + } + } else { + wallet.txAndBlockNotificationListeners[uniqueIdentifier] = txAndBlockNotificationListener + } + + return nil +} + +func (wallet *Wallet) RemoveTxAndBlockNotificationListener(uniqueIdentifier string) { + wallet.notificationListenersMu.Lock() + defer wallet.notificationListenersMu.Unlock() + + delete(wallet.txAndBlockNotificationListeners, uniqueIdentifier) +} + +func (wallet *Wallet) checkWalletMixers() { + if wallet.IsAccountMixerActive() { + unmixedAccount := wallet.ReadInt32ConfigValueForKey(AccountMixerUnmixedAccount, -1) + hasMixableOutput, err := wallet.accountHasMixableOutput(unmixedAccount) + if err != nil { + log.Errorf("Error checking for mixable outputs: %v", err) + } + + if !hasMixableOutput { + log.Infof("[%d] unmixed account does not have a mixable output, stopping account mixer", wallet.ID) + err = wallet.StopAccountMixer() + if err != nil { + log.Errorf("Error stopping account mixer: %v", err) + } + } + } +} + +func (wallet *Wallet) mempoolTransactionNotification(transaction string) { + wallet.notificationListenersMu.RLock() + defer wallet.notificationListenersMu.RUnlock() + + for _, txAndBlockNotifcationListener := range wallet.txAndBlockNotificationListeners { + txAndBlockNotifcationListener.OnTransaction(transaction) + } +} + +func (wallet *Wallet) publishTransactionConfirmed(transactionHash string, blockHeight int32) { + wallet.notificationListenersMu.RLock() + defer wallet.notificationListenersMu.RUnlock() + + for _, txAndBlockNotifcationListener := range wallet.txAndBlockNotificationListeners { + txAndBlockNotifcationListener.OnTransactionConfirmed(wallet.ID, transactionHash, blockHeight) + } +} + +func (wallet *Wallet) publishBlockAttached(blockHeight int32) { + wallet.notificationListenersMu.RLock() + defer wallet.notificationListenersMu.RUnlock() + + for _, txAndBlockNotifcationListener := range wallet.txAndBlockNotificationListeners { + txAndBlockNotifcationListener.OnBlockAttached(wallet.ID, blockHeight) + } +} diff --git a/wallets/dcr/types.go b/wallets/dcr/types.go index 063ea9542..1beb01436 100644 --- a/wallets/dcr/types.go +++ b/wallets/dcr/types.go @@ -4,14 +4,26 @@ import ( "context" "fmt" "net" + "net/http" + "sync" "decred.org/dcrwallet/v2/wallet/udb" "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/dcrutil/v4" + www "github.com/decred/politeia/politeiawww/api/www/v1" "github.com/planetdecred/dcrlibwallet/internal/vsp" ) +const ( + ProposalCategoryAll int32 = iota + 1 + ProposalCategoryPre + ProposalCategoryActive + ProposalCategoryApproved + ProposalCategoryRejected + ProposalCategoryAbandoned +) + // WalletConfig defines options for configuring wallet behaviour. // This is a subset of the config used by dcrwallet. type WalletConfig struct { @@ -401,6 +413,26 @@ type VSPTicketInfo struct { /** end ticket-related types */ /** begin politeia types */ +type Politeia struct { + WalletRef *Wallet + Host string + mu sync.RWMutex + ctx context.Context + cancelSync context.CancelFunc + Client *politeiaClient + notificationListenersMu sync.RWMutex + NotificationListeners map[string]ProposalNotificationListener +} + +type politeiaClient struct { + host string + httpClient *http.Client + + version *www.VersionReply + policy *www.PolicyReply + cookies []*http.Cookie +} + type Proposal struct { ID int `storm:"id,increment"` Token string `json:"token" storm:"unique"` diff --git a/wallets/dcr/utils.go b/wallets/dcr/utils.go index c23e66ab9..0d21ac8ba 100644 --- a/wallets/dcr/utils.go +++ b/wallets/dcr/utils.go @@ -16,7 +16,7 @@ import ( "strings" "time" - // "decred.org/dcrwallet/v2/errors" + "decred.org/dcrwallet/v2/errors" "decred.org/dcrwallet/v2/wallet" "decred.org/dcrwallet/v2/wallet/txrules" "decred.org/dcrwallet/v2/walletseed" @@ -101,18 +101,18 @@ func (wallet *Wallet) contextWithShutdownCancel() (context.Context, context.Canc return ctx, cancel } -// func (mw *MultiWallet) ValidateExtPubKey(extendedPubKey string) error { -// _, err := hdkeychain.NewKeyFromString(extendedPubKey, mw.chainParams) -// if err != nil { -// if err == hdkeychain.ErrInvalidChild { -// return errors.New(ErrUnusableSeed) -// } +func (wallet *Wallet) ValidateExtPubKey(extendedPubKey string) error { + _, err := hdkeychain.NewKeyFromString(extendedPubKey, wallet.chainParams) + if err != nil { + if err == hdkeychain.ErrInvalidChild { + return errors.New(ErrUnusableSeed) + } -// return errors.New(ErrInvalid) -// } + return errors.New(ErrInvalid) + } -// return nil -// } + return nil +} func NormalizeAddress(addr string, defaultPort string) (string, error) { // If the first SplitHostPort errors because of a missing port and not @@ -205,18 +205,18 @@ func ShannonEntropy(text string) (entropy float64) { return entropy } -// func TransactionDirectionName(direction int32) string { -// switch direction { -// case TxDirectionSent: -// return "Sent" -// case TxDirectionReceived: -// return "Received" -// case TxDirectionTransferred: -// return "Yourself" -// default: -// return "invalid" -// } -// } +func TransactionDirectionName(direction int32) string { + switch direction { + case TxDirectionSent: + return "Sent" + case TxDirectionReceived: + return "Received" + case TxDirectionTransferred: + return "Yourself" + default: + return "invalid" + } +} func CalculateTotalTimeRemaining(timeRemainingInSeconds int64) string { minutes := timeRemainingInSeconds / 60 @@ -500,3 +500,19 @@ func HttpGet(url string, respObj interface{}) (*http.Response, []byte, error) { err = json.Unmarshal(respBytes, respObj) return resp, respBytes, err } + + +func marshalResult(result interface{}, err error) (string, error) { + + if err != nil { + return "", translateError(err) + } + + response, err := json.Marshal(result) + if err != nil { + return "", fmt.Errorf("error marshalling result: %s", err.Error()) + } + + return string(response), nil +} + diff --git a/wallets/dcr/wallet.go b/wallets/dcr/wallet.go index 1e4ddd582..c4d294a2b 100644 --- a/wallets/dcr/wallet.go +++ b/wallets/dcr/wallet.go @@ -65,6 +65,9 @@ type Wallet struct { notificationListenersMu sync.RWMutex syncData *SyncData accountMixerNotificationListener map[string]AccountMixerNotificationListener + txAndBlockNotificationListeners map[string]TxAndBlockNotificationListener + blocksRescanProgressListener BlocksRescanProgressListener + } // prepare gets a wallet ready for use by opening the transactions index database diff --git a/wallets/dcr/wallet_utils.go b/wallets/dcr/wallet_utils.go new file mode 100644 index 000000000..5a493e834 --- /dev/null +++ b/wallets/dcr/wallet_utils.go @@ -0,0 +1,20 @@ +package dcr + +import ( + "decred.org/dcrwallet/v2/errors" +) + +func (wallet *Wallet) markWalletAsDiscoveredAccounts() error { + if wallet == nil { + return errors.New(ErrNotExist) + } + + log.Infof("Set discovered accounts = true for wallet %d", wallet.ID) + wallet.HasDiscoveredAccounts = true + err := wallet.db.Save(wallet) + if err != nil { + return err + } + + return nil +} From f0b595e4c7203adaec63c94679ad05e39dd5ee4b Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:27:06 +0100 Subject: [PATCH 03/16] remove wallet specific methods from multiwallet --- dcr.go | 2 +- multiwallet.go | 365 +------------ multiwallet_utils.go | 1 - politeia.go | 33 -- syncnotification.go | 686 ------------------------- utils.go | 4 +- wallets/dcr/message.go | 3 +- wallets/dcr/multiwallet_config.go | 152 ------ wallets/dcr/politeia.go | 218 ++++++++ wallets/dcr/politeia_sync.go | 190 ------- wallets/dcr/sync.go | 16 +- wallets/dcr/txandblocknotifications.go | 2 +- wallets/dcr/types.go | 9 - wallets/dcr/utils.go | 2 - wallets/dcr/wallet.go | 309 ++++++++++- wallets/dcr/wallet_config.go | 52 ++ wallets/dcr/wallet_utils.go | 110 ++++ 17 files changed, 708 insertions(+), 1446 deletions(-) delete mode 100644 politeia.go delete mode 100644 syncnotification.go delete mode 100644 wallets/dcr/multiwallet_config.go create mode 100644 wallets/dcr/politeia.go diff --git a/dcr.go b/dcr.go index 3ea1b5ba1..31d1f4c4c 100644 --- a/dcr.go +++ b/dcr.go @@ -18,7 +18,7 @@ import ( func initializeDCRWallet(rootDir, dbDriver, netType string) (*storm.DB, string, error) { var mwDB *storm.DB - + rootDir = filepath.Join(rootDir, netType, "dcr") err := os.MkdirAll(rootDir, os.ModePerm) if err != nil { diff --git a/multiwallet.go b/multiwallet.go index ea2cb3141..bb54c6f3c 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -6,18 +6,18 @@ import ( "fmt" "os" "path/filepath" - "strconv" + // "strconv" "strings" - "sync" - "time" + // "sync" + // "time" "decred.org/dcrwallet/v2/errors" - w "decred.org/dcrwallet/v2/wallet" + // w "decred.org/dcrwallet/v2/wallet" "github.com/asdine/storm" "github.com/asdine/storm/q" "github.com/decred/dcrd/chaincfg/v3" "github.com/planetdecred/dcrlibwallet/utils" - "github.com/planetdecred/dcrlibwallet/wallets/dcr/walletdata" + // "github.com/planetdecred/dcrlibwallet/wallets/dcr/walletdata" "github.com/planetdecred/dcrlibwallet/wallets/dcr" @@ -36,19 +36,18 @@ type MultiWallet struct { // syncData *dcr.SyncData // notificationListenersMu sync.RWMutex - txAndBlockNotificationListeners map[string]TxAndBlockNotificationListener + // txAndBlockNotificationListeners map[string]TxAndBlockNotificationListener - blocksRescanProgressListener BlocksRescanProgressListener + // blocksRescanProgressListener BlocksRescanProgressListener // accountMixerNotificationListener map[string]AccountMixerNotificationListener shuttingDown chan bool cancelFuncs []context.CancelFunc - Politeia *dcr.Politeia dexClient *DexClient - vspMu sync.RWMutex - vsps []*VSP + // vspMu sync.RWMutex + // vsps []*VSP } func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWallet, error) { @@ -72,19 +71,6 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall chainParams: chainParams, wallets: make(map[int]*dcr.Wallet), badWallets: make(map[int]*dcr.Wallet), - // syncData: &dcr.SyncData{ - // SyncProgressListeners: make(map[string]dcr.SyncProgressListener), - // }, - txAndBlockNotificationListeners: make(map[string]TxAndBlockNotificationListener), - } - - // syncData: &dcr.SyncData{ - // SyncProgressListeners: make(map[string]dcr.SyncProgressListener), - // }, - - mw.Politeia, err = newPoliteia(mw.wallets[0], politeiaHost) - if err != nil { - return nil, err } // read saved wallets info from db and initialize wallets @@ -107,6 +93,9 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall } else { mw.wallets[wallet.ID] = wallet } + + // initialize Politeia. + wallet.NewPoliteia(politeiaHost) } mw.listenForShutdown() @@ -281,259 +270,6 @@ func (mw *MultiWallet) AllWalletsAreWatchOnly() (bool, error) { return true, nil } -func (mw *MultiWallet) CreateWatchOnlyWallet(walletName, extendedPublicKey string) (*dcr.Wallet, error) { - wallet := &dcr.Wallet{ - Name: walletName, - IsRestored: true, - HasDiscoveredAccounts: true, - } - - return mw.saveNewWallet(wallet, func() error { - err := wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) - if err != nil { - return err - } - - return wallet.CreateWatchingOnlyWallet(extendedPublicKey) - }) -} - -func (mw *MultiWallet) CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32) (*dcr.Wallet, error) { - seed, err := GenerateSeed() - if err != nil { - return nil, err - } - - encryptedSeed, err := encryptWalletSeed([]byte(privatePassphrase), seed) - if err != nil { - return nil, err - } - wallet := &dcr.Wallet{ - Name: walletName, - CreatedAt: time.Now(), - EncryptedSeed: encryptedSeed, - PrivatePassphraseType: privatePassphraseType, - HasDiscoveredAccounts: true, - } - - return mw.saveNewWallet(wallet, func() error { - err := wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) - if err != nil { - return err - } - - return wallet.CreateWallet(privatePassphrase, seed) - }) -} - -func (mw *MultiWallet) RestoreWallet(walletName, seedMnemonic, privatePassphrase string, privatePassphraseType int32) (*dcr.Wallet, error) { - - wallet := &dcr.Wallet{ - Name: walletName, - PrivatePassphraseType: privatePassphraseType, - IsRestored: true, - HasDiscoveredAccounts: false, - } - - return mw.saveNewWallet(wallet, func() error { - err := wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) - if err != nil { - return err - } - - return wallet.CreateWallet(privatePassphrase, seedMnemonic) - }) -} - -func (mw *MultiWallet) LinkExistingWallet(walletName, walletDataDir, originalPubPass string, privatePassphraseType int32) (*dcr.Wallet, error) { - // check if `walletDataDir` contains wallet.db - if !WalletExistsAt(walletDataDir) { - return nil, errors.New(ErrNotExist) - } - - ctx, _ := mw.contextWithShutdownCancel() - - // verify the public passphrase for the wallet being linked before proceeding - if err := mw.loadWalletTemporarily(ctx, walletDataDir, originalPubPass, nil); err != nil { - return nil, err - } - - wallet := &dcr.Wallet{ - Name: walletName, - PrivatePassphraseType: privatePassphraseType, - IsRestored: true, - HasDiscoveredAccounts: false, // assume that account discovery hasn't been done - } - - return mw.saveNewWallet(wallet, func() error { - // move wallet.db and tx.db files to newly created dir for the wallet - currentWalletDbFilePath := filepath.Join(walletDataDir, walletDbName) - newWalletDbFilePath := filepath.Join(wallet.DataDir, walletDbName) - if err := moveFile(currentWalletDbFilePath, newWalletDbFilePath); err != nil { - return err - } - - currentTxDbFilePath := filepath.Join(walletDataDir, walletdata.OldDbName) - newTxDbFilePath := filepath.Join(wallet.DataDir, walletdata.DbName) - if err := moveFile(currentTxDbFilePath, newTxDbFilePath); err != nil { - return err - } - - // prepare the wallet for use and open it - err := (func() error { - err := wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) - if err != nil { - return err - } - - if originalPubPass == "" || originalPubPass == w.InsecurePubPassphrase { - return wallet.OpenWallet() - } - - err = mw.loadWalletTemporarily(ctx, wallet.DataDir, originalPubPass, func(tempWallet *w.Wallet) error { - return tempWallet.ChangePublicPassphrase(ctx, []byte(originalPubPass), []byte(w.InsecurePubPassphrase)) - }) - if err != nil { - return err - } - - return wallet.OpenWallet() - })() - - // restore db files to their original location if there was an error - // in the wallet setup process above - if err != nil { - moveFile(newWalletDbFilePath, currentWalletDbFilePath) - moveFile(newTxDbFilePath, currentTxDbFilePath) - } - - return err - }) -} - -// saveNewWallet performs the following tasks using a db batch operation to ensure -// that db changes are rolled back if any of the steps below return an error. -// -// - saves the initial wallet info to mw.walletsDb to get a wallet id -// - creates a data directory for the wallet using the auto-generated wallet id -// - updates the initial wallet info with name, dataDir (created above), db driver -// and saves the updated info to mw.walletsDb -// - calls the provided `setupWallet` function to perform any necessary creation, -// restoration or linking of the just saved wallet -// -// IFF all the above operations succeed, the wallet info will be persisted to db -// and the wallet will be added to `mw.wallets`. -func (mw *MultiWallet) saveNewWallet(wallet *dcr.Wallet, setupWallet func() error) (*dcr.Wallet, error) { - exists, err := mw.WalletNameExists(wallet.Name) - if err != nil { - return nil, err - } else if exists { - return nil, errors.New(ErrExist) - } - - // if mw.IsConnectedToDecredNetwork() { - // mw.CancelSync() - // defer mw.SpvSync() - // } - // Perform database save operations in batch transaction - // for automatic rollback if error occurs at any point. - err = mw.batchDbTransaction(func(db storm.Node) error { - // saving struct to update ID property with an auto-generated value - err := db.Save(wallet) - if err != nil { - return err - } - - walletDataDir := filepath.Join(mw.rootDir, strconv.Itoa(wallet.ID)) - - dirExists, err := fileExists(walletDataDir) - if err != nil { - return err - } else if dirExists { - newDirName, err := backupFile(walletDataDir, 1) - if err != nil { - return err - } - - log.Infof("Undocumented file at %s moved to %s", walletDataDir, newDirName) - } - - os.MkdirAll(walletDataDir, os.ModePerm) // create wallet dir - - if wallet.Name == "" { - wallet.Name = "wallet-" + strconv.Itoa(wallet.ID) // wallet-# - } - wallet.DataDir = walletDataDir - wallet.DbDriver = mw.dbDriver - - err = db.Save(wallet) // update database with complete wallet information - if err != nil { - return err - } - - return setupWallet() - }) - - if err != nil { - return nil, translateError(err) - } - - mw.wallets[wallet.ID] = wallet - - return wallet, nil -} - -func (mw *MultiWallet) RenameWallet(walletID int, newName string) error { - if strings.HasPrefix(newName, "wallet-") { - return errors.E(ErrReservedWalletName) - } - - if exists, err := mw.WalletNameExists(newName); err != nil { - return translateError(err) - } else if exists { - return errors.New(ErrExist) - } - - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return errors.New(ErrInvalid) - } - - wallet.Name = newName - return mw.db.Save(wallet) // update WalletName field -} - -func (mw *MultiWallet) DeleteWallet(walletID int, privPass []byte) error { - - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return errors.New(ErrNotExist) - } - - // if mw.IsConnectedToDecredNetwork() { - // mw.CancelSync() - // defer func() { - // if mw.OpenedWalletsCount() > 0 { - // mw.SpvSync() - // } - // }() - // } - - err := wallet.DeleteWallet(privPass) - if err != nil { - return translateError(err) - } - - err = mw.db.DeleteStruct(wallet) - if err != nil { - return translateError(err) - } - - delete(mw.wallets, walletID) - - return nil -} - func (mw *MultiWallet) BadWallets() map[int]*dcr.Wallet { return mw.badWallets } @@ -564,26 +300,6 @@ func (mw *MultiWallet) WalletWithID(walletID int) *dcr.Wallet { return nil } -// VerifySeedForWallet compares seedMnemonic with the decrypted wallet.EncryptedSeed and clears wallet.EncryptedSeed if they match. -func (mw *MultiWallet) VerifySeedForWallet(walletID int, seedMnemonic string, privpass []byte) (bool, error) { - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return false, errors.New(ErrNotExist) - } - - decryptedSeed, err := decryptWalletSeed(privpass, wallet.EncryptedSeed) - if err != nil { - return false, err - } - - if decryptedSeed == seedMnemonic { - wallet.EncryptedSeed = nil - return true, translateError(mw.db.Save(wallet)) - } - - return false, errors.New(ErrInvalid) -} - // NumWalletsNeedingSeedBackup returns the number of opened wallets whose seed haven't been verified. func (mw *MultiWallet) NumWalletsNeedingSeedBackup() int32 { var backupsNeeded int32 @@ -644,60 +360,3 @@ func (mw *MultiWallet) WalletNameExists(walletName string) (bool, error) { return false, nil } - -func (mw *MultiWallet) UnlockWallet(walletID int, privPass []byte) error { - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return errors.New(ErrNotExist) - } - - return wallet.UnlockWallet(privPass) -} - -// ChangePrivatePassphraseForWallet attempts to change the wallet's passphrase and re-encrypts the seed with the new passphrase. -func (mw *MultiWallet) ChangePrivatePassphraseForWallet(walletID int, oldPrivatePassphrase, newPrivatePassphrase []byte, privatePassphraseType int32) error { - if privatePassphraseType != PassphraseTypePin && privatePassphraseType != PassphraseTypePass { - return errors.New(ErrInvalid) - } - - wallet := mw.WalletWithID(walletID) - if wallet == nil { - return errors.New(ErrInvalid) - } - - encryptedSeed := wallet.EncryptedSeed - if encryptedSeed != nil { - decryptedSeed, err := decryptWalletSeed(oldPrivatePassphrase, encryptedSeed) - if err != nil { - return err - } - - encryptedSeed, err = encryptWalletSeed(newPrivatePassphrase, decryptedSeed) - if err != nil { - return err - } - } - - err := wallet.ChangePrivatePassphrase(oldPrivatePassphrase, newPrivatePassphrase) - if err != nil { - return translateError(err) - } - - wallet.EncryptedSeed = encryptedSeed - wallet.PrivatePassphraseType = privatePassphraseType - err = mw.db.Save(wallet) - if err != nil { - log.Errorf("error saving wallet-[%d] to database after passphrase change: %v", wallet.ID, err) - - err2 := wallet.ChangePrivatePassphrase(newPrivatePassphrase, oldPrivatePassphrase) - if err2 != nil { - log.Errorf("error undoing wallet passphrase change: %v", err2) - log.Errorf("error wallet passphrase was changed but passphrase type and newly encrypted seed could not be saved: %v", err) - return errors.New(ErrSavingWallet) - } - - return errors.New(ErrChangingPassphrase) - } - - return nil -} diff --git a/multiwallet_utils.go b/multiwallet_utils.go index 788f281e0..d94f28f25 100644 --- a/multiwallet_utils.go +++ b/multiwallet_utils.go @@ -18,7 +18,6 @@ import ( "golang.org/x/crypto/scrypt" "github.com/planetdecred/dcrlibwallet/wallets/dcr" - ) const ( diff --git a/politeia.go b/politeia.go deleted file mode 100644 index 6954faddd..000000000 --- a/politeia.go +++ /dev/null @@ -1,33 +0,0 @@ -package dcrlibwallet - -import ( - // "context" - // "encoding/json" - // "fmt" - // "sync" - - // "decred.org/dcrwallet/v2/errors" - // "github.com/asdine/storm" - // "github.com/asdine/storm/q" - "github.com/planetdecred/dcrlibwallet/wallets/dcr" -) - -const ( - ProposalCategoryAll int32 = iota + 1 - ProposalCategoryPre - ProposalCategoryActive - ProposalCategoryApproved - ProposalCategoryRejected - ProposalCategoryAbandoned -) - -func newPoliteia(walletRef *dcr.Wallet, host string) (*dcr.Politeia, error) { - p := &dcr.Politeia{ - WalletRef: walletRef, - Host: host, - Client: nil, - NotificationListeners: make(map[string]dcr.ProposalNotificationListener), - } - - return p, nil -} diff --git a/syncnotification.go b/syncnotification.go deleted file mode 100644 index 65edd71ba..000000000 --- a/syncnotification.go +++ /dev/null @@ -1,686 +0,0 @@ -package dcrlibwallet - -import ( - "math" - "time" - - "github.com/planetdecred/dcrlibwallet/spv" - // "golang.org/x/sync/errgroup" -) - -func (mw *MultiWallet) spvSyncNotificationCallbacks() *spv.Notifications { - return &spv.Notifications{ - PeerConnected: func(peerCount int32, addr string) { - mw.handlePeerCountUpdate(peerCount) - }, - PeerDisconnected: func(peerCount int32, addr string) { - mw.handlePeerCountUpdate(peerCount) - }, - Synced: mw.synced, - FetchHeadersStarted: mw.fetchHeadersStarted, - FetchHeadersProgress: mw.fetchHeadersProgress, - FetchHeadersFinished: mw.fetchHeadersFinished, - FetchMissingCFiltersStarted: mw.fetchCFiltersStarted, - FetchMissingCFiltersProgress: mw.fetchCFiltersProgress, - FetchMissingCFiltersFinished: mw.fetchCFiltersEnded, - DiscoverAddressesStarted: mw.discoverAddressesStarted, - DiscoverAddressesFinished: mw.discoverAddressesFinished, - RescanStarted: mw.rescanStarted, - RescanProgress: mw.rescanProgress, - RescanFinished: mw.rescanFinished, - } -} - -func (mw *MultiWallet) handlePeerCountUpdate(peerCount int32) { - // mw.syncData.mu.Lock() - // mw.syncData.connectedPeers = peerCount - // shouldLog := mw.syncData.showLogs && mw.syncData.syncing - // mw.syncData.mu.Unlock() - - // for _, syncProgressListener := range mw.syncProgressListeners() { - // syncProgressListener.OnPeerConnectedOrDisconnected(peerCount) - // } - - // if shouldLog { - // if peerCount == 1 { - // log.Infof("Connected to %d peer on %s.", peerCount, mw.chainParams.Name) - // } else { - // log.Infof("Connected to %d peers on %s.", peerCount, mw.chainParams.Name) - // } - // } -} - -// Fetch CFilters Callbacks - -func (mw *MultiWallet) fetchCFiltersStarted(walletID int) { - // mw.syncData.mu.Lock() - // mw.syncData.activeSyncData.syncStage = CFiltersFetchSyncStage - // mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp = time.Now().Unix() - // mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount = 0 - // showLogs := mw.syncData.showLogs - // mw.syncData.mu.Unlock() - - // if showLogs { - // log.Infof("Step 1 of 3 - fetching %d block headers.") - // } -} - -func (mw *MultiWallet) fetchCFiltersProgress(walletID int, startCFiltersHeight, endCFiltersHeight int32) { - - // lock the mutex before reading and writing to mw.syncData.* - // mw.syncData.mu.Lock() - - // if mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight == -1 { - // mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight = startCFiltersHeight - // } - - // wallet := mw.WalletWithID(walletID) - // mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount += endCFiltersHeight - startCFiltersHeight - - // totalCFiltersToFetch := wallet.GetBestBlock() - mw.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight - // // cfiltersLeftToFetch := totalCFiltersToFetch - mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount - - // cfiltersFetchProgress := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(totalCFiltersToFetch) - - // // If there was some period of inactivity, - // // assume that this process started at some point in the future, - // // thereby accounting for the total reported time of inactivity. - // mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp += mw.syncData.activeSyncData.totalInactiveSeconds - // mw.syncData.activeSyncData.totalInactiveSeconds = 0 - - // timeTakenSoFar := time.Now().Unix() - mw.syncData.activeSyncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp - // if timeTakenSoFar < 1 { - // timeTakenSoFar = 1 - // } - // estimatedTotalCFiltersFetchTime := float64(timeTakenSoFar) / cfiltersFetchProgress - - // // Use CFilters fetch rate to estimate headers fetch time. - // cfiltersFetchRate := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(timeTakenSoFar) - // estimatedHeadersLeftToFetch := mw.estimateBlockHeadersCountAfter(wallet.GetBestBlockTimeStamp()) - // estimatedTotalHeadersFetchTime := float64(estimatedHeadersLeftToFetch) / cfiltersFetchRate - // // increase estimated value by FetchPercentage - // estimatedTotalHeadersFetchTime /= FetchPercentage - - // estimatedDiscoveryTime := estimatedTotalHeadersFetchTime * DiscoveryPercentage - // estimatedRescanTime := estimatedTotalHeadersFetchTime * RescanPercentage - // estimatedTotalSyncTime := estimatedTotalCFiltersFetchTime + estimatedTotalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime - - // totalSyncProgress := float64(timeTakenSoFar) / estimatedTotalSyncTime - // totalTimeRemainingSeconds := int64(math.Round(estimatedTotalSyncTime)) - timeTakenSoFar - - // // update headers fetching progress report including total progress percentage and total time remaining - // mw.syncData.activeSyncData.cfiltersFetchProgress.TotalCFiltersToFetch = totalCFiltersToFetch - // mw.syncData.activeSyncData.cfiltersFetchProgress.CurrentCFilterHeight = startCFiltersHeight - // mw.syncData.activeSyncData.cfiltersFetchProgress.CFiltersFetchProgress = roundUp(cfiltersFetchProgress * 100.0) - // mw.syncData.activeSyncData.cfiltersFetchProgress.TotalSyncProgress = roundUp(totalSyncProgress * 100.0) - // mw.syncData.activeSyncData.cfiltersFetchProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds - - // mw.syncData.mu.Unlock() - - // // notify progress listener of estimated progress report - // mw.publishFetchCFiltersProgress() - - // cfiltersFetchTimeRemaining := estimatedTotalCFiltersFetchTime - float64(timeTakenSoFar) - // debugInfo := &DebugInfo{ - // timeTakenSoFar, - // totalTimeRemainingSeconds, - // timeTakenSoFar, - // int64(math.Round(cfiltersFetchTimeRemaining)), - // } - // mw.publishDebugInfo(debugInfo) -} - -func (mw *MultiWallet) publishFetchCFiltersProgress() { - // for _, syncProgressListener := range mw.syncProgressListeners() { - // syncProgressListener.OnCFiltersFetchProgress(&mw.syncData.cfiltersFetchProgress) - // } -} - -func (mw *MultiWallet) fetchCFiltersEnded(walletID int) { - // mw.syncData.mu.Lock() - // defer mw.syncData.mu.Unlock() - - // mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent = time.Now().Unix() - mw.syncData.cfiltersFetchProgress.beginFetchCFiltersTimeStamp - - // // If there is some period of inactivity reported at this stage, - // // subtract it from the total stage time. - // mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent -= mw.syncData.totalInactiveSeconds - // mw.syncData.activeSyncData.totalInactiveSeconds = 0 -} - -// Fetch Headers Callbacks - -func (mw *MultiWallet) fetchHeadersStarted(peerInitialHeight int32) { - // if !mw.IsSyncing() { - // return - // } - - // mw.syncData.mu.RLock() - // headersFetchingStarted := mw.syncData.headersFetchProgress.beginFetchTimeStamp != -1 - // showLogs := mw.syncData.showLogs - // mw.syncData.mu.RUnlock() - - // if headersFetchingStarted { - // // This function gets called for each newly connected peer so - // // ignore if headers fetching was already started. - // return - // } - - // for _, wallet := range mw.wallets { - // wallet.WaitingForHeaders = true - // } - - // lowestBlockHeight := mw.GetLowestBlock().Height - - // mw.syncData.mu.Lock() - // mw.syncData.activeSyncData.syncStage = HeadersFetchSyncStage - // mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp = time.Now().Unix() - // mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight = lowestBlockHeight - // mw.syncData.headersFetchProgress.totalFetchedHeadersCount = 0 - // mw.syncData.activeSyncData.totalInactiveSeconds = 0 - // mw.syncData.mu.Unlock() - - // if showLogs { - // log.Infof("Step 1 of 3 - fetching %d block headers.", peerInitialHeight-lowestBlockHeight) - // } -} - -func (mw *MultiWallet) fetchHeadersProgress(lastFetchedHeaderHeight int32, lastFetchedHeaderTime int64) { - // if !mw.IsSyncing() { - // return - // } - - // mw.syncData.mu.RLock() - // headersFetchingCompleted := mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent != -1 - // mw.syncData.mu.RUnlock() - - // if headersFetchingCompleted { - // // This function gets called for each newly connected peer so ignore - // // this call if the headers fetching phase was previously completed. - // return - // } - - // for _, wallet := range mw.wallets { - // if wallet.WaitingForHeaders { - // wallet.WaitingForHeaders = wallet.GetBestBlock() > lastFetchedHeaderHeight - // } - // } - - // // lock the mutex before reading and writing to mw.syncData.* - // mw.syncData.mu.Lock() - - // if lastFetchedHeaderHeight > mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight { - // mw.syncData.activeSyncData.headersFetchProgress.totalFetchedHeadersCount = lastFetchedHeaderHeight - mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight - // } - - // headersLeftToFetch := mw.estimateBlockHeadersCountAfter(lastFetchedHeaderTime) - // totalHeadersToFetch := lastFetchedHeaderHeight + headersLeftToFetch - // headersFetchProgress := float64(mw.syncData.activeSyncData.headersFetchProgress.totalFetchedHeadersCount) / float64(totalHeadersToFetch) - - // // If there was some period of inactivity, - // // assume that this process started at some point in the future, - // // thereby accounting for the total reported time of inactivity. - // mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp += mw.syncData.activeSyncData.totalInactiveSeconds - // mw.syncData.activeSyncData.totalInactiveSeconds = 0 - - // fetchTimeTakenSoFar := time.Now().Unix() - mw.syncData.activeSyncData.headersFetchProgress.beginFetchTimeStamp - // if fetchTimeTakenSoFar < 1 { - // fetchTimeTakenSoFar = 1 - // } - // estimatedTotalHeadersFetchTime := float64(fetchTimeTakenSoFar) / headersFetchProgress - - // // For some reason, the actual total headers fetch time is more than the predicted/estimated time. - // // Account for this difference by multiplying the estimatedTotalHeadersFetchTime by an incrementing factor. - // // The incrementing factor is inversely proportional to the headers fetch progress, - // // ranging from 0.5 to 0 as headers fetching progress increases from 0 to 1. - // // todo, the above noted (mal)calculation may explain this difference. - // // TODO: is this adjustment still needed since the calculation has been corrected. - // adjustmentFactor := 0.5 * (1 - headersFetchProgress) - // estimatedTotalHeadersFetchTime += estimatedTotalHeadersFetchTime * adjustmentFactor - - // estimatedDiscoveryTime := estimatedTotalHeadersFetchTime * DiscoveryPercentage - // estimatedRescanTime := estimatedTotalHeadersFetchTime * RescanPercentage - // estimatedTotalSyncTime := float64(mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent) + - // estimatedTotalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime - - // totalSyncProgress := float64(fetchTimeTakenSoFar) / estimatedTotalSyncTime - // totalTimeRemainingSeconds := int64(math.Round(estimatedTotalSyncTime)) - fetchTimeTakenSoFar - - // // update headers fetching progress report including total progress percentage and total time remaining - // mw.syncData.activeSyncData.headersFetchProgress.TotalHeadersToFetch = totalHeadersToFetch - // mw.syncData.activeSyncData.headersFetchProgress.CurrentHeaderHeight = lastFetchedHeaderHeight - // mw.syncData.activeSyncData.headersFetchProgress.CurrentHeaderTimestamp = lastFetchedHeaderTime - // mw.syncData.activeSyncData.headersFetchProgress.HeadersFetchProgress = roundUp(headersFetchProgress * 100.0) - // mw.syncData.activeSyncData.headersFetchProgress.TotalSyncProgress = roundUp(totalSyncProgress * 100.0) - // mw.syncData.activeSyncData.headersFetchProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds - - // // unlock the mutex before issuing notification callbacks to prevent potential deadlock - // // if any invoked callback takes a considerable amount of time to execute. - // mw.syncData.mu.Unlock() - - // // notify progress listener of estimated progress report - // mw.publishFetchHeadersProgress() - - // // todo: also log report if showLog == true - // timeTakenSoFar := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + fetchTimeTakenSoFar - // headersFetchTimeRemaining := estimatedTotalHeadersFetchTime - float64(fetchTimeTakenSoFar) - // debugInfo := &DebugInfo{ - // timeTakenSoFar, - // totalTimeRemainingSeconds, - // fetchTimeTakenSoFar, - // int64(math.Round(headersFetchTimeRemaining)), - // } - // mw.publishDebugInfo(debugInfo) -} - -func (mw *MultiWallet) publishFetchHeadersProgress() { - // for _, syncProgressListener := range mw.syncProgressListeners() { - // syncProgressListener.OnHeadersFetchProgress(&mw.syncData.headersFetchProgress) - // } -} - -func (mw *MultiWallet) fetchHeadersFinished() { - // mw.syncData.mu.Lock() - // defer mw.syncData.mu.Unlock() - - // if !mw.syncData.syncing { - // // ignore if sync is not in progress - // return - // } - - // mw.syncData.activeSyncData.headersFetchProgress.startHeaderHeight = -1 - // mw.syncData.headersFetchProgress.totalFetchedHeadersCount = 0 - // mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent = time.Now().Unix() - mw.syncData.headersFetchProgress.beginFetchTimeStamp - - // // If there is some period of inactivity reported at this stage, - // // subtract it from the total stage time. - // mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent -= mw.syncData.totalInactiveSeconds - // mw.syncData.activeSyncData.totalInactiveSeconds = 0 - - // if mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent < 150 { - // // This ensures that minimum ETA used for stage 2 (address discovery) is 120 seconds (80% of 150 seconds). - // mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent = 150 - // } - - // if mw.syncData.showLogs && mw.syncData.syncing { - // log.Info("Fetch headers completed.") - // } -} - -// Address/Account Discovery Callbacks - -func (mw *MultiWallet) discoverAddressesStarted(walletID int) { - // if !mw.IsSyncing() { - // return - // } - - // mw.syncData.mu.RLock() - // addressDiscoveryAlreadyStarted := mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime != -1 - // totalHeadersFetchTime := float64(mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent) - // mw.syncData.mu.RUnlock() - - // if addressDiscoveryAlreadyStarted { - // return - // } - - // mw.syncData.mu.Lock() - // mw.syncData.activeSyncData.syncStage = AddressDiscoverySyncStage - // mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime = time.Now().Unix() - // mw.syncData.activeSyncData.addressDiscoveryProgress.WalletID = walletID - // mw.syncData.addressDiscoveryCompletedOrCanceled = make(chan bool) - // mw.syncData.mu.Unlock() - - // go mw.updateAddressDiscoveryProgress(totalHeadersFetchTime) - - // if mw.syncData.showLogs { - // log.Info("Step 2 of 3 - discovering used addresses.") - // } -} - -func (mw *MultiWallet) updateAddressDiscoveryProgress(totalHeadersFetchTime float64) { - // use ticker to calculate and broadcast address discovery progress every second - // everySecondTicker := time.NewTicker(1 * time.Second) - - // // these values will be used every second to calculate the total sync progress - // estimatedDiscoveryTime := totalHeadersFetchTime * DiscoveryPercentage - // estimatedRescanTime := totalHeadersFetchTime * RescanPercentage - - // // track last logged time remaining and total percent to avoid re-logging same message - // var lastTimeRemaining int64 - // var lastTotalPercent int32 = -1 - - // for { - // if !mw.IsSyncing() { - // return - // } - - // // If there was some period of inactivity, - // // assume that this process started at some point in the future, - // // thereby accounting for the total reported time of inactivity. - // mw.syncData.mu.Lock() - // mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime += mw.syncData.totalInactiveSeconds - // mw.syncData.totalInactiveSeconds = 0 - // addressDiscoveryStartTime := mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime - // totalCfiltersFetchTime := float64(mw.syncData.cfiltersFetchProgress.cfiltersFetchTimeSpent) - // showLogs := mw.syncData.showLogs - // mw.syncData.mu.Unlock() - - // select { - // case <-mw.syncData.addressDiscoveryCompletedOrCanceled: - // // stop calculating and broadcasting address discovery progress - // everySecondTicker.Stop() - // if showLogs { - // log.Info("Address discovery complete.") - // } - // return - - // case <-everySecondTicker.C: - // // calculate address discovery progress - // elapsedDiscoveryTime := float64(time.Now().Unix() - addressDiscoveryStartTime) - // discoveryProgress := (elapsedDiscoveryTime / estimatedDiscoveryTime) * 100 - - // var totalSyncTime float64 - // if elapsedDiscoveryTime > estimatedDiscoveryTime { - // totalSyncTime = totalCfiltersFetchTime + totalHeadersFetchTime + elapsedDiscoveryTime + estimatedRescanTime - // } else { - // totalSyncTime = totalCfiltersFetchTime + totalHeadersFetchTime + estimatedDiscoveryTime + estimatedRescanTime - // } - - // totalElapsedTime := totalCfiltersFetchTime + totalHeadersFetchTime + elapsedDiscoveryTime - // totalProgress := (totalElapsedTime / totalSyncTime) * 100 - - // remainingAccountDiscoveryTime := math.Round(estimatedDiscoveryTime - elapsedDiscoveryTime) - // if remainingAccountDiscoveryTime < 0 { - // remainingAccountDiscoveryTime = 0 - // } - - // totalProgressPercent := int32(math.Round(totalProgress)) - // totalTimeRemainingSeconds := int64(math.Round(remainingAccountDiscoveryTime + estimatedRescanTime)) - - // // update address discovery progress, total progress and total time remaining - // mw.syncData.mu.Lock() - // mw.syncData.addressDiscoveryProgress.AddressDiscoveryProgress = int32(math.Round(discoveryProgress)) - // mw.syncData.addressDiscoveryProgress.TotalSyncProgress = totalProgressPercent - // mw.syncData.addressDiscoveryProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds - // mw.syncData.mu.Unlock() - - // mw.publishAddressDiscoveryProgress() - - // debugInfo := &DebugInfo{ - // int64(math.Round(totalElapsedTime)), - // totalTimeRemainingSeconds, - // int64(math.Round(elapsedDiscoveryTime)), - // int64(math.Round(remainingAccountDiscoveryTime)), - // } - // mw.publishDebugInfo(debugInfo) - - // if showLogs { - // // avoid logging same message multiple times - // if totalProgressPercent != lastTotalPercent || totalTimeRemainingSeconds != lastTimeRemaining { - // log.Infof("Syncing %d%%, %s remaining, discovering used addresses.", - // totalProgressPercent, CalculateTotalTimeRemaining(totalTimeRemainingSeconds)) - - // lastTotalPercent = totalProgressPercent - // lastTimeRemaining = totalTimeRemainingSeconds - // } - // } - // } - // } -} - -func (mw *MultiWallet) publishAddressDiscoveryProgress() { - // for _, syncProgressListener := range mw.syncProgressListeners() { - // syncProgressListener.OnAddressDiscoveryProgress(&mw.syncData.activeSyncData.addressDiscoveryProgress) - // } -} - -func (mw *MultiWallet) discoverAddressesFinished(walletID int) { - // if !mw.IsSyncing() { - // return - // } - - mw.stopUpdatingAddressDiscoveryProgress() -} - -func (mw *MultiWallet) stopUpdatingAddressDiscoveryProgress() { - // mw.syncData.mu.Lock() - // if mw.syncData.activeSyncData != nil && mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled != nil { - // close(mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled) - // mw.syncData.activeSyncData.addressDiscoveryCompletedOrCanceled = nil - // mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent = time.Now().Unix() - mw.syncData.addressDiscoveryProgress.addressDiscoveryStartTime - // } - // mw.syncData.mu.Unlock() -} - -// Blocks Scan Callbacks - -func (mw *MultiWallet) rescanStarted(walletID int) { - // mw.stopUpdatingAddressDiscoveryProgress() - - // mw.syncData.mu.Lock() - // defer mw.syncData.mu.Unlock() - - // if !mw.syncData.syncing { - // // ignore if sync is not in progress - // return - // } - - // mw.syncData.activeSyncData.syncStage = HeadersRescanSyncStage - // mw.syncData.activeSyncData.rescanStartTime = time.Now().Unix() - - // // retain last total progress report from address discovery phase - // mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = mw.syncData.activeSyncData.addressDiscoveryProgress.TotalTimeRemainingSeconds - // mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = mw.syncData.activeSyncData.addressDiscoveryProgress.TotalSyncProgress - // mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID - - // if mw.syncData.showLogs && mw.syncData.syncing { - // log.Info("Step 3 of 3 - Scanning block headers.") - // } -} - -func (mw *MultiWallet) rescanProgress(walletID int, rescannedThrough int32) { - // if !mw.IsSyncing() { - // // ignore if sync is not in progress - // return - // } - - // wallet := mw.wallets[walletID] - // totalHeadersToScan := wallet.GetBestBlock() - - // rescanRate := float64(rescannedThrough) / float64(totalHeadersToScan) - - // mw.syncData.mu.Lock() - - // // If there was some period of inactivity, - // // assume that this process started at some point in the future, - // // thereby accounting for the total reported time of inactivity. - // mw.syncData.activeSyncData.rescanStartTime += mw.syncData.activeSyncData.totalInactiveSeconds - // mw.syncData.activeSyncData.totalInactiveSeconds = 0 - - // elapsedRescanTime := time.Now().Unix() - mw.syncData.activeSyncData.rescanStartTime - // estimatedTotalRescanTime := int64(math.Round(float64(elapsedRescanTime) / rescanRate)) - // totalTimeRemainingSeconds := estimatedTotalRescanTime - elapsedRescanTime - // totalElapsedTime := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent + - // mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent + elapsedRescanTime - - // mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID - // mw.syncData.activeSyncData.headersRescanProgress.TotalHeadersToScan = totalHeadersToScan - // mw.syncData.activeSyncData.headersRescanProgress.RescanProgress = int32(math.Round(rescanRate * 100)) - // mw.syncData.activeSyncData.headersRescanProgress.CurrentRescanHeight = rescannedThrough - // mw.syncData.activeSyncData.headersRescanProgress.RescanTimeRemaining = totalTimeRemainingSeconds - - // // do not update total time taken and total progress percent if elapsedRescanTime is 0 - // // because the estimatedTotalRescanTime will be inaccurate (also 0) - // // which will make the estimatedTotalSyncTime equal to totalElapsedTime - // // giving the wrong impression that the process is complete - // if elapsedRescanTime > 0 { - // estimatedTotalSyncTime := mw.syncData.activeSyncData.cfiltersFetchProgress.cfiltersFetchTimeSpent + mw.syncData.activeSyncData.headersFetchProgress.headersFetchTimeSpent + - // mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent + estimatedTotalRescanTime - // totalProgress := (float64(totalElapsedTime) / float64(estimatedTotalSyncTime)) * 100 - - // mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = totalTimeRemainingSeconds - // mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = int32(math.Round(totalProgress)) - // } - - // mw.syncData.mu.Unlock() - - // mw.publishHeadersRescanProgress() - - // debugInfo := &DebugInfo{ - // totalElapsedTime, - // totalTimeRemainingSeconds, - // elapsedRescanTime, - // totalTimeRemainingSeconds, - // } - // mw.publishDebugInfo(debugInfo) - - // mw.syncData.mu.RLock() - // if mw.syncData.showLogs { - // log.Infof("Syncing %d%%, %s remaining, scanning %d of %d block headers.", - // mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress, - // CalculateTotalTimeRemaining(mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds), - // mw.syncData.activeSyncData.headersRescanProgress.CurrentRescanHeight, - // mw.syncData.activeSyncData.headersRescanProgress.TotalHeadersToScan, - // ) - // } - // mw.syncData.mu.RUnlock() -} - -func (mw *MultiWallet) publishHeadersRescanProgress() { - // for _, syncProgressListener := range mw.syncProgressListeners() { - // syncProgressListener.OnHeadersRescanProgress(&mw.syncData.activeSyncData.headersRescanProgress) - // } -} - -func (mw *MultiWallet) rescanFinished(walletID int) { - // if !mw.IsSyncing() { - // // ignore if sync is not in progress - // return - // } - - // mw.syncData.mu.Lock() - // mw.syncData.activeSyncData.headersRescanProgress.WalletID = walletID - // mw.syncData.activeSyncData.headersRescanProgress.TotalTimeRemainingSeconds = 0 - // mw.syncData.activeSyncData.headersRescanProgress.TotalSyncProgress = 100 - - // // Reset these value so that address discovery would - // // not be skipped for the next wallet. - // mw.syncData.activeSyncData.addressDiscoveryProgress.addressDiscoveryStartTime = -1 - // mw.syncData.activeSyncData.addressDiscoveryProgress.totalDiscoveryTimeSpent = -1 - // mw.syncData.mu.Unlock() - - // mw.publishHeadersRescanProgress() -} - -func (mw *MultiWallet) publishDebugInfo(debugInfo *DebugInfo) { - // for _, syncProgressListener := range mw.syncProgressListeners() { - // syncProgressListener.Debug(debugInfo) - // } -} - -/** Helper functions start here */ - -func (mw *MultiWallet) estimateBlockHeadersCountAfter(lastHeaderTime int64) int32 { - // Use the difference between current time (now) and last reported block time, - // to estimate total headers to fetch. - timeDifferenceInSeconds := float64(time.Now().Unix() - lastHeaderTime) - targetTimePerBlockInSeconds := mw.chainParams.TargetTimePerBlock.Seconds() - estimatedHeadersDifference := timeDifferenceInSeconds / targetTimePerBlockInSeconds - - // return next integer value (upper limit) if estimatedHeadersDifference is a fraction - return int32(math.Ceil(estimatedHeadersDifference)) -} - -func (mw *MultiWallet) notifySyncError(err error) { - // for _, syncProgressListener := range mw.syncProgressListeners() { - // syncProgressListener.OnSyncEndedWithError(err) - // } -} - -func (mw *MultiWallet) notifySyncCanceled() { - // mw.syncData.mu.RLock() - // restartSyncRequested := mw.syncData.restartSyncRequested - // mw.syncData.mu.RUnlock() - - // for _, syncProgressListener := range mw.syncProgressListeners() { - // syncProgressListener.OnSyncCanceled(restartSyncRequested) - // } -} - -func (mw *MultiWallet) resetSyncData() { - // It's possible that sync ends or errors while address discovery is ongoing. - // If this happens, it's important to stop the address discovery process before - // resetting sync data. - // mw.stopUpdatingAddressDiscoveryProgress() - - // mw.syncData.mu.Lock() - // mw.syncData.syncing = false - // mw.syncData.synced = false - // mw.syncData.cancelSync = nil - // mw.syncData.syncCanceled = nil - // mw.syncData.activeSyncData = nil - // mw.syncData.mu.Unlock() - - // for _, wallet := range mw.wallets { - // wallet.WaitingForHeaders = true - // wallet.LockWallet() // lock wallet if previously unlocked to perform account discovery. - // } -} - -func (mw *MultiWallet) synced(walletID int, synced bool) { - - // indexTransactions := func() { - // // begin indexing transactions after sync is completed, - // // syncProgressListeners.OnSynced() will be invoked after transactions are indexed - // var txIndexing errgroup.Group - // for _, wallet := range mw.wallets { - // txIndexing.Go(wallet.IndexTransactions) - // } - - // go func() { - // err := txIndexing.Wait() - // if err != nil { - // log.Errorf("Tx Index Error: %v", err) - // } - - // for _, syncProgressListener := range mw.syncProgressListeners() { - // if synced { - // syncProgressListener.OnSyncCompleted() - // } else { - // syncProgressListener.OnSyncCanceled(false) - // } - // } - // }() - // } - - // mw.syncData.mu.RLock() - // allWalletsSynced := mw.syncData.synced - // mw.syncData.mu.RUnlock() - - // if allWalletsSynced && synced { - // indexTransactions() - // return - // } - - // wallet := mw.wallets[walletID] - // wallet.Synced = synced - // wallet.Syncing = false - // // mw.listenForTransactions(wallet.ID) - - // if !wallet.Internal().Locked() { - // wallet.LockWallet() // lock wallet if previously unlocked to perform account discovery. - // err := mw.markWalletAsDiscoveredAccounts(walletID) - // if err != nil { - // log.Error(err) - // } - // } - - // if mw.OpenedWalletsCount() == mw.SyncedWalletsCount() { - // mw.syncData.mu.Lock() - // mw.syncData.syncing = false - // mw.syncData.synced = true - // mw.syncData.mu.Unlock() - - // indexTransactions() - // } -} diff --git a/utils.go b/utils.go index 68387a7e2..5f968244a 100644 --- a/utils.go +++ b/utils.go @@ -26,7 +26,7 @@ import ( "github.com/decred/dcrd/hdkeychain/v3" "github.com/decred/dcrd/wire" "github.com/planetdecred/dcrlibwallet/internal/loader" - + "github.com/planetdecred/dcrlibwallet/wallets/dcr" ) const ( @@ -296,7 +296,7 @@ func backupFile(fileName string, suffix int) (newName string, err error) { func initWalletLoader(chainParams *chaincfg.Params, walletDataDir, walletDbDriver string) *loader.Loader { // TODO: Allow users provide values to override these defaults. - cfg := &WalletConfig{ + cfg := &dcr.WalletConfig{ GapLimit: 20, AllowHighFees: false, RelayFee: txrules.DefaultRelayFeePerKb, diff --git a/wallets/dcr/message.go b/wallets/dcr/message.go index 5d21da557..3d99e2c49 100644 --- a/wallets/dcr/message.go +++ b/wallets/dcr/message.go @@ -3,9 +3,8 @@ package dcr import ( "decred.org/dcrwallet/v2/errors" w "decred.org/dcrwallet/v2/wallet" - "github.com/decred/dcrd/txscript/v4/stdaddr" "github.com/decred/dcrd/chaincfg/v3" - + "github.com/decred/dcrd/txscript/v4/stdaddr" ) func (wallet *Wallet) SignMessage(passphrase []byte, address string, message string) ([]byte, error) { diff --git a/wallets/dcr/multiwallet_config.go b/wallets/dcr/multiwallet_config.go deleted file mode 100644 index b4b33a166..000000000 --- a/wallets/dcr/multiwallet_config.go +++ /dev/null @@ -1,152 +0,0 @@ -package dcr - -import ( - // "github.com/asdine/storm" -) - -const ( - userConfigBucketName = "user_config" - - LogLevelConfigKey = "log_level" - - SpendUnconfirmedConfigKey = "spend_unconfirmed" - CurrencyConversionConfigKey = "currency_conversion_option" - - IsStartupSecuritySetConfigKey = "startup_security_set" - StartupSecurityTypeConfigKey = "startup_security_type" - UseBiometricConfigKey = "use_biometric" - - IncomingTxNotificationsConfigKey = "tx_notification_enabled" - BeepNewBlocksConfigKey = "beep_new_blocks" - - SyncOnCellularConfigKey = "always_sync" - NetworkModeConfigKey = "network_mode" - SpvPersistentPeerAddressesConfigKey = "spv_peer_addresses" - UserAgentConfigKey = "user_agent" - - PoliteiaNotificationConfigKey = "politeia_notification" - - LastTxHashConfigKey = "last_tx_hash" - - KnownVSPsConfigKey = "known_vsps" - - TicketBuyerVSPHostConfigKey = "tb_vsp_host" - TicketBuyerWalletConfigKey = "tb_wallet_id" - TicketBuyerAccountConfigKey = "tb_account_number" - TicketBuyerATMConfigKey = "tb_amount_to_maintain" - - PassphraseTypePin int32 = 0 - PassphraseTypePass int32 = 1 -) - -type configSaveFn = func(key string, value interface{}) error -type configReadFn = func(multiwallet bool, key string, valueOut interface{}) error - -// func (mw *MultiWallet) walletConfigSetFn(walletID int) configSaveFn { -// return func(key string, value interface{}) error { -// walletUniqueKey := WalletUniqueConfigKey(walletID, key) -// return mw.db.Set(userConfigBucketName, walletUniqueKey, value) -// } -// } - -// func (mw *MultiWallet) walletConfigReadFn(walletID int) configReadFn { -// return func(multiwallet bool, key string, valueOut interface{}) error { -// if !multiwallet { -// key = WalletUniqueConfigKey(walletID, key) -// } -// return mw.db.Get(userConfigBucketName, key, valueOut) -// } -// } - -// func (mw *MultiWallet) SaveUserConfigValue(key string, value interface{}) { -// err := mw.db.Set(userConfigBucketName, key, value) -// if err != nil { -// log.Errorf("error setting config value for key: %s, error: %v", key, err) -// } -// } - -// func (mw *MultiWallet) ReadUserConfigValue(key string, valueOut interface{}) error { -// err := mw.db.Get(userConfigBucketName, key, valueOut) -// if err != nil && err != storm.ErrNotFound { -// log.Errorf("error reading config value for key: %s, error: %v", key, err) -// } -// return err -// } - -// func (mw *MultiWallet) DeleteUserConfigValueForKey(key string) { -// err := mw.db.Delete(userConfigBucketName, key) -// if err != nil { -// log.Errorf("error deleting config value for key: %s, error: %v", key, err) -// } -// } - -// func (mw *MultiWallet) ClearConfig() { -// err := mw.db.Drop(userConfigBucketName) -// if err != nil { -// log.Errorf("error deleting config bucket: %v", err) -// } -// } - -// func (mw *MultiWallet) SetBoolConfigValueForKey(key string, value bool) { -// mw.SaveUserConfigValue(key, value) -// } - -// func (mw *MultiWallet) SetDoubleConfigValueForKey(key string, value float64) { -// mw.SaveUserConfigValue(key, value) -// } - -// func (mw *MultiWallet) SetIntConfigValueForKey(key string, value int) { -// mw.SaveUserConfigValue(key, value) -// } - -// func (mw *MultiWallet) SetInt32ConfigValueForKey(key string, value int32) { -// mw.SaveUserConfigValue(key, value) -// } - -// func (mw *MultiWallet) SetLongConfigValueForKey(key string, value int64) { -// mw.SaveUserConfigValue(key, value) -// } - -// func (mw *MultiWallet) SetStringConfigValueForKey(key, value string) { -// mw.SaveUserConfigValue(key, value) -// } - -// func (mw *MultiWallet) ReadBoolConfigValueForKey(key string, defaultValue bool) (valueOut bool) { -// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { -// valueOut = defaultValue -// } -// return -// } - -// func (mw *MultiWallet) ReadDoubleConfigValueForKey(key string, defaultValue float64) (valueOut float64) { -// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { -// valueOut = defaultValue -// } -// return -// } - -// func (mw *MultiWallet) ReadIntConfigValueForKey(key string, defaultValue int) (valueOut int) { -// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { -// valueOut = defaultValue -// } -// return -// } - -// func (mw *MultiWallet) ReadInt32ConfigValueForKey(key string, defaultValue int32) (valueOut int32) { -// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { -// valueOut = defaultValue -// } -// return -// } - -// func (mw *MultiWallet) ReadLongConfigValueForKey(key string, defaultValue int64) (valueOut int64) { -// if err := mw.ReadUserConfigValue(key, &valueOut); err == storm.ErrNotFound { -// valueOut = defaultValue -// } -// return -// } - -// func (mw *MultiWallet) ReadStringConfigValueForKey(key string) (valueOut string) { -// mw.ReadUserConfigValue(key, &valueOut) -// return -// } diff --git a/wallets/dcr/politeia.go b/wallets/dcr/politeia.go new file mode 100644 index 000000000..d7c240a78 --- /dev/null +++ b/wallets/dcr/politeia.go @@ -0,0 +1,218 @@ +package dcr + +import ( + "encoding/json" + "fmt" + + "decred.org/dcrwallet/v2/errors" + "github.com/asdine/storm" + "github.com/asdine/storm/q" +) + +const ( + ProposalCategoryAll int32 = iota + 1 + ProposalCategoryPre + ProposalCategoryActive + ProposalCategoryApproved + ProposalCategoryRejected + ProposalCategoryAbandoned +) + +func (wallet *Wallet) NewPoliteia(host string) (*Politeia, error) { + p := &Politeia{ + WalletRef: wallet, // Holds a refrence to the wallet initializing Politeia. + Host: host, + Client: nil, + NotificationListeners: make(map[string]ProposalNotificationListener), + } + + return p, nil +} + +func (p *Politeia) saveLastSyncedTimestamp(lastSyncedTimestamp int64) { + p.WalletRef.SetLongConfigValueForKey(PoliteiaLastSyncedTimestampConfigKey, lastSyncedTimestamp) +} + +func (p *Politeia) getLastSyncedTimestamp() int64 { + return p.WalletRef.ReadLongConfigValueForKey(PoliteiaLastSyncedTimestampConfigKey, 0) +} + +func (p *Politeia) saveOrOverwiteProposal(proposal *Proposal) error { + var oldProposal Proposal + err := p.WalletRef.db.One("Token", proposal.Token, &oldProposal) + if err != nil && err != storm.ErrNotFound { + return errors.Errorf("error checking if proposal was already indexed: %s", err.Error()) + } + + if oldProposal.Token != "" { + // delete old record before saving new (if it exists) + p.WalletRef.db.DeleteStruct(oldProposal) + } + + return p.WalletRef.db.Save(proposal) +} + +// GetProposalsRaw fetches and returns a proposals from the db +func (p *Politeia) GetProposalsRaw(category int32, offset, limit int32, newestFirst bool) ([]Proposal, error) { + return p.getProposalsRaw(category, offset, limit, newestFirst, false) +} + +func (p *Politeia) getProposalsRaw(category int32, offset, limit int32, newestFirst bool, skipAbandoned bool) ([]Proposal, error) { + + var query storm.Query + switch category { + case ProposalCategoryAll: + + if skipAbandoned { + query = p.WalletRef.db.Select( + q.Not(q.Eq("Category", ProposalCategoryAbandoned)), + ) + } else { + query = p.WalletRef.db.Select( + q.True(), + ) + } + default: + query = p.WalletRef.db.Select( + q.Eq("Category", category), + ) + } + + if offset > 0 { + query = query.Skip(int(offset)) + } + + if limit > 0 { + query = query.Limit(int(limit)) + } + + if newestFirst { + query = query.OrderBy("PublishedAt").Reverse() + } else { + query = query.OrderBy("PublishedAt") + } + + var proposals []Proposal + err := query.Find(&proposals) + if err != nil && err != storm.ErrNotFound { + return nil, fmt.Errorf("error fetching proposals: %s", err.Error()) + } + + return proposals, nil +} + +// GetProposals returns the result of GetProposalsRaw as a JSON string +func (p *Politeia) GetProposals(category int32, offset, limit int32, newestFirst bool) (string, error) { + + result, err := p.GetProposalsRaw(category, offset, limit, newestFirst) + if err != nil { + return "", err + } + + if len(result) == 0 { + return "[]", nil + } + + response, err := json.Marshal(result) + if err != nil { + return "", fmt.Errorf("error marshalling result: %s", err.Error()) + } + + return string(response), nil +} + +// GetProposalRaw fetches and returns a single proposal specified by it's censorship record token +func (p *Politeia) GetProposalRaw(censorshipToken string) (*Proposal, error) { + var proposal Proposal + err := p.WalletRef.db.One("Token", censorshipToken, &proposal) + if err != nil { + return nil, err + } + + return &proposal, nil +} + +// GetProposal returns the result of GetProposalRaw as a JSON string +func (p *Politeia) GetProposal(censorshipToken string) (string, error) { + return marshalResult(p.GetProposalRaw(censorshipToken)) +} + +// GetProposalByIDRaw fetches and returns a single proposal specified by it's ID +func (p *Politeia) GetProposalByIDRaw(proposalID int) (*Proposal, error) { + var proposal Proposal + err := p.WalletRef.db.One("ID", proposalID, &proposal) + if err != nil { + return nil, err + } + + return &proposal, nil +} + +// GetProposalByID returns the result of GetProposalByIDRaw as a JSON string +func (p *Politeia) GetProposalByID(proposalID int) (string, error) { + return marshalResult(p.GetProposalByIDRaw(proposalID)) +} + +// Count returns the number of proposals of a specified category +func (p *Politeia) Count(category int32) (int32, error) { + var matcher q.Matcher + + if category == ProposalCategoryAll { + matcher = q.True() + } else { + matcher = q.Eq("Category", category) + } + + count, err := p.WalletRef.db.Select(matcher).Count(&Proposal{}) + if err != nil { + return 0, err + } + + return int32(count), nil +} + +func (p *Politeia) Overview() (*ProposalOverview, error) { + + pre, err := p.Count(ProposalCategoryPre) + if err != nil { + return nil, err + } + + active, err := p.Count(ProposalCategoryActive) + if err != nil { + return nil, err + } + + approved, err := p.Count(ProposalCategoryApproved) + if err != nil { + return nil, err + } + + rejected, err := p.Count(ProposalCategoryRejected) + if err != nil { + return nil, err + } + + abandoned, err := p.Count(ProposalCategoryApproved) + if err != nil { + return nil, err + } + + return &ProposalOverview{ + All: pre + active + approved + rejected + abandoned, + Discussion: pre, + Voting: active, + Approved: approved, + Rejected: rejected, + Abandoned: abandoned, + }, nil +} + +func (p *Politeia) ClearSavedProposals() error { + err := p.WalletRef.db.Drop(&Proposal{}) + if err != nil { + return translateError(err) + } + + return p.WalletRef.db.Init(&Proposal{}) +} diff --git a/wallets/dcr/politeia_sync.go b/wallets/dcr/politeia_sync.go index 209103afb..1d0450275 100644 --- a/wallets/dcr/politeia_sync.go +++ b/wallets/dcr/politeia_sync.go @@ -4,14 +4,12 @@ import ( "decred.org/dcrwallet/v2/errors" "encoding/hex" "encoding/json" - // "errors" "fmt" "reflect" "strconv" "time" "github.com/asdine/storm" - "github.com/asdine/storm/q" tkv1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1" www "github.com/decred/politeia/politeiawww/api/www/v1" @@ -634,191 +632,3 @@ func getUniqueTokens(tokenInventory, savedTokens []string) ([]string, []string) return diff, savedTokens } - -func (p *Politeia) saveLastSyncedTimestamp(lastSyncedTimestamp int64) { - p.WalletRef.SetLongConfigValueForKey(PoliteiaLastSyncedTimestampConfigKey, lastSyncedTimestamp) -} - -func (p *Politeia) getLastSyncedTimestamp() int64 { - return p.WalletRef.ReadLongConfigValueForKey(PoliteiaLastSyncedTimestampConfigKey, 0) -} - -func (p *Politeia) saveOrOverwiteProposal(proposal *Proposal) error { - var oldProposal Proposal - err := p.WalletRef.db.One("Token", proposal.Token, &oldProposal) - if err != nil && err != storm.ErrNotFound { - return errors.Errorf("error checking if proposal was already indexed: %s", err.Error()) - } - - if oldProposal.Token != "" { - // delete old record before saving new (if it exists) - p.WalletRef.db.DeleteStruct(oldProposal) - } - - return p.WalletRef.db.Save(proposal) -} - -// GetProposalsRaw fetches and returns a proposals from the db -func (p *Politeia) GetProposalsRaw(category int32, offset, limit int32, newestFirst bool) ([]Proposal, error) { - return p.getProposalsRaw(category, offset, limit, newestFirst, false) -} - -func (p *Politeia) getProposalsRaw(category int32, offset, limit int32, newestFirst bool, skipAbandoned bool) ([]Proposal, error) { - - var query storm.Query - switch category { - case ProposalCategoryAll: - - if skipAbandoned { - query = p.WalletRef.db.Select( - q.Not(q.Eq("Category", ProposalCategoryAbandoned)), - ) - } else { - query = p.WalletRef.db.Select( - q.True(), - ) - } - default: - query = p.WalletRef.db.Select( - q.Eq("Category", category), - ) - } - - if offset > 0 { - query = query.Skip(int(offset)) - } - - if limit > 0 { - query = query.Limit(int(limit)) - } - - if newestFirst { - query = query.OrderBy("PublishedAt").Reverse() - } else { - query = query.OrderBy("PublishedAt") - } - - var proposals []Proposal - err := query.Find(&proposals) - if err != nil && err != storm.ErrNotFound { - return nil, fmt.Errorf("error fetching proposals: %s", err.Error()) - } - - return proposals, nil -} - -// GetProposals returns the result of GetProposalsRaw as a JSON string -func (p *Politeia) GetProposals(category int32, offset, limit int32, newestFirst bool) (string, error) { - - result, err := p.GetProposalsRaw(category, offset, limit, newestFirst) - if err != nil { - return "", err - } - - if len(result) == 0 { - return "[]", nil - } - - response, err := json.Marshal(result) - if err != nil { - return "", fmt.Errorf("error marshalling result: %s", err.Error()) - } - - return string(response), nil -} - -// GetProposalRaw fetches and returns a single proposal specified by it's censorship record token -func (p *Politeia) GetProposalRaw(censorshipToken string) (*Proposal, error) { - var proposal Proposal - err := p.WalletRef.db.One("Token", censorshipToken, &proposal) - if err != nil { - return nil, err - } - - return &proposal, nil -} - -// GetProposal returns the result of GetProposalRaw as a JSON string -func (p *Politeia) GetProposal(censorshipToken string) (string, error) { - return marshalResult(p.GetProposalRaw(censorshipToken)) -} - -// GetProposalByIDRaw fetches and returns a single proposal specified by it's ID -func (p *Politeia) GetProposalByIDRaw(proposalID int) (*Proposal, error) { - var proposal Proposal - err := p.WalletRef.db.One("ID", proposalID, &proposal) - if err != nil { - return nil, err - } - - return &proposal, nil -} - -// GetProposalByID returns the result of GetProposalByIDRaw as a JSON string -func (p *Politeia) GetProposalByID(proposalID int) (string, error) { - return marshalResult(p.GetProposalByIDRaw(proposalID)) -} - -// Count returns the number of proposals of a specified category -func (p *Politeia) Count(category int32) (int32, error) { - var matcher q.Matcher - - if category == ProposalCategoryAll { - matcher = q.True() - } else { - matcher = q.Eq("Category", category) - } - - count, err := p.WalletRef.db.Select(matcher).Count(&Proposal{}) - if err != nil { - return 0, err - } - - return int32(count), nil -} - -func (p *Politeia) Overview() (*ProposalOverview, error) { - - pre, err := p.Count(ProposalCategoryPre) - if err != nil { - return nil, err - } - - active, err := p.Count(ProposalCategoryActive) - if err != nil { - return nil, err - } - - approved, err := p.Count(ProposalCategoryApproved) - if err != nil { - return nil, err - } - - rejected, err := p.Count(ProposalCategoryRejected) - if err != nil { - return nil, err - } - - abandoned, err := p.Count(ProposalCategoryApproved) - if err != nil { - return nil, err - } - - return &ProposalOverview{ - All: pre + active + approved + rejected + abandoned, - Discussion: pre, - Voting: active, - Approved: approved, - Rejected: rejected, - Abandoned: abandoned, - }, nil -} - -func (p *Politeia) ClearSavedProposals() error { - err := p.WalletRef.db.Drop(&Proposal{}) - if err != nil { - return translateError(err) - } - - return p.WalletRef.db.Init(&Proposal{}) -} diff --git a/wallets/dcr/sync.go b/wallets/dcr/sync.go index 9b6c0a903..f8b0604f8 100644 --- a/wallets/dcr/sync.go +++ b/wallets/dcr/sync.go @@ -435,14 +435,14 @@ func (wallet *Wallet) GetLowestBlock() *BlockInfo { var lowestBlock int32 = -1 var blockInfo *BlockInfo // for _, wallet := range wallet.wallets { - if !wallet.WalletOpened() { - return nil - } - walletBestBLock := wallet.GetBestBlock() - if walletBestBLock < lowestBlock || lowestBlock == -1 { - lowestBlock = walletBestBLock - blockInfo = &BlockInfo{Height: lowestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} - } + if !wallet.WalletOpened() { + return nil + } + walletBestBLock := wallet.GetBestBlock() + if walletBestBLock < lowestBlock || lowestBlock == -1 { + lowestBlock = walletBestBLock + blockInfo = &BlockInfo{Height: lowestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} + } // } return blockInfo diff --git a/wallets/dcr/txandblocknotifications.go b/wallets/dcr/txandblocknotifications.go index b11ef52b2..89a17fafa 100644 --- a/wallets/dcr/txandblocknotifications.go +++ b/wallets/dcr/txandblocknotifications.go @@ -1,8 +1,8 @@ package dcr import ( - "encoding/json" "decred.org/dcrwallet/v2/errors" + "encoding/json" ) func (wallet *Wallet) listenForTransactions() { diff --git a/wallets/dcr/types.go b/wallets/dcr/types.go index 1beb01436..6485281fc 100644 --- a/wallets/dcr/types.go +++ b/wallets/dcr/types.go @@ -15,15 +15,6 @@ import ( "github.com/planetdecred/dcrlibwallet/internal/vsp" ) -const ( - ProposalCategoryAll int32 = iota + 1 - ProposalCategoryPre - ProposalCategoryActive - ProposalCategoryApproved - ProposalCategoryRejected - ProposalCategoryAbandoned -) - // WalletConfig defines options for configuring wallet behaviour. // This is a subset of the config used by dcrwallet. type WalletConfig struct { diff --git a/wallets/dcr/utils.go b/wallets/dcr/utils.go index 0d21ac8ba..3c3656970 100644 --- a/wallets/dcr/utils.go +++ b/wallets/dcr/utils.go @@ -501,7 +501,6 @@ func HttpGet(url string, respObj interface{}) (*http.Response, []byte, error) { return resp, respBytes, err } - func marshalResult(result interface{}, err error) (string, error) { if err != nil { @@ -515,4 +514,3 @@ func marshalResult(result interface{}, err error) (string, error) { return string(response), nil } - diff --git a/wallets/dcr/wallet.go b/wallets/dcr/wallet.go index c4d294a2b..4b354a954 100644 --- a/wallets/dcr/wallet.go +++ b/wallets/dcr/wallet.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "sync" "time" @@ -23,7 +24,7 @@ type Wallet struct { ID int `storm:"id,increment"` Name string `storm:"unique"` CreatedAt time.Time `storm:"index"` - DbDriver string + dbDriver string rootDir string db *storm.DB @@ -67,7 +68,6 @@ type Wallet struct { accountMixerNotificationListener map[string]AccountMixerNotificationListener txAndBlockNotificationListeners map[string]TxAndBlockNotificationListener blocksRescanProgressListener BlocksRescanProgressListener - } // prepare gets a wallet ready for use by opening the transactions index database @@ -94,8 +94,12 @@ func (wallet *Wallet) Prepare(rootDir string, chainParams *chaincfg.Params, return err } + wallet.syncData = &SyncData{ + SyncProgressListeners: make(map[string]SyncProgressListener), + } + // init loader - wallet.loader = initWalletLoader(wallet.chainParams, wallet.DataDir, wallet.DbDriver) + wallet.loader = initWalletLoader(wallet.chainParams, wallet.DataDir, wallet.dbDriver) // init cancelFuncs slice to hold cancel functions for long running // operations and start go routine to listen for shutdown signal @@ -158,6 +162,35 @@ func (wallet *Wallet) WalletExists() (bool, error) { return wallet.loader.WalletExists() } +func (wallet *Wallet) CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32) (*Wallet, error) { + seed, err := GenerateSeed() + if err != nil { + return nil, err + } + + encryptedSeed, err := encryptWalletSeed([]byte(privatePassphrase), seed) + if err != nil { + return nil, err + } + + wal := &Wallet{ + Name: walletName, + CreatedAt: time.Now(), + EncryptedSeed: encryptedSeed, + PrivatePassphraseType: privatePassphraseType, + HasDiscoveredAccounts: true, + } + + return wallet.saveNewWallet(func() error { + err := wallet.Prepare(wallet.rootDir, wallet.chainParams, wallet.walletConfigSetFn(wal.ID), wallet.walletConfigReadFn(wal.ID)) + if err != nil { + return err + } + + return wallet.CreateWallet(privatePassphrase, seed) + }) +} + func (wallet *Wallet) CreateWallet(privatePassphrase, seedMnemonic string) error { log.Info("Creating Wallet") if len(seedMnemonic) == 0 { @@ -182,7 +215,24 @@ func (wallet *Wallet) CreateWallet(privatePassphrase, seedMnemonic string) error return nil } -func (wallet *Wallet) CreateWatchingOnlyWallet(extendedPublicKey string) error { +func (wallet *Wallet) CreateWatchOnlyWallet(walletName, extendedPublicKey string) (*Wallet, error) { + wal := &Wallet{ + Name: walletName, + IsRestored: true, + HasDiscoveredAccounts: true, + } + + return wallet.saveNewWallet(func() error { + err := wallet.Prepare(wallet.rootDir, wallet.chainParams, wallet.walletConfigSetFn(wal.ID), wallet.walletConfigReadFn(wal.ID)) + if err != nil { + return err + } + + return wallet.createWatchingOnlyWallet(extendedPublicKey) + }) +} + +func (wallet *Wallet) createWatchingOnlyWallet(extendedPublicKey string) error { pubPass := []byte(w.InsecurePubPassphrase) _, err := wallet.loader.CreateWatchingOnlyWallet(wallet.ShutdownContext(), extendedPublicKey, pubPass) @@ -195,6 +245,192 @@ func (wallet *Wallet) CreateWatchingOnlyWallet(extendedPublicKey string) error { return nil } +func (wallet *Wallet) RenameWallet(newName string) error { + if strings.HasPrefix(newName, "wallet-") { + return errors.E(ErrReservedWalletName) + } + + if exists, err := wallet.WalletNameExists(newName); err != nil { + return translateError(err) + } else if exists { + return errors.New(ErrExist) + } + + wallet.Name = newName + return wallet.db.Save(wallet) // update WalletName field +} + +func (wallet *Wallet) RestoreWallet(walletName, seedMnemonic, privatePassphrase string, privatePassphraseType int32) (*Wallet, error) { + + wal := &Wallet{ + Name: walletName, + PrivatePassphraseType: privatePassphraseType, + IsRestored: true, + HasDiscoveredAccounts: false, + } + + return wallet.saveNewWallet(func() error { + err := wallet.Prepare(wallet.rootDir, wallet.chainParams, wallet.walletConfigSetFn(wal.ID), wallet.walletConfigReadFn(wal.ID)) + if err != nil { + return err + } + + return wallet.CreateWallet(privatePassphrase, seedMnemonic) + }) +} + +func (wallet *Wallet) DeleteWallet(privPass []byte) error { + + if wallet.IsConnectedToDecredNetwork() { + wallet.CancelSync() + defer func() { + // if wallet.OpenedWalletsCount() > 0 { + wallet.SpvSync() + // } + }() + } + + err := wallet.deleteWallet(privPass) + if err != nil { + return translateError(err) + } + + err = wallet.db.DeleteStruct(wallet) + if err != nil { + return translateError(err) + } + + // delete(wallet.ID) + + return nil +} + +func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { + exists, err := wallet.WalletNameExists(wallet.Name) + if err != nil { + return nil, err + } else if exists { + return nil, errors.New(ErrExist) + } + + if wallet.IsConnectedToDecredNetwork() { + wallet.CancelSync() + defer wallet.SpvSync() + } + // Perform database save operations in batch transaction + // for automatic rollback if error occurs at any point. + err = wallet.batchDbTransaction(func(db storm.Node) error { + // saving struct to update ID property with an auto-generated value + err := db.Save(wallet) + if err != nil { + return err + } + + walletDataDir := filepath.Join(wallet.rootDir, strconv.Itoa(wallet.ID)) + + dirExists, err := fileExists(walletDataDir) + if err != nil { + return err + } else if dirExists { + newDirName, err := backupFile(walletDataDir, 1) + if err != nil { + return err + } + + log.Infof("Undocumented file at %s moved to %s", walletDataDir, newDirName) + } + + os.MkdirAll(walletDataDir, os.ModePerm) // create wallet dir + + if wallet.Name == "" { + wallet.Name = "wallet-" + strconv.Itoa(wallet.ID) // wallet-# + } + wallet.DataDir = walletDataDir + wallet.dbDriver = wallet.dbDriver + + err = db.Save(wallet) // update database with complete wallet information + if err != nil { + return err + } + + return setupWallet() + }) + + if err != nil { + return nil, translateError(err) + } + + // wallet.wallets[wallet.ID] = wallet + + return wallet, nil +} + +func (wallet *Wallet) LinkExistingWallet(walletName, walletDataDir, originalPubPass string, privatePassphraseType int32) (*Wallet, error) { + // check if `walletDataDir` contains wallet.db + if !WalletExistsAt(walletDataDir) { + return nil, errors.New(ErrNotExist) + } + + ctx, _ := wallet.contextWithShutdownCancel() + + // verify the public passphrase for the wallet being linked before proceeding + if err := wallet.loadWalletTemporarily(ctx, walletDataDir, originalPubPass, nil); err != nil { + return nil, err + } + + wal := &Wallet{ + Name: walletName, + PrivatePassphraseType: privatePassphraseType, + IsRestored: true, + HasDiscoveredAccounts: false, // assume that account discovery hasn't been done + } + + return wallet.saveNewWallet(func() error { + // move wallet.db and tx.db files to newly created dir for the wallet + currentWalletDbFilePath := filepath.Join(walletDataDir, walletDbName) + newWalletDbFilePath := filepath.Join(wal.DataDir, walletDbName) + if err := moveFile(currentWalletDbFilePath, newWalletDbFilePath); err != nil { + return err + } + + currentTxDbFilePath := filepath.Join(walletDataDir, walletdata.OldDbName) + newTxDbFilePath := filepath.Join(wallet.DataDir, walletdata.DbName) + if err := moveFile(currentTxDbFilePath, newTxDbFilePath); err != nil { + return err + } + + // prepare the wallet for use and open it + err := (func() error { + err := wallet.Prepare(wallet.rootDir, wallet.chainParams, wallet.walletConfigSetFn(wallet.ID), wallet.walletConfigReadFn(wallet.ID)) + if err != nil { + return err + } + + if originalPubPass == "" || originalPubPass == w.InsecurePubPassphrase { + return wallet.OpenWallet() + } + + err = wallet.loadWalletTemporarily(ctx, wallet.DataDir, originalPubPass, func(tempWallet *w.Wallet) error { + return tempWallet.ChangePublicPassphrase(ctx, []byte(originalPubPass), []byte(w.InsecurePubPassphrase)) + }) + if err != nil { + return err + } + + return wallet.OpenWallet() + })() + + // restore db files to their original location if there was an error + // in the wallet setup process above + if err != nil { + moveFile(newWalletDbFilePath, currentWalletDbFilePath) + moveFile(newTxDbFilePath, currentTxDbFilePath) + } + + return err + }) +} + func (wallet *Wallet) IsWatchingOnlyWallet() bool { if w, ok := wallet.loader.LoadedWallet(); ok { return w.WatchingOnly() @@ -220,6 +456,10 @@ func (wallet *Wallet) WalletOpened() bool { } func (wallet *Wallet) UnlockWallet(privPass []byte) error { + return wallet.unlockWallet(privPass) +} + +func (wallet *Wallet) unlockWallet(privPass []byte) error { loadedWallet, ok := wallet.loader.LoadedWallet() if !ok { return fmt.Errorf("wallet has not been loaded") @@ -249,7 +489,49 @@ func (wallet *Wallet) IsLocked() bool { return wallet.Internal().Locked() } -func (wallet *Wallet) ChangePrivatePassphrase(oldPass []byte, newPass []byte) error { +// ChangePrivatePassphraseForWallet attempts to change the wallet's passphrase and re-encrypts the seed with the new passphrase. +func (wallet *Wallet) ChangePrivatePassphraseForWallet(oldPrivatePassphrase, newPrivatePassphrase []byte, privatePassphraseType int32) error { + if privatePassphraseType != PassphraseTypePin && privatePassphraseType != PassphraseTypePass { + return errors.New(ErrInvalid) + } + encryptedSeed := wallet.EncryptedSeed + if encryptedSeed != nil { + decryptedSeed, err := decryptWalletSeed(oldPrivatePassphrase, encryptedSeed) + if err != nil { + return err + } + + encryptedSeed, err = encryptWalletSeed(newPrivatePassphrase, decryptedSeed) + if err != nil { + return err + } + } + + err := wallet.changePrivatePassphrase(oldPrivatePassphrase, newPrivatePassphrase) + if err != nil { + return translateError(err) + } + + wallet.EncryptedSeed = encryptedSeed + wallet.PrivatePassphraseType = privatePassphraseType + err = wallet.db.Save(wallet) + if err != nil { + log.Errorf("error saving wallet-[%d] to database after passphrase change: %v", wallet.ID, err) + + err2 := wallet.changePrivatePassphrase(newPrivatePassphrase, oldPrivatePassphrase) + if err2 != nil { + log.Errorf("error undoing wallet passphrase change: %v", err2) + log.Errorf("error wallet passphrase was changed but passphrase type and newly encrypted seed could not be saved: %v", err) + return errors.New(ErrSavingWallet) + } + + return errors.New(ErrChangingPassphrase) + } + + return nil +} + +func (wallet *Wallet) changePrivatePassphrase(oldPass []byte, newPass []byte) error { defer func() { for i := range oldPass { oldPass[i] = 0 @@ -267,7 +549,7 @@ func (wallet *Wallet) ChangePrivatePassphrase(oldPass []byte, newPass []byte) er return nil } -func (wallet *Wallet) DeleteWallet(privatePassphrase []byte) error { +func (wallet *Wallet) deleteWallet(privatePassphrase []byte) error { defer func() { for i := range privatePassphrase { privatePassphrase[i] = 0 @@ -331,3 +613,18 @@ func (wallet *Wallet) AccountXPubMatches(account uint32, legacyXPub, slip044XPub return acctXPub == slip044XPub, nil } } + +// VerifySeedForWallet compares seedMnemonic with the decrypted wallet.EncryptedSeed and clears wallet.EncryptedSeed if they match. +func (wallet *Wallet) VerifySeedForWallet(seedMnemonic string, privpass []byte) (bool, error) { + decryptedSeed, err := decryptWalletSeed(privpass, wallet.EncryptedSeed) + if err != nil { + return false, err + } + + if decryptedSeed == seedMnemonic { + wallet.EncryptedSeed = nil + return true, translateError(wallet.db.Save(wallet)) + } + + return false, errors.New(ErrInvalid) +} \ No newline at end of file diff --git a/wallets/dcr/wallet_config.go b/wallets/dcr/wallet_config.go index 29636c2cc..a185bcc25 100644 --- a/wallets/dcr/wallet_config.go +++ b/wallets/dcr/wallet_config.go @@ -10,8 +10,60 @@ const ( AccountMixerMixedAccount = "account_mixer_mixed_account" AccountMixerUnmixedAccount = "account_mixer_unmixed_account" AccountMixerMixTxChange = "account_mixer_mix_tx_change" + + userConfigBucketName = "user_config" + + LogLevelConfigKey = "log_level" + + SpendUnconfirmedConfigKey = "spend_unconfirmed" + CurrencyConversionConfigKey = "currency_conversion_option" + + IsStartupSecuritySetConfigKey = "startup_security_set" + StartupSecurityTypeConfigKey = "startup_security_type" + UseBiometricConfigKey = "use_biometric" + + IncomingTxNotificationsConfigKey = "tx_notification_enabled" + BeepNewBlocksConfigKey = "beep_new_blocks" + + SyncOnCellularConfigKey = "always_sync" + NetworkModeConfigKey = "network_mode" + SpvPersistentPeerAddressesConfigKey = "spv_peer_addresses" + UserAgentConfigKey = "user_agent" + + PoliteiaNotificationConfigKey = "politeia_notification" + + LastTxHashConfigKey = "last_tx_hash" + + KnownVSPsConfigKey = "known_vsps" + + TicketBuyerVSPHostConfigKey = "tb_vsp_host" + TicketBuyerWalletConfigKey = "tb_wallet_id" + TicketBuyerAccountConfigKey = "tb_account_number" + TicketBuyerATMConfigKey = "tb_amount_to_maintain" + + PassphraseTypePin int32 = 0 + PassphraseTypePass int32 = 1 ) +type configSaveFn = func(key string, value interface{}) error +type configReadFn = func(multiwallet bool, key string, valueOut interface{}) error + +func (wallet *Wallet) walletConfigSetFn(walletID int) configSaveFn { + return func(key string, value interface{}) error { + walletUniqueKey := WalletUniqueConfigKey(walletID, key) + return wallet.db.Set(userConfigBucketName, walletUniqueKey, value) + } +} + +func (wallet *Wallet) walletConfigReadFn(walletID int) configReadFn { + return func(multiwallet bool, key string, valueOut interface{}) error { + if !multiwallet { + key = WalletUniqueConfigKey(walletID, key) + } + return wallet.db.Get(userConfigBucketName, key, valueOut) + } +} + func (wallet *Wallet) SaveUserConfigValue(key string, value interface{}) { if wallet.setUserConfigValue == nil { log.Errorf("call wallet.prepare before setting wallet config values") diff --git a/wallets/dcr/wallet_utils.go b/wallets/dcr/wallet_utils.go index 5a493e834..2ec5deb8d 100644 --- a/wallets/dcr/wallet_utils.go +++ b/wallets/dcr/wallet_utils.go @@ -1,7 +1,17 @@ package dcr import ( + "context" + "decred.org/dcrwallet/v2/errors" + "github.com/asdine/storm" + "github.com/kevinburke/nacl" + "github.com/kevinburke/nacl/secretbox" + "golang.org/x/crypto/scrypt" + + w "decred.org/dcrwallet/v2/wallet" + + "strings" ) func (wallet *Wallet) markWalletAsDiscoveredAccounts() error { @@ -18,3 +28,103 @@ func (wallet *Wallet) markWalletAsDiscoveredAccounts() error { return nil } + +func (wallet *Wallet) batchDbTransaction(dbOp func(node storm.Node) error) (err error) { + dbTx, err := wallet.db.Begin(true) + if err != nil { + return err + } + + // Commit or rollback the transaction after f returns or panics. Do not + // recover from the panic to keep the original stack trace intact. + panicked := true + defer func() { + if panicked || err != nil { + dbTx.Rollback() + return + } + + err = dbTx.Commit() + }() + + err = dbOp(dbTx) + panicked = false + return err +} + +func (wallet *Wallet) WalletNameExists(walletName string) (bool, error) { + if strings.HasPrefix(walletName, "wallet-") { + return false, errors.E(ErrReservedWalletName) + } + + err := wallet.db.One("Name", walletName, &Wallet{}) + if err == nil { + return true, nil + } else if err != storm.ErrNotFound { + return false, err + } + + return false, nil +} + +// naclLoadFromPass derives a nacl.Key from pass using scrypt.Key. +func naclLoadFromPass(pass []byte) (nacl.Key, error) { + + const N, r, p = 1 << 15, 8, 1 + + hash, err := scrypt.Key(pass, nil, N, r, p, 32) + if err != nil { + return nil, err + } + return nacl.Load(EncodeHex(hash)) +} + +// encryptWalletSeed encrypts the seed with secretbox.EasySeal using pass. +func encryptWalletSeed(pass []byte, seed string) ([]byte, error) { + key, err := naclLoadFromPass(pass) + if err != nil { + return nil, err + } + return secretbox.EasySeal([]byte(seed), key), nil +} + +// decryptWalletSeed decrypts the encryptedSeed with secretbox.EasyOpen using pass. +func decryptWalletSeed(pass []byte, encryptedSeed []byte) (string, error) { + key, err := naclLoadFromPass(pass) + if err != nil { + return "", err + } + + decryptedSeed, err := secretbox.EasyOpen(encryptedSeed, key) + if err != nil { + return "", errors.New(ErrInvalidPassphrase) + } + + return string(decryptedSeed), nil +} + +func (wallet *Wallet) loadWalletTemporarily(ctx context.Context, walletDataDir, walletPublicPass string, + onLoaded func(*w.Wallet) error) error { + + if walletPublicPass == "" { + walletPublicPass = w.InsecurePubPassphrase + } + + // initialize the wallet loader + walletLoader := initWalletLoader(wallet.chainParams, walletDataDir, wallet.dbDriver) + + // open the wallet to get ready for temporary use + wal, err := walletLoader.OpenExistingWallet(ctx, []byte(walletPublicPass)) + if err != nil { + return translateError(err) + } + + // unload wallet after temporary use + defer walletLoader.UnloadWallet() + + if onLoaded != nil { + return onLoaded(wal) + } + + return nil +} From bfabe3db8f1253ba8cd4460aef8d6573eb8e7f11 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:35:08 +0100 Subject: [PATCH 04/16] fix errors in dcr/sync.go --- wallets/dcr/sync.go | 75 +++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/wallets/dcr/sync.go b/wallets/dcr/sync.go index f8b0604f8..1b8f676e7 100644 --- a/wallets/dcr/sync.go +++ b/wallets/dcr/sync.go @@ -325,17 +325,17 @@ func (wallet *Wallet) IsConnectedToDecredNetwork() bool { return wallet.syncData.syncing || wallet.syncData.synced } -// func (wallet *Wallet) IsSynced() bool { -// wallet.syncData.mu.RLock() -// defer wallet.syncData.mu.RUnlock() -// return wallet.syncData.synced -// } - -// func (wallet *Wallet) IsSyncing() bool { -// wallet.syncData.mu.RLock() -// defer wallet.syncData.mu.RUnlock() -// return wallet.syncData.syncing -// } +func (wallet *Wallet) isSynced() bool { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + return wallet.syncData.synced +} + +func (wallet *Wallet) isSyncing() bool { + wallet.syncData.mu.RLock() + defer wallet.syncData.mu.RUnlock() + return wallet.syncData.syncing +} func (wallet *Wallet) CurrentSyncStage() int32 { wallet.syncData.mu.RLock() @@ -413,42 +413,38 @@ func (wallet *Wallet) PeerInfo() (string, error) { return string(result), nil } -// func (wallet *Wallet) GetBestBlock() *BlockInfo { -// var bestBlock int32 = -1 -// var blockInfo *BlockInfo -// for _, wallet := range wallet.wallets { -// if !wallet.WalletOpened() { -// continue -// } +func (wallet *Wallet) GetBestBlock() *BlockInfo { + var bestBlock int32 = -1 + var blockInfo *BlockInfo + if !wallet.WalletOpened() { + return nil + } -// walletBestBLock := wallet.GetBestBlock() -// if walletBestBLock > bestBlock || bestBlock == -1 { -// bestBlock = walletBestBLock -// blockInfo = &BlockInfo{Height: bestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} -// } -// } + walletBestBLock := wallet.getBestBlock() + if walletBestBLock > bestBlock || bestBlock == -1 { + bestBlock = walletBestBLock + blockInfo = &BlockInfo{Height: bestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} + } -// return blockInfo -// } + return blockInfo +} func (wallet *Wallet) GetLowestBlock() *BlockInfo { var lowestBlock int32 = -1 var blockInfo *BlockInfo - // for _, wallet := range wallet.wallets { if !wallet.WalletOpened() { return nil } - walletBestBLock := wallet.GetBestBlock() + walletBestBLock := wallet.getBestBlock() if walletBestBLock < lowestBlock || lowestBlock == -1 { lowestBlock = walletBestBLock blockInfo = &BlockInfo{Height: lowestBlock, Timestamp: wallet.GetBestBlockTimeStamp()} } - // } return blockInfo } -func (wallet *Wallet) GetBestBlock() int32 { +func (wallet *Wallet) getBestBlock() int32 { if wallet.Internal() == nil { // This method is sometimes called after a wallet is deleted and causes crash. log.Error("Attempting to read best block height without a loaded wallet.") @@ -477,13 +473,12 @@ func (wallet *Wallet) GetBestBlockTimeStamp() int64 { return info.Timestamp } -// func (wallet *Wallet) GetLowestBlockTimestamp() int64 { -// var timestamp int64 = -1 -// for _, wallet := range wallet.wallets { -// bestBlockTimestamp := wallet.GetBestBlockTimeStamp() -// if bestBlockTimestamp < timestamp || timestamp == -1 { -// timestamp = bestBlockTimestamp -// } -// } -// return timestamp -// } +func (wallet *Wallet) GetLowestBlockTimestamp() int64 { + var timestamp int64 = -1 + bestBlockTimestamp := wallet.GetBestBlockTimeStamp() + if bestBlockTimestamp < timestamp || timestamp == -1 { + timestamp = bestBlockTimestamp + } + + return timestamp +} From 2639ab03f0265c8bff0b788d0967544a27d2a829 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:37:34 +0100 Subject: [PATCH 05/16] fix bug where block info was being returned instead of int --- wallets/dcr/accounts.go | 2 +- wallets/dcr/rescan.go | 2 +- wallets/dcr/syncnotification.go | 6 +++--- wallets/dcr/transactions.go | 6 +++--- wallets/dcr/txindex.go | 2 +- wallets/dcr/wallet.go | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/wallets/dcr/accounts.go b/wallets/dcr/accounts.go index 05ad1d851..129dc19d1 100644 --- a/wallets/dcr/accounts.go +++ b/wallets/dcr/accounts.go @@ -160,7 +160,7 @@ func (wallet *Wallet) UnspentOutputs(account int32) ([]*UnspentOutput, error) { var confirmations int32 inputBlockHeight := int32(input.BlockHeight) if inputBlockHeight != -1 { - confirmations = wallet.GetBestBlock() - inputBlockHeight + 1 + confirmations = wallet.getBestBlock() - inputBlockHeight + 1 } unspentOutputs[i] = &UnspentOutput{ diff --git a/wallets/dcr/rescan.go b/wallets/dcr/rescan.go index 426dbb887..659c36919 100644 --- a/wallets/dcr/rescan.go +++ b/wallets/dcr/rescan.go @@ -60,7 +60,7 @@ func (wallet *Wallet) RescanBlocksFromHeight(walletID int, startHeight int32) er rescanProgressReport := &HeadersRescanProgressReport{ CurrentRescanHeight: p.ScannedThrough, - TotalHeadersToScan: wallet.GetBestBlock(), + TotalHeadersToScan: wallet.getBestBlock(), WalletID: walletID, } diff --git a/wallets/dcr/syncnotification.go b/wallets/dcr/syncnotification.go index 4bc96780d..e06b36cdf 100644 --- a/wallets/dcr/syncnotification.go +++ b/wallets/dcr/syncnotification.go @@ -77,7 +77,7 @@ func (w *Wallet) fetchCFiltersProgress(walletID int, startCFiltersHeight, endCFi // wallet := w.WalletWithID(walletID) w.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount += endCFiltersHeight - startCFiltersHeight - totalCFiltersToFetch := w.GetBestBlock() - w.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight + totalCFiltersToFetch := w.getBestBlock() - w.syncData.activeSyncData.cfiltersFetchProgress.startCFiltersHeight // cfiltersLeftToFetch := totalCFiltersToFetch - w.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount cfiltersFetchProgress := float64(w.syncData.activeSyncData.cfiltersFetchProgress.totalFetchedCFiltersCount) / float64(totalCFiltersToFetch) @@ -200,7 +200,7 @@ func (w *Wallet) fetchHeadersProgress(lastFetchedHeaderHeight int32, lastFetched // for _, wallet := range w.wallets { if w.WaitingForHeaders { - w.WaitingForHeaders = w.GetBestBlock() > lastFetchedHeaderHeight + w.WaitingForHeaders = w.getBestBlock() > lastFetchedHeaderHeight } // } @@ -482,7 +482,7 @@ func (w *Wallet) rescanProgress(walletID int, rescannedThrough int32) { return } - totalHeadersToScan := w.GetBestBlock() + totalHeadersToScan := w.getBestBlock() rescanRate := float64(rescannedThrough) / float64(totalHeadersToScan) diff --git a/wallets/dcr/transactions.go b/wallets/dcr/transactions.go index 9da4cf566..227f6134d 100644 --- a/wallets/dcr/transactions.go +++ b/wallets/dcr/transactions.go @@ -104,7 +104,7 @@ func (wallet *Wallet) GetTransactions(offset, limit, txFilter int32, newestFirst } func (wallet *Wallet) GetTransactionsRaw(offset, limit, txFilter int32, newestFirst bool) (transactions []Transaction, err error) { - err = wallet.WalletDataDB.Read(offset, limit, txFilter, newestFirst, wallet.RequiredConfirmations(), wallet.GetBestBlock(), &transactions) + err = wallet.WalletDataDB.Read(offset, limit, txFilter, newestFirst, wallet.RequiredConfirmations(), wallet.getBestBlock(), &transactions) return } @@ -150,7 +150,7 @@ func (wallet *Wallet) GetTransactionsRaw(offset, limit, txFilter int32, newestFi // } func (wallet *Wallet) CountTransactions(txFilter int32) (int, error) { - return wallet.WalletDataDB.Count(txFilter, wallet.RequiredConfirmations(), wallet.GetBestBlock(), &Transaction{}) + return wallet.WalletDataDB.Count(txFilter, wallet.RequiredConfirmations(), wallet.getBestBlock(), &Transaction{}) } func (wallet *Wallet) TicketHasVotedOrRevoked(ticketHash string) (bool, error) { @@ -219,7 +219,7 @@ func (wallet *Wallet) TransactionOverview() (txOverview *TransactionOverview, er } func (wallet *Wallet) TxMatchesFilter(tx *Transaction, txFilter int32) bool { - bestBlock := wallet.GetBestBlock() + bestBlock := wallet.getBestBlock() // tickets with block height less than this are matured. maturityBlock := bestBlock - int32(wallet.chainParams.TicketMaturity) diff --git a/wallets/dcr/txindex.go b/wallets/dcr/txindex.go index a91f92952..764012c63 100644 --- a/wallets/dcr/txindex.go +++ b/wallets/dcr/txindex.go @@ -61,7 +61,7 @@ func (wallet *Wallet) IndexTransactions() error { return err } - endHeight := wallet.GetBestBlock() + endHeight := wallet.getBestBlock() startBlock := w.NewBlockIdentifierFromHeight(beginHeight) endBlock := w.NewBlockIdentifierFromHeight(endHeight) diff --git a/wallets/dcr/wallet.go b/wallets/dcr/wallet.go index 4b354a954..4c31597d5 100644 --- a/wallets/dcr/wallet.go +++ b/wallets/dcr/wallet.go @@ -285,7 +285,7 @@ func (wallet *Wallet) DeleteWallet(privPass []byte) error { wallet.CancelSync() defer func() { // if wallet.OpenedWalletsCount() > 0 { - wallet.SpvSync() + wallet.SpvSync() // } }() } @@ -627,4 +627,4 @@ func (wallet *Wallet) VerifySeedForWallet(seedMnemonic string, privpass []byte) } return false, errors.New(ErrInvalid) -} \ No newline at end of file +} From 4703b0f924618c53c70f0190cfad45b2f09b05c9 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:43:16 +0100 Subject: [PATCH 06/16] fix compilation errors in dcr/ticket.go --- wallets/dcr/ticket.go | 283 ++++++++++++++++-------------------------- 1 file changed, 109 insertions(+), 174 deletions(-) diff --git a/wallets/dcr/ticket.go b/wallets/dcr/ticket.go index fcd7c959d..63b36313b 100644 --- a/wallets/dcr/ticket.go +++ b/wallets/dcr/ticket.go @@ -5,7 +5,7 @@ import ( "fmt" "runtime/trace" "sync" - // "time" + "time" "decred.org/dcrwallet/v2/errors" w "decred.org/dcrwallet/v2/wallet" @@ -13,7 +13,7 @@ import ( "github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/wire" "github.com/planetdecred/dcrlibwallet/internal/vsp" - // "github.com/planetdecred/dcrlibwallet/utils" + "github.com/planetdecred/dcrlibwallet/utils" ) func (wallet *Wallet) TotalStakingRewards() (int64, error) { @@ -30,27 +30,13 @@ func (wallet *Wallet) TotalStakingRewards() (int64, error) { return totalRewards, nil } -// func (mw *MultiWallet) TotalStakingRewards() (int64, error) { -// var totalRewards int64 -// for _, wal := range mw.wallets { -// walletTotalRewards, err := wal.TotalStakingRewards() -// if err != nil { -// return 0, err -// } - -// totalRewards += walletTotalRewards -// } - -// return totalRewards, nil -// } - -// func (mw *MultiWallet) TicketMaturity() int32 { -// return int32(mw.chainParams.TicketMaturity) -// } +func (wallet *Wallet) TicketMaturity() int32 { + return int32(wallet.chainParams.TicketMaturity) +} -// func (mw *MultiWallet) TicketExpiry() int32 { -// return int32(mw.chainParams.TicketExpiry) -// } +func (wallet *Wallet) TicketExpiry() int32 { + return int32(wallet.chainParams.TicketExpiry) +} func (wallet *Wallet) StakingOverview() (stOverview *StakingOverview, err error) { stOverview = &StakingOverview{} @@ -91,29 +77,6 @@ func (wallet *Wallet) StakingOverview() (stOverview *StakingOverview, err error) return stOverview, nil } -// func (mw *MultiWallet) StakingOverview() (stOverview *StakingOverview, err error) { -// stOverview = &StakingOverview{} - -// for _, wallet := range mw.wallets { -// st, err := wallet.StakingOverview() -// if err != nil { -// return nil, err -// } - -// stOverview.Unmined += st.Unmined -// stOverview.Immature += st.Immature -// stOverview.Live += st.Live -// stOverview.Voted += st.Voted -// stOverview.Revoked += st.Revoked -// stOverview.Expired += st.Expired -// } - -// stOverview.All = stOverview.Unmined + stOverview.Immature + stOverview.Live + stOverview.Voted + -// stOverview.Revoked + stOverview.Expired - -// return stOverview, nil -// } - // TicketPrice returns the price of a ticket for the next block, also known as // the stake difficulty. May be incorrect if blockchain sync is ongoing or if // blockchain is not up-to-date. @@ -132,22 +95,6 @@ func (wallet *Wallet) TicketPrice() (*TicketPriceResponse, error) { return resp, nil } -// func (mw *MultiWallet) TicketPrice() (*TicketPriceResponse, error) { -// bestBlock := mw.GetBestBlock() -// for _, wal := range mw.wallets { -// resp, err := wal.TicketPrice() -// if err != nil { -// return nil, err -// } - -// if resp.Height == bestBlock.Height { -// return resp, nil -// } -// } - -// return nil, errors.New(ErrWalletNotFound) -// } - // PurchaseTickets purchases tickets from the wallet. // Returns a slice of hashes for tickets purchased. func (wallet *Wallet) PurchaseTickets(account, numTickets int32, vspHost string, vspPubKey []byte, passphrase []byte) ([]*chainhash.Hash, error) { @@ -205,77 +152,73 @@ func (wallet *Wallet) PurchaseTickets(account, numTickets int32, vspHost string, // VSPTicketInfo returns vsp-related info for a given ticket. Returns an error // if the ticket is not yet assigned to a VSP. -// func (mw *MultiWallet) VSPTicketInfo(walletID int, hash string) (*VSPTicketInfo, error) { -// wallet := mw.WalletWithID(walletID) -// if wallet == nil { -// return nil, fmt.Errorf("no wallet with ID %d", walletID) -// } - -// ticketHash, err := chainhash.NewHashFromStr(hash) -// if err != nil { -// return nil, err -// } - -// // Read the VSP info for this ticket from the wallet db. -// ctx := wallet.shutdownContext() -// walletTicketInfo, err := wallet.Internal().VSPTicketInfo(ctx, ticketHash) -// if err != nil { -// return nil, err -// } - -// ticketInfo := &VSPTicketInfo{ -// VSP: walletTicketInfo.Host, -// FeeTxHash: walletTicketInfo.FeeHash.String(), -// FeeTxStatus: VSPFeeStatus(walletTicketInfo.FeeTxStatus), -// } - -// // Cannot submit a ticketstatus api request to the VSP if -// // the wallet is locked. Return just the wallet info. -// if wallet.IsLocked() { -// return ticketInfo, nil -// } - -// vspClient, err := wallet.VSPClient(walletTicketInfo.Host, walletTicketInfo.PubKey) -// if err != nil { -// log.Warnf("unable to get vsp ticket info for %s: %v", hash, err) -// return ticketInfo, nil -// } -// vspTicketStatus, err := vspClient.TicketStatus(ctx, ticketHash) -// if err != nil { -// log.Warnf("unable to get vsp ticket info for %s: %v", hash, err) -// return ticketInfo, nil -// } - -// // Parse the fee status returned by the vsp. -// var vspFeeStatus VSPFeeStatus -// switch vspTicketStatus.FeeTxStatus { -// case "received": // received but not broadcast -// vspFeeStatus = VSPFeeProcessStarted -// case "broadcast": // broadcast but not confirmed -// vspFeeStatus = VSPFeeProcessPaid -// case "confirmed": // broadcast and confirmed -// vspFeeStatus = VSPFeeProcessConfirmed -// case "error": -// vspFeeStatus = VSPFeeProcessErrored -// default: -// vspFeeStatus = VSPFeeProcessErrored -// log.Warnf("VSP responded with %v for %v", vspTicketStatus.FeeTxStatus, ticketHash) -// } - -// // Sanity check and log any observed discrepancies. -// if ticketInfo.FeeTxHash != vspTicketStatus.FeeTxHash { -// log.Warnf("wallet fee tx hash %s differs from vsp fee tx hash %s for ticket %s", -// ticketInfo.FeeTxHash, vspTicketStatus.FeeTxHash, ticketHash) -// ticketInfo.FeeTxHash = vspTicketStatus.FeeTxHash -// } -// if ticketInfo.FeeTxStatus != vspFeeStatus { -// log.Warnf("wallet fee status %q differs from vsp fee status %q for ticket %s", -// ticketInfo.FeeTxStatus, vspFeeStatus, ticketHash) -// ticketInfo.FeeTxStatus = vspFeeStatus -// } - -// return ticketInfo, nil -// } +func (wallet *Wallet) VSPTicketInfo(walletID int, hash string) (*VSPTicketInfo, error) { + + ticketHash, err := chainhash.NewHashFromStr(hash) + if err != nil { + return nil, err + } + + // Read the VSP info for this ticket from the wallet db. + ctx := wallet.ShutdownContext() + walletTicketInfo, err := wallet.Internal().VSPTicketInfo(ctx, ticketHash) + if err != nil { + return nil, err + } + + ticketInfo := &VSPTicketInfo{ + VSP: walletTicketInfo.Host, + FeeTxHash: walletTicketInfo.FeeHash.String(), + FeeTxStatus: VSPFeeStatus(walletTicketInfo.FeeTxStatus), + } + + // Cannot submit a ticketstatus api request to the VSP if + // the wallet is locked. Return just the wallet info. + if wallet.IsLocked() { + return ticketInfo, nil + } + + vspClient, err := wallet.VSPClient(walletTicketInfo.Host, walletTicketInfo.PubKey) + if err != nil { + log.Warnf("unable to get vsp ticket info for %s: %v", hash, err) + return ticketInfo, nil + } + vspTicketStatus, err := vspClient.TicketStatus(ctx, ticketHash) + if err != nil { + log.Warnf("unable to get vsp ticket info for %s: %v", hash, err) + return ticketInfo, nil + } + + // Parse the fee status returned by the vsp. + var vspFeeStatus VSPFeeStatus + switch vspTicketStatus.FeeTxStatus { + case "received": // received but not broadcast + vspFeeStatus = VSPFeeProcessStarted + case "broadcast": // broadcast but not confirmed + vspFeeStatus = VSPFeeProcessPaid + case "confirmed": // broadcast and confirmed + vspFeeStatus = VSPFeeProcessConfirmed + case "error": + vspFeeStatus = VSPFeeProcessErrored + default: + vspFeeStatus = VSPFeeProcessErrored + log.Warnf("VSP responded with %v for %v", vspTicketStatus.FeeTxStatus, ticketHash) + } + + // Sanity check and log any observed discrepancies. + if ticketInfo.FeeTxHash != vspTicketStatus.FeeTxHash { + log.Warnf("wallet fee tx hash %s differs from vsp fee tx hash %s for ticket %s", + ticketInfo.FeeTxHash, vspTicketStatus.FeeTxHash, ticketHash) + ticketInfo.FeeTxHash = vspTicketStatus.FeeTxHash + } + if ticketInfo.FeeTxStatus != vspFeeStatus { + log.Warnf("wallet fee status %q differs from vsp fee status %q for ticket %s", + ticketInfo.FeeTxStatus, vspFeeStatus, ticketHash) + ticketInfo.FeeTxStatus = vspFeeStatus + } + + return ticketInfo, nil +} // StartTicketBuyer starts the automatic ticket buyer. The wallet // should already be configured with the required parameters using @@ -545,23 +488,19 @@ func (wallet *Wallet) IsAutoTicketsPurchaseActive() bool { } // StopAutoTicketsPurchase stops the automatic ticket buyer. -// func (mw *MultiWallet) StopAutoTicketsPurchase(walletID int) error { -// wallet := mw.WalletWithID(walletID) -// if wallet == nil { -// return errors.New(ErrNotExist) -// } +func (wallet *Wallet) StopAutoTicketsPurchase(walletID int) error { -// wallet.cancelAutoTicketBuyerMu.Lock() -// defer wallet.cancelAutoTicketBuyerMu.Unlock() + wallet.cancelAutoTicketBuyerMu.Lock() + defer wallet.cancelAutoTicketBuyerMu.Unlock() -// if wallet.cancelAutoTicketBuyer == nil { -// return errors.New(ErrInvalid) -// } + if wallet.cancelAutoTicketBuyer == nil { + return errors.New(ErrInvalid) + } -// wallet.cancelAutoTicketBuyer() -// wallet.cancelAutoTicketBuyer = nil -// return nil -// } + wallet.cancelAutoTicketBuyer() + wallet.cancelAutoTicketBuyer = nil + return nil +} // SetAutoTicketsBuyerConfig sets ticket buyer config for the wallet. func (wallet *Wallet) SetAutoTicketsBuyerConfig(vspHost string, purchaseAccount int32, amountToMaintain int64) { @@ -590,39 +529,35 @@ func (wallet *Wallet) TicketBuyerConfigIsSet() bool { } // ClearTicketBuyerConfig clears the wallet's ticket buyer config. -// func (mw *MultiWallet) ClearTicketBuyerConfig(walletID int) error { -// wallet := mw.WalletWithID(walletID) -// if wallet == nil { -// return errors.New(ErrNotExist) -// } +func (wallet *Wallet) ClearTicketBuyerConfig(walletID int) error { -// mw.SetLongConfigValueForKey(TicketBuyerATMConfigKey, -1) -// mw.SetInt32ConfigValueForKey(TicketBuyerAccountConfigKey, -1) -// mw.SetStringConfigValueForKey(TicketBuyerVSPHostConfigKey, "") + wallet.SetLongConfigValueForKey(TicketBuyerATMConfigKey, -1) + wallet.SetInt32ConfigValueForKey(TicketBuyerAccountConfigKey, -1) + wallet.SetStringConfigValueForKey(TicketBuyerVSPHostConfigKey, "") -// return nil -// } + return nil +} // NextTicketPriceRemaining returns the remaning time in seconds of a ticket for the next block, // if secs equal 0 is imminent -// func (mw *MultiWallet) NextTicketPriceRemaining() (secs int64, err error) { -// params, er := utils.ChainParams(mw.chainParams.Name) -// if er != nil { -// secs, err = -1, er -// return -// } -// bestBestBlock := mw.GetBestBlock() -// idxBlockInWindow := int(int64(bestBestBlock.Height)%params.StakeDiffWindowSize) + 1 -// blockTime := params.TargetTimePerBlock.Nanoseconds() -// windowSize := params.StakeDiffWindowSize -// x := (windowSize - int64(idxBlockInWindow)) * blockTime -// if x == 0 { -// secs, err = 0, nil -// return -// } -// secs, err = int64(time.Duration(x).Seconds()), nil -// return -// } +func (wallet *Wallet) NextTicketPriceRemaining() (secs int64, err error) { + params, er := utils.ChainParams(wallet.chainParams.Name) + if er != nil { + secs, err = -1, er + return + } + bestBestBlock := wallet.GetBestBlock() + idxBlockInWindow := int(int64(bestBestBlock.Height)%params.StakeDiffWindowSize) + 1 + blockTime := params.TargetTimePerBlock.Nanoseconds() + windowSize := params.StakeDiffWindowSize + x := (windowSize - int64(idxBlockInWindow)) * blockTime + if x == 0 { + secs, err = 0, nil + return + } + secs, err = int64(time.Duration(x).Seconds()), nil + return +} // UnspentUnexpiredTickets returns all Unmined, Immature and Live tickets. func (wallet *Wallet) UnspentUnexpiredTickets() ([]Transaction, error) { From e9e99dddde173559f73069e270d8926c86889899 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:46:00 +0100 Subject: [PATCH 07/16] remove multiwallet method from dcr/txauthor --- wallets/dcr/transactions.go | 42 ------------------------------------- wallets/dcr/txauthor.go | 36 +++++++++++++++---------------- 2 files changed, 18 insertions(+), 60 deletions(-) diff --git a/wallets/dcr/transactions.go b/wallets/dcr/transactions.go index 227f6134d..f2d2d3a2a 100644 --- a/wallets/dcr/transactions.go +++ b/wallets/dcr/transactions.go @@ -2,7 +2,6 @@ package dcr import ( "encoding/json" - // "sort" "github.com/asdine/storm" "github.com/decred/dcrd/chaincfg/chainhash" @@ -108,47 +107,6 @@ func (wallet *Wallet) GetTransactionsRaw(offset, limit, txFilter int32, newestFi return } -// func (mw *MultiWallet) GetTransactions(offset, limit, txFilter int32, newestFirst bool) (string, error) { - -// transactions, err := mw.GetTransactionsRaw(offset, limit, txFilter, newestFirst) -// if err != nil { -// return "", err -// } - -// jsonEncodedTransactions, err := json.Marshal(&transactions) -// if err != nil { -// return "", err -// } - -// return string(jsonEncodedTransactions), nil -// } - -// func (mw *MultiWallet) GetTransactionsRaw(offset, limit, txFilter int32, newestFirst bool) ([]Transaction, error) { -// transactions := make([]Transaction, 0) -// for _, wallet := range mw.wallets { -// walletTransactions, err := wallet.GetTransactionsRaw(offset, limit, txFilter, newestFirst) -// if err != nil { -// return nil, err -// } - -// transactions = append(transactions, walletTransactions...) -// } - -// // sort transaction by timestamp in descending order -// sort.Slice(transactions[:], func(i, j int) bool { -// if newestFirst { -// return transactions[i].Timestamp > transactions[j].Timestamp -// } -// return transactions[i].Timestamp < transactions[j].Timestamp -// }) - -// if len(transactions) > int(limit) && limit > 0 { -// transactions = transactions[:limit] -// } - -// return transactions, nil -// } - func (wallet *Wallet) CountTransactions(txFilter int32) (int, error) { return wallet.WalletDataDB.Count(txFilter, wallet.RequiredConfirmations(), wallet.getBestBlock(), &Transaction{}) } diff --git a/wallets/dcr/txauthor.go b/wallets/dcr/txauthor.go index 73dcf2e5c..1d574bb04 100644 --- a/wallets/dcr/txauthor.go +++ b/wallets/dcr/txauthor.go @@ -32,24 +32,24 @@ type TxAuthor struct { needsConstruct bool } -// func (mw *MultiWallet) NewUnsignedTx(walletID int, sourceAccountNumber int32) (*TxAuthor, error) { -// sourceWallet := mw.WalletWithID(walletID) -// if sourceWallet == nil { -// return nil, fmt.Errorf(ErrWalletNotFound) -// } - -// _, err := sourceWallet.GetAccount(sourceAccountNumber) -// if err != nil { -// return nil, err -// } - -// return &TxAuthor{ -// sourceWallet: sourceWallet, -// sourceAccountNumber: uint32(sourceAccountNumber), -// destinations: make([]TransactionDestination, 0), -// needsConstruct: true, -// }, nil -// } +func (wallet *Wallet) NewUnsignedTx(walletID int, sourceAccountNumber int32) (*TxAuthor, error) { + sourceWallet := wallet + if sourceWallet == nil { + return nil, fmt.Errorf(ErrWalletNotFound) + } + + _, err := sourceWallet.GetAccount(sourceAccountNumber) + if err != nil { + return nil, err + } + + return &TxAuthor{ + sourceWallet: sourceWallet, + sourceAccountNumber: uint32(sourceAccountNumber), + destinations: make([]TransactionDestination, 0), + needsConstruct: true, + }, nil +} func (tx *TxAuthor) AddSendDestination(address string, atomAmount int64, sendMax bool) error { _, err := stdaddr.DecodeAddress(address, tx.sourceWallet.chainParams) From a9d0da26b45fdfbc0ef18987b439a699d6a274c4 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:53:29 +0100 Subject: [PATCH 08/16] convert dcr/vsp.go multiwallet methods to wallet methods --- multiwallet.go | 3 - wallets/dcr/utils.go | 28 +++--- wallets/dcr/vsp.go | 192 +++++++++++++++++++++--------------------- wallets/dcr/wallet.go | 3 + 4 files changed, 109 insertions(+), 117 deletions(-) diff --git a/multiwallet.go b/multiwallet.go index bb54c6f3c..26e785ec9 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -45,9 +45,6 @@ type MultiWallet struct { cancelFuncs []context.CancelFunc dexClient *DexClient - - // vspMu sync.RWMutex - // vsps []*VSP } func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWallet, error) { diff --git a/wallets/dcr/utils.go b/wallets/dcr/utils.go index 3c3656970..854fa7a12 100644 --- a/wallets/dcr/utils.go +++ b/wallets/dcr/utils.go @@ -55,14 +55,6 @@ const ( ShortestAbbreviationFormat = "shortest" ) -// func (mw *MultiWallet) RequiredConfirmations() int32 { -// spendUnconfirmed := mw.ReadBoolConfigValueForKey(SpendUnconfirmedConfigKey, false) -// if spendUnconfirmed { -// return 0 -// } -// return DefaultRequiredConfirmations -// } - func (wallet *Wallet) RequiredConfirmations() int32 { var spendUnconfirmed bool wallet.readUserConfigValue(true, SpendUnconfirmedConfigKey, &spendUnconfirmed) @@ -72,17 +64,17 @@ func (wallet *Wallet) RequiredConfirmations() int32 { return DefaultRequiredConfirmations } -// func (mw *MultiWallet) listenForShutdown() { +func (wallet *Wallet) listenForShutdown() { -// mw.cancelFuncs = make([]context.CancelFunc, 0) -// mw.shuttingDown = make(chan bool) -// go func() { -// <-mw.shuttingDown -// for _, cancel := range mw.cancelFuncs { -// cancel() -// } -// }() -// } + wallet.cancelFuncs = make([]context.CancelFunc, 0) + wallet.shuttingDown = make(chan bool) + go func() { + <-wallet.shuttingDown + for _, cancel := range wallet.cancelFuncs { + cancel() + } + }() +} func (wallet *Wallet) ShutdownContextWithCancel() (context.Context, context.CancelFunc) { ctx, cancel := context.WithCancel(context.Background()) diff --git a/wallets/dcr/vsp.go b/wallets/dcr/vsp.go index fe0054d4b..9f7050ecf 100644 --- a/wallets/dcr/vsp.go +++ b/wallets/dcr/vsp.go @@ -1,7 +1,7 @@ package dcr import ( - // "context" + "context" "crypto/ed25519" "encoding/base64" "fmt" @@ -36,120 +36,120 @@ func (wallet *Wallet) VSPClient(host string, pubKey []byte) (*vsp.Client, error) // KnownVSPs returns a list of known VSPs. This list may be updated by calling // ReloadVSPList. This method is safe for concurrent access. -// func (mw *MultiWallet) KnownVSPs() []*VSP { -// mw.vspMu.RLock() -// defer mw.vspMu.RUnlock() -// return mw.vsps // TODO: Return a copy. -// } +func (wallet *Wallet) KnownVSPs() []*VSP { + wallet.vspMu.RLock() + defer wallet.vspMu.RUnlock() + return wallet.vsps // TODO: Return a copy. +} // SaveVSP marks a VSP as known and will be susbequently included as part of // known VSPs. -// func (mw *MultiWallet) SaveVSP(host string) (err error) { -// // check if host already exists -// vspDbData := mw.getVSPDBData() -// for _, savedHost := range vspDbData.SavedHosts { -// if savedHost == host { -// return fmt.Errorf("duplicate host %s", host) -// } -// } - -// // validate host network -// info, err := vspInfo(host) -// if err != nil { -// return err -// } - -// // TODO: defaultVSPs() uses strings.Contains(network, vspInfo.Network). -// if info.Network != mw.NetType() { -// return fmt.Errorf("invalid net %s", info.Network) -// } - -// vspDbData.SavedHosts = append(vspDbData.SavedHosts, host) -// mw.updateVSPDBData(vspDbData) - -// mw.vspMu.Lock() -// mw.vsps = append(mw.vsps, &VSP{Host: host, VspInfoResponse: info}) -// mw.vspMu.Unlock() - -// return -// } +func (wallet *Wallet) SaveVSP(host string) (err error) { + // check if host already exists + vspDbData := wallet.getVSPDBData() + for _, savedHost := range vspDbData.SavedHosts { + if savedHost == host { + return fmt.Errorf("duplicate host %s", host) + } + } + + // validate host network + info, err := vspInfo(host) + if err != nil { + return err + } + + // TODO: defaultVSPs() uses strings.Contains(network, vspInfo.Network). + if info.Network != wallet.NetType() { + return fmt.Errorf("invalid net %s", info.Network) + } + + vspDbData.SavedHosts = append(vspDbData.SavedHosts, host) + wallet.updateVSPDBData(vspDbData) + + wallet.vspMu.Lock() + wallet.vsps = append(wallet.vsps, &VSP{Host: host, VspInfoResponse: info}) + wallet.vspMu.Unlock() + + return +} // LastUsedVSP returns the host of the last used VSP, as saved by the // SaveLastUsedVSP() method. -// func (mw *MultiWallet) LastUsedVSP() string { -// return mw.getVSPDBData().LastUsedVSP -// } +func (wallet *Wallet) LastUsedVSP() string { + return wallet.getVSPDBData().LastUsedVSP +} // SaveLastUsedVSP saves the host of the last used VSP. -// func (mw *MultiWallet) SaveLastUsedVSP(host string) { -// vspDbData := mw.getVSPDBData() -// vspDbData.LastUsedVSP = host -// mw.updateVSPDBData(vspDbData) -// } +func (wallet *Wallet) SaveLastUsedVSP(host string) { + vspDbData := wallet.getVSPDBData() + vspDbData.LastUsedVSP = host + wallet.updateVSPDBData(vspDbData) +} type vspDbData struct { SavedHosts []string LastUsedVSP string } -// func (mw *MultiWallet) getVSPDBData() *vspDbData { -// vspDbData := new(vspDbData) -// mw.ReadUserConfigValue(KnownVSPsConfigKey, vspDbData) -// return vspDbData -// } +func (wallet *Wallet) getVSPDBData() *vspDbData { + vspDbData := new(vspDbData) + wallet.ReadUserConfigValue(KnownVSPsConfigKey, vspDbData) + return vspDbData +} -// func (mw *MultiWallet) updateVSPDBData(data *vspDbData) { -// mw.SaveUserConfigValue(KnownVSPsConfigKey, data) -// } +func (wallet *Wallet) updateVSPDBData(data *vspDbData) { + wallet.SaveUserConfigValue(KnownVSPsConfigKey, data) +} // ReloadVSPList reloads the list of known VSPs. // This method makes multiple network calls; should be called in a goroutine // to prevent blocking the UI thread. -// func (mw *MultiWallet) ReloadVSPList(ctx context.Context) { -// log.Debugf("Reloading list of known VSPs") -// defer log.Debugf("Reloaded list of known VSPs") - -// vspDbData := mw.getVSPDBData() -// vspList := make(map[string]*VspInfoResponse) -// for _, host := range vspDbData.SavedHosts { -// vspInfo, err := vspInfo(host) -// if err != nil { -// // User saved this VSP. Log an error message. -// log.Errorf("get vsp info error for %s: %v", host, err) -// } else { -// vspList[host] = vspInfo -// } -// if ctx.Err() != nil { -// return // context canceled, abort -// } -// } - -// otherVSPHosts, err := defaultVSPs(mw.NetType()) -// if err != nil { -// log.Debugf("get default vsp list error: %v", err) -// } -// for _, host := range otherVSPHosts { -// if _, wasAdded := vspList[host]; wasAdded { -// continue -// } -// vspInfo, err := vspInfo(host) -// if err != nil { -// log.Debugf("vsp info error for %s: %v\n", host, err) // debug only, user didn't request this VSP -// } else { -// vspList[host] = vspInfo -// } -// if ctx.Err() != nil { -// return // context canceled, abort -// } -// } - -// mw.vspMu.Lock() -// mw.vsps = make([]*VSP, 0, len(vspList)) -// for host, info := range vspList { -// mw.vsps = append(mw.vsps, &VSP{Host: host, VspInfoResponse: info}) -// } -// mw.vspMu.Unlock() -// } +func (wallet *Wallet) ReloadVSPList(ctx context.Context) { + log.Debugf("Reloading list of known VSPs") + defer log.Debugf("Reloaded list of known VSPs") + + vspDbData := wallet.getVSPDBData() + vspList := make(map[string]*VspInfoResponse) + for _, host := range vspDbData.SavedHosts { + vspInfo, err := vspInfo(host) + if err != nil { + // User saved this VSP. Log an error message. + log.Errorf("get vsp info error for %s: %v", host, err) + } else { + vspList[host] = vspInfo + } + if ctx.Err() != nil { + return // context canceled, abort + } + } + + otherVSPHosts, err := defaultVSPs(wallet.NetType()) + if err != nil { + log.Debugf("get default vsp list error: %v", err) + } + for _, host := range otherVSPHosts { + if _, wasAdded := vspList[host]; wasAdded { + continue + } + vspInfo, err := vspInfo(host) + if err != nil { + log.Debugf("vsp info error for %s: %v\n", host, err) // debug only, user didn't request this VSP + } else { + vspList[host] = vspInfo + } + if ctx.Err() != nil { + return // context canceled, abort + } + } + + wallet.vspMu.Lock() + wallet.vsps = make([]*VSP, 0, len(vspList)) + for host, info := range vspList { + wallet.vsps = append(wallet.vsps, &VSP{Host: host, VspInfoResponse: info}) + } + wallet.vspMu.Unlock() +} func vspInfo(vspHost string) (*VspInfoResponse, error) { vspInfoResponse := new(VspInfoResponse) diff --git a/wallets/dcr/wallet.go b/wallets/dcr/wallet.go index 4c31597d5..5822d6797 100644 --- a/wallets/dcr/wallet.go +++ b/wallets/dcr/wallet.go @@ -68,6 +68,9 @@ type Wallet struct { accountMixerNotificationListener map[string]AccountMixerNotificationListener txAndBlockNotificationListeners map[string]TxAndBlockNotificationListener blocksRescanProgressListener BlocksRescanProgressListener + + vspMu sync.RWMutex + vsps []*VSP } // prepare gets a wallet ready for use by opening the transactions index database From c300f6efcbdb34b630a4ea1da54cad9d933adf1e Mon Sep 17 00:00:00 2001 From: dreacot Date: Fri, 29 Jul 2022 00:03:15 +0100 Subject: [PATCH 09/16] removed unused comments --- multiwallet.go | 13 ------------- wallets/dcr/politeia.go | 2 +- wallets/dcr/politeia_sync.go | 4 ++-- wallets/dcr/rescan.go | 3 +-- wallets/dcr/txandblocknotifications.go | 3 ++- wallets/dcr/wallet.go | 1 - 6 files changed, 6 insertions(+), 20 deletions(-) diff --git a/multiwallet.go b/multiwallet.go index 26e785ec9..fff078d1c 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -6,18 +6,13 @@ import ( "fmt" "os" "path/filepath" - // "strconv" "strings" - // "sync" - // "time" "decred.org/dcrwallet/v2/errors" - // w "decred.org/dcrwallet/v2/wallet" "github.com/asdine/storm" "github.com/asdine/storm/q" "github.com/decred/dcrd/chaincfg/v3" "github.com/planetdecred/dcrlibwallet/utils" - // "github.com/planetdecred/dcrlibwallet/wallets/dcr/walletdata" "github.com/planetdecred/dcrlibwallet/wallets/dcr" @@ -33,14 +28,6 @@ type MultiWallet struct { wallets map[int]*dcr.Wallet badWallets map[int]*dcr.Wallet - // syncData *dcr.SyncData - - // notificationListenersMu sync.RWMutex - // txAndBlockNotificationListeners map[string]TxAndBlockNotificationListener - - // blocksRescanProgressListener BlocksRescanProgressListener - // accountMixerNotificationListener map[string]AccountMixerNotificationListener - shuttingDown chan bool cancelFuncs []context.CancelFunc diff --git a/wallets/dcr/politeia.go b/wallets/dcr/politeia.go index d7c240a78..146a127ed 100644 --- a/wallets/dcr/politeia.go +++ b/wallets/dcr/politeia.go @@ -20,7 +20,7 @@ const ( func (wallet *Wallet) NewPoliteia(host string) (*Politeia, error) { p := &Politeia{ - WalletRef: wallet, // Holds a refrence to the wallet initializing Politeia. + WalletRef: wallet, // Holds a reference to the wallet initializing Politeia. Host: host, Client: nil, NotificationListeners: make(map[string]ProposalNotificationListener), diff --git a/wallets/dcr/politeia_sync.go b/wallets/dcr/politeia_sync.go index 1d0450275..f9a3f3473 100644 --- a/wallets/dcr/politeia_sync.go +++ b/wallets/dcr/politeia_sync.go @@ -1,7 +1,6 @@ package dcr import ( - "decred.org/dcrwallet/v2/errors" "encoding/hex" "encoding/json" "fmt" @@ -9,8 +8,9 @@ import ( "strconv" "time" - "github.com/asdine/storm" + "decred.org/dcrwallet/v2/errors" + "github.com/asdine/storm" tkv1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1" www "github.com/decred/politeia/politeiawww/api/www/v1" ) diff --git a/wallets/dcr/rescan.go b/wallets/dcr/rescan.go index 659c36919..fedce9881 100644 --- a/wallets/dcr/rescan.go +++ b/wallets/dcr/rescan.go @@ -32,8 +32,7 @@ func (wallet *Wallet) RescanBlocksFromHeight(walletID int, startHeight int32) er wallet.syncData.mu.Unlock() }() - ctx, _ := wallet.ShutdownContextWithCancel() - ctx, cancel := wallet.ShutdownContextWithCancel() //undo this lateer + ctx, cancel := wallet.ShutdownContextWithCancel() wallet.syncData.mu.Lock() wallet.syncData.rescanning = true diff --git a/wallets/dcr/txandblocknotifications.go b/wallets/dcr/txandblocknotifications.go index 89a17fafa..4152ca7ca 100644 --- a/wallets/dcr/txandblocknotifications.go +++ b/wallets/dcr/txandblocknotifications.go @@ -1,8 +1,9 @@ package dcr import ( - "decred.org/dcrwallet/v2/errors" "encoding/json" + + "decred.org/dcrwallet/v2/errors" ) func (wallet *Wallet) listenForTransactions() { diff --git a/wallets/dcr/wallet.go b/wallets/dcr/wallet.go index 5822d6797..14045d3ff 100644 --- a/wallets/dcr/wallet.go +++ b/wallets/dcr/wallet.go @@ -349,7 +349,6 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { wallet.Name = "wallet-" + strconv.Itoa(wallet.ID) // wallet-# } wallet.DataDir = walletDataDir - wallet.dbDriver = wallet.dbDriver err = db.Save(wallet) // update database with complete wallet information if err != nil { From 430e076181e81309850e81465c1bca65f832b74e Mon Sep 17 00:00:00 2001 From: song50119 <> Date: Thu, 26 May 2022 22:27:41 +0700 Subject: [PATCH 10/16] Add BTC wallet --- btcwallet.go | 193 ++++++++++++++++++++ btcwallet/go.mod | 24 +++ btcwallet/go.sum | 168 ++++++++++++++++++ btcwallet/wallet.go | 420 ++++++++++++++++++++++++++++++++++++++++++++ go.mod | 6 +- multiwallet.go | 6 + 6 files changed, 816 insertions(+), 1 deletion(-) create mode 100644 btcwallet.go create mode 100644 btcwallet/go.mod create mode 100644 btcwallet/go.sum create mode 100644 btcwallet/wallet.go diff --git a/btcwallet.go b/btcwallet.go new file mode 100644 index 000000000..62bc4c894 --- /dev/null +++ b/btcwallet.go @@ -0,0 +1,193 @@ +package dcrlibwallet + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + + "decred.org/dcrwallet/v2/errors" + "github.com/asdine/storm" + "github.com/asdine/storm/q" + "github.com/planetdecred/dcrlibwallet/btcwallet" +) + +const walletTypeSPV = "SPV" + +type btcWallet struct { + db *storm.DB + wallets map[int]*btcwallet.Wallet + cancelCoreCtx context.CancelFunc + btcDataDir string +} + +func (mw *MultiWallet) initBtcWallet() error { + if mw.btcWallet != nil { + return nil + } + + btcDataDir := filepath.Join(mw.rootDir, "btc") + + err := os.MkdirAll(btcDataDir, os.ModePerm) + if err != nil { + return err + } + + walletsDB, err := storm.Open(filepath.Join(btcDataDir, walletsDbName)) + if err != nil { + return err + } + + // init database for saving/reading wallet objects + err = walletsDB.Init(&btcwallet.Wallet{}) + if err != nil { + log.Errorf("Error initializing wallets database BTC: %s", err.Error()) + return err + } + + mw.btcWallet = &btcWallet{ + btcDataDir: filepath.Join(mw.rootDir, "btc"), + db: walletsDB, + wallets: make(map[int]*btcwallet.Wallet), + } + + // read saved wallets info from db and initialize wallets + query := walletsDB.Select(q.True()).OrderBy("ID") + var wallets []*btcwallet.Wallet + err = query.Find(&wallets) + if err != nil && err != storm.ErrNotFound { + return err + } + + // prepare the wallets loaded from db for use + for _, wallet := range wallets { + err = wallet.Prepare(mw.btcWallet.btcDataDir, mw.NetType(), log) + if err == nil && !WalletExistsAt(wallet.DataDir()) { + err = fmt.Errorf("missing wallet database file") + } + if err != nil { + log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) + } else { + mw.btcWallet.wallets[wallet.ID] = wallet + } + } + + return nil +} + +func (mw *MultiWallet) CreateNewBTCWallet(walletName, password string) (*btcwallet.Wallet, error) { + seed := "witch collapse practice feed shame open despair" + encryptedSeed := []byte(seed) + + wallet, err := btcwallet.NewSpvWallet(walletName, encryptedSeed, mw.NetType(), log) + if err != nil { + return nil, err + } + + walletNameExists := func(walletName string) (bool, error) { + if strings.HasPrefix(walletName, "wallet-") { + return false, errors.E(ErrReservedWalletName) + } + + err := mw.btcWallet.db.One("Name", walletName, &btcwallet.Wallet{}) + if err == nil { + return true, nil + } else if err != storm.ErrNotFound { + return false, err + } + + return false, nil + } + + exists, err := walletNameExists(wallet.Name) + if err != nil { + return nil, err + } else if exists { + return nil, errors.New(ErrExist) + } + + batchDbTransaction := func(dbOp func(node storm.Node) error) (err error) { + dbTx, err := mw.btcWallet.db.Begin(true) + if err != nil { + return err + } + + // Commit or rollback the transaction after f returns or panics. Do not + // recover from the panic to keep the original stack trace intact. + panicked := true + defer func() { + if panicked || err != nil { + dbTx.Rollback() + return + } + + err = dbTx.Commit() + }() + + err = dbOp(dbTx) + panicked = false + return err + } + + // Perform database save operations in batch transaction + // for automatic rollback if error occurs at any point. + err = batchDbTransaction(func(db storm.Node) error { + // saving struct to update ID property with an auto-generated value + err := db.Save(wallet) + if err != nil { + return err + } + walletDataDir := filepath.Join(mw.btcWallet.btcDataDir, strconv.Itoa(wallet.ID)) + + dirExists, err := fileExists(walletDataDir) + if err != nil { + return err + } else if dirExists { + newDirName, err := backupFile(walletDataDir, 1) + if err != nil { + return err + } + + log.Infof("Undocumented file at %s moved to %s", walletDataDir, newDirName) + } + + os.MkdirAll(walletDataDir, os.ModePerm) // create wallet dir + + if wallet.Name == "" { + wallet.Name = "wallet-" + strconv.Itoa(wallet.ID) // wallet-# + } + err = db.Save(wallet) // update database with complete wallet information + if err != nil { + return err + } + + err = wallet.CreateSPVWallet([]byte(password), encryptedSeed, mw.btcWallet.btcDataDir) + if err != nil { + return fmt.Errorf("Create BTC wallet error: %v", err) + } + + return nil + }) + + if err != nil { + return nil, err + } + + return wallet, nil +} + +func (mw *MultiWallet) ConnectBTCSPVWallets() error { + ctx, _ := mw.contextWithShutdownCancel() + var wg sync.WaitGroup + for _, wall := range mw.btcWallet.wallets { + err := wall.ConnectSPVWallet(ctx, &wg) + if err != nil { + return err + } + } + wg.Wait() + return nil +} diff --git a/btcwallet/go.mod b/btcwallet/go.mod new file mode 100644 index 000000000..b27ab0f4d --- /dev/null +++ b/btcwallet/go.mod @@ -0,0 +1,24 @@ +module github.com/planetdecred/dcrlibwallet/btcwallet + +require ( + github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f + github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // note: hoists btcd's own require of btcutil + github.com/btcsuite/btcwallet v0.12.0 + github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect + github.com/btcsuite/btcwallet/walletdb v1.4.0 + github.com/btcsuite/btcwallet/wtxmgr v1.3.0 + github.com/decred/dcrd/lru v1.1.1 // indirect + github.com/decred/slog v1.2.0 + github.com/jrick/logrotate v1.0.0 + github.com/kkdai/bstream v1.0.0 // indirect + github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 + github.com/stretchr/testify v1.7.0 // indirect + go.etcd.io/bbolt v1.3.5 // indirect + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 // indirect + golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect +) + +go 1.16 diff --git a/btcwallet/go.sum b/btcwallet/go.sum new file mode 100644 index 000000000..a9daf456e --- /dev/null +++ b/btcwallet/go.sum @@ -0,0 +1,168 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= +github.com/btcsuite/btcd v0.21.0-beta.0.20210426180113-7eba688b65e5/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e h1:d0NkvbJGQThTkhypOdtf5Orxe2+dUHLB86pQ4EXwrTo= +github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e/go.mod h1:3PH+KbvLFfzBTCevQenPiDedjGQGt6aa70dVjJDWGTA= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 h1:9aGy5p7oXRUB4MCTmWm0+jzuh79GpjPIfv1leA5POD4= +github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce h1:3PRwz+js0AMMV1fHRrCdQ55akoomx4Q3ulozHC3BDDY= +github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= +github.com/btcsuite/btcwallet v0.12.0 h1:0kT0rDN8vNcAvuHp2qUS/hLsfa0VUn2dNQ2GvM9ozBA= +github.com/btcsuite/btcwallet v0.12.0/go.mod h1:f1HuBGov5+OTp40Gh1vA+tvF6d7bbuLFTceJMRB7fXw= +github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= +github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk= +github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww= +github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w= +github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA= +github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= +github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= +github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= +github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= +github.com/btcsuite/btcwallet/walletdb v1.4.0/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/wtxmgr v1.3.0 h1:lrZaZXGJjDedYTV7s5UgU9xBe8+N+cIDW7BYwI/B8Fs= +github.com/btcsuite/btcwallet/wtxmgr v1.3.0/go.mod h1:awQsh1n/0ZrEQ+JZgWvHeo153ubzEisf/FyNtwI0dDk= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/decred/dcrd/lru v1.1.1 h1:kWFDaW0OWx6AD6Ki342c+JPmHbiVdE6rK81pT3fuo/Y= +github.com/decred/dcrd/lru v1.1.1/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= +github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= +github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/neutrino v0.12.1/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= +github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 h1:lf3i1CNI5j2XhKMvQNmnr2o1DFoyoE02mynRdAt+ss0= +github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= +github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= +github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= +github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= +github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= +github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/btcwallet/wallet.go b/btcwallet/wallet.go new file mode 100644 index 000000000..54ac5d252 --- /dev/null +++ b/btcwallet/wallet.go @@ -0,0 +1,420 @@ +package btcwallet + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btclog" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/gcs" + "github.com/btcsuite/btcwallet/chain" + "github.com/btcsuite/btcwallet/wallet" + "github.com/btcsuite/btcwallet/walletdb" + _ "github.com/btcsuite/btcwallet/walletdb/bdb" // bdb init() registers a driver + "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/decred/slog" + "github.com/jrick/logrotate/rotator" + "github.com/lightninglabs/neutrino" + "github.com/lightninglabs/neutrino/headerfs" +) + +type Wallet struct { + ID int `storm:"id,increment"` + Name string `storm:"unique"` + CreatedAt time.Time `storm:"index"` + EncryptedSeed []byte + + cl neutrinoService + neutrinoDB walletdb.DB + chainClient *chain.NeutrinoClient + dataDir string + chainParams *chaincfg.Params + loader *wallet.Loader + log slog.Logger + birthday time.Time +} + +// neutrinoService is satisfied by *neutrino.ChainService. +type neutrinoService interface { + GetBlockHash(int64) (*chainhash.Hash, error) + BestBlock() (*headerfs.BlockStamp, error) + Peers() []*neutrino.ServerPeer + GetBlockHeight(hash *chainhash.Hash) (int32, error) + GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, error) + GetCFilter(blockHash chainhash.Hash, filterType wire.FilterType, options ...neutrino.QueryOption) (*gcs.Filter, error) + GetBlock(blockHash chainhash.Hash, options ...neutrino.QueryOption) (*btcutil.Block, error) + Stop() error +} + +var _ neutrinoService = (*neutrino.ChainService)(nil) + +var ( + walletBirthday time.Time + loggingInited uint32 +) + +const ( + neutrinoDBName = "neutrino.db" + logDirName = "logs" + logFileName = "neutrino.log" +) + +func NewSpvWallet(walletName string, encryptedSeed []byte, net string, log slog.Logger) (*Wallet, error) { + chainParams, err := parseChainParams(net) + if err != nil { + return nil, err + } + + return &Wallet{ + Name: walletName, + chainParams: chainParams, + CreatedAt: time.Now(), + EncryptedSeed: encryptedSeed, + log: log, + }, nil +} + +func parseChainParams(net string) (*chaincfg.Params, error) { + switch net { + case "mainnet": + return &chaincfg.MainNetParams, nil + case "testnet3": + return &chaincfg.TestNet3Params, nil + case "regtest", "regnet", "simnet": + return &chaincfg.RegressionNetParams, nil + } + return nil, fmt.Errorf("unknown network ID %v", net) +} + +// logWriter implements an io.Writer that outputs to a rotating log file. +type logWriter struct { + *rotator.Rotator +} + +// logNeutrino initializes logging in the neutrino + wallet packages. Logging +// only has to be initialized once, so an atomic flag is used internally to +// return early on subsequent invocations. +// +// In theory, the the rotating file logger must be Close'd at some point, but +// there are concurrency issues with that since btcd and btcwallet have +// unsupervised goroutines still running after shutdown. So we leave the rotator +// running at the risk of losing some logs. +func logNeutrino(walletDir string) error { + if !atomic.CompareAndSwapUint32(&loggingInited, 0, 1) { + return nil + } + + logSpinner, err := logRotator(walletDir) + if err != nil { + return fmt.Errorf("error initializing log rotator: %w", err) + } + + backendLog := btclog.NewBackend(logWriter{logSpinner}) + + logger := func(name string, lvl btclog.Level) btclog.Logger { + l := backendLog.Logger(name) + l.SetLevel(lvl) + return l + } + + neutrino.UseLogger(logger("NTRNO", btclog.LevelDebug)) + wallet.UseLogger(logger("BTCW", btclog.LevelInfo)) + wtxmgr.UseLogger(logger("TXMGR", btclog.LevelInfo)) + chain.UseLogger(logger("CHAIN", btclog.LevelInfo)) + + return nil +} + +// logRotator initializes a rotating file logger. +func logRotator(netDir string) (*rotator.Rotator, error) { + const maxLogRolls = 8 + logDir := filepath.Join(netDir, logDirName) + if err := os.MkdirAll(logDir, 0744); err != nil { + return nil, fmt.Errorf("error creating log directory: %w", err) + } + + logFilename := filepath.Join(logDir, logFileName) + return rotator.New(logFilename, 32*1024, false, maxLogRolls) +} +func (w *Wallet) RawRequest(method string, params []json.RawMessage) (json.RawMessage, error) { + // Not needed for spv wallet. + return nil, errors.New("RawRequest not available on spv") +} + +// createSPVWallet creates a new SPV wallet. +func (w *Wallet) CreateSPVWallet(privPass []byte, seed []byte, dbDir string) error { + net := w.chainParams + w.dataDir = filepath.Join(dbDir, strconv.Itoa(w.ID)) + + if err := logNeutrino(w.dataDir); err != nil { + return fmt.Errorf("error initializing btcwallet+neutrino logging: %v", err) + } + + logDir := filepath.Join(w.dataDir, logDirName) + err := os.MkdirAll(logDir, 0744) + if err != nil { + return fmt.Errorf("error creating wallet directories: %v", err) + } + + loader := wallet.NewLoader(net, w.dataDir, true, 60*time.Second, 250) + pubPass := []byte(wallet.InsecurePubPassphrase) + + _, err = loader.CreateNewWallet(pubPass, privPass, seed, walletBirthday) + if err != nil { + return fmt.Errorf("CreateNewWallet error: %w", err) + } + + bailOnWallet := func() { + if err := loader.UnloadWallet(); err != nil { + w.log.Errorf("Error unloading wallet after createSPVWallet error: %v", err) + } + } + + neutrinoDBPath := filepath.Join(w.dataDir, neutrinoDBName) + db, err := walletdb.Create("bdb", neutrinoDBPath, true, 5*time.Second) + if err != nil { + bailOnWallet() + return fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err) + } + if err = db.Close(); err != nil { + bailOnWallet() + return fmt.Errorf("error closing newly created wallet database: %w", err) + } + + if err := loader.UnloadWallet(); err != nil { + return fmt.Errorf("error unloading wallet: %w", err) + } + + return nil +} + +func (wallet *Wallet) DataDir() string { + return wallet.dataDir +} + +// prepare gets a wallet ready for use by opening the transactions index database +// and initializing the wallet loader which can be used subsequently to create, +// load and unload the wallet. +func (w *Wallet) Prepare(rootDir string, net string, log slog.Logger) (err error) { + chainParams, err := parseChainParams(net) + if err != nil { + return err + } + + w.chainParams = chainParams + w.dataDir = filepath.Join(rootDir, strconv.Itoa(w.ID)) + w.log = log + w.loader = wallet.NewLoader(w.chainParams, w.dataDir, true, 60*time.Second, 250) + return nil +} + +func (w *Wallet) ConnectSPVWallet(ctx context.Context, wg *sync.WaitGroup) (err error) { + return w.connect(ctx, wg) +} + +// connect will start the wallet and begin syncing. +func (w *Wallet) connect(ctx context.Context, wg *sync.WaitGroup) error { + if err := logNeutrino(w.dataDir); err != nil { + return fmt.Errorf("error initializing btcwallet+neutrino logging: %v", err) + } + + err := w.startWallet() + if err != nil { + return err + } + + // txNotes := w.wallet.txNotifications() + + // Nanny for the caches checkpoints and txBlocks caches. + wg.Add(1) + // go func() { + // defer wg.Done() + // defer w.stop() + // defer txNotes.Done() + + // ticker := time.NewTicker(time.Minute * 20) + // defer ticker.Stop() + // expiration := time.Hour * 2 + // for { + // select { + // case <-ticker.C: + // w.txBlocksMtx.Lock() + // for txHash, entry := range w.txBlocks { + // if time.Since(entry.lastAccess) > expiration { + // delete(w.txBlocks, txHash) + // } + // } + // w.txBlocksMtx.Unlock() + + // w.checkpointMtx.Lock() + // for outPt, check := range w.checkpoints { + // if time.Since(check.lastAccess) > expiration { + // delete(w.checkpoints, outPt) + // } + // } + // w.checkpointMtx.Unlock() + + // case note := <-txNotes.C: + // if len(note.AttachedBlocks) > 0 { + // lastBlock := note.AttachedBlocks[len(note.AttachedBlocks)-1] + // syncTarget := atomic.LoadInt32(&w.syncTarget) + + // for ib := range note.AttachedBlocks { + // for _, nt := range note.AttachedBlocks[ib].Transactions { + // w.log.Debugf("Block %d contains wallet transaction %v", note.AttachedBlocks[ib].Height, nt.Hash) + // } + // } + + // if syncTarget == 0 || (lastBlock.Height < syncTarget && lastBlock.Height%10_000 != 0) { + // continue + // } + + // select { + // case w.tipChan <- &block{ + // hash: *lastBlock.Hash, + // height: int64(lastBlock.Height), + // }: + // default: + // w.log.Warnf("tip report channel was blocking") + // } + // } + + // case <-ctx.Done(): + // return + // } + // } + // }() + + return nil +} + +// startWallet initializes the *btcwallet.Wallet and its supporting players and +// starts syncing. +func (w *Wallet) startWallet() error { + // timeout and recoverWindow arguments borrowed from btcwallet directly. + w.loader = wallet.NewLoader(w.chainParams, w.dataDir, true, 60*time.Second, 250) + + exists, err := w.loader.WalletExists() + if err != nil { + return fmt.Errorf("error verifying wallet existence: %v", err) + } + if !exists { + return errors.New("wallet not found") + } + + w.log.Debug("Starting native BTC wallet...") + btcw, err := w.loader.OpenExistingWallet([]byte(wallet.InsecurePubPassphrase), false) + if err != nil { + return fmt.Errorf("couldn't load wallet: %w", err) + } + + bailOnWallet := func() { + if err := w.loader.UnloadWallet(); err != nil { + w.log.Errorf("Error unloading wallet: %v", err) + } + } + + neutrinoDBPath := filepath.Join(w.dataDir, neutrinoDBName) + w.neutrinoDB, err = walletdb.Create("bdb", neutrinoDBPath, true, wallet.DefaultDBTimeout) + if err != nil { + bailOnWallet() + return fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err) + } + + bailOnWalletAndDB := func() { + if err := w.neutrinoDB.Close(); err != nil { + w.log.Errorf("Error closing neutrino database: %v", err) + } + bailOnWallet() + } + + // Depending on the network, we add some addpeers or a connect peer. On + // regtest, if the peers haven't been explicitly set, add the simnet harness + // alpha node as an additional peer so we don't have to type it in. On + // mainet and testnet3, add a known reliable persistent peer to be used in + // addition to normal DNS seed-based peer discovery. + var addPeers []string + var connectPeers []string + switch w.chainParams.Net { + case wire.MainNet: + addPeers = []string{"cfilters.ssgen.io"} + case wire.TestNet3: + addPeers = []string{"dex-test.ssgen.io"} + case wire.TestNet, wire.SimNet: // plain "wire.TestNet" is regnet! + connectPeers = []string{"localhost:20575"} + } + w.log.Debug("Starting neutrino chain service...") + chainService, err := neutrino.NewChainService(neutrino.Config{ + DataDir: w.dataDir, + Database: w.neutrinoDB, + ChainParams: *w.chainParams, + PersistToDisk: true, // keep cfilter headers on disk for efficient rescanning + AddPeers: addPeers, + ConnectPeers: connectPeers, + // WARNING: PublishTransaction currently uses the entire duration + // because if an external bug, but even if the resolved, a typical + // inv/getdata round trip is ~4 seconds, so we set this so neutrino does + // not cancel queries too readily. + BroadcastTimeout: 6 * time.Second, + }) + if err != nil { + bailOnWalletAndDB() + return fmt.Errorf("couldn't create Neutrino ChainService: %v", err) + } + + bailOnEverything := func() { + if err := chainService.Stop(); err != nil { + w.log.Errorf("Error closing neutrino chain service: %v", err) + } + bailOnWalletAndDB() + } + + w.cl = chainService + w.chainClient = chain.NewNeutrinoClient(w.chainParams, chainService) + // w.wallet = &walletExtender{btcw, w.chainParams} + + // oldBday := btcw.Manager.Birthday() + // wdb := btcw.Database() + + // performRescan := w.birthday.Before(oldBday) + // if performRescan && !w.allowAutomaticRescan { + // bailOnWalletAndDB() + // return errors.New("cannot set earlier birthday while there are active deals") + // } + + // if !oldBday.Equal(w.birthday) { + // err = walletdb.Update(wdb, func(dbtx walletdb.ReadWriteTx) error { + // ns := dbtx.ReadWriteBucket(wAddrMgrBkt) + // return btcw.Manager.SetBirthday(ns, w.birthday) + // }) + // if err != nil { + // w.log.Errorf("Failed to reset wallet manager birthday: %v", err) + // performRescan = false + // } + // } + + // if performRescan { + // w.forceRescan() + // } + + if err = w.chainClient.Start(); err != nil { // lazily starts connmgr + bailOnEverything() + return fmt.Errorf("couldn't start Neutrino client: %v", err) + } + + w.log.Info("Synchronizing wallet with network...") + btcw.SynchronizeRPC(w.chainClient) + + return nil +} diff --git a/go.mod b/go.mod index 8dcbb46bc..d98e7234e 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 + github.com/planetdecred/dcrlibwallet/btcwallet v0.0.0-00010101000000-000000000000 github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d go.etcd.io/bbolt v1.3.6 golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f @@ -43,6 +44,9 @@ require ( // and dcrdex (v1.10.3) but only v1.10.4 and above can be compiled for // the android OS using gomobile. This replace can be removed once any // of those projects update their github.com/lib/pq dependency. -replace github.com/lib/pq => github.com/lib/pq v1.10.4 +replace ( + github.com/lib/pq => github.com/lib/pq v1.10.4 + github.com/planetdecred/dcrlibwallet/btcwallet => ./btcwallet // TODO: testing purpose, will remove when have new version +) go 1.16 diff --git a/multiwallet.go b/multiwallet.go index fff078d1c..194ccdab4 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -32,6 +32,8 @@ type MultiWallet struct { cancelFuncs []context.CancelFunc dexClient *DexClient + + btcWallet *btcWallet } func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWallet, error) { @@ -93,6 +95,10 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall log.Errorf("DEX client set up error: %v", err) } + if err = mw.initBtcWallet(); err != nil { + log.Errorf("BTC wallet set up error: %v", err) + } + return mw, nil } From 861137d563ba26e21675d29abdc922f0be554030 Mon Sep 17 00:00:00 2001 From: dreacot Date: Fri, 29 Jul 2022 21:56:35 +0100 Subject: [PATCH 11/16] add btc package --- wallets/btc/go.mod | 20 ++ wallets/btc/wallet.go | 743 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 763 insertions(+) create mode 100644 wallets/btc/go.mod create mode 100644 wallets/btc/wallet.go diff --git a/wallets/btc/go.mod b/wallets/btc/go.mod new file mode 100644 index 000000000..b803a1073 --- /dev/null +++ b/wallets/btc/go.mod @@ -0,0 +1,20 @@ +module github.com/planetdecred/dcrlibwallet/wallets/btc + +require ( + decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 + github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 + github.com/btcsuite/btcd v0.22.1 + github.com/btcsuite/btcd/btcutil v1.1.1 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f + github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // note: hoists btcd's own require of btcutil + github.com/btcsuite/btcwallet v0.12.0 + github.com/btcsuite/btcwallet/walletdb v1.4.0 + github.com/btcsuite/btcwallet/wtxmgr v1.3.0 + github.com/decred/slog v1.2.0 + github.com/jrick/logrotate v1.0.0 + github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 + github.com/planetdecred/dcrlibwallet v1.7.0 // indirect +) + +go 1.16 diff --git a/wallets/btc/wallet.go b/wallets/btc/wallet.go new file mode 100644 index 000000000..eac447967 --- /dev/null +++ b/wallets/btc/wallet.go @@ -0,0 +1,743 @@ +package btc + +import ( + "context" + "encoding/json" + // "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "sync" + "sync/atomic" + "strings" + "time" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btclog" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/gcs" + "github.com/btcsuite/btcwallet/chain" + // "github.com/btcsuite/btcwallet/wallet" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + w "github.com/btcsuite/btcwallet/wallet" + "github.com/btcsuite/btcwallet/walletdb" + // "github.com/planetdecred/dcrlibwallet" + _ "github.com/btcsuite/btcwallet/walletdb/bdb" // bdb init() registers a driver + "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/decred/slog" + "github.com/jrick/logrotate/rotator" + "github.com/lightninglabs/neutrino" + "github.com/lightninglabs/neutrino/headerfs" + "decred.org/dcrwallet/v2/errors" + + "github.com/asdine/storm" + "github.com/asdine/storm/q" +) + +type Wallet struct { + ID int `storm:"id,increment"` + Name string `storm:"unique"` + CreatedAt time.Time `storm:"index"` + EncryptedSeed []byte + + cl neutrinoService + neutrinoDB walletdb.DB + chainClient *chain.NeutrinoClient + + dataDir string + db *storm.DB + cancelFuncs []context.CancelFunc + + chainParams *chaincfg.Params + loader *w.Loader + log slog.Logger + birthday time.Time + // mw dcrlibwallet.MultiWallet +} + +// neutrinoService is satisfied by *neutrino.ChainService. +type neutrinoService interface { + GetBlockHash(int64) (*chainhash.Hash, error) + BestBlock() (*headerfs.BlockStamp, error) + Peers() []*neutrino.ServerPeer + GetBlockHeight(hash *chainhash.Hash) (int32, error) + GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, error) + GetCFilter(blockHash chainhash.Hash, filterType wire.FilterType, options ...neutrino.QueryOption) (*gcs.Filter, error) + GetBlock(blockHash chainhash.Hash, options ...neutrino.QueryOption) (*btcutil.Block, error) + Stop() error +} + +var _ neutrinoService = (*neutrino.ChainService)(nil) + +var ( + walletBirthday time.Time + loggingInited uint32 +) + +const ( + neutrinoDBName = "neutrino.db" + logDirName = "logs" + logFileName = "neutrino.log" +) + +func NewSpvWallet(walletName string, encryptedSeed []byte, net string, log slog.Logger) (*Wallet, error) { + chainParams, err := parseChainParams(net) + if err != nil { + return nil, err + } + + return &Wallet{ + Name: walletName, + chainParams: chainParams, + CreatedAt: time.Now(), + EncryptedSeed: encryptedSeed, + log: log, + }, nil +} + +func parseChainParams(net string) (*chaincfg.Params, error) { + switch net { + case "mainnet": + return &chaincfg.MainNetParams, nil + case "testnet3": + return &chaincfg.TestNet3Params, nil + case "regtest", "regnet", "simnet": + return &chaincfg.RegressionNetParams, nil + } + return nil, fmt.Errorf("unknown network ID %v", net) +} + +// logWriter implements an io.Writer that outputs to a rotating log file. +type logWriter struct { + *rotator.Rotator +} + +// logNeutrino initializes logging in the neutrino + wallet packages. Logging +// only has to be initialized once, so an atomic flag is used internally to +// return early on subsequent invocations. +// +// In theory, the the rotating file logger must be Close'd at some point, but +// there are concurrency issues with that since btcd and btcwallet have +// unsupervised goroutines still running after shutdown. So we leave the rotator +// running at the risk of losing some logs. +func logNeutrino(walletDir string) error { + if !atomic.CompareAndSwapUint32(&loggingInited, 0, 1) { + return nil + } + + logSpinner, err := logRotator(walletDir) + if err != nil { + return fmt.Errorf("error initializing log rotator: %w", err) + } + + backendLog := btclog.NewBackend(logWriter{logSpinner}) + + logger := func(name string, lvl btclog.Level) btclog.Logger { + l := backendLog.Logger(name) + l.SetLevel(lvl) + return l + } + + neutrino.UseLogger(logger("NTRNO", btclog.LevelDebug)) + w.UseLogger(logger("BTCW", btclog.LevelInfo)) + wtxmgr.UseLogger(logger("TXMGR", btclog.LevelInfo)) + chain.UseLogger(logger("CHAIN", btclog.LevelInfo)) + + return nil +} + +// logRotator initializes a rotating file logger. +func logRotator(netDir string) (*rotator.Rotator, error) { + const maxLogRolls = 8 + logDir := filepath.Join(netDir, logDirName) + if err := os.MkdirAll(logDir, 0744); err != nil { + return nil, fmt.Errorf("error creating log directory: %w", err) + } + + logFilename := filepath.Join(logDir, logFileName) + return rotator.New(logFilename, 32*1024, false, maxLogRolls) +} +func (wallet *Wallet) RawRequest(method string, params []json.RawMessage) (json.RawMessage, error) { + // Not needed for spv wallet. + return nil, errors.New("RawRequest not available on spv") +} + +// createSPVWallet creates a new SPV wallet. +// func (wallet *Wallet) CreateWallet(privPass []byte, seed []byte, dbDir string) error { +// net := wallet.chainParams +// wallet.dataDir = filepath.Join(dbDir, strconv.Itoa(wallet.ID)) + +// if err := logNeutrino(wallet.dataDir); err != nil { +// return fmt.Errorf("error initializing btcwallet+neutrino logging: %v", err) +// } + +// logDir := filepath.Join(wallet.dataDir, logDirName) +// err := os.MkdirAll(logDir, 0744) +// if err != nil { +// return fmt.Errorf("error creating wallet directories: %v", err) +// } + +// loader := w.NewLoader(net, wallet.dataDir, true, 60*time.Second, 250) +// pubPass := []byte(w.InsecurePubPassphrase) + +// _, err = loader.CreateNewWallet(pubPass, privPass, seed, walletBirthday) +// if err != nil { +// return fmt.Errorf("CreateNewWallet error: %w", err) +// } + +// bailOnWallet := func() { +// if err := loader.UnloadWallet(); err != nil { +// wallet.log.Errorf("Error unloading wallet after createSPVWallet error: %v", err) +// } +// } + +// neutrinoDBPath := filepath.Join(wallet.dataDir, neutrinoDBName) +// db, err := walletdb.Create("bdb", neutrinoDBPath, true, 5*time.Second) +// if err != nil { +// bailOnWallet() +// return fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err) +// } +// if err = db.Close(); err != nil { +// bailOnWallet() +// return fmt.Errorf("error closing newly created wallet database: %w", err) +// } + +// if err := loader.UnloadWallet(); err != nil { +// return fmt.Errorf("error unloading wallet: %w", err) +// } + +// return nil +// } + +func (wallet *Wallet) DataDir() string { + return wallet.dataDir +} + +func (wallet *Wallet) ConnectSPVWallet(ctx context.Context, wg *sync.WaitGroup) (err error) { + return wallet.connect(ctx, wg) +} + +// connect will start the wallet and begin syncing. +func (wallet *Wallet) connect(ctx context.Context, wg *sync.WaitGroup) error { + if err := logNeutrino(wallet.dataDir); err != nil { + return fmt.Errorf("error initializing btcwallet+neutrino logging: %v", err) + } + + err := wallet.startWallet() + if err != nil { + return err + } + + // txNotes := wallet.wallet.txNotifications() + + // Nanny for the caches checkpoints and txBlocks caches. + wg.Add(1) + // go func() { + // defer wg.Done() + // defer wallet.stop() + // defer txNotes.Done() + + // ticker := time.NewTicker(time.Minute * 20) + // defer ticker.Stop() + // expiration := time.Hour * 2 + // for { + // select { + // case <-ticker.C: + // wallet.txBlocksMtx.Lock() + // for txHash, entry := range wallet.txBlocks { + // if time.Since(entry.lastAccess) > expiration { + // delete(wallet.txBlocks, txHash) + // } + // } + // wallet.txBlocksMtx.Unlock() + + // wallet.checkpointMtx.Lock() + // for outPt, check := range wallet.checkpoints { + // if time.Since(check.lastAccess) > expiration { + // delete(wallet.checkpoints, outPt) + // } + // } + // wallet.checkpointMtx.Unlock() + + // case note := <-txNotes.C: + // if len(note.AttachedBlocks) > 0 { + // lastBlock := note.AttachedBlocks[len(note.AttachedBlocks)-1] + // syncTarget := atomic.LoadInt32(&wallet.syncTarget) + + // for ib := range note.AttachedBlocks { + // for _, nt := range note.AttachedBlocks[ib].Transactions { + // wallet.log.Debugf("Block %d contains wallet transaction %v", note.AttachedBlocks[ib].Height, nt.Hash) + // } + // } + + // if syncTarget == 0 || (lastBlock.Height < syncTarget && lastBlock.Height%10_000 != 0) { + // continue + // } + + // select { + // case wallet.tipChan <- &block{ + // hash: *lastBlock.Hash, + // height: int64(lastBlock.Height), + // }: + // default: + // wallet.log.Warnf("tip report channel was blocking") + // } + // } + + // case <-ctx.Done(): + // return + // } + // } + // }() + + return nil +} + +// startWallet initializes the *btcwallet.Wallet and its supporting players and +// starts syncing. +func (wallet *Wallet) startWallet() error { + // timeout and recoverWindow arguments borrowed from btcwallet directly. + wallet.loader = w.NewLoader(wallet.chainParams, wallet.dataDir, true, 60*time.Second, 250) + + exists, err := wallet.loader.WalletExists() + if err != nil { + return fmt.Errorf("error verifying wallet existence: %v", err) + } + if !exists { + return errors.New("wallet not found") + } + + wallet.log.Debug("Starting native BTC wallet...") + btcw, err := wallet.loader.OpenExistingWallet([]byte(w.InsecurePubPassphrase), false) + if err != nil { + return fmt.Errorf("couldn't load wallet: %w", err) + } + + bailOnWallet := func() { + if err := wallet.loader.UnloadWallet(); err != nil { + wallet.log.Errorf("Error unloading wallet: %v", err) + } + } + + neutrinoDBPath := filepath.Join(wallet.dataDir, neutrinoDBName) + wallet.neutrinoDB, err = walletdb.Create("bdb", neutrinoDBPath, true, w.DefaultDBTimeout) + if err != nil { + bailOnWallet() + return fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err) + } + + bailOnWalletAndDB := func() { + if err := wallet.neutrinoDB.Close(); err != nil { + wallet.log.Errorf("Error closing neutrino database: %v", err) + } + bailOnWallet() + } + + // Depending on the network, we add some addpeers or a connect peer. On + // regtest, if the peers haven't been explicitly set, add the simnet harness + // alpha node as an additional peer so we don't have to type it in. On + // mainet and testnet3, add a known reliable persistent peer to be used in + // addition to normal DNS seed-based peer discovery. + var addPeers []string + var connectPeers []string + switch wallet.chainParams.Net { + case wire.MainNet: + addPeers = []string{"cfilters.ssgen.io"} + case wire.TestNet3: + addPeers = []string{"dex-test.ssgen.io"} + case wire.TestNet, wire.SimNet: // plain "wire.TestNet" is regnet! + connectPeers = []string{"localhost:20575"} + } + wallet.log.Debug("Starting neutrino chain service...") + chainService, err := neutrino.NewChainService(neutrino.Config{ + DataDir: wallet.dataDir, + Database: wallet.neutrinoDB, + ChainParams: *wallet.chainParams, + PersistToDisk: true, // keep cfilter headers on disk for efficient rescanning + AddPeers: addPeers, + ConnectPeers: connectPeers, + // WARNING: PublishTransaction currently uses the entire duration + // because if an external bug, but even if the resolved, a typical + // inv/getdata round trip is ~4 seconds, so we set this so neutrino does + // not cancel queries too readily. + BroadcastTimeout: 6 * time.Second, + }) + if err != nil { + bailOnWalletAndDB() + return fmt.Errorf("couldn't create Neutrino ChainService: %v", err) + } + + bailOnEverything := func() { + if err := chainService.Stop(); err != nil { + wallet.log.Errorf("Error closing neutrino chain service: %v", err) + } + bailOnWalletAndDB() + } + + wallet.cl = chainService + wallet.chainClient = chain.NewNeutrinoClient(wallet.chainParams, chainService) + // wallet.wallet = &walletExtender{btcw, wallet.chainParams} + + // oldBday := btcw.Manager.Birthday() + // wdb := btcw.Database() + + // performRescan := wallet.birthday.Before(oldBday) + // if performRescan && !wallet.allowAutomaticRescan { + // bailOnWalletAndDB() + // return errors.New("cannot set earlier birthday while there are active deals") + // } + + // if !oldBday.Equal(wallet.birthday) { + // err = walletdb.Update(wdb, func(dbtx walletdb.ReadWriteTx) error { + // ns := dbtx.ReadWriteBucket(wAddrMgrBkt) + // return btcw.Manager.SetBirthday(ns, wallet.birthday) + // }) + // if err != nil { + // wallet.log.Errorf("Failed to reset wallet manager birthday: %v", err) + // performRescan = false + // } + // } + + // if performRescan { + // wallet.forceRescan() + // } + + if err = wallet.chainClient.Start(); err != nil { // lazily starts connmgr + bailOnEverything() + return fmt.Errorf("couldn't start Neutrino client: %v", err) + } + + wallet.log.Info("Synchronizing wallet with network...") + btcw.SynchronizeRPC(wallet.chainClient) + + return nil +} + +// prepare gets a wallet ready for use by opening the transactions index database +// and initializing the wallet loader which can be used subsequently to create, +// load and unload the wallet. +func (wallet *Wallet) Prepare(rootDir string, net string, log slog.Logger) (err error) { + chainParams, err := parseChainParams(net) + if err != nil { + return err + } + + wallet.chainParams = chainParams + wallet.dataDir = filepath.Join(rootDir, strconv.Itoa(wallet.ID)) + wallet.log = log + wallet.loader = w.NewLoader(wallet.chainParams, wallet.dataDir, true, 60*time.Second, 250) + return nil +} + +func (wallet *Wallet) NetType() string { + return wallet.chainParams.Name +} + +func (wallet *Wallet) initBtcWallet(rootDir string) error { + // if btcWallet.BtcWallet != nil { + // return nil + // } + + btcDataDir := filepath.Join(rootDir, "btc") + + err := os.MkdirAll(btcDataDir, os.ModePerm) + if err != nil { + return err + } + + walletsDB, err := storm.Open(filepath.Join(btcDataDir, "wallets.db")) + if err != nil { + return err + } + + // init database for saving/reading wallet objects + err = walletsDB.Init(&Wallet{}) + if err != nil { + // log.Errorf("Error initializing wallets database BTC: %s", err.Error()) + return err + } + + wallet = &Wallet{ + dataDir: filepath.Join(rootDir, "btc"), + db: walletsDB, + // wallets: make(map[int]*Wallet), + } + + // read saved wallets info from db and initialize wallets + query := walletsDB.Select(q.True()).OrderBy("ID") + var wallets []*Wallet + err = query.Find(&wallets) + if err != nil && err != storm.ErrNotFound { + return err + } + + // prepare the wallets loaded from db for use + // for _, wallet := range wallets { + // // err = wallet.Prepare(btcWallet.btcDataDir, btcWallet.NetType(), log) + // // if err == nil && !WalletExistsAt(wallet.DataDir()) { + // // err = fmt.Errorf("missing wallet database file") + // // } + // if err != nil { + // // log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) + // } else { + // btcWallet.wallets[wallet.ID] = wallet + // } + // } + + return nil +} + +func (wallet *Wallet) createWallet(privatePassphrase string, seedMnemonic []byte) error { + // log.Info("Creating Wallet") + if len(seedMnemonic) == 0 { + return errors.New("ErrEmptySeed") + } + + pubPass := []byte(w.InsecurePubPassphrase) + privPass := []byte(privatePassphrase) + // seed, err := walletseed.DecodeUserInput(seedMnemonic) + // if err != nil { + // // log.Error(err) + // return err + // } + + _, err := wallet.loader.CreateNewWallet(pubPass, privPass, seedMnemonic, wallet.CreatedAt) + if err != nil { + // log.Error(err) + return err + } + + bailOnWallet := func() { + if err := wallet.loader.UnloadWallet(); err != nil { + fmt.Errorf("Error unloading wallet after createSPVWallet error: %v", err) + } + } + + neutrinoDBPath := filepath.Join(wallet.dataDir, neutrinoDBName) + db, err := walletdb.Create("bdb", neutrinoDBPath, true, 5*time.Second) + if err != nil { + bailOnWallet() + return fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err) + } + if err = db.Close(); err != nil { + bailOnWallet() + return fmt.Errorf("error closing newly created wallet database: %w", err) + } + + if err := wallet.loader.UnloadWallet(); err != nil { + return fmt.Errorf("error unloading wallet: %w", err) + } + + // log.Info("Created Wallet") + return nil +} + +func (wallet *Wallet) CreateNewWallet(rootDir, walletName, privatePassphrase string) (*Wallet, error) { + seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) + if err != nil { + return nil, err + } + + // encryptedSeed, err := encryptWalletSeed([]byte(privatePassphrase), seed) + // if err != nil { + // return nil, err + // } + + btcWallet := &Wallet{ + Name: walletName, + CreatedAt: time.Now(), + EncryptedSeed: seed, + } + + return btcWallet.saveNewWallet( func() error { + err := wallet.Prepare(rootDir, "testnet3", nil) + if err != nil { + return err + } + + return wallet.createWallet(privatePassphrase, seed) + }) +} + +// saveNewWallet performs the following tasks using a db batch operation to ensure +// that db changes are rolled back if any of the steps below return an error. +// +// - saves the initial wallet info to btcWallet.walletsDb to get a wallet id +// - creates a data directory for the wallet using the auto-generated wallet id +// - updates the initial wallet info with name, dataDir (created above), db driver +// and saves the updated info to btcWallet.walletsDb +// - calls the provided `setupWallet` function to perform any necessary creation, +// restoration or linking of the just saved wallet +// +// IFF all the above operations succeed, the wallet info will be persisted to db +// and the wallet will be added to `btcWallet.wallets`. +func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { + exists, err := wallet.WalletNameExists() + if err != nil { + return nil, err + } else if exists { + return nil, errors.New(ErrExist) + } + + // if btcWallet.IsConnectedToDecredNetwork() { + // btcWallet.CancelSync() + // defer btcWallet.SpvSync() + // } + // Perform database save operations in batch transaction + // for automatic rollback if error occurs at any point. + // walletNameExists := func(walletName string) (bool, error) { + // if strings.HasPrefix(walletName, "wallet-") { + // return false, errors.E(ErrReservedWalletName) + // } + + // err := wallet.db.One("Name", walletName, &Wallet{}) + // if err == nil { + // return true, nil + // } else if err != storm.ErrNotFound { + // return false, err + // } + + // return false, nil + // } + + // exists, err := walletNameExists(wallet.Name) + // if err != nil { + // return nil, err + // } else if exists { + // return nil, errors.New(ErrExist) + // } + + batchDbTransaction := func(dbOp func(node storm.Node) error) (err error) { + dbTx, err := wallet.db.Begin(true) + if err != nil { + return err + } + + // Commit or rollback the transaction after f returns or panics. Do not + // recover from the panic to keep the original stack trace intact. + panicked := true + defer func() { + if panicked || err != nil { + dbTx.Rollback() + return + } + + err = dbTx.Commit() + }() + + err = dbOp(dbTx) + panicked = false + return err + } + + // Perform database save operations in batch transaction + // for automatic rollback if error occurs at any point. + err = batchDbTransaction(func(db storm.Node) error { + // saving struct to update ID property with an auto-generated value + err := db.Save(wallet) + if err != nil { + return err + } + walletDataDir := filepath.Join(wallet.dataDir, strconv.Itoa(wallet.ID)) + + dirExists, err := fileExists(walletDataDir) + if err != nil { + return err + } else if dirExists { + _, err := backupFile(walletDataDir, 1) + if err != nil { + return err + } + + // log.Infof("Undocumented file at %s moved to %s", walletDataDir, newDirName) + } + + os.MkdirAll(walletDataDir, os.ModePerm) // create wallet dir + + if wallet.Name == "" { + wallet.Name = "wallet-" + strconv.Itoa(wallet.ID) // wallet-# + } + err = db.Save(wallet) // update database with complete wallet information + if err != nil { + return err + } + + return setupWallet() + // err = wallet.createWallet([]byte(password), encryptedSeed, wallet.dataDir) + // if err != nil { + // return fmt.Errorf("Create BTC wallet error: %v", err) + // } +// + // return nil + }) + + if err != nil { + return nil, err + } + + return wallet, nil +} + +func (wallet *Wallet) WalletNameExists() (bool, error) { + if strings.HasPrefix(wallet.Name, "wallet-") { + return false, errors.E(ErrReservedWalletName) + } + + err := wallet.db.One("Name", wallet.Name, &Wallet{}) + if err == nil { + return true, nil + } else if err != storm.ErrNotFound { + return false, err + } + + return false, nil +} + +func fileExists(filePath string) (bool, error) { + _, err := os.Stat(filePath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func backupFile(fileName string, suffix int) (newName string, err error) { + newName = fileName + ".bak" + strconv.Itoa(suffix) + exists, err := fileExists(newName) + if err != nil { + return "", err + } else if exists { + return backupFile(fileName, suffix+1) + } + + err = moveFile(fileName, newName) + if err != nil { + return "", err + } + + return newName, nil +} + +func moveFile(sourcePath, destinationPath string) error { + if exists, _ := fileExists(sourcePath); exists { + return os.Rename(sourcePath, destinationPath) + } + return nil +} + +func (wallet *Wallet) shutdownContextWithCancel() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + wallet.cancelFuncs = append(wallet.cancelFuncs, cancel) + return ctx, cancel +} + +func (wallet *Wallet) shutdownContext() (ctx context.Context) { + ctx, _ = wallet.shutdownContextWithCancel() + return +} \ No newline at end of file From dc3e86ccb3fb324c4428fdf62c69ca4e117bddda Mon Sep 17 00:00:00 2001 From: dreacot Date: Mon, 15 Aug 2022 10:27:15 +0100 Subject: [PATCH 12/16] - rebase with #257(dcr package PR), restructure multiwallet to hold mutiple assets, fix crash while creating a wallet --- btc.go | 51 ++ btcwallet.go | 193 ----- go.mod | 14 +- go.sum | 13 +- multiwallet.go | 144 +++- wallets/btc/errors.go | 61 ++ wallets/btc/go.mod | 6 +- wallets/btc/go.sum | 1434 ++++++++++++++++++++++++++++++++++ wallets/btc/wallet.go | 252 ++---- wallets/btc/wallet_utils.go | 130 +++ wallets/dcr/politeia.go | 22 +- wallets/dcr/politeia_sync.go | 2 +- wallets/dcr/wallet.go | 20 +- wallets/dcr/wallet_config.go | 4 +- wallets/dcr/wallet_utils.go | 6 +- 15 files changed, 1925 insertions(+), 427 deletions(-) create mode 100644 btc.go delete mode 100644 btcwallet.go create mode 100644 wallets/btc/errors.go create mode 100644 wallets/btc/go.sum create mode 100644 wallets/btc/wallet_utils.go diff --git a/btc.go b/btc.go new file mode 100644 index 000000000..12f9c7d9e --- /dev/null +++ b/btc.go @@ -0,0 +1,51 @@ +package dcrlibwallet + +import ( + // "context" + // "fmt" + "os" + "path/filepath" + + "decred.org/dcrwallet/v2/errors" + + "github.com/asdine/storm" + // "github.com/asdine/storm/q" + + bolt "go.etcd.io/bbolt" + + "github.com/planetdecred/dcrlibwallet/wallets/btc" +) + +func initializeBTCWallet(rootDir, dbDriver, netType string) (*storm.DB, string, error) { + var btcDB *storm.DB + + rootDir = filepath.Join(rootDir, netType, "btc") + err := os.MkdirAll(rootDir, os.ModePerm) + if err != nil { + return btcDB, "", errors.Errorf("failed to create btc rootDir: %v", err) + } + + err = initLogRotator(filepath.Join(rootDir, logFileName)) + if err != nil { + return btcDB, "", errors.Errorf("failed to init btc logRotator: %v", err.Error()) + } + + btcDB, err = storm.Open(filepath.Join(rootDir, walletsDbName)) + if err != nil { + log.Errorf("Error opening btc wallets database: %s", err.Error()) + if err == bolt.ErrTimeout { + // timeout error occurs if storm fails to acquire a lock on the database file + return btcDB, "", errors.E(ErrWalletDatabaseInUse) + } + return btcDB, "", errors.Errorf("error opening btc wallets database: %s", err.Error()) + } + + // init database for saving/reading wallet objects + err = btcDB.Init(&btc.Wallet{}) + if err != nil { + log.Errorf("Error initializing btc wallets database: %s", err.Error()) + return btcDB, "", err + } + + return btcDB, rootDir, nil +} diff --git a/btcwallet.go b/btcwallet.go deleted file mode 100644 index 62bc4c894..000000000 --- a/btcwallet.go +++ /dev/null @@ -1,193 +0,0 @@ -package dcrlibwallet - -import ( - "context" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "sync" - - "decred.org/dcrwallet/v2/errors" - "github.com/asdine/storm" - "github.com/asdine/storm/q" - "github.com/planetdecred/dcrlibwallet/btcwallet" -) - -const walletTypeSPV = "SPV" - -type btcWallet struct { - db *storm.DB - wallets map[int]*btcwallet.Wallet - cancelCoreCtx context.CancelFunc - btcDataDir string -} - -func (mw *MultiWallet) initBtcWallet() error { - if mw.btcWallet != nil { - return nil - } - - btcDataDir := filepath.Join(mw.rootDir, "btc") - - err := os.MkdirAll(btcDataDir, os.ModePerm) - if err != nil { - return err - } - - walletsDB, err := storm.Open(filepath.Join(btcDataDir, walletsDbName)) - if err != nil { - return err - } - - // init database for saving/reading wallet objects - err = walletsDB.Init(&btcwallet.Wallet{}) - if err != nil { - log.Errorf("Error initializing wallets database BTC: %s", err.Error()) - return err - } - - mw.btcWallet = &btcWallet{ - btcDataDir: filepath.Join(mw.rootDir, "btc"), - db: walletsDB, - wallets: make(map[int]*btcwallet.Wallet), - } - - // read saved wallets info from db and initialize wallets - query := walletsDB.Select(q.True()).OrderBy("ID") - var wallets []*btcwallet.Wallet - err = query.Find(&wallets) - if err != nil && err != storm.ErrNotFound { - return err - } - - // prepare the wallets loaded from db for use - for _, wallet := range wallets { - err = wallet.Prepare(mw.btcWallet.btcDataDir, mw.NetType(), log) - if err == nil && !WalletExistsAt(wallet.DataDir()) { - err = fmt.Errorf("missing wallet database file") - } - if err != nil { - log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) - } else { - mw.btcWallet.wallets[wallet.ID] = wallet - } - } - - return nil -} - -func (mw *MultiWallet) CreateNewBTCWallet(walletName, password string) (*btcwallet.Wallet, error) { - seed := "witch collapse practice feed shame open despair" - encryptedSeed := []byte(seed) - - wallet, err := btcwallet.NewSpvWallet(walletName, encryptedSeed, mw.NetType(), log) - if err != nil { - return nil, err - } - - walletNameExists := func(walletName string) (bool, error) { - if strings.HasPrefix(walletName, "wallet-") { - return false, errors.E(ErrReservedWalletName) - } - - err := mw.btcWallet.db.One("Name", walletName, &btcwallet.Wallet{}) - if err == nil { - return true, nil - } else if err != storm.ErrNotFound { - return false, err - } - - return false, nil - } - - exists, err := walletNameExists(wallet.Name) - if err != nil { - return nil, err - } else if exists { - return nil, errors.New(ErrExist) - } - - batchDbTransaction := func(dbOp func(node storm.Node) error) (err error) { - dbTx, err := mw.btcWallet.db.Begin(true) - if err != nil { - return err - } - - // Commit or rollback the transaction after f returns or panics. Do not - // recover from the panic to keep the original stack trace intact. - panicked := true - defer func() { - if panicked || err != nil { - dbTx.Rollback() - return - } - - err = dbTx.Commit() - }() - - err = dbOp(dbTx) - panicked = false - return err - } - - // Perform database save operations in batch transaction - // for automatic rollback if error occurs at any point. - err = batchDbTransaction(func(db storm.Node) error { - // saving struct to update ID property with an auto-generated value - err := db.Save(wallet) - if err != nil { - return err - } - walletDataDir := filepath.Join(mw.btcWallet.btcDataDir, strconv.Itoa(wallet.ID)) - - dirExists, err := fileExists(walletDataDir) - if err != nil { - return err - } else if dirExists { - newDirName, err := backupFile(walletDataDir, 1) - if err != nil { - return err - } - - log.Infof("Undocumented file at %s moved to %s", walletDataDir, newDirName) - } - - os.MkdirAll(walletDataDir, os.ModePerm) // create wallet dir - - if wallet.Name == "" { - wallet.Name = "wallet-" + strconv.Itoa(wallet.ID) // wallet-# - } - err = db.Save(wallet) // update database with complete wallet information - if err != nil { - return err - } - - err = wallet.CreateSPVWallet([]byte(password), encryptedSeed, mw.btcWallet.btcDataDir) - if err != nil { - return fmt.Errorf("Create BTC wallet error: %v", err) - } - - return nil - }) - - if err != nil { - return nil, err - } - - return wallet, nil -} - -func (mw *MultiWallet) ConnectBTCSPVWallets() error { - ctx, _ := mw.contextWithShutdownCancel() - var wg sync.WaitGroup - for _, wall := range mw.btcWallet.wallets { - err := wall.ConnectSPVWallet(ctx, &wg) - if err != nil { - return err - } - } - wg.Wait() - return nil -} diff --git a/go.mod b/go.mod index d98e7234e..e1b8734db 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,8 @@ module github.com/planetdecred/dcrlibwallet require ( decred.org/dcrdex v0.4.1 decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 - github.com/DataDog/zstd v1.4.8 // indirect github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 - github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a // indirect - github.com/dchest/siphash v1.2.3 // indirect - github.com/decred/base58 v1.0.4 // indirect + github.com/btcsuite/btcd v0.22.1 github.com/decred/dcrd/addrmgr/v2 v2.0.0 github.com/decred/dcrd/blockchain/stake/v4 v4.0.0 github.com/decred/dcrd/chaincfg/chainhash v1.0.3 @@ -24,20 +21,15 @@ require ( github.com/decred/politeia v1.3.1 github.com/decred/slog v1.2.0 github.com/dgraph-io/badger v1.6.2 - github.com/gorilla/websocket v1.5.0 // indirect github.com/jrick/logrotate v1.0.0 github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 - github.com/planetdecred/dcrlibwallet/btcwallet v0.0.0-00010101000000-000000000000 github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d + github.com/planetdecred/dcrlibwallet/wallets/btc v0.0.0-00010101000000-000000000000 go.etcd.io/bbolt v1.3.6 golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f - golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect - golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect - google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect ) // Older versions of github.com/lib/pq are required by politeia (v1.9.0) @@ -46,7 +38,7 @@ require ( // of those projects update their github.com/lib/pq dependency. replace ( github.com/lib/pq => github.com/lib/pq v1.10.4 - github.com/planetdecred/dcrlibwallet/btcwallet => ./btcwallet // TODO: testing purpose, will remove when have new version + github.com/planetdecred/dcrlibwallet/wallets/btc => ./wallets/btc // TODO: testing purpose, will remove when have new version ) go 1.16 diff --git a/go.sum b/go.sum index 30fcd2956..d56c03dbc 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,16 @@ github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13P github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= github.com/btcsuite/btcd v0.21.0-beta.0.20210426180113-7eba688b65e5/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= -github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e h1:d0NkvbJGQThTkhypOdtf5Orxe2+dUHLB86pQ4EXwrTo= github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e/go.mod h1:3PH+KbvLFfzBTCevQenPiDedjGQGt6aa70dVjJDWGTA= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= +github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= @@ -696,6 +704,7 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M github.com/jessevdk/go-flags v0.0.0-20181221193153-c0795c8afcf4/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= @@ -891,6 +900,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/planetdecred/dcrlibwallet v1.7.0/go.mod h1:QTADevJvo+ugI0LZkwVVwCMiqtVf/W0DKlODTYwbnCs= github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d h1:egC6nx+qP3QMwKQhA+JdJReKV9NhG17Ag+3oCVCsj3c= github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d/go.mod h1:jO4RP2rgqom8CLgl3rMwZ4cGzmalJqBkKjHgVS812lM= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1239,6 +1249,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/multiwallet.go b/multiwallet.go index 194ccdab4..6193c72d9 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -11,20 +11,46 @@ import ( "decred.org/dcrwallet/v2/errors" "github.com/asdine/storm" "github.com/asdine/storm/q" + btccfg "github.com/btcsuite/btcd/chaincfg" "github.com/decred/dcrd/chaincfg/v3" + "github.com/planetdecred/dcrlibwallet/utils" + "github.com/planetdecred/dcrlibwallet/wallets/btc" "github.com/planetdecred/dcrlibwallet/wallets/dcr" "golang.org/x/crypto/bcrypt" ) +// type AllWallets struct { +// DCR *dcr.Wallet +// BTC *btc.Wallet +// } +type AllWallets struct { + DCR map[int]*dcr.Wallet + BTC map[int]*btc.Wallet +} + +type Assets struct { + DCR map[int]*dcr.Wallet + BTC struct { + Wallets map[int]*btc.Wallet + BadWallets map[int]*btc.Wallet + DBDriver string + RootDir string + DB *storm.DB + ChainParams *btccfg.Params + } +} + type MultiWallet struct { dbDriver string rootDir string db *storm.DB chainParams *chaincfg.Params + AllWallets *AllWallets + Assets *Assets wallets map[int]*dcr.Wallet badWallets map[int]*dcr.Wallet @@ -32,8 +58,6 @@ type MultiWallet struct { cancelFuncs []context.CancelFunc dexClient *DexClient - - btcWallet *btcWallet } func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWallet, error) { @@ -50,25 +74,48 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall return nil, errors.Errorf("error initializing DCRWallet: %s", err.Error()) } + btcDB, btcRootDir, err := initializeBTCWallet(rootDir, dbDriver, netType) + if err != nil { + log.Errorf("error initializing BTCWallet: %s", err.Error()) + return nil, errors.Errorf("error initializing BTCWallet: %s", err.Error()) + } + mw := &MultiWallet{ dbDriver: dbDriver, rootDir: dcrRootDir, db: dcrDB, chainParams: chainParams, - wallets: make(map[int]*dcr.Wallet), - badWallets: make(map[int]*dcr.Wallet), + Assets: &Assets{ + BTC: struct { + Wallets map[int]*btc.Wallet + BadWallets map[int]*btc.Wallet + DBDriver string + RootDir string + DB *storm.DB + ChainParams *btccfg.Params + }{ + Wallets: make(map[int]*btc.Wallet), + BadWallets: make(map[int]*btc.Wallet), + DBDriver: dbDriver, + RootDir: btcRootDir, + DB: btcDB, + ChainParams: &btccfg.TestNet3Params, + }, + }, + wallets: make(map[int]*dcr.Wallet), + badWallets: make(map[int]*dcr.Wallet), } // read saved wallets info from db and initialize wallets query := mw.db.Select(q.True()).OrderBy("ID") - var wallets []*dcr.Wallet - err = query.Find(&wallets) + var dcrWallets []*dcr.Wallet + err = query.Find(&dcrWallets) if err != nil && err != storm.ErrNotFound { return nil, err } // prepare the wallets loaded from db for use - for _, wallet := range wallets { + for _, wallet := range dcrWallets { err = wallet.Prepare(rootDir, chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) if err == nil && !WalletExistsAt(wallet.DataDir) { err = fmt.Errorf("missing wallet database file") @@ -77,13 +124,36 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall mw.badWallets[wallet.ID] = wallet log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) } else { - mw.wallets[wallet.ID] = wallet + mw.AllWallets.DCR[wallet.ID] = wallet } // initialize Politeia. wallet.NewPoliteia(politeiaHost) } + // read saved wallets info from db and initialize wallets + query = btcDB.Select(q.True()).OrderBy("ID") + var btcWallets []*btc.Wallet + err = query.Find(&btcWallets) + if err != nil && err != storm.ErrNotFound { + return nil, err + } + + // prepare the wallets loaded from db for use + for _, wallet := range btcWallets { + err = wallet.Prepare(btcRootDir, mw.NetType(), log) + if err == nil && !WalletExistsAt(wallet.DataDir()) { + err = fmt.Errorf("missing wallet database file") + } + if err != nil { + mw.Assets.BTC.BadWallets[wallet.ID] = wallet + + log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) + } else { + mw.Assets.BTC.Wallets[wallet.ID] = wallet + } + } + mw.listenForShutdown() logLevel := mw.ReadStringConfigValueForKey(LogLevelConfigKey) @@ -95,13 +165,65 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall log.Errorf("DEX client set up error: %v", err) } - if err = mw.initBtcWallet(); err != nil { - log.Errorf("BTC wallet set up error: %v", err) - } + // if err = mw.initBtcWallet(); err != nil { + // log.Errorf("BTC wallet set up error: %v", err) + // } return mw, nil } +func (mw *MultiWallet) prepareWallets() error { + // read saved wallets info from db and initialize wallets + query := mw.db.Select(q.True()).OrderBy("ID") + var dcrWallets []*dcr.Wallet + err := query.Find(&dcrWallets) + if err != nil && err != storm.ErrNotFound { + return err + } + + // prepare the wallets loaded from db for use + for _, wallet := range dcrWallets { + err = wallet.Prepare(mw.rootDir, mw.chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) + if err == nil && !WalletExistsAt(wallet.DataDir) { + err = fmt.Errorf("missing wallet database file") + } + if err != nil { + mw.badWallets[wallet.ID] = wallet + log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) + } else { + mw.AllWallets.DCR[wallet.ID] = wallet + } + + // initialize Politeia. + wallet.NewPoliteia("") + } + + // read saved wallets info from db and initialize wallets + query = mw.Assets.BTC.DB.Select(q.True()).OrderBy("ID") + var btcWallets []*btc.Wallet + err = query.Find(&btcWallets) + if err != nil && err != storm.ErrNotFound { + return err + } + + // prepare the wallets loaded from db for use + for _, wallet := range btcWallets { + err = wallet.Prepare(mw.Assets.BTC.RootDir, mw.NetType(), log) + if err == nil && !WalletExistsAt(wallet.DataDir()) { + err = fmt.Errorf("missing wallet database file") + } + if err != nil { + mw.Assets.BTC.BadWallets[wallet.ID] = wallet + + log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) + } else { + mw.Assets.BTC.Wallets[wallet.ID] = wallet + } + } + + return nil +} + func (mw *MultiWallet) Shutdown() { log.Info("Shutting down dcrlibwallet") diff --git a/wallets/btc/errors.go b/wallets/btc/errors.go new file mode 100644 index 000000000..c054ef2e8 --- /dev/null +++ b/wallets/btc/errors.go @@ -0,0 +1,61 @@ +package btc + +import ( + "decred.org/dcrwallet/v2/errors" + "github.com/asdine/storm" +) + +const ( + // Error Codes + ErrInsufficientBalance = "insufficient_balance" + ErrInvalid = "invalid" + ErrWalletLocked = "wallet_locked" + ErrWalletDatabaseInUse = "wallet_db_in_use" + ErrWalletNotLoaded = "wallet_not_loaded" + ErrWalletNotFound = "wallet_not_found" + ErrWalletNameExist = "wallet_name_exists" + ErrReservedWalletName = "wallet_name_reserved" + ErrWalletIsRestored = "wallet_is_restored" + ErrWalletIsWatchOnly = "watch_only_wallet" + ErrUnusableSeed = "unusable_seed" + ErrPassphraseRequired = "passphrase_required" + ErrInvalidPassphrase = "invalid_passphrase" + ErrNotConnected = "not_connected" + ErrExist = "exists" + ErrNotExist = "not_exists" + ErrEmptySeed = "empty_seed" + ErrInvalidAddress = "invalid_address" + ErrInvalidAuth = "invalid_auth" + ErrUnavailable = "unavailable" + ErrContextCanceled = "context_canceled" + ErrFailedPrecondition = "failed_precondition" + ErrSyncAlreadyInProgress = "sync_already_in_progress" + ErrNoPeers = "no_peers" + ErrInvalidPeers = "invalid_peers" + ErrListenerAlreadyExist = "listener_already_exist" + ErrLoggerAlreadyRegistered = "logger_already_registered" + ErrLogRotatorAlreadyInitialized = "log_rotator_already_initialized" + ErrAddressDiscoveryNotDone = "address_discovery_not_done" + ErrChangingPassphrase = "err_changing_passphrase" + ErrSavingWallet = "err_saving_wallet" + ErrIndexOutOfRange = "err_index_out_of_range" + ErrNoMixableOutput = "err_no_mixable_output" + ErrInvalidVoteBit = "err_invalid_vote_bit" +) + +// todo, should update this method to translate more error kinds. +func translateError(err error) error { + if err, ok := err.(*errors.Error); ok { + switch err.Kind { + case errors.InsufficientBalance: + return errors.New(ErrInsufficientBalance) + case errors.NotExist, storm.ErrNotFound: + return errors.New(ErrNotExist) + case errors.Passphrase: + return errors.New(ErrInvalidPassphrase) + case errors.NoPeers: + return errors.New(ErrNoPeers) + } + } + return err +} diff --git a/wallets/btc/go.mod b/wallets/btc/go.mod index b803a1073..2c38a6b6e 100644 --- a/wallets/btc/go.mod +++ b/wallets/btc/go.mod @@ -4,17 +4,21 @@ require ( decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 github.com/btcsuite/btcd v0.22.1 - github.com/btcsuite/btcd/btcutil v1.1.1 + github.com/btcsuite/btcd/btcutil v1.1.1 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // note: hoists btcd's own require of btcutil github.com/btcsuite/btcwallet v0.12.0 github.com/btcsuite/btcwallet/walletdb v1.4.0 github.com/btcsuite/btcwallet/wtxmgr v1.3.0 + github.com/decred/dcrd/addrmgr/v2 v2.0.0 // indirect + github.com/decred/dcrd/connmgr/v3 v3.1.0 // indirect github.com/decred/slog v1.2.0 github.com/jrick/logrotate v1.0.0 + github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 // indirect github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 github.com/planetdecred/dcrlibwallet v1.7.0 // indirect + golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect ) go 1.16 diff --git a/wallets/btc/go.sum b/wallets/btc/go.sum new file mode 100644 index 000000000..bf621ae16 --- /dev/null +++ b/wallets/btc/go.sum @@ -0,0 +1,1434 @@ +bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= +bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= +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.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +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.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +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.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= +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/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +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/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= +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= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +decred.org/cspp v0.3.0 h1:2AkSsWzA7HIMZImfw0gT82Gdp8OXIM4NsBn7vna22uE= +decred.org/cspp v0.3.0/go.mod h1:UygjYilC94dER3BEU65Zzyoqy9ngJfWCD2rdJqvUs2A= +decred.org/cspp/v2 v2.0.0-20211122173608-ee00e4952d5f/go.mod h1:USyJS44Kqxz2wT/VaNsf9iTAONegO/qKXRdLg1nvrWI= +decred.org/cspp/v2 v2.0.0-20211207170141-a6b5f958a91f/go.mod h1:USyJS44Kqxz2wT/VaNsf9iTAONegO/qKXRdLg1nvrWI= +decred.org/cspp/v2 v2.0.0 h1:b4fZrElRufz30rYnBZ2shhC8AjNVTN4i6TMzDi+hk44= +decred.org/cspp/v2 v2.0.0/go.mod h1:0shJWKTWY3LxZEWGxtbER1Y45+HVjC0WZtj4bctSzCI= +decred.org/dcrdex v0.4.1/go.mod h1:C9PstuxJQIQWmFWJj2L1gWi+YSH9/WYZb0UgCGJ7cCE= +decred.org/dcrwallet v1.7.0 h1:U/ew00YBdUlx3rJAynt2OdKDgGzBKK4O89FijBq8iVg= +decred.org/dcrwallet v1.7.0/go.mod h1:hNOGyvH53gWdgFB601/ubGRzCPfPtWnEVAi9Grs90y4= +decred.org/dcrwallet/v2 v2.0.0-20211206163037-9537363becbb/go.mod h1:rbFJaCuXCfDhYoI5ZdeZr8TmF4A4Sb1zE7jQAwtaFMo= +decred.org/dcrwallet/v2 v2.0.0-20211207180344-e2bce3d3b877/go.mod h1:nRvFh0CChWgRxXxxCWG2wBpzJnfOhGhdxU7meaMhSfA= +decred.org/dcrwallet/v2 v2.0.1/go.mod h1:lZXgx5OcLDaWyNWFkBekqER1gdqiVwua1w68SFC1/Nk= +decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 h1:qwUXrsjgm6qU7+/1mvaOgIfMeC2v0San3rm1fQVoCaU= +decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895/go.mod h1:lZXgx5OcLDaWyNWFkBekqER1gdqiVwua1w68SFC1/Nk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +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/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenBazaar/jsonpb v0.0.0-20171123000858-37d32ddf4eef/go.mod h1:55mCznBcN9WQgrtgaAkv+p2LxeW/tQRdidyyE9D0I5k= +github.com/Sereal/Sereal v0.0.0-20181211220259-509a78ddbda3/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/aead/siphash v0.0.0-20170329201724-e404fcfc8885/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/apache/beam v2.27.0+incompatible/go.mod h1:/8NX3Qi8vGstDLLaeaU7+lzVEu/ACaQhYjeefzQ0y1o= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 h1:DmSVc81daQAPvXwcCZi0W6A14sTCYQ1QI21C0E37KoY= +github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282/go.mod h1:cMLKpjHSP4q0P133fV15ojQgwWWB2IMv+hrFsmBF/wI= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= +github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= +github.com/btcsuite/btcd v0.21.0-beta.0.20210426180113-7eba688b65e5/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e/go.mod h1:3PH+KbvLFfzBTCevQenPiDedjGQGt6aa70dVjJDWGTA= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= +github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 h1:9aGy5p7oXRUB4MCTmWm0+jzuh79GpjPIfv1leA5POD4= +github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce h1:3PRwz+js0AMMV1fHRrCdQ55akoomx4Q3ulozHC3BDDY= +github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= +github.com/btcsuite/btcwallet v0.12.0 h1:0kT0rDN8vNcAvuHp2qUS/hLsfa0VUn2dNQ2GvM9ozBA= +github.com/btcsuite/btcwallet v0.12.0/go.mod h1:f1HuBGov5+OTp40Gh1vA+tvF6d7bbuLFTceJMRB7fXw= +github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= +github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk= +github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww= +github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w= +github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA= +github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= +github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= +github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= +github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= +github.com/btcsuite/btcwallet/walletdb v1.4.0/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/wtxmgr v1.3.0 h1:lrZaZXGJjDedYTV7s5UgU9xBe8+N+cIDW7BYwI/B8Fs= +github.com/btcsuite/btcwallet/wtxmgr v1.3.0/go.mod h1:awQsh1n/0ZrEQ+JZgWvHeo153ubzEisf/FyNtwI0dDk= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +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/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/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= +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/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/companyzero/sntrup4591761 v0.0.0-20200131011700-2b0d299dbd22/go.mod h1:LoZJNGDWmVPqMEHmeJzj4Weq4Stjc6FKY6FVpY3Hem0= +github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a h1:clYxJ3Os0EQUKDDVU8M0oipllX0EkuFNBfhVQuIfyF0= +github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a/go.mod h1:z/9Ck1EDixEbBbZ2KH2qNHekEmDLTOZ+FyoIPWWSVOI= +github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/dajohi/goemail v1.0.0/go.mod h1:YyX3pgj9VJX6VQYu8Cbs0GYHzgFUs8q0vX5pLmFvops= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/blake256 v1.0.0/go.mod h1:xXNWCE1jsAP8DAjP+rKw2MbeqLczjI3TRx2VK+9OEYY= +github.com/dchest/siphash v1.2.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/decred/base58 v1.0.0/go.mod h1:LLY1p5e3g91byL/UO1eiZaYd+uRoVRarybgcoymu9Ks= +github.com/decred/base58 v1.0.1/go.mod h1:H2ENcsJjye1G7CbRa67kV9OFaui0LGr56ntKKoY5g9c= +github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= +github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA= +github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E= +github.com/decred/dcrd/addrmgr v1.0.2/go.mod h1:gNnmTuf/Xkg8ZX3j5GXbajzPrSdf5bA7HitO2bjmq0Q= +github.com/decred/dcrd/addrmgr v1.2.0/go.mod h1:QlZF9vkzwYh0qs25C76SAFZBRscjETga/K28GEE6qIc= +github.com/decred/dcrd/addrmgr/v2 v2.0.0/go.mod h1:5g9jPzBSQotmSnPri4oc1n5VVgWzPLlXwbr6HGoUVrg= +github.com/decred/dcrd/blockchain v1.0.0/go.mod h1:nNMgOz12wlasmEJDCuSuMWYSnjDdmB4l38GKuQ/Yd+8= +github.com/decred/dcrd/blockchain v1.0.1/go.mod h1:R/4XnwNOTj5IP8jQIUzrJ8zhr/7EOk09IMODwBamZoI= +github.com/decred/dcrd/blockchain v1.0.2 h1:+gJFfgv5LK+LcadyoiMln838/aU3rxDd0Smqogd6fkA= +github.com/decred/dcrd/blockchain v1.0.2/go.mod h1:R/4XnwNOTj5IP8jQIUzrJ8zhr/7EOk09IMODwBamZoI= +github.com/decred/dcrd/blockchain/stake v1.0.0/go.mod h1:opuzF8UouYyQyRJVF00Rdd7OgWb1WKyy1pyU0QYaxz0= +github.com/decred/dcrd/blockchain/stake v1.0.1/go.mod h1:hgoGmWMIu2LLApBbcguVpzCEEfX7M2YhuMrQdpohJzc= +github.com/decred/dcrd/blockchain/stake v1.0.2 h1:trUDgZsT5DYiwKu255k4hFtGiVEOAM9IDmP2GqOLYGU= +github.com/decred/dcrd/blockchain/stake v1.0.2/go.mod h1:hgoGmWMIu2LLApBbcguVpzCEEfX7M2YhuMrQdpohJzc= +github.com/decred/dcrd/blockchain/stake/v2 v2.0.0/go.mod h1:jv/rKMcZ87lhvVkHot/tElxeAYEUJ3mnKPHJ7WPq86U= +github.com/decred/dcrd/blockchain/stake/v2 v2.0.1/go.mod h1:jv/rKMcZ87lhvVkHot/tElxeAYEUJ3mnKPHJ7WPq86U= +github.com/decred/dcrd/blockchain/stake/v3 v3.0.0/go.mod h1:5GIUwsrHQCJauacgCegIR6t92SaeVi28Qls/BLN9vOw= +github.com/decred/dcrd/blockchain/stake/v4 v4.0.0-20210906140327-598bf66f24a6/go.mod h1:CStg0VQxxpVWphul8V3BtBOlhkkHfGE3CgwZK00xYwE= +github.com/decred/dcrd/blockchain/stake/v4 v4.0.0-20211110133211-e53d26e01d1f/go.mod h1:CStg0VQxxpVWphul8V3BtBOlhkkHfGE3CgwZK00xYwE= +github.com/decred/dcrd/blockchain/stake/v4 v4.0.0 h1:PwoCjCTbRvDUZKKs6N2Haus8XcbVXCJ9iGVs8C9sKwQ= +github.com/decred/dcrd/blockchain/stake/v4 v4.0.0/go.mod h1:bOgG7YTbTOWQgtHLL2l1Y9gBHIuM86zwVcQtsoGlZlQ= +github.com/decred/dcrd/blockchain/standalone v1.0.0 h1:bPkFgSV7/NeZI+ZEGhaOP+XccCUBTIJb3YTf8dMwe8g= +github.com/decred/dcrd/blockchain/standalone v1.0.0/go.mod h1:U5lOleFSi1nL7heSdLgEtuvg0udS1p3cvHxvLJbihfE= +github.com/decred/dcrd/blockchain/standalone/v2 v2.0.0/go.mod h1:t2qaZ3hNnxHZ5kzVJDgW5sp47/8T5hYJt7SR+/JtRhI= +github.com/decred/dcrd/blockchain/standalone/v2 v2.1.0 h1:aXh7a+86p+H65MGy0QKu4Juf3/j+Y5koVSyVYFMdqP0= +github.com/decred/dcrd/blockchain/standalone/v2 v2.1.0/go.mod h1:t2qaZ3hNnxHZ5kzVJDgW5sp47/8T5hYJt7SR+/JtRhI= +github.com/decred/dcrd/blockchain/v3 v3.0.2/go.mod h1:LD5VA95qdb+DlRiPI8VLBimDqvlDCAJsidZ5oD6nc/U= +github.com/decred/dcrd/blockchain/v4 v4.0.0-20211120053236-81ae286f2347/go.mod h1:GWN1XIAKTbQT+VoLYjtEIYA0PQ0uTOebtVG1Nw6zlfg= +github.com/decred/dcrd/blockchain/v4 v4.0.0/go.mod h1:i1FeTNN0LUEWBSMoI3riAFgfVE1X/7Seoz1aJ7YQGbk= +github.com/decred/dcrd/certgen v1.0.1/go.mod h1:NxEyGwzPHak+h3tNLYAXU4vWuL98HrY9Z59hc1E3SGI= +github.com/decred/dcrd/certgen v1.1.0/go.mod h1:ivkPLChfjdAgFh7ZQOtl6kJRqVkfrCq67dlq3AbZBQE= +github.com/decred/dcrd/certgen v1.1.1/go.mod h1:ivkPLChfjdAgFh7ZQOtl6kJRqVkfrCq67dlq3AbZBQE= +github.com/decred/dcrd/chaincfg v1.0.1/go.mod h1:O+443mQNPjci+WqWkKta3v2MgJn2u20YWy5mW3c2T7M= +github.com/decred/dcrd/chaincfg v1.1.1 h1:qRZkiA7ucsfsQPE/G/U1OnEUFozDl1MvM4ysJCUndLU= +github.com/decred/dcrd/chaincfg v1.1.1/go.mod h1:UlGtnp8Xx9YK+etBTybGjoFGoGXSw2bxZQuAnwfKv6I= +github.com/decred/dcrd/chaincfg/chainhash v1.0.1/go.mod h1:OVfvaOsNLS/A1y4Eod0Ip/Lf8qga7VXCQjUQLbkY0Go= +github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= +github.com/decred/dcrd/chaincfg/chainhash v1.0.3-0.20200921185235-6d75c7ec1199/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= +github.com/decred/dcrd/chaincfg/chainhash v1.0.3 h1:PF2czcYZGW3dz4i/35AUfVAgnqHl9TMNQt1ADTYGOoE= +github.com/decred/dcrd/chaincfg/chainhash v1.0.3/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= +github.com/decred/dcrd/chaincfg/v2 v2.0.2/go.mod h1:hpKvhLCDAD/xDZ3V1Pqpv9fIKVYYi11DyxETguazyvg= +github.com/decred/dcrd/chaincfg/v2 v2.1.0/go.mod h1:hpKvhLCDAD/xDZ3V1Pqpv9fIKVYYi11DyxETguazyvg= +github.com/decred/dcrd/chaincfg/v2 v2.2.0/go.mod h1:hpKvhLCDAD/xDZ3V1Pqpv9fIKVYYi11DyxETguazyvg= +github.com/decred/dcrd/chaincfg/v2 v2.3.0/go.mod h1:7qUJTvn+y/kswSRZ4sT2+EmvlDTDyy2InvNFtX/hxk0= +github.com/decred/dcrd/chaincfg/v3 v3.0.0/go.mod h1:EspyubQ7D2w6tjP7rBGDIE7OTbuMgBjR2F2kZFnh31A= +github.com/decred/dcrd/chaincfg/v3 v3.1.0/go.mod h1:4XF9nlx2NeGD4xzw1+L0DGICZMl0a5rKV8nnuHLgk8o= +github.com/decred/dcrd/chaincfg/v3 v3.1.1 h1:Ki8kq5IXGmjriiQyPCrCTF1aZSBiORb91/Sr5xW4otw= +github.com/decred/dcrd/chaincfg/v3 v3.1.1/go.mod h1:4XF9nlx2NeGD4xzw1+L0DGICZMl0a5rKV8nnuHLgk8o= +github.com/decred/dcrd/connmgr v1.0.1/go.mod h1:jR+woh3BTbP/35v0nHMiz6GfV1RO0uF1JA+mKeXNk04= +github.com/decred/dcrd/connmgr/v3 v3.0.0/go.mod h1:cPI43Aggp1lOhrVG75eJ3c3BwuFx0NhT77FK34ky+ak= +github.com/decred/dcrd/connmgr/v3 v3.1.0/go.mod h1:NVzQpMSu87fzwEgYmoz+xfVHI6un4+xMkvcMoDjdaRs= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1-0.20200921185235-6d75c7ec1199 h1:sqVg68MjCKwsahuL7AbbdkUSULnZF0vGFOM8FDGscjo= +github.com/decred/dcrd/crypto/blake256 v1.0.1-0.20200921185235-6d75c7ec1199/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/ripemd160 v1.0.0/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg= +github.com/decred/dcrd/crypto/ripemd160 v1.0.1 h1:TjRL4LfftzTjXzaufov96iDAkbY2R3aTvH2YMYa1IOc= +github.com/decred/dcrd/crypto/ripemd160 v1.0.1/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg= +github.com/decred/dcrd/database v1.0.0/go.mod h1:eQOhTdO3oYBshjCVxMt747CP6yKKIls6IIdqYxMRzEk= +github.com/decred/dcrd/database v1.0.1/go.mod h1:ILCeyOHFew3fZ7K2B9jl+tp5qFOap/pEGoo6Yy6Wk0g= +github.com/decred/dcrd/database v1.0.2 h1:/Q+1rxvCFUcFH3FfnzVXv+3NmVPoRZ3UQmqMr2KYReA= +github.com/decred/dcrd/database v1.0.2/go.mod h1:ILCeyOHFew3fZ7K2B9jl+tp5qFOap/pEGoo6Yy6Wk0g= +github.com/decred/dcrd/database/v2 v2.0.0/go.mod h1:Sj2lvTRB0mfSu9uD7ObfwCY/eJ954GFU/X+AndJIyfE= +github.com/decred/dcrd/database/v2 v2.0.2/go.mod h1:S78KbTCCJWUTJDVTByiQuB+HmL0DM2vIMsa2WsrF9KM= +github.com/decred/dcrd/database/v3 v3.0.0-20210802132946-9ede6ae83e0f/go.mod h1:3WUAfz3R0FOz6wJcqTZ0CcUDfyIMrlO10f3aqa2/7vk= +github.com/decred/dcrd/database/v3 v3.0.0-20211012235250-77033596a107/go.mod h1:3WUAfz3R0FOz6wJcqTZ0CcUDfyIMrlO10f3aqa2/7vk= +github.com/decred/dcrd/database/v3 v3.0.0 h1:7VVN2sWjKB934jvXzjnyGJFUVH9d8Qh5VULi+NMRjek= +github.com/decred/dcrd/database/v3 v3.0.0/go.mod h1:8EyKddB8rXDi6/CDOdYc/7qL1//sb6iwg9DctP0ZJF4= +github.com/decred/dcrd/dcrec v0.0.0-20180721005212-59fe2b293f69/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= +github.com/decred/dcrd/dcrec v0.0.0-20180721005914-d26200ec716b/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= +github.com/decred/dcrd/dcrec v0.0.0-20180721031028-5369a485acf6/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= +github.com/decred/dcrd/dcrec v0.0.0-20180801202239-0761de129164/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= +github.com/decred/dcrd/dcrec v0.0.0-20180809193022-9536f0c88fa8/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= +github.com/decred/dcrd/dcrec v0.0.0-20180816212643-20eda7ec9229/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= +github.com/decred/dcrd/dcrec v1.0.0/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8= +github.com/decred/dcrd/dcrec v1.0.1-0.20200921185235-6d75c7ec1199 h1:MkfApk/KhuIh3llbjdnTFY5G4lb7zA+EEKVKIRgAfmg= +github.com/decred/dcrd/dcrec v1.0.1-0.20200921185235-6d75c7ec1199/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8= +github.com/decred/dcrd/dcrec/edwards v0.0.0-20180721005212-59fe2b293f69/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= +github.com/decred/dcrd/dcrec/edwards v0.0.0-20180721031028-5369a485acf6/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= +github.com/decred/dcrd/dcrec/edwards v0.0.0-20180809193022-9536f0c88fa8/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= +github.com/decred/dcrd/dcrec/edwards v0.0.0-20180816212643-20eda7ec9229/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= +github.com/decred/dcrd/dcrec/edwards v1.0.0 h1:UDcPNzclKiJlWqV3x1Fl8xMCJrolo4PB4X9t8LwKDWU= +github.com/decred/dcrd/dcrec/edwards v1.0.0/go.mod h1:HblVh1OfMt7xSxUL1ufjToaEvpbjpWvvTAUx4yem8BI= +github.com/decred/dcrd/dcrec/edwards/v2 v2.0.0/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= +github.com/decred/dcrd/dcrec/edwards/v2 v2.0.1/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= +github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 h1:bX7rtGTMBDJxujZ29GNqtn7YCAdINjHKnA6J6tBBv6s= +github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= +github.com/decred/dcrd/dcrec/secp256k1 v1.0.0/go.mod h1:JPMFscGlgXTV684jxQNDijae2qrh0fLG7pJBimaYotE= +github.com/decred/dcrd/dcrec/secp256k1 v1.0.1/go.mod h1:lhu4eZFSfTJWUnR3CFRcpD+Vta0KUAqnhTsTksHXgy0= +github.com/decred/dcrd/dcrec/secp256k1 v1.0.2 h1:awk7sYJ4pGWmtkiGHFfctztJjHMKGLV8jctGQhAbKe0= +github.com/decred/dcrd/dcrec/secp256k1 v1.0.2/go.mod h1:CHTUIVfmDDd0KFVFpNX1pFVCBUegxW387nN0IGwNKR0= +github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY= +github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210127014238-b33b46cf1a24/go.mod h1:UkVqoxmJlLgUvBjJD+GdJz6mgdSdf3UjX83xfwUAYDk= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrjson v1.0.0 h1:50DnA0XeV2JrQXoHh43TCKmH+kz2gHjZ1Mj/Pdk7Oz0= +github.com/decred/dcrd/dcrjson v1.0.0/go.mod h1:ozddIaeF+EAvZZvFuB3zpfxhyxBGfvbt22crQh+PYuI= +github.com/decred/dcrd/dcrjson/v3 v3.0.0/go.mod h1:pWYlHJ3VFidPwqD5HHiJXjfGaplif8uspAL2qFdifkY= +github.com/decred/dcrd/dcrjson/v3 v3.1.0/go.mod h1:fnTHev/ABGp8IxFudDhjGi9ghLiXRff1qZz/wvq12Mg= +github.com/decred/dcrd/dcrjson/v4 v4.0.0 h1:KsaFhHAYO+vLYz7Qmx/fs1gOY5ouTEz8hRuDm8jmJtU= +github.com/decred/dcrd/dcrjson/v4 v4.0.0/go.mod h1:DMnSpU8lsVh+Nt5kHl63tkrjBDA7UIs4+ov8Kwwgvjs= +github.com/decred/dcrd/dcrutil v1.0.0/go.mod h1:CBpbItyMKkL/4i1qPJDsE/cdSYklsWFcTYgprRZh4yk= +github.com/decred/dcrd/dcrutil v1.1.1 h1:zOkGiumN/JkobhAgpG/zfFgUoolGKVGYT5na1hbYUoE= +github.com/decred/dcrd/dcrutil v1.1.1/go.mod h1:Jsttr0pEvzPAw+qay1kS1/PsbZYPyhluiNwwY6yBJS4= +github.com/decred/dcrd/dcrutil/v2 v2.0.0/go.mod h1:gUshVAXpd51DlcEhr51QfWL2HJGkMDM1U8chY+9VvQg= +github.com/decred/dcrd/dcrutil/v2 v2.0.1/go.mod h1:JdEgF6eh0TTohPeiqDxqDSikTSvAczq0J7tFMyyeD+k= +github.com/decred/dcrd/dcrutil/v3 v3.0.0/go.mod h1:iVsjcqVzLmYFGCZLet2H7Nq+7imV9tYcuY+0lC2mNsY= +github.com/decred/dcrd/dcrutil/v4 v4.0.0-20210129181600-6ae0142d3b28/go.mod h1:xe59jKcMx5G/dbRmsZ8+FzY+WQDE/7YBP3k3uzJTtmI= +github.com/decred/dcrd/dcrutil/v4 v4.0.0 h1:AY00fWy/ETrMHN0DNV3XUbH1aip2RG1AoTy5dp0+sJE= +github.com/decred/dcrd/dcrutil/v4 v4.0.0/go.mod h1:QQpX5WVH3/ixVtiW15xZMe+neugXX3l2bsrYgq6nz4M= +github.com/decred/dcrd/gcs v1.0.0/go.mod h1:5uHIPAzn4SdGP2/FhVBK2YdAoKmufds3ZI8yNzojUCM= +github.com/decred/dcrd/gcs v1.0.1/go.mod h1:YwutGzusSdJM79CJtxCo9t7WRCvnkLtWSD19TPo1i9g= +github.com/decred/dcrd/gcs v1.0.2/go.mod h1:eLCvrzUsWro48TlTyrmFcZAZqnllYFz0vEv5VZtufF4= +github.com/decred/dcrd/gcs v1.1.0 h1:djuYzaFUzUTJR+6ulMSRZOQ+P9rxtIyuxQeViAEfB8s= +github.com/decred/dcrd/gcs v1.1.0/go.mod h1:yBjhj217Vw5lw3aKnCdHip7fYb9zwMos8bCy5s79M9w= +github.com/decred/dcrd/gcs/v2 v2.1.0/go.mod h1:MbnJOINFcp42NMRAQ+CjX/xGz+53AwNgMzKZhwBibdM= +github.com/decred/dcrd/gcs/v3 v3.0.0-20210916172859-ca03de05ecd0/go.mod h1:cfMmjV1ArvX+nemwwq1texW5pD0wrUt5TEw4IYuTmMM= +github.com/decred/dcrd/gcs/v3 v3.0.0 h1:MjWevhoAzKENUgpaJAbZkJlKDN4HIz2nR/i3laZAT5c= +github.com/decred/dcrd/gcs/v3 v3.0.0/go.mod h1:/OVb/rYrAz4TCtxcPneYfBs0+YI1pGIp8RA6RUNqOp4= +github.com/decred/dcrd/hdkeychain v1.1.0 h1:6bFdL672dCmtg/JEzb3Jw0dTRO2jLxcA7BK2J+JaoUM= +github.com/decred/dcrd/hdkeychain v1.1.0/go.mod h1:zyUZtZ3PdnTPHt2XUr1x76b8ZuiM+9aVkP8Rq8Scp1k= +github.com/decred/dcrd/hdkeychain/v2 v2.0.1/go.mod h1:qPv+vTla19liVHFuXVnQ70dMI4ERPCniDXbV5RzwQiM= +github.com/decred/dcrd/hdkeychain/v3 v3.0.0/go.mod h1:Vz7PJSlLzhqmOR2lmjGD9JqAZgmUnM8P6r8hg7U4Zho= +github.com/decred/dcrd/hdkeychain/v3 v3.0.1/go.mod h1:rDCdqwGkcTfEyRheG1g8Wc38appT2C9+D1XTlLy21lo= +github.com/decred/dcrd/hdkeychain/v3 v3.1.0 h1:NlUjzPMzexbk1PyJu6vrQaiilep5WsEPB0KdhLYrEcE= +github.com/decred/dcrd/hdkeychain/v3 v3.1.0/go.mod h1:rDCdqwGkcTfEyRheG1g8Wc38appT2C9+D1XTlLy21lo= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/decred/dcrd/lru v1.1.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/decred/dcrd/lru v1.1.1 h1:kWFDaW0OWx6AD6Ki342c+JPmHbiVdE6rK81pT3fuo/Y= +github.com/decred/dcrd/lru v1.1.1/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/decred/dcrd/mempool v1.0.1/go.mod h1:r+/DGiiluXi1EyMCCPPH58Qu+rsr8nZv0DialAG5VZQ= +github.com/decred/dcrd/mining v1.0.0/go.mod h1:VA5H4zhJgXb8LK5lqM5H58dhMRXJRcaQQoX3G8QRpP8= +github.com/decred/dcrd/mining v1.0.1/go.mod h1:+CSOLPi7TM8OlQg7mJ7XzWLXCDb4nHK8R6cvXOzhEoU= +github.com/decred/dcrd/rpc/jsonrpc/types v1.0.0 h1:d5ptnjuSADTQMa3i83VpeJNoMRTOJZZBqk7P+E41VXM= +github.com/decred/dcrd/rpc/jsonrpc/types v1.0.0/go.mod h1:0dwmpIP21tJxjg/UuUHWIFMbfoLv2ifCBMokNKlOxpo= +github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.3.0/go.mod h1:krn89ZOgSa8yc7sA4WpDK95p61NnjNWFkNlMnGrKbMc= +github.com/decred/dcrd/rpc/jsonrpc/types/v3 v3.0.0-20210129200153-14fd1a785bf2/go.mod h1:9izQEJ5wU0ZwYHESMaaOIvE6H6y3IvDsQL3ByYGn9oc= +github.com/decred/dcrd/rpc/jsonrpc/types/v3 v3.0.0 h1:WzG2IARR6OghjhWdxfUbXSPE4GEF2hZlCE5y2L/45f4= +github.com/decred/dcrd/rpc/jsonrpc/types/v3 v3.0.0/go.mod h1:1ILDxMKVS/qY71MylpZzuEX4O0u1SON4RPKbaZP71K0= +github.com/decred/dcrd/rpcclient v1.0.1/go.mod h1:tApXK3wwrAQtz7lcXeeqBwuktUZesvrFfvhAdedYqdM= +github.com/decred/dcrd/rpcclient/v4 v4.0.0/go.mod h1:DNGwfiL5H+K/pk3hVB0z5ypRdiDXMssR+YEqDUEXCQo= +github.com/decred/dcrd/rpcclient/v6 v6.0.2/go.mod h1:t6ECC72j2xWQ323poL85IFNq0EUfcSTfwL8j7jDJ6mw= +github.com/decred/dcrd/rpcclient/v7 v7.0.0-20211119190215-20dc378a4341/go.mod h1:fHve6G5hIaq2RklBG8Tsiz8id2S+0U3bvBACcYqZStc= +github.com/decred/dcrd/rpcclient/v7 v7.0.0/go.mod h1:k4UDXFt0iwTRhKzdMGJbz/0wD/1lIKrQ5iYWyY7w8R4= +github.com/decred/dcrd/txscript v1.0.0/go.mod h1:9byvrOaBSBVVnDG7Cm0JgN8bZytl1oi9Ba245VBeI18= +github.com/decred/dcrd/txscript v1.0.1 h1:IMgxZFCw3AyG4EbKwywE3SDNshOSHsoUK1Wk/5GqWJ0= +github.com/decred/dcrd/txscript v1.0.1/go.mod h1:FqUX07Y+u3cJ1eIGPoyWbJg+Wk1NTllln/TyDpx9KnY= +github.com/decred/dcrd/txscript/v2 v2.0.0/go.mod h1:WStcyYYJa+PHJB4XjrLDRzV96/Z4thtsu8mZoVrU6C0= +github.com/decred/dcrd/txscript/v2 v2.1.0/go.mod h1:XaJAVrZU4NWRx4UEzTiDAs86op1m8GRJLz24SDBKOi0= +github.com/decred/dcrd/txscript/v3 v3.0.0/go.mod h1:pdvnlD4KGdDoc09cvWRJ8EoRQUaiUz41uDevOWuEfII= +github.com/decred/dcrd/txscript/v4 v4.0.0-20210415215133-96b98390a9a9/go.mod h1:LBGwMZRfpS50huRsc0Bihy7w2Sl9vK3TNqv8nhCRj0U= +github.com/decred/dcrd/txscript/v4 v4.0.0 h1:BwaBUCMCmg58MCYoBhxVjL8ZZKUIfoJuxu/djmh8h58= +github.com/decred/dcrd/txscript/v4 v4.0.0/go.mod h1:OJtxNc5RqwQyfrRnG2gG8uMeNPo8IAJp+TD1UKXkqk8= +github.com/decred/dcrd/wire v1.0.1/go.mod h1:zpKZnBiN59CrzfXFigwgXmUDVYf34OLbEr8xwAwriHc= +github.com/decred/dcrd/wire v1.1.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM= +github.com/decred/dcrd/wire v1.2.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM= +github.com/decred/dcrd/wire v1.3.0/go.mod h1:fnKGlUY2IBuqnpxx5dYRU5Oiq392OBqAuVjRVSkIoXM= +github.com/decred/dcrd/wire v1.4.0/go.mod h1:WxC/0K+cCAnBh+SKsRjIX9YPgvrjhmE+6pZlel1G7Ro= +github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg= +github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w= +github.com/decred/dcrdata/api/types/v4 v4.0.4/go.mod h1:CCu2Itqv/K3lqFxqSYDC49XWu5OuZRvNFkPiFHb0tYU= +github.com/decred/dcrdata/db/dbtypes/v2 v2.1.4/go.mod h1:UF4KWxcCYhdXqaTwbA2Mb10os4H0UFSZaiu5eeMWQT8= +github.com/decred/dcrdata/semver v1.0.0/go.mod h1:z+nQqiAd9fYkHhBLbejysZ2FPHtgkrErWDgMf+JlZWE= +github.com/decred/dcrdata/txhelpers/v3 v3.0.4/go.mod h1:tKEDhoO+TbYrFrx+5qKZDxcla8ELQFYs4f5+8gL4cuY= +github.com/decred/dcrdata/v6 v6.0.0-20210510222533-6a2ca18d4382/go.mod h1:CWT5trkQ+8KUSBeyuI5/V1TQMleYyCh1vo/Ry6PiFWU= +github.com/decred/dcrdata/v7 v7.0.0-20211216152310-365c9dc820eb/go.mod h1:rNWn4zkXfTvBqv+O4oZQDJPRXT6rYzkobyrbGUiL270= +github.com/decred/dcrtime v0.0.0-20191018193024-8d8b4ef0458e/go.mod h1:IyZnyBE3E6RBFsEjwEs21FrO/UsrLrL15hUnpZZQxpU= +github.com/decred/dcrtime/api/v2 v2.0.0-20200912200806-b1e4dbc46be9/go.mod h1:JdIX208vnNj4TdU6hDRaN+ccxmxp1I1R6sWGZNK1BAQ= +github.com/decred/dcrwallet v1.2.2/go.mod h1:BrSus0F+Rx8UhvPNBfuRMIjRJBNrW2sLspN9iQR5hm8= +github.com/decred/dcrwallet/chain v1.0.0/go.mod h1:KpZFaKlKajfUZt36+RmBn2HKwTbwoa3yt9HPALqlShI= +github.com/decred/dcrwallet/deployments v1.0.0/go.mod h1:0bWER/DAYoGbzkWzbUf6k2agW4YkSyvNLZDhBGThz/4= +github.com/decred/dcrwallet/errors v1.0.0/go.mod h1:XUm95dWmm9XmQGvneBXJkkIaFeRsQVBB6ni/KTy1hrY= +github.com/decred/dcrwallet/internal/helpers v1.0.0/go.mod h1:FsihtjCyFrGL6gdmkxBWTYQ1CUgbfM9tyinYNOzLnlk= +github.com/decred/dcrwallet/internal/zero v1.0.0/go.mod h1:vULuNLRTcnifKCepcIxUDL4jrR3rJOwVR9UDH89Qpms= +github.com/decred/dcrwallet/lru v1.0.0/go.mod h1:jEty7mdT5VaaV06DEV2Avv0R3HpGvUwvDW4lw8ECtiY= +github.com/decred/dcrwallet/p2p v1.0.0/go.mod h1:b1CLZAkl/K5dr5I5B4SdFT8FrE11jSkfA4VAA862ACA= +github.com/decred/dcrwallet/pgpwordlist v1.0.0/go.mod h1:Fek3uYn+9DnEFIreA/8PnTIXUl2lBO64JpEBkL9BXtk= +github.com/decred/dcrwallet/rpc/jsonrpc/types v1.1.0/go.mod h1:xUT7XXATLOzE0pwwmvgfRWtZdrB+PsWFilo+jkH5/Ig= +github.com/decred/dcrwallet/rpc/walletrpc v0.1.0/go.mod h1:Zp1ZFTCUo7S6MJvUyS5tYfaDUxGAMHkZ+vbsLgAdd4A= +github.com/decred/dcrwallet/rpc/walletrpc v0.2.0/go.mod h1:uhjgcju9lSb/+42Ms4VY1zpBOxstCLM5wVlL3mq/SYc= +github.com/decred/dcrwallet/spv v1.0.0/go.mod h1:lz39nz9P/HVoxYa4XAT6ithyR3WgdF0oVu4jtFwnCxE= +github.com/decred/dcrwallet/ticketbuyer v1.0.0/go.mod h1:mrAlRjOJ6txO8Zyqo5koxVOMEYLK2POUX35a/QcKN8g= +github.com/decred/dcrwallet/ticketbuyer/v2 v2.0.0/go.mod h1:VKo2PjXAlF/E46tSBKrIgqKbVcHVLfM5ACyOehT1unA= +github.com/decred/dcrwallet/validate v1.0.0/go.mod h1:zHIlcrjAWl6LK+X+R7jc3F9wIM/qxjtMjG/mdEwt4tY= +github.com/decred/dcrwallet/validate v1.0.1/go.mod h1:9DCtLFnnTOC/7PKkF7jehvDyHkfUBl41ZbcT1u4PmQM= +github.com/decred/dcrwallet/version v1.0.0/go.mod h1:rXeMsUaI03WtlQrSol7Q7sJ8HBOB+tZvT7YQRXD5Y7M= +github.com/decred/dcrwallet/wallet v1.0.0/go.mod h1:VWRnpNFRiKPo7FUPbzj0t5ElcGxNXMPIa4vGcGe94uM= +github.com/decred/dcrwallet/walletseed v1.0.0/go.mod h1:xSF6hZW+5Xhm0jJFsI5jQSfViuZUQJoDXa/cQxtgncs= +github.com/decred/go-socks v1.0.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0= +github.com/decred/go-socks v1.1.0 h1:dnENcc0KIqQo3HSXdgboXAHgqsCIutkqq6ntQjYtm2U= +github.com/decred/go-socks v1.1.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0= +github.com/decred/politeia v1.3.1/go.mod h1:IWytM75wWPGbQsEcQTEeHfX1KbcUt2dGDCtYbrrvqHI= +github.com/decred/slog v1.0.0/go.mod h1:zR98rEZHSnbZ4WHZtO0iqmSZjDLKhkXfrPTZQKtAonQ= +github.com/decred/slog v1.1.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= +github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +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.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/ethereum/go-ethereum v1.10.11/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQxhYBkKyu5mEDHw= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gcash/bchd v0.14.7/go.mod h1:Gk/O1ktRVW5Kao0RsnVXp3bWxeYQadqawZ1Im9HE78M= +github.com/gcash/bchd v0.15.2/go.mod h1:k9wIjgwnhbrAw+ruIPZ2tHZMzfFNdyUnORZZX7lqXGY= +github.com/gcash/bchd v0.17.2-0.20201218180520-5708823e0e99/go.mod h1:qwEZ/wr6LyUo5IBgAPcAbYHzXrjnr5gc4tj03n1TwKc= +github.com/gcash/bchlog v0.0.0-20180913005452-b4f036f92fa6/go.mod h1:PpfmXTLfjRp7Tf6v/DCGTRXHz+VFbiRcsoUxi7HvwlQ= +github.com/gcash/bchutil v0.0.0-20190625002603-800e62fe9aff/go.mod h1:zXSP0Fg2L52wpSEDApQDQMiSygnQiK5HDquDl0a5BHg= +github.com/gcash/bchutil v0.0.0-20191012211144-98e73ec336ba/go.mod h1:nUIrcbbtEQdCsRwcp+j/CndDKMQE9Fi8p2F8cIZmIqI= +github.com/gcash/bchutil v0.0.0-20200506001747-c2894cd54b33/go.mod h1:wB++2ZcHUvGLN1OgO9swBmJK1vmyshJLW9SNS+apXwc= +github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000/go.mod h1:H2USFGwtiu6CNMxiVQPqZkDzsoVSt9BLNqTfBBqGXRo= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +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/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +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 v1.1.0/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-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.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/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +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.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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +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-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/trillian v1.3.13/go.mod h1:8y3zC8XuqFxsslWPkP0r3sprERfFf7hCWmicL0yHZNI= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.5/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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/improbable-eng/grpc-web v0.9.1/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= +github.com/improbable-eng/grpc-web v0.13.0/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v0.0.0-20181221193153-c0795c8afcf4/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/bitset v1.0.0 h1:Ws0PXV3PwXqWK2n7Vz6idCdrV/9OrBXgHEJi27ZB9Dw= +github.com/jrick/bitset v1.0.0/go.mod h1:ZOYB5Uvkla7wIEY4FEssPVi3IQXa02arznRaYaAEPe4= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/jrick/wsrpc/v2 v2.3.2/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU= +github.com/jrick/wsrpc/v2 v2.3.4 h1:+GzRtp/TyXaSB61pN92lIAVyvdVv0RSqniIEB/rPx1Q= +github.com/jrick/wsrpc/v2 v2.3.4/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/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/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/karalabe/usb v0.0.0-20211005121534-4c5740d64559/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 h1:YFXjWLfS9lQsxu8GQTQo+O7sjK+6M9njoBOnvVLc9kw= +github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8/go.mod h1:VUp2yfq+wAk8hMl3NNN34fXjzUD9xMpGvUL8eSJz9Ns= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= +github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.2/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/neutrino v0.12.1/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= +github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 h1:lf3i1CNI5j2XhKMvQNmnr2o1DFoyoE02mynRdAt+ss0= +github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= +github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= +github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= +github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= +github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= +github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/marcopeereboom/sbox v1.1.0/go.mod h1:u2fh4EbQDXQXXzGypWkf2nMn2TnsqA23t224mii7oog= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= +github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZeFXocWuvtcS0XSFLcf2XUSDHkq9t1jU4= +github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= +github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/planetdecred/dcrlibwallet v1.7.0 h1:UHZwYnvm/8cpSVG17lB3rx1wOAf+ZP4v5RbkPk6MYKE= +github.com/planetdecred/dcrlibwallet v1.7.0/go.mod h1:QTADevJvo+ugI0LZkwVVwCMiqtVf/W0DKlODTYwbnCs= +github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d/go.mod h1:jO4RP2rgqom8CLgl3rMwZ4cGzmalJqBkKjHgVS812lM= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= +github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gozaru v0.0.0-20190625071150-416082cce636/go.mod h1:LIpwO1yApZNrEQZdu5REqRtRrkaU+52ueA7WGT+CvSw= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +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/zquestz/grab v0.0.0-20190224022517-abcee96e61b1/go.mod h1:bslhAiUxakrA6z6CHmVyvkfpnxx18RJBwVyx2TluJWw= +go.etcd.io/bbolt v1.3.0/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +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/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +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/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.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180718160520-a2144134853f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180808211826-de0752318171/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +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-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +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/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/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-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-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-20190923162816-aa69164e4478/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-20200421231249-e086a090c8fd/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-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-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-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/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-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180810070207-f0d5e33068cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-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-20190614084037-d442b75600c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/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-20191008105621-543471e840be/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-20191220142924-d4481acd189f/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-20200107162124-548cf772de50/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-20200420163511-1957bb5e6d1f/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-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-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= +golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +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= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/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-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/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-20200108203644-89082a384178/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-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +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-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +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.10.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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +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.2/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/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180808183934-383e8b2c3b9e/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/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-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +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-20200108215221-bd8f9a0ef82f/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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +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.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +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.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +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/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/wallets/btc/wallet.go b/wallets/btc/wallet.go index eac447967..888eae56e 100644 --- a/wallets/btc/wallet.go +++ b/wallets/btc/wallet.go @@ -8,9 +8,9 @@ import ( "os" "path/filepath" "strconv" + "strings" "sync" "sync/atomic" - "strings" "time" "github.com/btcsuite/btcd/chaincfg" @@ -21,26 +21,29 @@ import ( "github.com/btcsuite/btcutil/gcs" "github.com/btcsuite/btcwallet/chain" // "github.com/btcsuite/btcwallet/wallet" - "github.com/btcsuite/btcd/btcutil/hdkeychain" + // "github.com/btcsuite/btcd/btcutil/hdkeychain" w "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" // "github.com/planetdecred/dcrlibwallet" + "decred.org/dcrwallet/v2/errors" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // bdb init() registers a driver "github.com/btcsuite/btcwallet/wtxmgr" "github.com/decred/slog" "github.com/jrick/logrotate/rotator" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/headerfs" - "decred.org/dcrwallet/v2/errors" "github.com/asdine/storm" - "github.com/asdine/storm/q" + // "github.com/asdine/storm/q" ) type Wallet struct { ID int `storm:"id,increment"` Name string `storm:"unique"` CreatedAt time.Time `storm:"index"` + dbDriver string + rootDir string + db *storm.DB EncryptedSeed []byte cl neutrinoService @@ -48,8 +51,7 @@ type Wallet struct { chainClient *chain.NeutrinoClient dataDir string - db *storm.DB - cancelFuncs []context.CancelFunc + cancelFuncs []context.CancelFunc chainParams *chaincfg.Params loader *w.Loader @@ -83,7 +85,7 @@ const ( logFileName = "neutrino.log" ) -func NewSpvWallet(walletName string, encryptedSeed []byte, net string, log slog.Logger) (*Wallet, error) { +func NewSpvWallet(walletName string, encryptedSeed []byte, net string) (*Wallet, error) { chainParams, err := parseChainParams(net) if err != nil { return nil, err @@ -94,7 +96,6 @@ func NewSpvWallet(walletName string, encryptedSeed []byte, net string, log slog. chainParams: chainParams, CreatedAt: time.Now(), EncryptedSeed: encryptedSeed, - log: log, }, nil } @@ -165,52 +166,61 @@ func (wallet *Wallet) RawRequest(method string, params []json.RawMessage) (json. return nil, errors.New("RawRequest not available on spv") } -// createSPVWallet creates a new SPV wallet. -// func (wallet *Wallet) CreateWallet(privPass []byte, seed []byte, dbDir string) error { -// net := wallet.chainParams -// wallet.dataDir = filepath.Join(dbDir, strconv.Itoa(wallet.ID)) - -// if err := logNeutrino(wallet.dataDir); err != nil { -// return fmt.Errorf("error initializing btcwallet+neutrino logging: %v", err) -// } - -// logDir := filepath.Join(wallet.dataDir, logDirName) -// err := os.MkdirAll(logDir, 0744) -// if err != nil { -// return fmt.Errorf("error creating wallet directories: %v", err) -// } - -// loader := w.NewLoader(net, wallet.dataDir, true, 60*time.Second, 250) -// pubPass := []byte(w.InsecurePubPassphrase) - -// _, err = loader.CreateNewWallet(pubPass, privPass, seed, walletBirthday) -// if err != nil { -// return fmt.Errorf("CreateNewWallet error: %w", err) -// } - -// bailOnWallet := func() { -// if err := loader.UnloadWallet(); err != nil { -// wallet.log.Errorf("Error unloading wallet after createSPVWallet error: %v", err) -// } -// } - -// neutrinoDBPath := filepath.Join(wallet.dataDir, neutrinoDBName) -// db, err := walletdb.Create("bdb", neutrinoDBPath, true, 5*time.Second) -// if err != nil { -// bailOnWallet() -// return fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err) -// } -// if err = db.Close(); err != nil { -// bailOnWallet() -// return fmt.Errorf("error closing newly created wallet database: %w", err) -// } - -// if err := loader.UnloadWallet(); err != nil { -// return fmt.Errorf("error unloading wallet: %w", err) -// } - -// return nil -// } +func CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32, db *storm.DB, rootDir, dbDriver string, chainParams *chaincfg.Params) (*Wallet, error) { + seed := "witch collapse practice feed shame open despair" + encryptedSeed := []byte(seed) + + chainParams, err := parseChainParams("testnet3") + if err != nil { + return nil, err + } + + wallet := &Wallet{ + Name: walletName, + db: db, + dbDriver: dbDriver, + rootDir: rootDir, + chainParams: chainParams, + CreatedAt: time.Now(), + EncryptedSeed: encryptedSeed, + // log: log, + } + + return wallet.saveNewWallet(func() error { + err := wallet.Prepare(wallet.rootDir, "testnet3", wallet.log) + if err != nil { + return err + } + + return wallet.createWallet(privatePassphrase, encryptedSeed) + }) +} + +func (wallet *Wallet) RenameWallet(newName string) error { + if strings.HasPrefix(newName, "wallet-") { + return errors.E(ErrReservedWalletName) + } + + if exists, err := wallet.WalletNameExists(newName); err != nil { + return translateError(err) + } else if exists { + return errors.New(ErrExist) + } + + wallet.Name = newName + return wallet.db.Save(wallet) // update WalletName field +} + +func (wallet *Wallet) WalletWithID(walletName string) (*Wallet, error) { + var w *Wallet + + err := wallet.db.One("ID", wallet.ID, &w) + if err == nil && err != storm.ErrNotFound { + return nil, err + } + + return w, nil +} func (wallet *Wallet) DataDir() string { return wallet.dataDir @@ -436,60 +446,6 @@ func (wallet *Wallet) NetType() string { return wallet.chainParams.Name } -func (wallet *Wallet) initBtcWallet(rootDir string) error { - // if btcWallet.BtcWallet != nil { - // return nil - // } - - btcDataDir := filepath.Join(rootDir, "btc") - - err := os.MkdirAll(btcDataDir, os.ModePerm) - if err != nil { - return err - } - - walletsDB, err := storm.Open(filepath.Join(btcDataDir, "wallets.db")) - if err != nil { - return err - } - - // init database for saving/reading wallet objects - err = walletsDB.Init(&Wallet{}) - if err != nil { - // log.Errorf("Error initializing wallets database BTC: %s", err.Error()) - return err - } - - wallet = &Wallet{ - dataDir: filepath.Join(rootDir, "btc"), - db: walletsDB, - // wallets: make(map[int]*Wallet), - } - - // read saved wallets info from db and initialize wallets - query := walletsDB.Select(q.True()).OrderBy("ID") - var wallets []*Wallet - err = query.Find(&wallets) - if err != nil && err != storm.ErrNotFound { - return err - } - - // prepare the wallets loaded from db for use - // for _, wallet := range wallets { - // // err = wallet.Prepare(btcWallet.btcDataDir, btcWallet.NetType(), log) - // // if err == nil && !WalletExistsAt(wallet.DataDir()) { - // // err = fmt.Errorf("missing wallet database file") - // // } - // if err != nil { - // // log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) - // } else { - // btcWallet.wallets[wallet.ID] = wallet - // } - // } - - return nil -} - func (wallet *Wallet) createWallet(privatePassphrase string, seedMnemonic []byte) error { // log.Info("Creating Wallet") if len(seedMnemonic) == 0 { @@ -509,7 +465,7 @@ func (wallet *Wallet) createWallet(privatePassphrase string, seedMnemonic []byte // log.Error(err) return err } - + bailOnWallet := func() { if err := wallet.loader.UnloadWallet(); err != nil { fmt.Errorf("Error unloading wallet after createSPVWallet error: %v", err) @@ -535,33 +491,6 @@ func (wallet *Wallet) createWallet(privatePassphrase string, seedMnemonic []byte return nil } -func (wallet *Wallet) CreateNewWallet(rootDir, walletName, privatePassphrase string) (*Wallet, error) { - seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) - if err != nil { - return nil, err - } - - // encryptedSeed, err := encryptWalletSeed([]byte(privatePassphrase), seed) - // if err != nil { - // return nil, err - // } - - btcWallet := &Wallet{ - Name: walletName, - CreatedAt: time.Now(), - EncryptedSeed: seed, - } - - return btcWallet.saveNewWallet( func() error { - err := wallet.Prepare(rootDir, "testnet3", nil) - if err != nil { - return err - } - - return wallet.createWallet(privatePassphrase, seed) - }) -} - // saveNewWallet performs the following tasks using a db batch operation to ensure // that db changes are rolled back if any of the steps below return an error. // @@ -575,7 +504,7 @@ func (wallet *Wallet) CreateNewWallet(rootDir, walletName, privatePassphrase str // IFF all the above operations succeed, the wallet info will be persisted to db // and the wallet will be added to `btcWallet.wallets`. func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { - exists, err := wallet.WalletNameExists() + exists, err := wallet.WalletNameExists(wallet.Name) if err != nil { return nil, err } else if exists { @@ -610,32 +539,9 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { // return nil, errors.New(ErrExist) // } - batchDbTransaction := func(dbOp func(node storm.Node) error) (err error) { - dbTx, err := wallet.db.Begin(true) - if err != nil { - return err - } - - // Commit or rollback the transaction after f returns or panics. Do not - // recover from the panic to keep the original stack trace intact. - panicked := true - defer func() { - if panicked || err != nil { - dbTx.Rollback() - return - } - - err = dbTx.Commit() - }() - - err = dbOp(dbTx) - panicked = false - return err - } - // Perform database save operations in batch transaction // for automatic rollback if error occurs at any point. - err = batchDbTransaction(func(db storm.Node) error { + err = wallet.batchDbTransaction(func(db storm.Node) error { // saving struct to update ID property with an auto-generated value err := db.Save(wallet) if err != nil { @@ -666,12 +572,7 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { } return setupWallet() - // err = wallet.createWallet([]byte(password), encryptedSeed, wallet.dataDir) - // if err != nil { - // return fmt.Errorf("Create BTC wallet error: %v", err) - // } -// - // return nil + }) if err != nil { @@ -681,21 +582,6 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { return wallet, nil } -func (wallet *Wallet) WalletNameExists() (bool, error) { - if strings.HasPrefix(wallet.Name, "wallet-") { - return false, errors.E(ErrReservedWalletName) - } - - err := wallet.db.One("Name", wallet.Name, &Wallet{}) - if err == nil { - return true, nil - } else if err != storm.ErrNotFound { - return false, err - } - - return false, nil -} - func fileExists(filePath string) (bool, error) { _, err := os.Stat(filePath) if err != nil { @@ -740,4 +626,4 @@ func (wallet *Wallet) shutdownContextWithCancel() (context.Context, context.Canc func (wallet *Wallet) shutdownContext() (ctx context.Context) { ctx, _ = wallet.shutdownContextWithCancel() return -} \ No newline at end of file +} diff --git a/wallets/btc/wallet_utils.go b/wallets/btc/wallet_utils.go new file mode 100644 index 000000000..f542185e3 --- /dev/null +++ b/wallets/btc/wallet_utils.go @@ -0,0 +1,130 @@ +package btc + +import ( + // "context" + + "decred.org/dcrwallet/v2/errors" + "github.com/asdine/storm" + // "github.com/kevinburke/nacl" + // "github.com/kevinburke/nacl/secretbox" + // "golang.org/x/crypto/scrypt" + + // w "decred.org/dcrwallet/v2/wallet" + + "strings" +) + +// func (wallet *Wallet) markWalletAsDiscoveredAccounts() error { +// if wallet == nil { +// return errors.New(ErrNotExist) +// } + +// log.Infof("Set discovered accounts = true for wallet %d", wallet.ID) +// wallet.HasDiscoveredAccounts = true +// err := wallet.DB.Save(wallet) +// if err != nil { +// return err +// } + +// return nil +// } + +func (wallet *Wallet) batchDbTransaction(dbOp func(node storm.Node) error) (err error) { + dbTx, err := wallet.db.Begin(true) + if err != nil { + return err + } + + // Commit or rollback the transaction after f returns or panics. Do not + // recover from the panic to keep the original stack trace intact. + panicked := true + defer func() { + if panicked || err != nil { + dbTx.Rollback() + return + } + + err = dbTx.Commit() + }() + + err = dbOp(dbTx) + panicked = false + return err +} + +func (wallet *Wallet) WalletNameExists(walletName string) (bool, error) { + if strings.HasPrefix(walletName, "wallet-") { + return false, errors.E(ErrReservedWalletName) + } + + err := wallet.db.One("Name", walletName, &Wallet{}) + if err == nil { + return true, nil + } else if err != storm.ErrNotFound { + return false, err + } + + return false, nil +} + +// // naclLoadFromPass derives a nacl.Key from pass using scrypt.Key. +// func naclLoadFromPass(pass []byte) (nacl.Key, error) { + +// const N, r, p = 1 << 15, 8, 1 + +// hash, err := scrypt.Key(pass, nil, N, r, p, 32) +// if err != nil { +// return nil, err +// } +// return nacl.Load(EncodeHex(hash)) +// } + +// encryptWalletSeed encrypts the seed with secretbox.EasySeal using pass. +// func encryptWalletSeed(pass []byte, seed string) ([]byte, error) { +// key, err := naclLoadFromPass(pass) +// if err != nil { +// return nil, err +// } +// return secretbox.EasySeal([]byte(seed), key), nil +// } + +// decryptWalletSeed decrypts the encryptedSeed with secretbox.EasyOpen using pass. +// func decryptWalletSeed(pass []byte, encryptedSeed []byte) (string, error) { +// key, err := naclLoadFromPass(pass) +// if err != nil { +// return "", err +// } + +// decryptedSeed, err := secretbox.EasyOpen(encryptedSeed, key) +// if err != nil { +// return "", errors.New(ErrInvalidPassphrase) +// } + +// return string(decryptedSeed), nil +// } + +// func (wallet *Wallet) loadWalletTemporarily(ctx context.Context, walletDataDir, walletPublicPass string, +// onLoaded func(*w.Wallet) error) error { + +// if walletPublicPass == "" { +// walletPublicPass = w.InsecurePubPassphrase +// } + +// // initialize the wallet loader +// walletLoader := initWalletLoader(wallet.chainParams, walletDataDir, wallet.dbDriver) + +// // open the wallet to get ready for temporary use +// wal, err := walletLoader.OpenExistingWallet(ctx, []byte(walletPublicPass)) +// if err != nil { +// return translateError(err) +// } + +// // unload wallet after temporary use +// defer walletLoader.UnloadWallet() + +// if onLoaded != nil { +// return onLoaded(wal) +// } + +// return nil +// } diff --git a/wallets/dcr/politeia.go b/wallets/dcr/politeia.go index 146a127ed..078377faf 100644 --- a/wallets/dcr/politeia.go +++ b/wallets/dcr/politeia.go @@ -39,17 +39,17 @@ func (p *Politeia) getLastSyncedTimestamp() int64 { func (p *Politeia) saveOrOverwiteProposal(proposal *Proposal) error { var oldProposal Proposal - err := p.WalletRef.db.One("Token", proposal.Token, &oldProposal) + err := p.WalletRef.DB.One("Token", proposal.Token, &oldProposal) if err != nil && err != storm.ErrNotFound { return errors.Errorf("error checking if proposal was already indexed: %s", err.Error()) } if oldProposal.Token != "" { // delete old record before saving new (if it exists) - p.WalletRef.db.DeleteStruct(oldProposal) + p.WalletRef.DB.DeleteStruct(oldProposal) } - return p.WalletRef.db.Save(proposal) + return p.WalletRef.DB.Save(proposal) } // GetProposalsRaw fetches and returns a proposals from the db @@ -64,16 +64,16 @@ func (p *Politeia) getProposalsRaw(category int32, offset, limit int32, newestFi case ProposalCategoryAll: if skipAbandoned { - query = p.WalletRef.db.Select( + query = p.WalletRef.DB.Select( q.Not(q.Eq("Category", ProposalCategoryAbandoned)), ) } else { - query = p.WalletRef.db.Select( + query = p.WalletRef.DB.Select( q.True(), ) } default: - query = p.WalletRef.db.Select( + query = p.WalletRef.DB.Select( q.Eq("Category", category), ) } @@ -124,7 +124,7 @@ func (p *Politeia) GetProposals(category int32, offset, limit int32, newestFirst // GetProposalRaw fetches and returns a single proposal specified by it's censorship record token func (p *Politeia) GetProposalRaw(censorshipToken string) (*Proposal, error) { var proposal Proposal - err := p.WalletRef.db.One("Token", censorshipToken, &proposal) + err := p.WalletRef.DB.One("Token", censorshipToken, &proposal) if err != nil { return nil, err } @@ -140,7 +140,7 @@ func (p *Politeia) GetProposal(censorshipToken string) (string, error) { // GetProposalByIDRaw fetches and returns a single proposal specified by it's ID func (p *Politeia) GetProposalByIDRaw(proposalID int) (*Proposal, error) { var proposal Proposal - err := p.WalletRef.db.One("ID", proposalID, &proposal) + err := p.WalletRef.DB.One("ID", proposalID, &proposal) if err != nil { return nil, err } @@ -163,7 +163,7 @@ func (p *Politeia) Count(category int32) (int32, error) { matcher = q.Eq("Category", category) } - count, err := p.WalletRef.db.Select(matcher).Count(&Proposal{}) + count, err := p.WalletRef.DB.Select(matcher).Count(&Proposal{}) if err != nil { return 0, err } @@ -209,10 +209,10 @@ func (p *Politeia) Overview() (*ProposalOverview, error) { } func (p *Politeia) ClearSavedProposals() error { - err := p.WalletRef.db.Drop(&Proposal{}) + err := p.WalletRef.DB.Drop(&Proposal{}) if err != nil { return translateError(err) } - return p.WalletRef.db.Init(&Proposal{}) + return p.WalletRef.DB.Init(&Proposal{}) } diff --git a/wallets/dcr/politeia_sync.go b/wallets/dcr/politeia_sync.go index f9a3f3473..30f644285 100644 --- a/wallets/dcr/politeia_sync.go +++ b/wallets/dcr/politeia_sync.go @@ -221,7 +221,7 @@ func (p *Politeia) updateProposalDetails(oldProposal, updatedProposal Proposal) } } - err := p.WalletRef.db.Update(&updatedProposal) + err := p.WalletRef.DB.Update(&updatedProposal) if err != nil { return fmt.Errorf("error saving updated proposal: %s", err.Error()) } diff --git a/wallets/dcr/wallet.go b/wallets/dcr/wallet.go index 14045d3ff..24e4288b5 100644 --- a/wallets/dcr/wallet.go +++ b/wallets/dcr/wallet.go @@ -26,7 +26,7 @@ type Wallet struct { CreatedAt time.Time `storm:"index"` dbDriver string rootDir string - db *storm.DB + DB *storm.DB EncryptedSeed []byte IsRestored bool @@ -184,13 +184,13 @@ func (wallet *Wallet) CreateNewWallet(walletName, privatePassphrase string, priv HasDiscoveredAccounts: true, } - return wallet.saveNewWallet(func() error { - err := wallet.Prepare(wallet.rootDir, wallet.chainParams, wallet.walletConfigSetFn(wal.ID), wallet.walletConfigReadFn(wal.ID)) + return wal.saveNewWallet(func() error { + err := wal.Prepare(wal.rootDir, wal.chainParams, wal.walletConfigSetFn(wal.ID), wal.walletConfigReadFn(wal.ID)) if err != nil { return err } - return wallet.CreateWallet(privatePassphrase, seed) + return wal.CreateWallet(privatePassphrase, seed) }) } @@ -260,7 +260,7 @@ func (wallet *Wallet) RenameWallet(newName string) error { } wallet.Name = newName - return wallet.db.Save(wallet) // update WalletName field + return wallet.DB.Save(wallet) // update WalletName field } func (wallet *Wallet) RestoreWallet(walletName, seedMnemonic, privatePassphrase string, privatePassphraseType int32) (*Wallet, error) { @@ -298,7 +298,7 @@ func (wallet *Wallet) DeleteWallet(privPass []byte) error { return translateError(err) } - err = wallet.db.DeleteStruct(wallet) + err = wallet.DB.DeleteStruct(wallet) if err != nil { return translateError(err) } @@ -368,7 +368,7 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { } func (wallet *Wallet) LinkExistingWallet(walletName, walletDataDir, originalPubPass string, privatePassphraseType int32) (*Wallet, error) { - // check if `walletDataDir` contains wallet.db + // check if `walletDataDir` contains wallet.DB if !WalletExistsAt(walletDataDir) { return nil, errors.New(ErrNotExist) } @@ -388,7 +388,7 @@ func (wallet *Wallet) LinkExistingWallet(walletName, walletDataDir, originalPubP } return wallet.saveNewWallet(func() error { - // move wallet.db and tx.db files to newly created dir for the wallet + // move wallet.DB and tx.DB files to newly created dir for the wallet currentWalletDbFilePath := filepath.Join(walletDataDir, walletDbName) newWalletDbFilePath := filepath.Join(wal.DataDir, walletDbName) if err := moveFile(currentWalletDbFilePath, newWalletDbFilePath); err != nil { @@ -516,7 +516,7 @@ func (wallet *Wallet) ChangePrivatePassphraseForWallet(oldPrivatePassphrase, new wallet.EncryptedSeed = encryptedSeed wallet.PrivatePassphraseType = privatePassphraseType - err = wallet.db.Save(wallet) + err = wallet.DB.Save(wallet) if err != nil { log.Errorf("error saving wallet-[%d] to database after passphrase change: %v", wallet.ID, err) @@ -625,7 +625,7 @@ func (wallet *Wallet) VerifySeedForWallet(seedMnemonic string, privpass []byte) if decryptedSeed == seedMnemonic { wallet.EncryptedSeed = nil - return true, translateError(wallet.db.Save(wallet)) + return true, translateError(wallet.DB.Save(wallet)) } return false, errors.New(ErrInvalid) diff --git a/wallets/dcr/wallet_config.go b/wallets/dcr/wallet_config.go index a185bcc25..29abd6c71 100644 --- a/wallets/dcr/wallet_config.go +++ b/wallets/dcr/wallet_config.go @@ -51,7 +51,7 @@ type configReadFn = func(multiwallet bool, key string, valueOut interface{}) err func (wallet *Wallet) walletConfigSetFn(walletID int) configSaveFn { return func(key string, value interface{}) error { walletUniqueKey := WalletUniqueConfigKey(walletID, key) - return wallet.db.Set(userConfigBucketName, walletUniqueKey, value) + return wallet.DB.Set(userConfigBucketName, walletUniqueKey, value) } } @@ -60,7 +60,7 @@ func (wallet *Wallet) walletConfigReadFn(walletID int) configReadFn { if !multiwallet { key = WalletUniqueConfigKey(walletID, key) } - return wallet.db.Get(userConfigBucketName, key, valueOut) + return wallet.DB.Get(userConfigBucketName, key, valueOut) } } diff --git a/wallets/dcr/wallet_utils.go b/wallets/dcr/wallet_utils.go index 2ec5deb8d..474bc32a0 100644 --- a/wallets/dcr/wallet_utils.go +++ b/wallets/dcr/wallet_utils.go @@ -21,7 +21,7 @@ func (wallet *Wallet) markWalletAsDiscoveredAccounts() error { log.Infof("Set discovered accounts = true for wallet %d", wallet.ID) wallet.HasDiscoveredAccounts = true - err := wallet.db.Save(wallet) + err := wallet.DB.Save(wallet) if err != nil { return err } @@ -30,7 +30,7 @@ func (wallet *Wallet) markWalletAsDiscoveredAccounts() error { } func (wallet *Wallet) batchDbTransaction(dbOp func(node storm.Node) error) (err error) { - dbTx, err := wallet.db.Begin(true) + dbTx, err := wallet.DB.Begin(true) if err != nil { return err } @@ -57,7 +57,7 @@ func (wallet *Wallet) WalletNameExists(walletName string) (bool, error) { return false, errors.E(ErrReservedWalletName) } - err := wallet.db.One("Name", walletName, &Wallet{}) + err := wallet.DB.One("Name", walletName, &Wallet{}) if err == nil { return true, nil } else if err != storm.ErrNotFound { From 475282b3981bf9d1846a8e3e7a095ff9efa50231 Mon Sep 17 00:00:00 2001 From: dreacot Date: Mon, 15 Aug 2022 23:31:47 +0100 Subject: [PATCH 13/16] - create watch only wallet, cleanup code --- dexclient.go | 2 +- go.sum | 10 ++ multiwallet.go | 211 ++++++++++++++++++++---------------- multiwallet_utils.go | 2 +- wallets/btc/go.mod | 2 +- wallets/btc/go.sum | 2 + wallets/btc/wallet.go | 87 ++++++++++++--- wallets/btc/wallet_utils.go | 4 +- 8 files changed, 207 insertions(+), 113 deletions(-) diff --git a/dexclient.go b/dexclient.go index 5d9079e90..6c3c11dee 100644 --- a/dexclient.go +++ b/dexclient.go @@ -89,7 +89,7 @@ func (mw *MultiWallet) prepareDexSupportForDcrWalletLibrary() error { return nil, fmt.Errorf("invalid wallet ID %q in settings", walletIDStr) } - wallet := mw.WalletWithID(walletID) + wallet, _ := mw.WalletWithID(walletID, "DCR") if wallet == nil { return nil, fmt.Errorf("no wallet exists with ID %q", walletIDStr) } diff --git a/go.sum b/go.sum index d56c03dbc..a12b86c73 100644 --- a/go.sum +++ b/go.sum @@ -144,9 +144,19 @@ github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e/go.mod h1:3P github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.1 h1:xxivBG6pU3wwxx9qPNZP+2K0PXO9VmFLaSrwOFr24Hw= github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.1 h1:hDcDaXiP0uEzR8Biqo2weECKqEw0uHDZ9ixIWevVQqY= github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= +github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= +github.com/btcsuite/btcd/btcutil v1.1.2/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= diff --git a/multiwallet.go b/multiwallet.go index 6193c72d9..0e6808fba 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -22,17 +22,15 @@ import ( "golang.org/x/crypto/bcrypt" ) -// type AllWallets struct { -// DCR *dcr.Wallet -// BTC *btc.Wallet -// } -type AllWallets struct { - DCR map[int]*dcr.Wallet - BTC map[int]*btc.Wallet -} - type Assets struct { - DCR map[int]*dcr.Wallet + DCR struct { + Wallets map[int]*dcr.Wallet + BadWallets map[int]*dcr.Wallet + DBDriver string + RootDir string + DB *storm.DB + ChainParams *chaincfg.Params + } BTC struct { Wallets map[int]*btc.Wallet BadWallets map[int]*btc.Wallet @@ -49,7 +47,6 @@ type MultiWallet struct { db *storm.DB chainParams *chaincfg.Params - AllWallets *AllWallets Assets *Assets wallets map[int]*dcr.Wallet badWallets map[int]*dcr.Wallet @@ -86,6 +83,21 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall db: dcrDB, chainParams: chainParams, Assets: &Assets{ + DCR: struct { + Wallets map[int]*dcr.Wallet + BadWallets map[int]*dcr.Wallet + DBDriver string + RootDir string + DB *storm.DB + ChainParams *chaincfg.Params + }{ + Wallets: make(map[int]*dcr.Wallet), + BadWallets: make(map[int]*dcr.Wallet), + DBDriver: dbDriver, + RootDir: dcrRootDir, + DB: dcrDB, + ChainParams: chainParams, + }, BTC: struct { Wallets map[int]*btc.Wallet BadWallets map[int]*btc.Wallet @@ -102,57 +114,9 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall ChainParams: &btccfg.TestNet3Params, }, }, - wallets: make(map[int]*dcr.Wallet), - badWallets: make(map[int]*dcr.Wallet), } - // read saved wallets info from db and initialize wallets - query := mw.db.Select(q.True()).OrderBy("ID") - var dcrWallets []*dcr.Wallet - err = query.Find(&dcrWallets) - if err != nil && err != storm.ErrNotFound { - return nil, err - } - - // prepare the wallets loaded from db for use - for _, wallet := range dcrWallets { - err = wallet.Prepare(rootDir, chainParams, mw.walletConfigSetFn(wallet.ID), mw.walletConfigReadFn(wallet.ID)) - if err == nil && !WalletExistsAt(wallet.DataDir) { - err = fmt.Errorf("missing wallet database file") - } - if err != nil { - mw.badWallets[wallet.ID] = wallet - log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) - } else { - mw.AllWallets.DCR[wallet.ID] = wallet - } - - // initialize Politeia. - wallet.NewPoliteia(politeiaHost) - } - - // read saved wallets info from db and initialize wallets - query = btcDB.Select(q.True()).OrderBy("ID") - var btcWallets []*btc.Wallet - err = query.Find(&btcWallets) - if err != nil && err != storm.ErrNotFound { - return nil, err - } - - // prepare the wallets loaded from db for use - for _, wallet := range btcWallets { - err = wallet.Prepare(btcRootDir, mw.NetType(), log) - if err == nil && !WalletExistsAt(wallet.DataDir()) { - err = fmt.Errorf("missing wallet database file") - } - if err != nil { - mw.Assets.BTC.BadWallets[wallet.ID] = wallet - - log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) - } else { - mw.Assets.BTC.Wallets[wallet.ID] = wallet - } - } + mw.prepareWallets() mw.listenForShutdown() @@ -165,10 +129,6 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall log.Errorf("DEX client set up error: %v", err) } - // if err = mw.initBtcWallet(); err != nil { - // log.Errorf("BTC wallet set up error: %v", err) - // } - return mw, nil } @@ -188,10 +148,10 @@ func (mw *MultiWallet) prepareWallets() error { err = fmt.Errorf("missing wallet database file") } if err != nil { - mw.badWallets[wallet.ID] = wallet + mw.Assets.DCR.BadWallets[wallet.ID] = wallet log.Warnf("Ignored wallet load error for wallet %d (%s)", wallet.ID, wallet.Name) } else { - mw.AllWallets.DCR[wallet.ID] = wallet + mw.Assets.DCR.Wallets[wallet.ID] = wallet } // initialize Politeia. @@ -230,30 +190,46 @@ func (mw *MultiWallet) Shutdown() { // Trigger shuttingDown signal to cancel all contexts created with `shutdownContextWithCancel`. mw.shuttingDown <- true - for _, wallet := range mw.wallets { + mw.shuttingDownDCR() + + mw.shuttingDownBTC() + + if logRotator != nil { + log.Info("Shutting down log rotator") + logRotator.Close() + log.Info("Shutdown log rotator successfully") + } +} + +func (mw *MultiWallet) shuttingDownDCR() { + for _, wallet := range mw.Assets.DCR.Wallets { wallet.CancelRescan() } - for _, wallet := range mw.wallets { + for _, wallet := range mw.Assets.DCR.Wallets { wallet.CancelSync() } - for _, wallet := range mw.wallets { + for _, wallet := range mw.Assets.DCR.Wallets { wallet.Shutdown() } - if mw.db != nil { - if err := mw.db.Close(); err != nil { - log.Errorf("db closed with error: %v", err) + if mw.Assets.DCR.DB != nil { + if err := mw.Assets.DCR.DB.Close(); err != nil { + log.Errorf("dcr db closed with error: %v", err) } else { - log.Info("db closed successfully") + log.Info("dcr db closed successfully") } } +} - if logRotator != nil { - log.Info("Shutting down log rotator") - logRotator.Close() - log.Info("Shutdown log rotator successfully") +func (mw *MultiWallet) shuttingDownBTC() { + if mw.Assets.BTC.DB != nil { + if err := mw.Assets.BTC.DB.Close(); err != nil { + log.Errorf("btc db closed with error: %v", err) + } else { + log.Info("btc db closed successfully") + } } } @@ -349,16 +325,24 @@ func (mw *MultiWallet) StartupSecurityType() int32 { } func (mw *MultiWallet) OpenWallets(startupPassphrase []byte) error { - // if mw.IsSyncing() { - // return errors.New(ErrSyncAlreadyInProgress) - // } - err := mw.VerifyStartupPassphrase(startupPassphrase) if err != nil { return err } - for _, wallet := range mw.wallets { + // Open DCR wallets + for _, wallet := range mw.Assets.DCR.Wallets { + if wallet.IsSyncing() { + return errors.New(ErrSyncAlreadyInProgress) + } + err = wallet.OpenWallet() + if err != nil { + return err + } + } + + // Open BTC wallets + for _, wallet := range mw.Assets.BTC.Wallets { err = wallet.OpenWallet() if err != nil { return err @@ -369,11 +353,17 @@ func (mw *MultiWallet) OpenWallets(startupPassphrase []byte) error { } func (mw *MultiWallet) AllWalletsAreWatchOnly() (bool, error) { - if len(mw.wallets) == 0 { + if len(mw.Assets.DCR.Wallets) == 0 || len(mw.Assets.BTC.Wallets) == 0 { return false, errors.New(ErrInvalid) } - for _, w := range mw.wallets { + for _, w := range mw.Assets.DCR.Wallets { + if !w.IsWatchingOnlyWallet() { + return false, nil + } + } + + for _, w := range mw.Assets.BTC.Wallets { if !w.IsWatchingOnlyWallet() { return false, nil } @@ -405,17 +395,33 @@ func (mw *MultiWallet) DeleteBadWallet(walletID int) error { return nil } -func (mw *MultiWallet) WalletWithID(walletID int) *dcr.Wallet { - if wallet, ok := mw.wallets[walletID]; ok { - return wallet +func (mw *MultiWallet) WalletWithID(walletID int, Asset string) (*dcr.Wallet, *btc.Wallet) { + switch Asset { + case "DCR", "dcr": + if wallet, ok := mw.Assets.DCR.Wallets[walletID]; ok { + return wallet, nil + } + return nil, nil + case "BTC", "btc": + if wallet, ok := mw.Assets.BTC.Wallets[walletID]; ok { + return nil, wallet + } + return nil, nil + default: + return nil, nil } - return nil } // NumWalletsNeedingSeedBackup returns the number of opened wallets whose seed haven't been verified. func (mw *MultiWallet) NumWalletsNeedingSeedBackup() int32 { var backupsNeeded int32 - for _, wallet := range mw.wallets { + for _, wallet := range mw.Assets.DCR.Wallets { + if wallet.WalletOpened() && wallet.EncryptedSeed != nil { + backupsNeeded++ + } + } + + for _, wallet := range mw.Assets.BTC.Wallets { if wallet.WalletOpened() && wallet.EncryptedSeed != nil { backupsNeeded++ } @@ -424,12 +430,18 @@ func (mw *MultiWallet) NumWalletsNeedingSeedBackup() int32 { } func (mw *MultiWallet) LoadedWalletsCount() int32 { - return int32(len(mw.wallets)) + return int32(len(mw.Assets.DCR.Wallets)) + int32(len(mw.Assets.BTC.Wallets)) } func (mw *MultiWallet) OpenedWalletIDsRaw() []int { walletIDs := make([]int, 0) - for _, wallet := range mw.wallets { + for _, wallet := range mw.Assets.DCR.Wallets { + if wallet.WalletOpened() { + walletIDs = append(walletIDs, wallet.ID) + } + } + + for _, wallet := range mw.Assets.BTC.Wallets { if wallet.WalletOpened() { walletIDs = append(walletIDs, wallet.ID) } @@ -449,7 +461,13 @@ func (mw *MultiWallet) OpenedWalletsCount() int32 { func (mw *MultiWallet) SyncedWalletsCount() int32 { var syncedWallets int32 - for _, wallet := range mw.wallets { + for _, wallet := range mw.Assets.DCR.Wallets { + if wallet.WalletOpened() && wallet.Synced { + syncedWallets++ + } + } + + for _, wallet := range mw.Assets.BTC.Wallets { if wallet.WalletOpened() && wallet.Synced { syncedWallets++ } @@ -463,7 +481,14 @@ func (mw *MultiWallet) WalletNameExists(walletName string) (bool, error) { return false, errors.E(ErrReservedWalletName) } - err := mw.db.One("Name", walletName, &dcr.Wallet{}) + err := mw.Assets.DCR.DB.One("Name", walletName, &dcr.Wallet{}) + if err == nil { + return true, nil + } else if err != storm.ErrNotFound { + return false, err + } + + err = mw.Assets.BTC.DB.One("Name", walletName, &btc.Wallet{}) if err == nil { return true, nil } else if err != storm.ErrNotFound { diff --git a/multiwallet_utils.go b/multiwallet_utils.go index d94f28f25..5caaf96aa 100644 --- a/multiwallet_utils.go +++ b/multiwallet_utils.go @@ -83,7 +83,7 @@ func (mw *MultiWallet) loadWalletTemporarily(ctx context.Context, walletDataDir, } func (mw *MultiWallet) markWalletAsDiscoveredAccounts(walletID int) error { - wallet := mw.WalletWithID(walletID) + wallet, _ := mw.WalletWithID(walletID, "DCR") if wallet == nil { return errors.New(ErrNotExist) } diff --git a/wallets/btc/go.mod b/wallets/btc/go.mod index 2c38a6b6e..ce2366bbc 100644 --- a/wallets/btc/go.mod +++ b/wallets/btc/go.mod @@ -4,7 +4,7 @@ require ( decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 github.com/btcsuite/btcd v0.22.1 - github.com/btcsuite/btcd/btcutil v1.1.1 // indirect + github.com/btcsuite/btcd/btcutil v1.1.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // note: hoists btcd's own require of btcutil diff --git a/wallets/btc/go.sum b/wallets/btc/go.sum index bf621ae16..4c963814a 100644 --- a/wallets/btc/go.sum +++ b/wallets/btc/go.sum @@ -139,8 +139,10 @@ github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e/go.mod h1:3P github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.1.1 h1:xxivBG6pU3wwxx9qPNZP+2K0PXO9VmFLaSrwOFr24Hw= github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.1 h1:hDcDaXiP0uEzR8Biqo2weECKqEw0uHDZ9ixIWevVQqY= github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= diff --git a/wallets/btc/wallet.go b/wallets/btc/wallet.go index 888eae56e..fff0bcade 100644 --- a/wallets/btc/wallet.go +++ b/wallets/btc/wallet.go @@ -21,7 +21,7 @@ import ( "github.com/btcsuite/btcutil/gcs" "github.com/btcsuite/btcwallet/chain" // "github.com/btcsuite/btcwallet/wallet" - // "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/hdkeychain" w "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" // "github.com/planetdecred/dcrlibwallet" @@ -53,6 +53,9 @@ type Wallet struct { dataDir string cancelFuncs []context.CancelFunc + Synced bool + + chainParams *chaincfg.Params loader *w.Loader log slog.Logger @@ -167,10 +170,11 @@ func (wallet *Wallet) RawRequest(method string, params []json.RawMessage) (json. } func CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32, db *storm.DB, rootDir, dbDriver string, chainParams *chaincfg.Params) (*Wallet, error) { - seed := "witch collapse practice feed shame open despair" - encryptedSeed := []byte(seed) - - chainParams, err := parseChainParams("testnet3") + // seed := "witch collapse practice feed shame open despair" + // encryptedSeed := []byte(seed) + encryptedSeed, err := hdkeychain.GenerateSeed( + hdkeychain.RecommendedSeedLen, + ) if err != nil { return nil, err } @@ -196,30 +200,83 @@ func CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType }) } -func (wallet *Wallet) RenameWallet(newName string) error { +func (wallet *Wallet) RenameWallet(newName string, walledDbRef *storm.DB) error { if strings.HasPrefix(newName, "wallet-") { return errors.E(ErrReservedWalletName) } - if exists, err := wallet.WalletNameExists(newName); err != nil { + if exists, err := WalletNameExists(newName, walledDbRef); err != nil { return translateError(err) } else if exists { return errors.New(ErrExist) } wallet.Name = newName - return wallet.db.Save(wallet) // update WalletName field + return walledDbRef.Save(wallet) // update WalletName field } -func (wallet *Wallet) WalletWithID(walletName string) (*Wallet, error) { - var w *Wallet - err := wallet.db.One("ID", wallet.ID, &w) - if err == nil && err != storm.ErrNotFound { - return nil, err +func (wallet *Wallet) OpenWallet() error { + pubPass := []byte(w.InsecurePubPassphrase) + + _, err := wallet.loader.OpenExistingWallet(pubPass, false) + if err != nil { + // log.Error(err) + return translateError(err) + } + + return nil +} + +func (wallet *Wallet) WalletExists() (bool, error) { + return wallet.loader.WalletExists() +} + +func (wallet *Wallet) IsWatchingOnlyWallet() bool { + if _, ok := wallet.loader.LoadedWallet(); ok { + // return w.WatchingOnly() + return false + } + + return false +} + +func (wallet *Wallet) WalletOpened() bool { + return wallet.Internal() != nil +} + +func (wallet *Wallet) Internal() *w.Wallet { + lw, _ := wallet.loader.LoadedWallet() + return lw +} + +func CreateNewWatchOnlyWallet(walletName string, chainParams *chaincfg.Params) (*Wallet, error) { + wallet := &Wallet{ + Name: walletName, + chainParams: chainParams, + } + + return wallet.saveNewWallet(func() error { + err := wallet.Prepare(wallet.rootDir, "testnet3", wallet.log) + if err != nil { + return err + } + + return wallet.createWatchingOnlyWallet() + }) +} + +func (wallet *Wallet) createWatchingOnlyWallet() error { + pubPass := []byte(w.InsecurePubPassphrase) + + _, err := wallet.loader.CreateNewWatchingOnlyWallet(pubPass, time.Now()) + if err != nil { + // log.Error(err) + return err } - return w, nil + // log.Info("Created Watching Only Wallet") + return nil } func (wallet *Wallet) DataDir() string { @@ -504,7 +561,7 @@ func (wallet *Wallet) createWallet(privatePassphrase string, seedMnemonic []byte // IFF all the above operations succeed, the wallet info will be persisted to db // and the wallet will be added to `btcWallet.wallets`. func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { - exists, err := wallet.WalletNameExists(wallet.Name) + exists, err := WalletNameExists(wallet.Name, wallet.db) if err != nil { return nil, err } else if exists { diff --git a/wallets/btc/wallet_utils.go b/wallets/btc/wallet_utils.go index f542185e3..671da1612 100644 --- a/wallets/btc/wallet_utils.go +++ b/wallets/btc/wallet_utils.go @@ -52,12 +52,12 @@ func (wallet *Wallet) batchDbTransaction(dbOp func(node storm.Node) error) (err return err } -func (wallet *Wallet) WalletNameExists(walletName string) (bool, error) { +func WalletNameExists(walletName string, walledDbRef *storm.DB) (bool, error) { if strings.HasPrefix(walletName, "wallet-") { return false, errors.E(ErrReservedWalletName) } - err := wallet.db.One("Name", walletName, &Wallet{}) + err := walledDbRef.One("Name", walletName, &Wallet{}) if err == nil { return true, nil } else if err != storm.ErrNotFound { From afd4606d6eb27c264e1f5ecd6e34bf3619993f5a Mon Sep 17 00:00:00 2001 From: dreacot Date: Tue, 16 Aug 2022 00:01:42 +0100 Subject: [PATCH 14/16] delete wallet functionality: W.I.P --- multiwallet.go | 64 +++++++++++++++++++++++++++++++++++++++++++ wallets/btc/wallet.go | 58 +++++++++++++++++++++++++++++++++++---- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/multiwallet.go b/multiwallet.go index 0e6808fba..62d50b21b 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -395,6 +395,70 @@ func (mw *MultiWallet) DeleteBadWallet(walletID int) error { return nil } +func (mw *MultiWallet) DeleteWallet(walletID int, privPass []byte, Asset string) error { + switch Asset { + case "DCR", "dcr": + wallet, _ := mw.WalletWithID(walletID, "dcr") + if wallet == nil { + return errors.New(ErrNotExist) + } + + if wallet.IsConnectedToDecredNetwork() { + wallet.CancelSync() + defer func() { + if mw.OpenedWalletsCount() > 0 { + wallet.SpvSync() + } + }() + } + + // err := wallet.deleteWallet(privPass) + // if err != nil { + // return translateError(err) + // } + + err := mw.Assets.DCR.DB.DeleteStruct(wallet) + if err != nil { + return translateError(err) + } + + delete(mw.wallets, walletID) + + return nil + case "BTC", "btc": + _, wallet := mw.WalletWithID(walletID, "btc") + if wallet == nil { + return errors.New(ErrNotExist) + } + + // if wallet.IsConnectedToDecredNetwork() { + // wallet.CancelSync() + // defer func() { + // if mw.OpenedWalletsCount() > 0 { + // wallet.SpvSync() + // } + // }() + // } + + err := wallet.DeleteWallet(privPass) + if err != nil { + return translateError(err) + } + + err = mw.Assets.BTC.DB.DeleteStruct(wallet) + if err != nil { + return translateError(err) + } + + delete(mw.Assets.BTC.Wallets, walletID) + + return nil + default: + return nil + } + +} + func (mw *MultiWallet) WalletWithID(walletID int, Asset string) (*dcr.Wallet, *btc.Wallet) { switch Asset { case "DCR", "dcr": diff --git a/wallets/btc/wallet.go b/wallets/btc/wallet.go index fff0bcade..b143d102b 100644 --- a/wallets/btc/wallet.go +++ b/wallets/btc/wallet.go @@ -53,8 +53,7 @@ type Wallet struct { dataDir string cancelFuncs []context.CancelFunc - Synced bool - + Synced bool chainParams *chaincfg.Params loader *w.Loader @@ -215,7 +214,6 @@ func (wallet *Wallet) RenameWallet(newName string, walledDbRef *storm.DB) error return walledDbRef.Save(wallet) // update WalletName field } - func (wallet *Wallet) OpenWallet() error { pubPass := []byte(w.InsecurePubPassphrase) @@ -252,8 +250,8 @@ func (wallet *Wallet) Internal() *w.Wallet { func CreateNewWatchOnlyWallet(walletName string, chainParams *chaincfg.Params) (*Wallet, error) { wallet := &Wallet{ - Name: walletName, - chainParams: chainParams, + Name: walletName, + chainParams: chainParams, } return wallet.saveNewWallet(func() error { @@ -279,6 +277,56 @@ func (wallet *Wallet) createWatchingOnlyWallet() error { return nil } +// func (wallet *Wallet) DeleteWallet(privPass []byte, walledDbRef *storm.DB) error { +// // if wallet.IsConnectedToDecredNetwork() { +// // wallet.CancelSync() +// // defer func() { +// // // if mw.OpenedWalletsCount() > 0 { +// // wallet.SpvSync() +// // // } +// // }() +// // } + +// err := wallet.deleteWallet(privPass) +// if err != nil { +// return translateError(err) +// } + +// err = walledDbRef.DeleteStruct(wallet) +// if err != nil { +// return translateError(err) +// } + +// // delete(mw.wallets, walletID) + +// return nil +// } + +func (wallet *Wallet) DeleteWallet(privatePassphrase []byte) error { + defer func() { + for i := range privatePassphrase { + privatePassphrase[i] = 0 + } + }() + + if _, loaded := wallet.loader.LoadedWallet(); !loaded { + return errors.New(ErrWalletNotLoaded) + } + + if !wallet.IsWatchingOnlyWallet() { + err := wallet.Internal().Unlock(privatePassphrase, nil) + if err != nil { + return translateError(err) + } + wallet.Internal().Lock() + } + + // wallet.Shutdown() + + // log.Info("Deleting Wallet") + return os.RemoveAll(wallet.dataDir) +} + func (wallet *Wallet) DataDir() string { return wallet.dataDir } From 747ee1d5d9b4942e0889ae01819fb103372e21cc Mon Sep 17 00:00:00 2001 From: dreacot Date: Wed, 17 Aug 2022 19:45:40 +0100 Subject: [PATCH 15/16] cleanup code --- go.mod | 14 +- go.sum | 12 + wallets/btc/go.mod | 24 - wallets/btc/go.sum | 1436 ----------------------------------- wallets/btc/wallet.go | 389 +++++----- wallets/btc/wallet_utils.go | 48 ++ 6 files changed, 250 insertions(+), 1673 deletions(-) delete mode 100644 wallets/btc/go.mod delete mode 100644 wallets/btc/go.sum diff --git a/go.mod b/go.mod index e1b8734db..096067637 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,13 @@ require ( decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 github.com/btcsuite/btcd v0.22.1 + github.com/btcsuite/btcd/btcutil v1.1.1 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f + github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // note: hoists btcd's own require of btcutil + github.com/btcsuite/btcwallet v0.12.0 + github.com/btcsuite/btcwallet/walletdb v1.4.0 + github.com/btcsuite/btcwallet/wtxmgr v1.3.0 github.com/decred/dcrd/addrmgr/v2 v2.0.0 github.com/decred/dcrd/blockchain/stake/v4 v4.0.0 github.com/decred/dcrd/chaincfg/chainhash v1.0.3 @@ -23,10 +30,10 @@ require ( github.com/dgraph-io/badger v1.6.2 github.com/jrick/logrotate v1.0.0 github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 + github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d - github.com/planetdecred/dcrlibwallet/wallets/btc v0.0.0-00010101000000-000000000000 go.etcd.io/bbolt v1.3.6 golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f golang.org/x/sync v0.0.0-20210220032951-036812b2e83c @@ -36,9 +43,6 @@ require ( // and dcrdex (v1.10.3) but only v1.10.4 and above can be compiled for // the android OS using gomobile. This replace can be removed once any // of those projects update their github.com/lib/pq dependency. -replace ( - github.com/lib/pq => github.com/lib/pq v1.10.4 - github.com/planetdecred/dcrlibwallet/wallets/btc => ./wallets/btc // TODO: testing purpose, will remove when have new version -) +replace github.com/lib/pq => github.com/lib/pq v1.10.4 go 1.16 diff --git a/go.sum b/go.sum index a12b86c73..13aebe8e5 100644 --- a/go.sum +++ b/go.sum @@ -218,6 +218,7 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/companyzero/sntrup4591761 v0.0.0-20200131011700-2b0d299dbd22 h1:vfqLMkB1UqwJliW0I/34oscQawInrVfL1uPjGEEt2YY= github.com/companyzero/sntrup4591761 v0.0.0-20200131011700-2b0d299dbd22/go.mod h1:LoZJNGDWmVPqMEHmeJzj4Weq4Stjc6FKY6FVpY3Hem0= github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a h1:clYxJ3Os0EQUKDDVU8M0oipllX0EkuFNBfhVQuIfyF0= github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a/go.mod h1:z/9Ck1EDixEbBbZ2KH2qNHekEmDLTOZ+FyoIPWWSVOI= @@ -247,12 +248,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dchest/blake256 v1.0.0/go.mod h1:xXNWCE1jsAP8DAjP+rKw2MbeqLczjI3TRx2VK+9OEYY= github.com/dchest/siphash v1.2.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I= github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/base58 v1.0.0/go.mod h1:LLY1p5e3g91byL/UO1eiZaYd+uRoVRarybgcoymu9Ks= github.com/decred/base58 v1.0.1/go.mod h1:H2ENcsJjye1G7CbRa67kV9OFaui0LGr56ntKKoY5g9c= +github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI= github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA= github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E= @@ -586,6 +589,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W 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 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= @@ -648,6 +652,7 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -1175,6 +1180,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1266,12 +1272,14 @@ golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= @@ -1283,6 +1291,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -1418,6 +1427,7 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 h1:q1kiSVscqoDeqTF27eQ2NnLLDmqF0I373qQNXYMy0fo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= @@ -1444,6 +1454,7 @@ google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= @@ -1457,6 +1468,7 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= diff --git a/wallets/btc/go.mod b/wallets/btc/go.mod deleted file mode 100644 index ce2366bbc..000000000 --- a/wallets/btc/go.mod +++ /dev/null @@ -1,24 +0,0 @@ -module github.com/planetdecred/dcrlibwallet/wallets/btc - -require ( - decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 - github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 - github.com/btcsuite/btcd v0.22.1 - github.com/btcsuite/btcd/btcutil v1.1.1 - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 - github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f - github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // note: hoists btcd's own require of btcutil - github.com/btcsuite/btcwallet v0.12.0 - github.com/btcsuite/btcwallet/walletdb v1.4.0 - github.com/btcsuite/btcwallet/wtxmgr v1.3.0 - github.com/decred/dcrd/addrmgr/v2 v2.0.0 // indirect - github.com/decred/dcrd/connmgr/v3 v3.1.0 // indirect - github.com/decred/slog v1.2.0 - github.com/jrick/logrotate v1.0.0 - github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 // indirect - github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 - github.com/planetdecred/dcrlibwallet v1.7.0 // indirect - golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect -) - -go 1.16 diff --git a/wallets/btc/go.sum b/wallets/btc/go.sum deleted file mode 100644 index 4c963814a..000000000 --- a/wallets/btc/go.sum +++ /dev/null @@ -1,1436 +0,0 @@ -bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= -bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= -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.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -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.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -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.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= -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/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -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/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= -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= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= -decred.org/cspp v0.3.0 h1:2AkSsWzA7HIMZImfw0gT82Gdp8OXIM4NsBn7vna22uE= -decred.org/cspp v0.3.0/go.mod h1:UygjYilC94dER3BEU65Zzyoqy9ngJfWCD2rdJqvUs2A= -decred.org/cspp/v2 v2.0.0-20211122173608-ee00e4952d5f/go.mod h1:USyJS44Kqxz2wT/VaNsf9iTAONegO/qKXRdLg1nvrWI= -decred.org/cspp/v2 v2.0.0-20211207170141-a6b5f958a91f/go.mod h1:USyJS44Kqxz2wT/VaNsf9iTAONegO/qKXRdLg1nvrWI= -decred.org/cspp/v2 v2.0.0 h1:b4fZrElRufz30rYnBZ2shhC8AjNVTN4i6TMzDi+hk44= -decred.org/cspp/v2 v2.0.0/go.mod h1:0shJWKTWY3LxZEWGxtbER1Y45+HVjC0WZtj4bctSzCI= -decred.org/dcrdex v0.4.1/go.mod h1:C9PstuxJQIQWmFWJj2L1gWi+YSH9/WYZb0UgCGJ7cCE= -decred.org/dcrwallet v1.7.0 h1:U/ew00YBdUlx3rJAynt2OdKDgGzBKK4O89FijBq8iVg= -decred.org/dcrwallet v1.7.0/go.mod h1:hNOGyvH53gWdgFB601/ubGRzCPfPtWnEVAi9Grs90y4= -decred.org/dcrwallet/v2 v2.0.0-20211206163037-9537363becbb/go.mod h1:rbFJaCuXCfDhYoI5ZdeZr8TmF4A4Sb1zE7jQAwtaFMo= -decred.org/dcrwallet/v2 v2.0.0-20211207180344-e2bce3d3b877/go.mod h1:nRvFh0CChWgRxXxxCWG2wBpzJnfOhGhdxU7meaMhSfA= -decred.org/dcrwallet/v2 v2.0.1/go.mod h1:lZXgx5OcLDaWyNWFkBekqER1gdqiVwua1w68SFC1/Nk= -decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895 h1:qwUXrsjgm6qU7+/1mvaOgIfMeC2v0San3rm1fQVoCaU= -decred.org/dcrwallet/v2 v2.0.2-0.20220505152146-ece5da349895/go.mod h1:lZXgx5OcLDaWyNWFkBekqER1gdqiVwua1w68SFC1/Nk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= -github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -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/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenBazaar/jsonpb v0.0.0-20171123000858-37d32ddf4eef/go.mod h1:55mCznBcN9WQgrtgaAkv+p2LxeW/tQRdidyyE9D0I5k= -github.com/Sereal/Sereal v0.0.0-20181211220259-509a78ddbda3/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/aead/siphash v0.0.0-20170329201724-e404fcfc8885/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= -github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/apache/beam v2.27.0+incompatible/go.mod h1:/8NX3Qi8vGstDLLaeaU7+lzVEu/ACaQhYjeefzQ0y1o= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282 h1:DmSVc81daQAPvXwcCZi0W6A14sTCYQ1QI21C0E37KoY= -github.com/asdine/storm v0.0.0-20190216191021-fe89819f6282/go.mod h1:cMLKpjHSP4q0P133fV15ojQgwWWB2IMv+hrFsmBF/wI= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= -github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= -github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= -github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= -github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= -github.com/btcsuite/btcd v0.21.0-beta.0.20210426180113-7eba688b65e5/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= -github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= -github.com/btcsuite/btcd v0.22.0-beta.0.20211026140004-31791ba4dc6e/go.mod h1:3PH+KbvLFfzBTCevQenPiDedjGQGt6aa70dVjJDWGTA= -github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= -github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= -github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= -github.com/btcsuite/btcd/btcec/v2 v2.1.1 h1:xxivBG6pU3wwxx9qPNZP+2K0PXO9VmFLaSrwOFr24Hw= -github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= -github.com/btcsuite/btcd/btcutil v1.1.1 h1:hDcDaXiP0uEzR8Biqo2weECKqEw0uHDZ9ixIWevVQqY= -github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 h1:9aGy5p7oXRUB4MCTmWm0+jzuh79GpjPIfv1leA5POD4= -github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce h1:3PRwz+js0AMMV1fHRrCdQ55akoomx4Q3ulozHC3BDDY= -github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= -github.com/btcsuite/btcwallet v0.12.0 h1:0kT0rDN8vNcAvuHp2qUS/hLsfa0VUn2dNQ2GvM9ozBA= -github.com/btcsuite/btcwallet v0.12.0/go.mod h1:f1HuBGov5+OTp40Gh1vA+tvF6d7bbuLFTceJMRB7fXw= -github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= -github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk= -github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww= -github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w= -github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA= -github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= -github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= -github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= -github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= -github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= -github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= -github.com/btcsuite/btcwallet/walletdb v1.4.0/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= -github.com/btcsuite/btcwallet/wtxmgr v1.3.0 h1:lrZaZXGJjDedYTV7s5UgU9xBe8+N+cIDW7BYwI/B8Fs= -github.com/btcsuite/btcwallet/wtxmgr v1.3.0/go.mod h1:awQsh1n/0ZrEQ+JZgWvHeo153ubzEisf/FyNtwI0dDk= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -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/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/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= -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/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/companyzero/sntrup4591761 v0.0.0-20200131011700-2b0d299dbd22/go.mod h1:LoZJNGDWmVPqMEHmeJzj4Weq4Stjc6FKY6FVpY3Hem0= -github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a h1:clYxJ3Os0EQUKDDVU8M0oipllX0EkuFNBfhVQuIfyF0= -github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a/go.mod h1:z/9Ck1EDixEbBbZ2KH2qNHekEmDLTOZ+FyoIPWWSVOI= -github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= -github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= -github.com/dajohi/goemail v1.0.0/go.mod h1:YyX3pgj9VJX6VQYu8Cbs0GYHzgFUs8q0vX5pLmFvops= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= -github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/blake256 v1.0.0/go.mod h1:xXNWCE1jsAP8DAjP+rKw2MbeqLczjI3TRx2VK+9OEYY= -github.com/dchest/siphash v1.2.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= -github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= -github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= -github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= -github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= -github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= -github.com/decred/base58 v1.0.0/go.mod h1:LLY1p5e3g91byL/UO1eiZaYd+uRoVRarybgcoymu9Ks= -github.com/decred/base58 v1.0.1/go.mod h1:H2ENcsJjye1G7CbRa67kV9OFaui0LGr56ntKKoY5g9c= -github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= -github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA= -github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E= -github.com/decred/dcrd/addrmgr v1.0.2/go.mod h1:gNnmTuf/Xkg8ZX3j5GXbajzPrSdf5bA7HitO2bjmq0Q= -github.com/decred/dcrd/addrmgr v1.2.0/go.mod h1:QlZF9vkzwYh0qs25C76SAFZBRscjETga/K28GEE6qIc= -github.com/decred/dcrd/addrmgr/v2 v2.0.0/go.mod h1:5g9jPzBSQotmSnPri4oc1n5VVgWzPLlXwbr6HGoUVrg= -github.com/decred/dcrd/blockchain v1.0.0/go.mod h1:nNMgOz12wlasmEJDCuSuMWYSnjDdmB4l38GKuQ/Yd+8= -github.com/decred/dcrd/blockchain v1.0.1/go.mod h1:R/4XnwNOTj5IP8jQIUzrJ8zhr/7EOk09IMODwBamZoI= -github.com/decred/dcrd/blockchain v1.0.2 h1:+gJFfgv5LK+LcadyoiMln838/aU3rxDd0Smqogd6fkA= -github.com/decred/dcrd/blockchain v1.0.2/go.mod h1:R/4XnwNOTj5IP8jQIUzrJ8zhr/7EOk09IMODwBamZoI= -github.com/decred/dcrd/blockchain/stake v1.0.0/go.mod h1:opuzF8UouYyQyRJVF00Rdd7OgWb1WKyy1pyU0QYaxz0= -github.com/decred/dcrd/blockchain/stake v1.0.1/go.mod h1:hgoGmWMIu2LLApBbcguVpzCEEfX7M2YhuMrQdpohJzc= -github.com/decred/dcrd/blockchain/stake v1.0.2 h1:trUDgZsT5DYiwKu255k4hFtGiVEOAM9IDmP2GqOLYGU= -github.com/decred/dcrd/blockchain/stake v1.0.2/go.mod h1:hgoGmWMIu2LLApBbcguVpzCEEfX7M2YhuMrQdpohJzc= -github.com/decred/dcrd/blockchain/stake/v2 v2.0.0/go.mod h1:jv/rKMcZ87lhvVkHot/tElxeAYEUJ3mnKPHJ7WPq86U= -github.com/decred/dcrd/blockchain/stake/v2 v2.0.1/go.mod h1:jv/rKMcZ87lhvVkHot/tElxeAYEUJ3mnKPHJ7WPq86U= -github.com/decred/dcrd/blockchain/stake/v3 v3.0.0/go.mod h1:5GIUwsrHQCJauacgCegIR6t92SaeVi28Qls/BLN9vOw= -github.com/decred/dcrd/blockchain/stake/v4 v4.0.0-20210906140327-598bf66f24a6/go.mod h1:CStg0VQxxpVWphul8V3BtBOlhkkHfGE3CgwZK00xYwE= -github.com/decred/dcrd/blockchain/stake/v4 v4.0.0-20211110133211-e53d26e01d1f/go.mod h1:CStg0VQxxpVWphul8V3BtBOlhkkHfGE3CgwZK00xYwE= -github.com/decred/dcrd/blockchain/stake/v4 v4.0.0 h1:PwoCjCTbRvDUZKKs6N2Haus8XcbVXCJ9iGVs8C9sKwQ= -github.com/decred/dcrd/blockchain/stake/v4 v4.0.0/go.mod h1:bOgG7YTbTOWQgtHLL2l1Y9gBHIuM86zwVcQtsoGlZlQ= -github.com/decred/dcrd/blockchain/standalone v1.0.0 h1:bPkFgSV7/NeZI+ZEGhaOP+XccCUBTIJb3YTf8dMwe8g= -github.com/decred/dcrd/blockchain/standalone v1.0.0/go.mod h1:U5lOleFSi1nL7heSdLgEtuvg0udS1p3cvHxvLJbihfE= -github.com/decred/dcrd/blockchain/standalone/v2 v2.0.0/go.mod h1:t2qaZ3hNnxHZ5kzVJDgW5sp47/8T5hYJt7SR+/JtRhI= -github.com/decred/dcrd/blockchain/standalone/v2 v2.1.0 h1:aXh7a+86p+H65MGy0QKu4Juf3/j+Y5koVSyVYFMdqP0= -github.com/decred/dcrd/blockchain/standalone/v2 v2.1.0/go.mod h1:t2qaZ3hNnxHZ5kzVJDgW5sp47/8T5hYJt7SR+/JtRhI= -github.com/decred/dcrd/blockchain/v3 v3.0.2/go.mod h1:LD5VA95qdb+DlRiPI8VLBimDqvlDCAJsidZ5oD6nc/U= -github.com/decred/dcrd/blockchain/v4 v4.0.0-20211120053236-81ae286f2347/go.mod h1:GWN1XIAKTbQT+VoLYjtEIYA0PQ0uTOebtVG1Nw6zlfg= -github.com/decred/dcrd/blockchain/v4 v4.0.0/go.mod h1:i1FeTNN0LUEWBSMoI3riAFgfVE1X/7Seoz1aJ7YQGbk= -github.com/decred/dcrd/certgen v1.0.1/go.mod h1:NxEyGwzPHak+h3tNLYAXU4vWuL98HrY9Z59hc1E3SGI= -github.com/decred/dcrd/certgen v1.1.0/go.mod h1:ivkPLChfjdAgFh7ZQOtl6kJRqVkfrCq67dlq3AbZBQE= -github.com/decred/dcrd/certgen v1.1.1/go.mod h1:ivkPLChfjdAgFh7ZQOtl6kJRqVkfrCq67dlq3AbZBQE= -github.com/decred/dcrd/chaincfg v1.0.1/go.mod h1:O+443mQNPjci+WqWkKta3v2MgJn2u20YWy5mW3c2T7M= -github.com/decred/dcrd/chaincfg v1.1.1 h1:qRZkiA7ucsfsQPE/G/U1OnEUFozDl1MvM4ysJCUndLU= -github.com/decred/dcrd/chaincfg v1.1.1/go.mod h1:UlGtnp8Xx9YK+etBTybGjoFGoGXSw2bxZQuAnwfKv6I= -github.com/decred/dcrd/chaincfg/chainhash v1.0.1/go.mod h1:OVfvaOsNLS/A1y4Eod0Ip/Lf8qga7VXCQjUQLbkY0Go= -github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= -github.com/decred/dcrd/chaincfg/chainhash v1.0.3-0.20200921185235-6d75c7ec1199/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= -github.com/decred/dcrd/chaincfg/chainhash v1.0.3 h1:PF2czcYZGW3dz4i/35AUfVAgnqHl9TMNQt1ADTYGOoE= -github.com/decred/dcrd/chaincfg/chainhash v1.0.3/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= -github.com/decred/dcrd/chaincfg/v2 v2.0.2/go.mod h1:hpKvhLCDAD/xDZ3V1Pqpv9fIKVYYi11DyxETguazyvg= -github.com/decred/dcrd/chaincfg/v2 v2.1.0/go.mod h1:hpKvhLCDAD/xDZ3V1Pqpv9fIKVYYi11DyxETguazyvg= -github.com/decred/dcrd/chaincfg/v2 v2.2.0/go.mod h1:hpKvhLCDAD/xDZ3V1Pqpv9fIKVYYi11DyxETguazyvg= -github.com/decred/dcrd/chaincfg/v2 v2.3.0/go.mod h1:7qUJTvn+y/kswSRZ4sT2+EmvlDTDyy2InvNFtX/hxk0= -github.com/decred/dcrd/chaincfg/v3 v3.0.0/go.mod h1:EspyubQ7D2w6tjP7rBGDIE7OTbuMgBjR2F2kZFnh31A= -github.com/decred/dcrd/chaincfg/v3 v3.1.0/go.mod h1:4XF9nlx2NeGD4xzw1+L0DGICZMl0a5rKV8nnuHLgk8o= -github.com/decred/dcrd/chaincfg/v3 v3.1.1 h1:Ki8kq5IXGmjriiQyPCrCTF1aZSBiORb91/Sr5xW4otw= -github.com/decred/dcrd/chaincfg/v3 v3.1.1/go.mod h1:4XF9nlx2NeGD4xzw1+L0DGICZMl0a5rKV8nnuHLgk8o= -github.com/decred/dcrd/connmgr v1.0.1/go.mod h1:jR+woh3BTbP/35v0nHMiz6GfV1RO0uF1JA+mKeXNk04= -github.com/decred/dcrd/connmgr/v3 v3.0.0/go.mod h1:cPI43Aggp1lOhrVG75eJ3c3BwuFx0NhT77FK34ky+ak= -github.com/decred/dcrd/connmgr/v3 v3.1.0/go.mod h1:NVzQpMSu87fzwEgYmoz+xfVHI6un4+xMkvcMoDjdaRs= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/crypto/blake256 v1.0.1-0.20200921185235-6d75c7ec1199 h1:sqVg68MjCKwsahuL7AbbdkUSULnZF0vGFOM8FDGscjo= -github.com/decred/dcrd/crypto/blake256 v1.0.1-0.20200921185235-6d75c7ec1199/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/crypto/ripemd160 v1.0.0/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg= -github.com/decred/dcrd/crypto/ripemd160 v1.0.1 h1:TjRL4LfftzTjXzaufov96iDAkbY2R3aTvH2YMYa1IOc= -github.com/decred/dcrd/crypto/ripemd160 v1.0.1/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg= -github.com/decred/dcrd/database v1.0.0/go.mod h1:eQOhTdO3oYBshjCVxMt747CP6yKKIls6IIdqYxMRzEk= -github.com/decred/dcrd/database v1.0.1/go.mod h1:ILCeyOHFew3fZ7K2B9jl+tp5qFOap/pEGoo6Yy6Wk0g= -github.com/decred/dcrd/database v1.0.2 h1:/Q+1rxvCFUcFH3FfnzVXv+3NmVPoRZ3UQmqMr2KYReA= -github.com/decred/dcrd/database v1.0.2/go.mod h1:ILCeyOHFew3fZ7K2B9jl+tp5qFOap/pEGoo6Yy6Wk0g= -github.com/decred/dcrd/database/v2 v2.0.0/go.mod h1:Sj2lvTRB0mfSu9uD7ObfwCY/eJ954GFU/X+AndJIyfE= -github.com/decred/dcrd/database/v2 v2.0.2/go.mod h1:S78KbTCCJWUTJDVTByiQuB+HmL0DM2vIMsa2WsrF9KM= -github.com/decred/dcrd/database/v3 v3.0.0-20210802132946-9ede6ae83e0f/go.mod h1:3WUAfz3R0FOz6wJcqTZ0CcUDfyIMrlO10f3aqa2/7vk= -github.com/decred/dcrd/database/v3 v3.0.0-20211012235250-77033596a107/go.mod h1:3WUAfz3R0FOz6wJcqTZ0CcUDfyIMrlO10f3aqa2/7vk= -github.com/decred/dcrd/database/v3 v3.0.0 h1:7VVN2sWjKB934jvXzjnyGJFUVH9d8Qh5VULi+NMRjek= -github.com/decred/dcrd/database/v3 v3.0.0/go.mod h1:8EyKddB8rXDi6/CDOdYc/7qL1//sb6iwg9DctP0ZJF4= -github.com/decred/dcrd/dcrec v0.0.0-20180721005212-59fe2b293f69/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= -github.com/decred/dcrd/dcrec v0.0.0-20180721005914-d26200ec716b/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= -github.com/decred/dcrd/dcrec v0.0.0-20180721031028-5369a485acf6/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= -github.com/decred/dcrd/dcrec v0.0.0-20180801202239-0761de129164/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= -github.com/decred/dcrd/dcrec v0.0.0-20180809193022-9536f0c88fa8/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= -github.com/decred/dcrd/dcrec v0.0.0-20180816212643-20eda7ec9229/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= -github.com/decred/dcrd/dcrec v1.0.0/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8= -github.com/decred/dcrd/dcrec v1.0.1-0.20200921185235-6d75c7ec1199 h1:MkfApk/KhuIh3llbjdnTFY5G4lb7zA+EEKVKIRgAfmg= -github.com/decred/dcrd/dcrec v1.0.1-0.20200921185235-6d75c7ec1199/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8= -github.com/decred/dcrd/dcrec/edwards v0.0.0-20180721005212-59fe2b293f69/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= -github.com/decred/dcrd/dcrec/edwards v0.0.0-20180721031028-5369a485acf6/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= -github.com/decred/dcrd/dcrec/edwards v0.0.0-20180809193022-9536f0c88fa8/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= -github.com/decred/dcrd/dcrec/edwards v0.0.0-20180816212643-20eda7ec9229/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= -github.com/decred/dcrd/dcrec/edwards v1.0.0 h1:UDcPNzclKiJlWqV3x1Fl8xMCJrolo4PB4X9t8LwKDWU= -github.com/decred/dcrd/dcrec/edwards v1.0.0/go.mod h1:HblVh1OfMt7xSxUL1ufjToaEvpbjpWvvTAUx4yem8BI= -github.com/decred/dcrd/dcrec/edwards/v2 v2.0.0/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= -github.com/decred/dcrd/dcrec/edwards/v2 v2.0.1/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= -github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 h1:bX7rtGTMBDJxujZ29GNqtn7YCAdINjHKnA6J6tBBv6s= -github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= -github.com/decred/dcrd/dcrec/secp256k1 v1.0.0/go.mod h1:JPMFscGlgXTV684jxQNDijae2qrh0fLG7pJBimaYotE= -github.com/decred/dcrd/dcrec/secp256k1 v1.0.1/go.mod h1:lhu4eZFSfTJWUnR3CFRcpD+Vta0KUAqnhTsTksHXgy0= -github.com/decred/dcrd/dcrec/secp256k1 v1.0.2 h1:awk7sYJ4pGWmtkiGHFfctztJjHMKGLV8jctGQhAbKe0= -github.com/decred/dcrd/dcrec/secp256k1 v1.0.2/go.mod h1:CHTUIVfmDDd0KFVFpNX1pFVCBUegxW387nN0IGwNKR0= -github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY= -github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210127014238-b33b46cf1a24/go.mod h1:UkVqoxmJlLgUvBjJD+GdJz6mgdSdf3UjX83xfwUAYDk= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/dcrjson v1.0.0 h1:50DnA0XeV2JrQXoHh43TCKmH+kz2gHjZ1Mj/Pdk7Oz0= -github.com/decred/dcrd/dcrjson v1.0.0/go.mod h1:ozddIaeF+EAvZZvFuB3zpfxhyxBGfvbt22crQh+PYuI= -github.com/decred/dcrd/dcrjson/v3 v3.0.0/go.mod h1:pWYlHJ3VFidPwqD5HHiJXjfGaplif8uspAL2qFdifkY= -github.com/decred/dcrd/dcrjson/v3 v3.1.0/go.mod h1:fnTHev/ABGp8IxFudDhjGi9ghLiXRff1qZz/wvq12Mg= -github.com/decred/dcrd/dcrjson/v4 v4.0.0 h1:KsaFhHAYO+vLYz7Qmx/fs1gOY5ouTEz8hRuDm8jmJtU= -github.com/decred/dcrd/dcrjson/v4 v4.0.0/go.mod h1:DMnSpU8lsVh+Nt5kHl63tkrjBDA7UIs4+ov8Kwwgvjs= -github.com/decred/dcrd/dcrutil v1.0.0/go.mod h1:CBpbItyMKkL/4i1qPJDsE/cdSYklsWFcTYgprRZh4yk= -github.com/decred/dcrd/dcrutil v1.1.1 h1:zOkGiumN/JkobhAgpG/zfFgUoolGKVGYT5na1hbYUoE= -github.com/decred/dcrd/dcrutil v1.1.1/go.mod h1:Jsttr0pEvzPAw+qay1kS1/PsbZYPyhluiNwwY6yBJS4= -github.com/decred/dcrd/dcrutil/v2 v2.0.0/go.mod h1:gUshVAXpd51DlcEhr51QfWL2HJGkMDM1U8chY+9VvQg= -github.com/decred/dcrd/dcrutil/v2 v2.0.1/go.mod h1:JdEgF6eh0TTohPeiqDxqDSikTSvAczq0J7tFMyyeD+k= -github.com/decred/dcrd/dcrutil/v3 v3.0.0/go.mod h1:iVsjcqVzLmYFGCZLet2H7Nq+7imV9tYcuY+0lC2mNsY= -github.com/decred/dcrd/dcrutil/v4 v4.0.0-20210129181600-6ae0142d3b28/go.mod h1:xe59jKcMx5G/dbRmsZ8+FzY+WQDE/7YBP3k3uzJTtmI= -github.com/decred/dcrd/dcrutil/v4 v4.0.0 h1:AY00fWy/ETrMHN0DNV3XUbH1aip2RG1AoTy5dp0+sJE= -github.com/decred/dcrd/dcrutil/v4 v4.0.0/go.mod h1:QQpX5WVH3/ixVtiW15xZMe+neugXX3l2bsrYgq6nz4M= -github.com/decred/dcrd/gcs v1.0.0/go.mod h1:5uHIPAzn4SdGP2/FhVBK2YdAoKmufds3ZI8yNzojUCM= -github.com/decred/dcrd/gcs v1.0.1/go.mod h1:YwutGzusSdJM79CJtxCo9t7WRCvnkLtWSD19TPo1i9g= -github.com/decred/dcrd/gcs v1.0.2/go.mod h1:eLCvrzUsWro48TlTyrmFcZAZqnllYFz0vEv5VZtufF4= -github.com/decred/dcrd/gcs v1.1.0 h1:djuYzaFUzUTJR+6ulMSRZOQ+P9rxtIyuxQeViAEfB8s= -github.com/decred/dcrd/gcs v1.1.0/go.mod h1:yBjhj217Vw5lw3aKnCdHip7fYb9zwMos8bCy5s79M9w= -github.com/decred/dcrd/gcs/v2 v2.1.0/go.mod h1:MbnJOINFcp42NMRAQ+CjX/xGz+53AwNgMzKZhwBibdM= -github.com/decred/dcrd/gcs/v3 v3.0.0-20210916172859-ca03de05ecd0/go.mod h1:cfMmjV1ArvX+nemwwq1texW5pD0wrUt5TEw4IYuTmMM= -github.com/decred/dcrd/gcs/v3 v3.0.0 h1:MjWevhoAzKENUgpaJAbZkJlKDN4HIz2nR/i3laZAT5c= -github.com/decred/dcrd/gcs/v3 v3.0.0/go.mod h1:/OVb/rYrAz4TCtxcPneYfBs0+YI1pGIp8RA6RUNqOp4= -github.com/decred/dcrd/hdkeychain v1.1.0 h1:6bFdL672dCmtg/JEzb3Jw0dTRO2jLxcA7BK2J+JaoUM= -github.com/decred/dcrd/hdkeychain v1.1.0/go.mod h1:zyUZtZ3PdnTPHt2XUr1x76b8ZuiM+9aVkP8Rq8Scp1k= -github.com/decred/dcrd/hdkeychain/v2 v2.0.1/go.mod h1:qPv+vTla19liVHFuXVnQ70dMI4ERPCniDXbV5RzwQiM= -github.com/decred/dcrd/hdkeychain/v3 v3.0.0/go.mod h1:Vz7PJSlLzhqmOR2lmjGD9JqAZgmUnM8P6r8hg7U4Zho= -github.com/decred/dcrd/hdkeychain/v3 v3.0.1/go.mod h1:rDCdqwGkcTfEyRheG1g8Wc38appT2C9+D1XTlLy21lo= -github.com/decred/dcrd/hdkeychain/v3 v3.1.0 h1:NlUjzPMzexbk1PyJu6vrQaiilep5WsEPB0KdhLYrEcE= -github.com/decred/dcrd/hdkeychain/v3 v3.1.0/go.mod h1:rDCdqwGkcTfEyRheG1g8Wc38appT2C9+D1XTlLy21lo= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/decred/dcrd/lru v1.1.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/decred/dcrd/lru v1.1.1 h1:kWFDaW0OWx6AD6Ki342c+JPmHbiVdE6rK81pT3fuo/Y= -github.com/decred/dcrd/lru v1.1.1/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/decred/dcrd/mempool v1.0.1/go.mod h1:r+/DGiiluXi1EyMCCPPH58Qu+rsr8nZv0DialAG5VZQ= -github.com/decred/dcrd/mining v1.0.0/go.mod h1:VA5H4zhJgXb8LK5lqM5H58dhMRXJRcaQQoX3G8QRpP8= -github.com/decred/dcrd/mining v1.0.1/go.mod h1:+CSOLPi7TM8OlQg7mJ7XzWLXCDb4nHK8R6cvXOzhEoU= -github.com/decred/dcrd/rpc/jsonrpc/types v1.0.0 h1:d5ptnjuSADTQMa3i83VpeJNoMRTOJZZBqk7P+E41VXM= -github.com/decred/dcrd/rpc/jsonrpc/types v1.0.0/go.mod h1:0dwmpIP21tJxjg/UuUHWIFMbfoLv2ifCBMokNKlOxpo= -github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.3.0/go.mod h1:krn89ZOgSa8yc7sA4WpDK95p61NnjNWFkNlMnGrKbMc= -github.com/decred/dcrd/rpc/jsonrpc/types/v3 v3.0.0-20210129200153-14fd1a785bf2/go.mod h1:9izQEJ5wU0ZwYHESMaaOIvE6H6y3IvDsQL3ByYGn9oc= -github.com/decred/dcrd/rpc/jsonrpc/types/v3 v3.0.0 h1:WzG2IARR6OghjhWdxfUbXSPE4GEF2hZlCE5y2L/45f4= -github.com/decred/dcrd/rpc/jsonrpc/types/v3 v3.0.0/go.mod h1:1ILDxMKVS/qY71MylpZzuEX4O0u1SON4RPKbaZP71K0= -github.com/decred/dcrd/rpcclient v1.0.1/go.mod h1:tApXK3wwrAQtz7lcXeeqBwuktUZesvrFfvhAdedYqdM= -github.com/decred/dcrd/rpcclient/v4 v4.0.0/go.mod h1:DNGwfiL5H+K/pk3hVB0z5ypRdiDXMssR+YEqDUEXCQo= -github.com/decred/dcrd/rpcclient/v6 v6.0.2/go.mod h1:t6ECC72j2xWQ323poL85IFNq0EUfcSTfwL8j7jDJ6mw= -github.com/decred/dcrd/rpcclient/v7 v7.0.0-20211119190215-20dc378a4341/go.mod h1:fHve6G5hIaq2RklBG8Tsiz8id2S+0U3bvBACcYqZStc= -github.com/decred/dcrd/rpcclient/v7 v7.0.0/go.mod h1:k4UDXFt0iwTRhKzdMGJbz/0wD/1lIKrQ5iYWyY7w8R4= -github.com/decred/dcrd/txscript v1.0.0/go.mod h1:9byvrOaBSBVVnDG7Cm0JgN8bZytl1oi9Ba245VBeI18= -github.com/decred/dcrd/txscript v1.0.1 h1:IMgxZFCw3AyG4EbKwywE3SDNshOSHsoUK1Wk/5GqWJ0= -github.com/decred/dcrd/txscript v1.0.1/go.mod h1:FqUX07Y+u3cJ1eIGPoyWbJg+Wk1NTllln/TyDpx9KnY= -github.com/decred/dcrd/txscript/v2 v2.0.0/go.mod h1:WStcyYYJa+PHJB4XjrLDRzV96/Z4thtsu8mZoVrU6C0= -github.com/decred/dcrd/txscript/v2 v2.1.0/go.mod h1:XaJAVrZU4NWRx4UEzTiDAs86op1m8GRJLz24SDBKOi0= -github.com/decred/dcrd/txscript/v3 v3.0.0/go.mod h1:pdvnlD4KGdDoc09cvWRJ8EoRQUaiUz41uDevOWuEfII= -github.com/decred/dcrd/txscript/v4 v4.0.0-20210415215133-96b98390a9a9/go.mod h1:LBGwMZRfpS50huRsc0Bihy7w2Sl9vK3TNqv8nhCRj0U= -github.com/decred/dcrd/txscript/v4 v4.0.0 h1:BwaBUCMCmg58MCYoBhxVjL8ZZKUIfoJuxu/djmh8h58= -github.com/decred/dcrd/txscript/v4 v4.0.0/go.mod h1:OJtxNc5RqwQyfrRnG2gG8uMeNPo8IAJp+TD1UKXkqk8= -github.com/decred/dcrd/wire v1.0.1/go.mod h1:zpKZnBiN59CrzfXFigwgXmUDVYf34OLbEr8xwAwriHc= -github.com/decred/dcrd/wire v1.1.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM= -github.com/decred/dcrd/wire v1.2.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM= -github.com/decred/dcrd/wire v1.3.0/go.mod h1:fnKGlUY2IBuqnpxx5dYRU5Oiq392OBqAuVjRVSkIoXM= -github.com/decred/dcrd/wire v1.4.0/go.mod h1:WxC/0K+cCAnBh+SKsRjIX9YPgvrjhmE+6pZlel1G7Ro= -github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg= -github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w= -github.com/decred/dcrdata/api/types/v4 v4.0.4/go.mod h1:CCu2Itqv/K3lqFxqSYDC49XWu5OuZRvNFkPiFHb0tYU= -github.com/decred/dcrdata/db/dbtypes/v2 v2.1.4/go.mod h1:UF4KWxcCYhdXqaTwbA2Mb10os4H0UFSZaiu5eeMWQT8= -github.com/decred/dcrdata/semver v1.0.0/go.mod h1:z+nQqiAd9fYkHhBLbejysZ2FPHtgkrErWDgMf+JlZWE= -github.com/decred/dcrdata/txhelpers/v3 v3.0.4/go.mod h1:tKEDhoO+TbYrFrx+5qKZDxcla8ELQFYs4f5+8gL4cuY= -github.com/decred/dcrdata/v6 v6.0.0-20210510222533-6a2ca18d4382/go.mod h1:CWT5trkQ+8KUSBeyuI5/V1TQMleYyCh1vo/Ry6PiFWU= -github.com/decred/dcrdata/v7 v7.0.0-20211216152310-365c9dc820eb/go.mod h1:rNWn4zkXfTvBqv+O4oZQDJPRXT6rYzkobyrbGUiL270= -github.com/decred/dcrtime v0.0.0-20191018193024-8d8b4ef0458e/go.mod h1:IyZnyBE3E6RBFsEjwEs21FrO/UsrLrL15hUnpZZQxpU= -github.com/decred/dcrtime/api/v2 v2.0.0-20200912200806-b1e4dbc46be9/go.mod h1:JdIX208vnNj4TdU6hDRaN+ccxmxp1I1R6sWGZNK1BAQ= -github.com/decred/dcrwallet v1.2.2/go.mod h1:BrSus0F+Rx8UhvPNBfuRMIjRJBNrW2sLspN9iQR5hm8= -github.com/decred/dcrwallet/chain v1.0.0/go.mod h1:KpZFaKlKajfUZt36+RmBn2HKwTbwoa3yt9HPALqlShI= -github.com/decred/dcrwallet/deployments v1.0.0/go.mod h1:0bWER/DAYoGbzkWzbUf6k2agW4YkSyvNLZDhBGThz/4= -github.com/decred/dcrwallet/errors v1.0.0/go.mod h1:XUm95dWmm9XmQGvneBXJkkIaFeRsQVBB6ni/KTy1hrY= -github.com/decred/dcrwallet/internal/helpers v1.0.0/go.mod h1:FsihtjCyFrGL6gdmkxBWTYQ1CUgbfM9tyinYNOzLnlk= -github.com/decred/dcrwallet/internal/zero v1.0.0/go.mod h1:vULuNLRTcnifKCepcIxUDL4jrR3rJOwVR9UDH89Qpms= -github.com/decred/dcrwallet/lru v1.0.0/go.mod h1:jEty7mdT5VaaV06DEV2Avv0R3HpGvUwvDW4lw8ECtiY= -github.com/decred/dcrwallet/p2p v1.0.0/go.mod h1:b1CLZAkl/K5dr5I5B4SdFT8FrE11jSkfA4VAA862ACA= -github.com/decred/dcrwallet/pgpwordlist v1.0.0/go.mod h1:Fek3uYn+9DnEFIreA/8PnTIXUl2lBO64JpEBkL9BXtk= -github.com/decred/dcrwallet/rpc/jsonrpc/types v1.1.0/go.mod h1:xUT7XXATLOzE0pwwmvgfRWtZdrB+PsWFilo+jkH5/Ig= -github.com/decred/dcrwallet/rpc/walletrpc v0.1.0/go.mod h1:Zp1ZFTCUo7S6MJvUyS5tYfaDUxGAMHkZ+vbsLgAdd4A= -github.com/decred/dcrwallet/rpc/walletrpc v0.2.0/go.mod h1:uhjgcju9lSb/+42Ms4VY1zpBOxstCLM5wVlL3mq/SYc= -github.com/decred/dcrwallet/spv v1.0.0/go.mod h1:lz39nz9P/HVoxYa4XAT6ithyR3WgdF0oVu4jtFwnCxE= -github.com/decred/dcrwallet/ticketbuyer v1.0.0/go.mod h1:mrAlRjOJ6txO8Zyqo5koxVOMEYLK2POUX35a/QcKN8g= -github.com/decred/dcrwallet/ticketbuyer/v2 v2.0.0/go.mod h1:VKo2PjXAlF/E46tSBKrIgqKbVcHVLfM5ACyOehT1unA= -github.com/decred/dcrwallet/validate v1.0.0/go.mod h1:zHIlcrjAWl6LK+X+R7jc3F9wIM/qxjtMjG/mdEwt4tY= -github.com/decred/dcrwallet/validate v1.0.1/go.mod h1:9DCtLFnnTOC/7PKkF7jehvDyHkfUBl41ZbcT1u4PmQM= -github.com/decred/dcrwallet/version v1.0.0/go.mod h1:rXeMsUaI03WtlQrSol7Q7sJ8HBOB+tZvT7YQRXD5Y7M= -github.com/decred/dcrwallet/wallet v1.0.0/go.mod h1:VWRnpNFRiKPo7FUPbzj0t5ElcGxNXMPIa4vGcGe94uM= -github.com/decred/dcrwallet/walletseed v1.0.0/go.mod h1:xSF6hZW+5Xhm0jJFsI5jQSfViuZUQJoDXa/cQxtgncs= -github.com/decred/go-socks v1.0.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0= -github.com/decred/go-socks v1.1.0 h1:dnENcc0KIqQo3HSXdgboXAHgqsCIutkqq6ntQjYtm2U= -github.com/decred/go-socks v1.1.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0= -github.com/decred/politeia v1.3.1/go.mod h1:IWytM75wWPGbQsEcQTEeHfX1KbcUt2dGDCtYbrrvqHI= -github.com/decred/slog v1.0.0/go.mod h1:zR98rEZHSnbZ4WHZtO0iqmSZjDLKhkXfrPTZQKtAonQ= -github.com/decred/slog v1.1.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= -github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= -github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= -github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= -github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= -github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= -github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= -github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= -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.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/ethereum/go-ethereum v1.10.11/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQxhYBkKyu5mEDHw= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/gcash/bchd v0.14.7/go.mod h1:Gk/O1ktRVW5Kao0RsnVXp3bWxeYQadqawZ1Im9HE78M= -github.com/gcash/bchd v0.15.2/go.mod h1:k9wIjgwnhbrAw+ruIPZ2tHZMzfFNdyUnORZZX7lqXGY= -github.com/gcash/bchd v0.17.2-0.20201218180520-5708823e0e99/go.mod h1:qwEZ/wr6LyUo5IBgAPcAbYHzXrjnr5gc4tj03n1TwKc= -github.com/gcash/bchlog v0.0.0-20180913005452-b4f036f92fa6/go.mod h1:PpfmXTLfjRp7Tf6v/DCGTRXHz+VFbiRcsoUxi7HvwlQ= -github.com/gcash/bchutil v0.0.0-20190625002603-800e62fe9aff/go.mod h1:zXSP0Fg2L52wpSEDApQDQMiSygnQiK5HDquDl0a5BHg= -github.com/gcash/bchutil v0.0.0-20191012211144-98e73ec336ba/go.mod h1:nUIrcbbtEQdCsRwcp+j/CndDKMQE9Fi8p2F8cIZmIqI= -github.com/gcash/bchutil v0.0.0-20200506001747-c2894cd54b33/go.mod h1:wB++2ZcHUvGLN1OgO9swBmJK1vmyshJLW9SNS+apXwc= -github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000/go.mod h1:H2USFGwtiu6CNMxiVQPqZkDzsoVSt9BLNqTfBBqGXRo= -github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= -github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -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/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -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 v1.1.0/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-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.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/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -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.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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -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-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/trillian v1.3.13/go.mod h1:8y3zC8XuqFxsslWPkP0r3sprERfFf7hCWmicL0yHZNI= -github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.5/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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= -github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= -github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/improbable-eng/grpc-web v0.9.1/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= -github.com/improbable-eng/grpc-web v0.13.0/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= -github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= -github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v0.0.0-20181221193153-c0795c8afcf4/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= -github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jrick/bitset v1.0.0 h1:Ws0PXV3PwXqWK2n7Vz6idCdrV/9OrBXgHEJi27ZB9Dw= -github.com/jrick/bitset v1.0.0/go.mod h1:ZOYB5Uvkla7wIEY4FEssPVi3IQXa02arznRaYaAEPe4= -github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/jrick/wsrpc/v2 v2.3.2/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU= -github.com/jrick/wsrpc/v2 v2.3.4 h1:+GzRtp/TyXaSB61pN92lIAVyvdVv0RSqniIEB/rPx1Q= -github.com/jrick/wsrpc/v2 v2.3.4/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/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/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karalabe/usb v0.0.0-20211005121534-4c5740d64559/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8 h1:YFXjWLfS9lQsxu8GQTQo+O7sjK+6M9njoBOnvVLc9kw= -github.com/kevinburke/nacl v0.0.0-20190829012316-f3ed23dbd7f8/go.mod h1:VUp2yfq+wAk8hMl3NNN34fXjzUD9xMpGvUL8eSJz9Ns= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= -github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.2/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= -github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= -github.com/lightninglabs/neutrino v0.12.1/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= -github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756 h1:lf3i1CNI5j2XhKMvQNmnr2o1DFoyoE02mynRdAt+ss0= -github.com/lightninglabs/neutrino v0.13.1-0.20211214231330-53b628ce1756/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= -github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= -github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= -github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= -github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= -github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= -github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/marcopeereboom/sbox v1.1.0/go.mod h1:u2fh4EbQDXQXXzGypWkf2nMn2TnsqA23t224mii7oog= -github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= -github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZeFXocWuvtcS0XSFLcf2XUSDHkq9t1jU4= -github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= -github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= -github.com/planetdecred/dcrlibwallet v1.7.0 h1:UHZwYnvm/8cpSVG17lB3rx1wOAf+ZP4v5RbkPk6MYKE= -github.com/planetdecred/dcrlibwallet v1.7.0/go.mod h1:QTADevJvo+ugI0LZkwVVwCMiqtVf/W0DKlODTYwbnCs= -github.com/planetdecred/dcrlibwallet/dexdcr v0.0.0-20220223161805-c736f970653d/go.mod h1:jO4RP2rgqom8CLgl3rMwZ4cGzmalJqBkKjHgVS812lM= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= -github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gozaru v0.0.0-20190625071150-416082cce636/go.mod h1:LIpwO1yApZNrEQZdu5REqRtRrkaU+52ueA7WGT+CvSw= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -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/zquestz/grab v0.0.0-20190224022517-abcee96e61b1/go.mod h1:bslhAiUxakrA6z6CHmVyvkfpnxx18RJBwVyx2TluJWw= -go.etcd.io/bbolt v1.3.0/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -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/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -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/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.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180718160520-a2144134853f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180808211826-de0752318171/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -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-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -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/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/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-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-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-20190923162816-aa69164e4478/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-20200421231249-e086a090c8fd/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-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-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-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/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-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180810070207-f0d5e33068cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-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-20190614084037-d442b75600c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/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-20191008105621-543471e840be/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-20191220142924-d4481acd189f/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-20200107162124-548cf772de50/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-20200420163511-1957bb5e6d1f/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-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-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= -golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -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= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -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.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/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-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/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-20200108203644-89082a384178/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-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -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-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -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.10.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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -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.2/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/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180808183934-383e8b2c3b9e/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/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-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -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-20200108215221-bd8f9a0ef82f/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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= -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.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -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.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -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/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/wallets/btc/wallet.go b/wallets/btc/wallet.go index b143d102b..e57d2b9fd 100644 --- a/wallets/btc/wallet.go +++ b/wallets/btc/wallet.go @@ -45,6 +45,7 @@ type Wallet struct { rootDir string db *storm.DB EncryptedSeed []byte + IsRestored bool cl neutrinoService neutrinoDB walletdb.DB @@ -87,20 +88,6 @@ const ( logFileName = "neutrino.log" ) -func NewSpvWallet(walletName string, encryptedSeed []byte, net string) (*Wallet, error) { - chainParams, err := parseChainParams(net) - if err != nil { - return nil, err - } - - return &Wallet{ - Name: walletName, - chainParams: chainParams, - CreatedAt: time.Now(), - EncryptedSeed: encryptedSeed, - }, nil -} - func parseChainParams(net string) (*chaincfg.Params, error) { switch net { case "mainnet": @@ -168,6 +155,69 @@ func (wallet *Wallet) RawRequest(method string, params []json.RawMessage) (json. return nil, errors.New("RawRequest not available on spv") } +// prepare gets a wallet ready for use by opening the transactions index database +// and initializing the wallet loader which can be used subsequently to create, +// load and unload the wallet. +func (wallet *Wallet) Prepare(rootDir string, net string, log slog.Logger) (err error) { + chainParams, err := parseChainParams(net) + if err != nil { + return err + } + + wallet.chainParams = chainParams + wallet.dataDir = filepath.Join(rootDir, strconv.Itoa(wallet.ID)) + wallet.log = log + wallet.loader = w.NewLoader(wallet.chainParams, wallet.dataDir, true, 60*time.Second, 250) + return nil +} + +func (wallet *Wallet) Shutdown(walletDBRef *storm.DB) { + // Trigger shuttingDown signal to cancel all contexts created with + // `wallet.shutdownContext()` or `wallet.shutdownContextWithCancel()`. + // wallet.shuttingDown <- true + + if _, loaded := wallet.loader.LoadedWallet(); loaded { + err := wallet.loader.UnloadWallet() + if err != nil { + // log.Errorf("Failed to close wallet: %v", err) + } else { + // log.Info("Closed wallet") + } + } + + if walletDBRef != nil { + err := walletDBRef.Close() + if err != nil { + // log.Errorf("tx db closed with error: %v", err) + } else { + // log.Info("tx db closed successfully") + } + } +} + +// WalletCreationTimeInMillis returns the wallet creation time for new +// wallets. Restored wallets would return an error. +func (wallet *Wallet) WalletCreationTimeInMillis() (int64, error) { + if wallet.IsRestored { + return 0, errors.New(ErrWalletIsRestored) + } + + return wallet.CreatedAt.UnixNano() / int64(time.Millisecond), nil +} + +func (wallet *Wallet) NetType() string { + return wallet.chainParams.Name +} + +func (wallet *Wallet) Internal() *w.Wallet { + lw, _ := wallet.loader.LoadedWallet() + return lw +} + +func (wallet *Wallet) WalletExists() (bool, error) { + return wallet.loader.WalletExists() +} + func CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32, db *storm.DB, rootDir, dbDriver string, chainParams *chaincfg.Params) (*Wallet, error) { // seed := "witch collapse practice feed shame open despair" // encryptedSeed := []byte(seed) @@ -199,59 +249,55 @@ func CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType }) } -func (wallet *Wallet) RenameWallet(newName string, walledDbRef *storm.DB) error { - if strings.HasPrefix(newName, "wallet-") { - return errors.E(ErrReservedWalletName) - } - - if exists, err := WalletNameExists(newName, walledDbRef); err != nil { - return translateError(err) - } else if exists { - return errors.New(ErrExist) +func (wallet *Wallet) createWallet(privatePassphrase string, seedMnemonic []byte) error { + // log.Info("Creating Wallet") + if len(seedMnemonic) == 0 { + return errors.New("ErrEmptySeed") } - wallet.Name = newName - return walledDbRef.Save(wallet) // update WalletName field -} - -func (wallet *Wallet) OpenWallet() error { pubPass := []byte(w.InsecurePubPassphrase) + privPass := []byte(privatePassphrase) + // seed, err := walletseed.DecodeUserInput(seedMnemonic) + // if err != nil { + // // log.Error(err) + // return err + // } - _, err := wallet.loader.OpenExistingWallet(pubPass, false) + _, err := wallet.loader.CreateNewWallet(pubPass, privPass, seedMnemonic, wallet.CreatedAt) if err != nil { // log.Error(err) - return translateError(err) + return err } - return nil -} - -func (wallet *Wallet) WalletExists() (bool, error) { - return wallet.loader.WalletExists() -} - -func (wallet *Wallet) IsWatchingOnlyWallet() bool { - if _, ok := wallet.loader.LoadedWallet(); ok { - // return w.WatchingOnly() - return false + bailOnWallet := func() { + if err := wallet.loader.UnloadWallet(); err != nil { + fmt.Errorf("Error unloading wallet after createSPVWallet error: %v", err) + } } - return false -} + neutrinoDBPath := filepath.Join(wallet.dataDir, neutrinoDBName) + db, err := walletdb.Create("bdb", neutrinoDBPath, true, 5*time.Second) + if err != nil { + bailOnWallet() + return fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err) + } + if err = db.Close(); err != nil { + bailOnWallet() + return fmt.Errorf("error closing newly created wallet database: %w", err) + } -func (wallet *Wallet) WalletOpened() bool { - return wallet.Internal() != nil -} + if err := wallet.loader.UnloadWallet(); err != nil { + return fmt.Errorf("error unloading wallet: %w", err) + } -func (wallet *Wallet) Internal() *w.Wallet { - lw, _ := wallet.loader.LoadedWallet() - return lw + // log.Info("Created Wallet") + return nil } func CreateNewWatchOnlyWallet(walletName string, chainParams *chaincfg.Params) (*Wallet, error) { wallet := &Wallet{ - Name: walletName, - chainParams: chainParams, + Name: walletName, + IsRestored: true, } return wallet.saveNewWallet(func() error { @@ -277,30 +323,87 @@ func (wallet *Wallet) createWatchingOnlyWallet() error { return nil } -// func (wallet *Wallet) DeleteWallet(privPass []byte, walledDbRef *storm.DB) error { -// // if wallet.IsConnectedToDecredNetwork() { -// // wallet.CancelSync() -// // defer func() { -// // // if mw.OpenedWalletsCount() > 0 { -// // wallet.SpvSync() -// // // } -// // }() -// // } +func (wallet *Wallet) IsWatchingOnlyWallet() bool { + if _, ok := wallet.loader.LoadedWallet(); ok { + // return w.WatchingOnly() + return false + } + + return false +} -// err := wallet.deleteWallet(privPass) -// if err != nil { -// return translateError(err) -// } +func (wallet *Wallet) RenameWallet(newName string, walledDbRef *storm.DB) error { + if strings.HasPrefix(newName, "wallet-") { + return errors.E(ErrReservedWalletName) + } -// err = walledDbRef.DeleteStruct(wallet) -// if err != nil { -// return translateError(err) -// } + if exists, err := WalletNameExists(newName, walledDbRef); err != nil { + return translateError(err) + } else if exists { + return errors.New(ErrExist) + } -// // delete(mw.wallets, walletID) + wallet.Name = newName + return walledDbRef.Save(wallet) // update WalletName field +} -// return nil -// } +func (wallet *Wallet) OpenWallet() error { + pubPass := []byte(w.InsecurePubPassphrase) + + _, err := wallet.loader.OpenExistingWallet(pubPass, false) + if err != nil { + // log.Error(err) + return translateError(err) + } + + return nil +} + +func (wallet *Wallet) WalletOpened() bool { + return wallet.Internal() != nil +} + +func (wallet *Wallet) UnlockWallet(privPass []byte) error { + loadedWallet, ok := wallet.loader.LoadedWallet() + if !ok { + return fmt.Errorf("wallet has not been loaded") + } + + err := loadedWallet.Unlock(privPass, nil) + if err != nil { + return translateError(err) + } + + return nil +} + +func (wallet *Wallet) LockWallet() { + if !wallet.Internal().Locked() { + wallet.Internal().Lock() + } +} + +func (wallet *Wallet) IsLocked() bool { + return wallet.Internal().Locked() +} + +func (wallet *Wallet) ChangePrivatePassphrase(oldPass []byte, newPass []byte) error { + defer func() { + for i := range oldPass { + oldPass[i] = 0 + } + + for i := range newPass { + newPass[i] = 0 + } + }() + + err := wallet.Internal().ChangePrivatePassphrase(oldPass, newPass) + if err != nil { + return translateError(err) + } + return nil +} func (wallet *Wallet) DeleteWallet(privatePassphrase []byte) error { defer func() { @@ -327,10 +430,6 @@ func (wallet *Wallet) DeleteWallet(privatePassphrase []byte) error { return os.RemoveAll(wallet.dataDir) } -func (wallet *Wallet) DataDir() string { - return wallet.dataDir -} - func (wallet *Wallet) ConnectSPVWallet(ctx context.Context, wg *sync.WaitGroup) (err error) { return wallet.connect(ctx, wg) } @@ -531,71 +630,6 @@ func (wallet *Wallet) startWallet() error { return nil } -// prepare gets a wallet ready for use by opening the transactions index database -// and initializing the wallet loader which can be used subsequently to create, -// load and unload the wallet. -func (wallet *Wallet) Prepare(rootDir string, net string, log slog.Logger) (err error) { - chainParams, err := parseChainParams(net) - if err != nil { - return err - } - - wallet.chainParams = chainParams - wallet.dataDir = filepath.Join(rootDir, strconv.Itoa(wallet.ID)) - wallet.log = log - wallet.loader = w.NewLoader(wallet.chainParams, wallet.dataDir, true, 60*time.Second, 250) - return nil -} - -func (wallet *Wallet) NetType() string { - return wallet.chainParams.Name -} - -func (wallet *Wallet) createWallet(privatePassphrase string, seedMnemonic []byte) error { - // log.Info("Creating Wallet") - if len(seedMnemonic) == 0 { - return errors.New("ErrEmptySeed") - } - - pubPass := []byte(w.InsecurePubPassphrase) - privPass := []byte(privatePassphrase) - // seed, err := walletseed.DecodeUserInput(seedMnemonic) - // if err != nil { - // // log.Error(err) - // return err - // } - - _, err := wallet.loader.CreateNewWallet(pubPass, privPass, seedMnemonic, wallet.CreatedAt) - if err != nil { - // log.Error(err) - return err - } - - bailOnWallet := func() { - if err := wallet.loader.UnloadWallet(); err != nil { - fmt.Errorf("Error unloading wallet after createSPVWallet error: %v", err) - } - } - - neutrinoDBPath := filepath.Join(wallet.dataDir, neutrinoDBName) - db, err := walletdb.Create("bdb", neutrinoDBPath, true, 5*time.Second) - if err != nil { - bailOnWallet() - return fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err) - } - if err = db.Close(); err != nil { - bailOnWallet() - return fmt.Errorf("error closing newly created wallet database: %w", err) - } - - if err := wallet.loader.UnloadWallet(); err != nil { - return fmt.Errorf("error unloading wallet: %w", err) - } - - // log.Info("Created Wallet") - return nil -} - // saveNewWallet performs the following tasks using a db batch operation to ensure // that db changes are rolled back if any of the steps below return an error. // @@ -620,29 +654,6 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { // btcWallet.CancelSync() // defer btcWallet.SpvSync() // } - // Perform database save operations in batch transaction - // for automatic rollback if error occurs at any point. - // walletNameExists := func(walletName string) (bool, error) { - // if strings.HasPrefix(walletName, "wallet-") { - // return false, errors.E(ErrReservedWalletName) - // } - - // err := wallet.db.One("Name", walletName, &Wallet{}) - // if err == nil { - // return true, nil - // } else if err != storm.ErrNotFound { - // return false, err - // } - - // return false, nil - // } - - // exists, err := walletNameExists(wallet.Name) - // if err != nil { - // return nil, err - // } else if exists { - // return nil, errors.New(ErrExist) - // } // Perform database save operations in batch transaction // for automatic rollback if error occurs at any point. @@ -652,6 +663,7 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { if err != nil { return err } + walletDataDir := filepath.Join(wallet.dataDir, strconv.Itoa(wallet.ID)) dirExists, err := fileExists(walletDataDir) @@ -671,6 +683,9 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { if wallet.Name == "" { wallet.Name = "wallet-" + strconv.Itoa(wallet.ID) // wallet-# } + + wallet.dataDir = walletDataDir + err = db.Save(wallet) // update database with complete wallet information if err != nil { return err @@ -681,54 +696,12 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { }) if err != nil { - return nil, err + return nil, translateError(err) } return wallet, nil } -func fileExists(filePath string) (bool, error) { - _, err := os.Stat(filePath) - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, err - } - return true, nil -} - -func backupFile(fileName string, suffix int) (newName string, err error) { - newName = fileName + ".bak" + strconv.Itoa(suffix) - exists, err := fileExists(newName) - if err != nil { - return "", err - } else if exists { - return backupFile(fileName, suffix+1) - } - - err = moveFile(fileName, newName) - if err != nil { - return "", err - } - - return newName, nil -} - -func moveFile(sourcePath, destinationPath string) error { - if exists, _ := fileExists(sourcePath); exists { - return os.Rename(sourcePath, destinationPath) - } - return nil -} - -func (wallet *Wallet) shutdownContextWithCancel() (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(context.Background()) - wallet.cancelFuncs = append(wallet.cancelFuncs, cancel) - return ctx, cancel -} - -func (wallet *Wallet) shutdownContext() (ctx context.Context) { - ctx, _ = wallet.shutdownContextWithCancel() - return +func (wallet *Wallet) DataDir() string { + return wallet.dataDir } diff --git a/wallets/btc/wallet_utils.go b/wallets/btc/wallet_utils.go index 671da1612..a7489c992 100644 --- a/wallets/btc/wallet_utils.go +++ b/wallets/btc/wallet_utils.go @@ -2,6 +2,8 @@ package btc import ( // "context" + "os" + "strconv" "decred.org/dcrwallet/v2/errors" "github.com/asdine/storm" @@ -128,3 +130,49 @@ func WalletNameExists(walletName string, walledDbRef *storm.DB) (bool, error) { // return nil // } + +func fileExists(filePath string) (bool, error) { + _, err := os.Stat(filePath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func backupFile(fileName string, suffix int) (newName string, err error) { + newName = fileName + ".bak" + strconv.Itoa(suffix) + exists, err := fileExists(newName) + if err != nil { + return "", err + } else if exists { + return backupFile(fileName, suffix+1) + } + + err = moveFile(fileName, newName) + if err != nil { + return "", err + } + + return newName, nil +} + +func moveFile(sourcePath, destinationPath string) error { + if exists, _ := fileExists(sourcePath); exists { + return os.Rename(sourcePath, destinationPath) + } + return nil +} + +// func (wallet *Wallet) shutdownContextWithCancel() (context.Context, context.CancelFunc) { +// ctx, cancel := context.WithCancel(context.Background()) +// wallet.cancelFuncs = append(wallet.cancelFuncs, cancel) +// return ctx, cancel +// } + +// func (wallet *Wallet) shutdownContext() (ctx context.Context) { +// ctx, _ = wallet.shutdownContextWithCancel() +// return +// } From 9f88b16bcd0e1ed76fec8467b20b8ae79fd38c4a Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 18 Aug 2022 11:00:55 +0100 Subject: [PATCH 16/16] cleanup code --- treasury.go | 194 ---------------------------------------------------- 1 file changed, 194 deletions(-) delete mode 100644 treasury.go diff --git a/treasury.go b/treasury.go deleted file mode 100644 index cd78ec46b..000000000 --- a/treasury.go +++ /dev/null @@ -1,194 +0,0 @@ -package dcrlibwallet - -import ( - "encoding/hex" - "fmt" - - "decred.org/dcrwallet/v2/errors" - - "github.com/decred/dcrd/blockchain/stake/v4" - "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/dcrec/secp256k1/v4" -) - -// SetTreasuryPolicy saves the voting policy for treasury spends by a particular -// PI key. -// If a ticket hash is provided, the voting policy is also updated with the VSP -// controlling the ticket. If a ticket hash isn't provided, the vote choice is -// saved to the local wallet database and the VSPs controlling all unspent, -// unexpired tickets are updated to use the specified vote policy. -func (wallet *Wallet) SetTreasuryPolicy(PiKey, newVotingPolicy, tixHash string, passphrase []byte) error { - var ticketHash *chainhash.Hash - if tixHash != "" { - tixHash, err := chainhash.NewHashFromStr(tixHash) - if err != nil { - return fmt.Errorf("invalid ticket hash: %w", err) - } - ticketHash = tixHash - } - - pikey, err := hex.DecodeString(PiKey) - if err != nil { - return fmt.Errorf("invalid pikey: %w", err) - } - if len(pikey) != secp256k1.PubKeyBytesLenCompressed { - return fmt.Errorf("treasury pikey must be %d bytes", secp256k1.PubKeyBytesLenCompressed) - } - - var policy stake.TreasuryVoteT - switch newVotingPolicy { - case "abstain", "invalid", "": - policy = stake.TreasuryVoteInvalid - case "yes": - policy = stake.TreasuryVoteYes - case "no": - policy = stake.TreasuryVoteNo - default: - return fmt.Errorf("invalid policy: unknown policy %q", newVotingPolicy) - } - - // The wallet will need to be unlocked to sign the API - // request(s) for setting this voting policy with the VSP. - err = wallet.UnlockWallet(passphrase) - if err != nil { - return translateError(err) - } - defer wallet.LockWallet() - - currentVotingPolicy := wallet.Internal().TreasuryKeyPolicy(pikey, ticketHash) - - ctx := wallet.shutdownContext() - - err = wallet.Internal().SetTreasuryKeyPolicy(ctx, pikey, policy, ticketHash) - if err != nil { - return err - } - - var vspPreferenceUpdateSuccess bool - defer func() { - if !vspPreferenceUpdateSuccess { - // Updating the treasury spend voting preference with the vsp failed, - // revert the locally saved voting preference for the treasury spend. - revertError := wallet.Internal().SetTreasuryKeyPolicy(ctx, pikey, currentVotingPolicy, ticketHash) - if revertError != nil { - log.Errorf("unable to revert locally saved voting preference: %v", revertError) - } - } - }() - - // If a ticket hash is provided, set the specified vote policy with - // the VSP associated with the provided ticket. Otherwise, set the - // vote policy with the VSPs associated with all "votable" tickets. - ticketHashes := make([]*chainhash.Hash, 0) - if ticketHash != nil { - ticketHashes = append(ticketHashes, ticketHash) - } else { - err = wallet.Internal().ForUnspentUnexpiredTickets(ctx, func(hash *chainhash.Hash) error { - ticketHashes = append(ticketHashes, hash) - return nil - }) - if err != nil { - return fmt.Errorf("unable to fetch hashes for all unspent, unexpired tickets: %v", err) - } - } - - // Never return errors from this for loop, so all tickets are tried. - // The first error will be returned to the caller. - var firstErr error - // Update voting preferences on VSPs if required. - policyMap := map[string]string{ - PiKey: newVotingPolicy, - } - for _, tHash := range ticketHashes { - vspTicketInfo, err := wallet.Internal().VSPTicketInfo(ctx, tHash) - if err != nil { - // Ignore NotExist error, just means the ticket is not - // registered with a VSP, nothing more to do here. - if firstErr == nil && !errors.Is(err, errors.NotExist) { - firstErr = err - } - continue // try next tHash - } - - // Update the vote policy for the ticket with the associated VSP. - vspClient, err := wallet.VSPClient(vspTicketInfo.Host, vspTicketInfo.PubKey) - if err != nil && firstErr == nil { - firstErr = err - continue // try next tHash - } - err = vspClient.SetVoteChoice(ctx, tHash, nil, nil, policyMap) - if err != nil && firstErr == nil { - firstErr = err - continue // try next tHash - } - } - - vspPreferenceUpdateSuccess = firstErr == nil - return firstErr -} - -// TreasuryPolicies returns saved voting policies for treasury spends -// per pi key. If a pi key is specified, the policy for that pi key -// is returned; otherwise the policies for all pi keys are returned. -// If a ticket hash is provided, the policy(ies) for that ticket -// is/are returned. -func (wallet *Wallet) TreasuryPolicies(PiKey, tixHash string) ([]*TreasuryKeyPolicy, error) { - var ticketHash *chainhash.Hash - if tixHash != "" { - tixHash, err := chainhash.NewHashFromStr(tixHash) - if err != nil { - return nil, fmt.Errorf("inavlid hash: %w", err) - } - ticketHash = tixHash - } - - if PiKey != "" { - pikey, err := hex.DecodeString(PiKey) - if err != nil { - return nil, fmt.Errorf("invalid pikey: %w", err) - } - var policy string - switch wallet.Internal().TreasuryKeyPolicy(pikey, ticketHash) { - case stake.TreasuryVoteYes: - policy = "yes" - case stake.TreasuryVoteNo: - policy = "no" - default: - policy = "abstain" - } - res := []*TreasuryKeyPolicy{ - { - TicketHash: tixHash, - PiKey: PiKey, - Policy: policy, - }, - } - return res, nil - } - - policies := wallet.Internal().TreasuryKeyPolicies() - res := make([]*TreasuryKeyPolicy, len(policies)) - for i := range policies { - var policy string - switch policies[i].Policy { - case stake.TreasuryVoteYes: - policy = "yes" - case stake.TreasuryVoteNo: - policy = "no" - } - r := &TreasuryKeyPolicy{ - PiKey: hex.EncodeToString(policies[i].PiKey), - Policy: policy, - } - if policies[i].Ticket != nil { - r.TicketHash = policies[i].Ticket.String() - } - res[i] = r - } - return res, nil -} - -// PiKeys returns the sanctioned Politeia keys for the current network. -func (mw *MultiWallet) PiKeys() [][]byte { - return mw.chainParams.PiKeys -}