From 2b0f8106e8edb9f4bcf09feb5c7228b5a4b6d4d5 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 21 Jul 2022 23:59:19 +0100 Subject: [PATCH 01/11] move dcr related capability to the dcr package --- dcr.go | 58 + dexclient.go | 2 +- go.mod | 8 + 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, 4075 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 5ee2f89f9..2afb2a507 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 @@ -16,6 +22,7 @@ require ( github.com/decred/dcrd/dcrutil/v4 v4.0.0 github.com/decred/dcrd/gcs/v3 v3.0.0 github.com/decred/dcrd/hdkeychain/v3 v3.1.0 + github.com/decred/dcrd/rpc/jsonrpc/types/v3 v3.0.0 github.com/decred/dcrd/txscript/v4 v4.0.0 github.com/decred/dcrd/wire v1.5.0 github.com/decred/dcrdata/v7 v7.0.0-20211216152310-365c9dc820eb @@ -26,6 +33,7 @@ require ( github.com/jessevdk/go-flags 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 05d4bccdc..c39bb5dbe 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 4ef5384bd9d935acefa18854a4c8bd04555b34d8 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 12:31:57 +0100 Subject: [PATCH 02/11] - move politeia to dcr package - uncomment some multiwallet method and convert then to wallet methods --- go.mod | 8 - go.sum | 20 +- 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 ++ 16 files changed, 648 insertions(+), 648 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 2afb2a507..d999117bb 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 @@ -30,10 +24,8 @@ require ( 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/jessevdk/go-flags 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/go.sum b/go.sum index 0565a88f5..30fcd2956 100644 --- a/go.sum +++ b/go.sum @@ -47,7 +47,6 @@ 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 h1:f4zxCskK6PKUUCifzcXLaq+0UNC1dxxQybS7CKKMP4U= 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= @@ -201,7 +200,6 @@ 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= @@ -231,14 +229,12 @@ 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= @@ -572,7 +568,6 @@ 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= @@ -593,9 +588,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw 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 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= 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 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 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= @@ -635,7 +630,6 @@ 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= @@ -702,8 +696,6 @@ 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 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= -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= @@ -1021,7 +1013,6 @@ 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 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= 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= @@ -1074,7 +1065,6 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh 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 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= 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= @@ -1164,7 +1154,6 @@ golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v 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 h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg= 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 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= @@ -1250,21 +1239,18 @@ 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= 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= @@ -1276,7 +1262,6 @@ 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= @@ -1412,7 +1397,6 @@ 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= @@ -1439,7 +1423,6 @@ 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= @@ -1453,7 +1436,6 @@ 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/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 e1dac38a01d25120a8fd7dde82ec40cf45a51b0b Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:27:06 +0100 Subject: [PATCH 03/11] remove wallet specific methods from multiwallet --- dcr.go | 2 +- multiwallet.go | 365 +------------ multiwallet_utils.go | 1 - politeia.go | 33 -- syncnotification.go | 686 ------------------------- types.go | 530 ------------------- 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 ++++ 18 files changed, 708 insertions(+), 1976 deletions(-) delete mode 100644 politeia.go delete mode 100644 syncnotification.go delete mode 100644 types.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/types.go b/types.go deleted file mode 100644 index c39bb5dbe..000000000 --- a/types.go +++ /dev/null @@ -1,530 +0,0 @@ -package dcrlibwallet - -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/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 e583a4964e47ed03e23b9a737612c5a98bd1e3ab Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:35:08 +0100 Subject: [PATCH 04/11] 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 07d948eeeb2c184d7b7a9f64e7983a708d3aac01 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:37:34 +0100 Subject: [PATCH 05/11] 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 48f76916b98cf98e2e179e52ae973253b7306fb3 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:43:16 +0100 Subject: [PATCH 06/11] 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 ab7707db8f47015afa4bf99b9aa95e2e75a5f0d4 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:46:00 +0100 Subject: [PATCH 07/11] 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 25f756aec3d81cefacc83a13447f7bf78e7b0bb3 Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 28 Jul 2022 23:53:29 +0100 Subject: [PATCH 08/11] 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 8e33f3e5d490c9db4feb72d05ab57e4b6731988b Mon Sep 17 00:00:00 2001 From: dreacot Date: Fri, 29 Jul 2022 00:03:15 +0100 Subject: [PATCH 09/11] 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 75cf4ffff77fa10cd6e8cd49ba944c47e4f82ada Mon Sep 17 00:00:00 2001 From: dreacot Date: Thu, 11 Aug 2022 13:06:18 +0100 Subject: [PATCH 10/11] fix crash that occured while trying to create a wallet --- dcr.go | 3 --- dexclient.go | 2 +- multiwallet.go | 43 ++++++++++++++++--------------- multiwallet_config.go | 12 ++++----- multiwallet_utils.go | 14 +++++----- utils.go | 2 +- wallets/dcr/sync.go | 2 +- wallets/dcr/wallet.go | 60 ++++++++++++++++++++++++------------------- 8 files changed, 73 insertions(+), 65 deletions(-) diff --git a/dcr.go b/dcr.go index 31d1f4c4c..4801c6ffc 100644 --- a/dcr.go +++ b/dcr.go @@ -1,15 +1,12 @@ 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" diff --git a/dexclient.go b/dexclient.go index 5d9079e90..3d2503a12 100644 --- a/dexclient.go +++ b/dexclient.go @@ -44,7 +44,7 @@ func (mw *MultiWallet) initDexClient() error { mw.dexClient = &DexClient{ log: dex.NewLogger("DEXC", log.Level(), logWriter{}, true), - dexDataDir: filepath.Join(mw.rootDir, "dex"), + dexDataDir: filepath.Join(mw.RootDir, "dex"), } err := os.MkdirAll(mw.dexClient.dexDataDir, os.ModePerm) diff --git a/multiwallet.go b/multiwallet.go index fff078d1c..f9467452d 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -20,11 +20,11 @@ import ( ) type MultiWallet struct { - dbDriver string - rootDir string - db *storm.DB + DbDriver string + RootDir string + DB *storm.DB - chainParams *chaincfg.Params + ChainParams *chaincfg.Params wallets map[int]*dcr.Wallet badWallets map[int]*dcr.Wallet @@ -49,16 +49,16 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall } mw := &MultiWallet{ - dbDriver: dbDriver, - rootDir: dcrRootDir, - db: dcrDB, - chainParams: chainParams, + DbDriver: dbDriver, + RootDir: dcrRootDir, + DB: dcrDB, + ChainParams: chainParams, 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") + query := mw.DB.Select(q.True()).OrderBy("ID") var wallets []*dcr.Wallet err = query.Find(&wallets) if err != nil && err != storm.ErrNotFound { @@ -67,7 +67,7 @@ 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)) + 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") } @@ -78,6 +78,9 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall mw.wallets[wallet.ID] = wallet } + logLevel := wallet.ReadStringConfigValueForKey(LogLevelConfigKey, "") + SetLogLevels(logLevel) + // initialize Politeia. wallet.NewPoliteia(politeiaHost) } @@ -114,8 +117,8 @@ func (mw *MultiWallet) Shutdown() { wallet.Shutdown() } - if mw.db != nil { - if err := mw.db.Close(); err != nil { + if mw.DB != nil { + if err := mw.DB.Close(); err != nil { log.Errorf("db closed with error: %v", err) } else { log.Info("db closed successfully") @@ -130,15 +133,15 @@ func (mw *MultiWallet) Shutdown() { } func (mw *MultiWallet) NetType() string { - return mw.chainParams.Name + return mw.ChainParams.Name } func (mw *MultiWallet) LogDir() string { - return filepath.Join(mw.rootDir, logFileName) + return filepath.Join(mw.RootDir, logFileName) } func (mw *MultiWallet) TargetTimePerBlockMinutes() float64 { - return mw.chainParams.TargetTimePerBlock.Minutes() + return mw.ChainParams.TargetTimePerBlock.Minutes() } func (mw *MultiWallet) SetStartupPassphrase(passphrase []byte, passphraseType int32) error { @@ -147,7 +150,7 @@ func (mw *MultiWallet) SetStartupPassphrase(passphrase []byte, passphraseType in func (mw *MultiWallet) VerifyStartupPassphrase(startupPassphrase []byte) error { var startupPassphraseHash []byte - err := mw.db.Get(walletsMetadataBucketName, walletstartupPassphraseField, &startupPassphraseHash) + err := mw.DB.Get(walletsMetadataBucketName, walletstartupPassphraseField, &startupPassphraseHash) if err != nil && err != storm.ErrNotFound { return err } @@ -184,7 +187,7 @@ func (mw *MultiWallet) ChangeStartupPassphrase(oldPassphrase, newPassphrase []by return err } - err = mw.db.Set(walletsMetadataBucketName, walletstartupPassphraseField, startupPassphraseHash) + err = mw.DB.Set(walletsMetadataBucketName, walletstartupPassphraseField, startupPassphraseHash) if err != nil { return err } @@ -201,7 +204,7 @@ func (mw *MultiWallet) RemoveStartupPassphrase(oldPassphrase []byte) error { return err } - err = mw.db.Delete(walletsMetadataBucketName, walletstartupPassphraseField) + err = mw.DB.Delete(walletsMetadataBucketName, walletstartupPassphraseField) if err != nil { return err } @@ -266,7 +269,7 @@ func (mw *MultiWallet) DeleteBadWallet(walletID int) error { log.Info("Deleting bad wallet") - err := mw.db.DeleteStruct(wallet) + err := mw.DB.DeleteStruct(wallet) if err != nil { return translateError(err) } @@ -335,7 +338,7 @@ func (mw *MultiWallet) WalletNameExists(walletName string) (bool, error) { return false, errors.E(ErrReservedWalletName) } - err := mw.db.One("Name", walletName, &dcr.Wallet{}) + err := mw.DB.One("Name", walletName, &dcr.Wallet{}) if err == nil { return true, nil } else if err != storm.ErrNotFound { diff --git a/multiwallet_config.go b/multiwallet_config.go index f26408192..22259f31b 100644 --- a/multiwallet_config.go +++ b/multiwallet_config.go @@ -45,7 +45,7 @@ type configReadFn = func(multiwallet bool, key string, valueOut interface{}) err 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) + return mw.DB.Set(userConfigBucketName, walletUniqueKey, value) } } @@ -54,19 +54,19 @@ func (mw *MultiWallet) walletConfigReadFn(walletID int) configReadFn { if !multiwallet { key = WalletUniqueConfigKey(walletID, key) } - return mw.db.Get(userConfigBucketName, key, valueOut) + return mw.DB.Get(userConfigBucketName, key, valueOut) } } func (mw *MultiWallet) SaveUserConfigValue(key string, value interface{}) { - err := mw.db.Set(userConfigBucketName, key, value) + 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) + 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) } @@ -74,14 +74,14 @@ func (mw *MultiWallet) ReadUserConfigValue(key string, valueOut interface{}) err } func (mw *MultiWallet) DeleteUserConfigValueForKey(key string) { - err := mw.db.Delete(userConfigBucketName, key) + 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) + err := mw.DB.Drop(userConfigBucketName) if err != nil { log.Errorf("error deleting config bucket: %v", err) } diff --git a/multiwallet_utils.go b/multiwallet_utils.go index d94f28f25..a227a65a2 100644 --- a/multiwallet_utils.go +++ b/multiwallet_utils.go @@ -22,7 +22,7 @@ import ( const ( logFileName = "dcrlibwallet.log" - walletsDbName = "wallets.db" + walletsDbName = "wallets.DB" walletsMetadataBucketName = "metadata" walletstartupPassphraseField = "startup-passphrase" @@ -34,7 +34,7 @@ var ( ) func (mw *MultiWallet) batchDbTransaction(dbOp func(node storm.Node) error) (err error) { - dbTx, err := mw.db.Begin(true) + dbTx, err := mw.DB.Begin(true) if err != nil { return err } @@ -64,7 +64,7 @@ func (mw *MultiWallet) loadWalletTemporarily(ctx context.Context, walletDataDir, } // initialize the wallet loader - walletLoader := initWalletLoader(mw.chainParams, walletDataDir, mw.dbDriver) + walletLoader := initWalletLoader(mw.ChainParams, walletDataDir, mw.DbDriver) // open the wallet to get ready for temporary use wallet, err := walletLoader.OpenExistingWallet(ctx, []byte(walletPublicPass)) @@ -90,7 +90,7 @@ func (mw *MultiWallet) markWalletAsDiscoveredAccounts(walletID int) error { log.Infof("Set discovered accounts = true for wallet %d", wallet.ID) wallet.HasDiscoveredAccounts = true - err := mw.db.Save(wallet) + err := mw.DB.Save(wallet) if err != nil { return err } @@ -102,7 +102,7 @@ func (mw *MultiWallet) markWalletAsDiscoveredAccounts(walletID int) error { // multiwallet's root directory in bytes. func (mw *MultiWallet) RootDirFileSizeInBytes() (int64, error) { var size int64 - err := filepath.Walk(mw.rootDir, func(_ string, info os.FileInfo, err error) error { + err := filepath.Walk(mw.RootDir, func(_ string, info os.FileInfo, err error) error { if err != nil { return err } @@ -119,7 +119,7 @@ func (mw *MultiWallet) RootDirFileSizeInBytes() (int64, error) { // changes to the stake difficulty algorithm. func (mw *MultiWallet) DCP0001ActivationBlockHeight() int32 { var activationHeight int32 = -1 - switch strings.ToLower(mw.chainParams.Name) { + switch strings.ToLower(mw.ChainParams.Name) { case strings.ToLower(Mainnet): activationHeight = deployments.DCP0001.MainNetActivationHeight case strings.ToLower(Testnet3): @@ -168,7 +168,7 @@ func (mw *MultiWallet) WalletWithSeed(seedMnemonic string) (int, error) { return -1, errors.New(ErrEmptySeed) } - newSeedLegacyXPUb, newSeedSLIP0044XPUb, err := deriveBIP44AccountXPubs(seedMnemonic, dcr.DefaultAccountNum, mw.chainParams) + newSeedLegacyXPUb, newSeedSLIP0044XPUb, err := deriveBIP44AccountXPubs(seedMnemonic, dcr.DefaultAccountNum, mw.ChainParams) if err != nil { return -1, err } diff --git a/utils.go b/utils.go index 5f968244a..e91e37770 100644 --- a/utils.go +++ b/utils.go @@ -83,7 +83,7 @@ func (mw *MultiWallet) contextWithShutdownCancel() (context.Context, context.Can } func (mw *MultiWallet) ValidateExtPubKey(extendedPubKey string) error { - _, err := hdkeychain.NewKeyFromString(extendedPubKey, mw.chainParams) + _, err := hdkeychain.NewKeyFromString(extendedPubKey, mw.ChainParams) if err != nil { if err == hdkeychain.ErrInvalidChild { return errors.New(ErrUnusableSeed) diff --git a/wallets/dcr/sync.go b/wallets/dcr/sync.go index 1b8f676e7..ccfca7c9b 100644 --- a/wallets/dcr/sync.go +++ b/wallets/dcr/sync.go @@ -218,7 +218,7 @@ func (wallet *Wallet) SpvSync() error { wallet.initActiveSyncData() wallets := make(map[int]*w.Wallet) - wallets[wallet.ID] = wallet.Internal() + wallets[0] = wallet.Internal() wallet.WaitingForHeaders = true wallet.Syncing = true diff --git a/wallets/dcr/wallet.go b/wallets/dcr/wallet.go index 14045d3ff..ff735595e 100644 --- a/wallets/dcr/wallet.go +++ b/wallets/dcr/wallet.go @@ -44,10 +44,11 @@ type Wallet struct { shuttingDown chan bool cancelFuncs []context.CancelFunc - CancelAccountMixer context.CancelFunc + cancel context.CancelFunc + CancelAccountMixer context.CancelFunc `json:"-"` cancelAutoTicketBuyerMu sync.Mutex - cancelAutoTicketBuyer context.CancelFunc + cancelAutoTicketBuyer context.CancelFunc `json:"-"` vspClientsMu sync.Mutex vspClients map[string]*vsp.Client @@ -97,9 +98,9 @@ func (wallet *Wallet) Prepare(rootDir string, chainParams *chaincfg.Params, return err } - wallet.syncData = &SyncData{ - SyncProgressListeners: make(map[string]SyncProgressListener), - } + // wallet.syncData = &SyncData{ + // SyncProgressListeners: make(map[string]SyncProgressListener), + // } // init loader wallet.loader = initWalletLoader(wallet.chainParams, wallet.DataDir, wallet.dbDriver) @@ -165,7 +166,7 @@ func (wallet *Wallet) WalletExists() (bool, error) { return wallet.loader.WalletExists() } -func (wallet *Wallet) CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32) (*Wallet, error) { +func CreateNewWallet(walletName, privatePassphrase string, privatePassphraseType int32, db *storm.DB, rootDir, dbDriver string, chainParams *chaincfg.Params) (*Wallet, error) { seed, err := GenerateSeed() if err != nil { return nil, err @@ -176,26 +177,37 @@ func (wallet *Wallet) CreateNewWallet(walletName, privatePassphrase string, priv return nil, err } - wal := &Wallet{ - Name: walletName, - CreatedAt: time.Now(), - EncryptedSeed: encryptedSeed, - PrivatePassphraseType: privatePassphraseType, - HasDiscoveredAccounts: true, + wallet := &Wallet{ + Name: walletName, + db: db, + dbDriver: dbDriver, + rootDir: rootDir, + chainParams: chainParams, + syncData: &SyncData{ + SyncProgressListeners: make(map[string]SyncProgressListener), + }, + txAndBlockNotificationListeners: make(map[string]TxAndBlockNotificationListener), + accountMixerNotificationListener: make(map[string]AccountMixerNotificationListener), + cancelFuncs: make([]context.CancelFunc, 0), + CreatedAt: time.Now(), + EncryptedSeed: encryptedSeed, + PrivatePassphraseType: privatePassphraseType, + HasDiscoveredAccounts: true, } + wallet.cancelFuncs = make([]context.CancelFunc, 0) + return wallet.saveNewWallet(func() error { - err := wallet.Prepare(wallet.rootDir, wallet.chainParams, wallet.walletConfigSetFn(wal.ID), wallet.walletConfigReadFn(wal.ID)) + err := wallet.Prepare(wallet.rootDir, wallet.chainParams, wallet.walletConfigSetFn(wallet.ID), wallet.walletConfigReadFn(wallet.ID)) if err != nil { return err } - return wallet.CreateWallet(privatePassphrase, seed) }) } func (wallet *Wallet) CreateWallet(privatePassphrase, seedMnemonic string) error { - log.Info("Creating Wallet") + // log.Info("Creating Wallet") if len(seedMnemonic) == 0 { return errors.New(ErrEmptySeed) } @@ -214,7 +226,7 @@ func (wallet *Wallet) CreateWallet(privatePassphrase, seedMnemonic string) error return err } - log.Info("Created Wallet") + // log.Info("Created Wallet") return nil } @@ -287,9 +299,7 @@ func (wallet *Wallet) DeleteWallet(privPass []byte) error { if wallet.IsConnectedToDecredNetwork() { wallet.CancelSync() defer func() { - // if wallet.OpenedWalletsCount() > 0 { wallet.SpvSync() - // } }() } @@ -362,8 +372,6 @@ func (wallet *Wallet) saveNewWallet(setupWallet func() error) (*Wallet, error) { return nil, translateError(err) } - // wallet.wallets[wallet.ID] = wallet - return wallet, nil } @@ -577,13 +585,13 @@ func (wallet *Wallet) deleteWallet(privatePassphrase []byte) error { } // 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 From 490aa643b3bb8499c8d343cdada6a3818b530b61 Mon Sep 17 00:00:00 2001 From: dreacot Date: Mon, 22 Aug 2022 19:02:30 +0100 Subject: [PATCH 11/11] manage coins under an Asset struct --- multiwallet.go | 65 ++++++++++++++++++++++++++++++-------------- multiwallet_utils.go | 4 +-- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/multiwallet.go b/multiwallet.go index f9467452d..b2ff792dd 100644 --- a/multiwallet.go +++ b/multiwallet.go @@ -19,14 +19,24 @@ import ( "golang.org/x/crypto/bcrypt" ) +type Assets struct { + DCR struct { + Wallets map[int]*dcr.Wallet + BadWallets map[int]*dcr.Wallet + DBDriver string + RootDir string + DB *storm.DB + ChainParams *chaincfg.Params + } +} + type MultiWallet struct { DbDriver string RootDir string DB *storm.DB ChainParams *chaincfg.Params - wallets map[int]*dcr.Wallet - badWallets map[int]*dcr.Wallet + Assets *Assets shuttingDown chan bool cancelFuncs []context.CancelFunc @@ -53,8 +63,23 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall RootDir: dcrRootDir, DB: dcrDB, ChainParams: chainParams, - wallets: make(map[int]*dcr.Wallet), - badWallets: make(map[int]*dcr.Wallet), + 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, + }, + }, } // read saved wallets info from db and initialize wallets @@ -72,10 +97,10 @@ func NewMultiWallet(rootDir, dbDriver, netType, politeiaHost string) (*MultiWall 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.wallets[wallet.ID] = wallet + mw.Assets.DCR.Wallets[wallet.ID] = wallet } logLevel := wallet.ReadStringConfigValueForKey(LogLevelConfigKey, "") @@ -105,15 +130,15 @@ func (mw *MultiWallet) Shutdown() { // Trigger shuttingDown signal to cancel all contexts created with `shutdownContextWithCancel`. mw.shuttingDown <- true - for _, wallet := range mw.wallets { + 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() } @@ -233,7 +258,7 @@ func (mw *MultiWallet) OpenWallets(startupPassphrase []byte) error { return err } - for _, wallet := range mw.wallets { + for _, wallet := range mw.Assets.DCR.Wallets { err = wallet.OpenWallet() if err != nil { return err @@ -244,11 +269,11 @@ 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 { return false, errors.New(ErrInvalid) } - for _, w := range mw.wallets { + for _, w := range mw.Assets.DCR.Wallets { if !w.IsWatchingOnlyWallet() { return false, nil } @@ -258,11 +283,11 @@ func (mw *MultiWallet) AllWalletsAreWatchOnly() (bool, error) { } func (mw *MultiWallet) BadWallets() map[int]*dcr.Wallet { - return mw.badWallets + return mw.Assets.DCR.BadWallets } func (mw *MultiWallet) DeleteBadWallet(walletID int) error { - wallet := mw.badWallets[walletID] + wallet := mw.Assets.DCR.BadWallets[walletID] if wallet == nil { return errors.New(ErrNotExist) } @@ -275,13 +300,13 @@ func (mw *MultiWallet) DeleteBadWallet(walletID int) error { } os.RemoveAll(wallet.DataDir) - delete(mw.badWallets, walletID) + delete(mw.Assets.DCR.BadWallets, walletID) return nil } func (mw *MultiWallet) WalletWithID(walletID int) *dcr.Wallet { - if wallet, ok := mw.wallets[walletID]; ok { + if wallet, ok := mw.Assets.DCR.Wallets[walletID]; ok { return wallet } return nil @@ -290,7 +315,7 @@ func (mw *MultiWallet) WalletWithID(walletID int) *dcr.Wallet { // 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++ } @@ -299,12 +324,12 @@ func (mw *MultiWallet) NumWalletsNeedingSeedBackup() int32 { } func (mw *MultiWallet) LoadedWalletsCount() int32 { - return int32(len(mw.wallets)) + return int32(len(mw.Assets.DCR.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) } @@ -324,7 +349,7 @@ 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++ } diff --git a/multiwallet_utils.go b/multiwallet_utils.go index a227a65a2..8e4f6bf7e 100644 --- a/multiwallet_utils.go +++ b/multiwallet_utils.go @@ -136,7 +136,7 @@ func (mw *MultiWallet) WalletWithXPub(xpub string) (int, error) { ctx, cancel := mw.contextWithShutdownCancel() defer cancel() - for _, w := range mw.wallets { + for _, w := range mw.Assets.DCR.Wallets { if !w.WalletOpened() { return -1, errors.Errorf("wallet %d is not open and cannot be checked", w.ID) } @@ -173,7 +173,7 @@ func (mw *MultiWallet) WalletWithSeed(seedMnemonic string) (int, error) { return -1, err } - for _, wallet := range mw.wallets { + for _, wallet := range mw.Assets.DCR.Wallets { if !wallet.WalletOpened() { return -1, errors.Errorf("cannot check if seed matches unloaded wallet %d", wallet.ID) }