diff --git a/wallet/chainntfns.go b/wallet/chainntfns.go index d73764404..f41a670f5 100644 --- a/wallet/chainntfns.go +++ b/wallet/chainntfns.go @@ -620,7 +620,7 @@ func (w *Wallet) processTransactionRecord(ctx context.Context, dbtx walletdb.Rea func selectOwnedTickets(w *Wallet, dbtx walletdb.ReadTx, tickets []*chainhash.Hash) []*chainhash.Hash { var owned []*chainhash.Hash for _, ticketHash := range tickets { - if w.txStore.OwnTicket(dbtx, ticketHash) || w.stakeMgr.OwnTicket(ticketHash) { + if w.txStore.OwnTicket(dbtx, ticketHash) { owned = append(owned, ticketHash) } } @@ -685,9 +685,6 @@ func (w *Wallet) VoteOnOwnedTickets(ctx context.Context, winningTicketHashes []* for i, ticketHash := range ticketHashes { ticketPurchase, err := w.txStore.Tx(txmgrNs, ticketHash) - if err != nil && errors.Is(err, errors.NotExist) { - ticketPurchase, err = w.stakeMgr.TicketPurchase(dbtx, ticketHash) - } if err != nil { log.Errorf("Failed to read ticket purchase transaction for "+ "owned winning ticket %v: %v", ticketHash, err) diff --git a/wallet/tickets.go b/wallet/tickets.go index bb5861a10..7cd651b75 100644 --- a/wallet/tickets.go +++ b/wallet/tickets.go @@ -10,22 +10,12 @@ import ( "decred.org/dcrwallet/v5/errors" "decred.org/dcrwallet/v5/wallet/walletdb" "github.com/decred/dcrd/chaincfg/chainhash" - dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4" "github.com/jrick/bitset" - "golang.org/x/sync/errgroup" ) // LiveTicketQuerier defines the functions required of a (trusted) network // backend that provides information about the live ticket pool. type LiveTicketQuerier interface { - // May be removed if/when there is a header commitment to the utxo - // set. - GetTxOut(ctx context.Context, txHash *chainhash.Hash, index uint32, tree int8, includeMempool bool) (*dcrdtypes.GetTxOutResult, error) - - // GetConfirmationHeight relies on the transaction index being enabled - // on the backing dcrd node. - GetConfirmationHeight(ctx context.Context, txHash *chainhash.Hash) (int32, error) - // ExistsLiveTickets relies on the node having the entire live ticket // pool available. May be removed if/when there is a header commitment // to the live ticket pool. @@ -41,24 +31,9 @@ func (w *Wallet) LiveTicketHashes(ctx context.Context, rpc LiveTicketQuerier, in var ticketHashes []chainhash.Hash var maybeLive []*chainhash.Hash - extraTickets := w.stakeMgr.DumpSStxHashes() - var tipHeight int32 // Assigned in view below. err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { - txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) - - // Remove tickets from the extraTickets slice if they will appear in the - // ticket iteration below. - hashes := extraTickets - extraTickets = hashes[:0] - for i := range hashes { - h := &hashes[i] - if !w.txStore.ExistsTx(txmgrNs, h) { - extraTickets = append(extraTickets, *h) - } - } - _, tipHeight = w.txStore.MainChainTip(dbtx) it := w.txStore.IterateTickets(dbtx) @@ -92,62 +67,11 @@ func (w *Wallet) LiveTicketHashes(ctx context.Context, rpc LiveTicketQuerier, in return nil, errors.E(op, err) } - // SPV wallet can't evaluate extraTickets. + // SPV wallet can't evaluate possibly live tickets. if rpc == nil { return ticketHashes, nil } - // Determine if the extra tickets are immature or possibly live. Because - // these transactions are not part of the wallet's transaction history, dcrd - // must be queried for their blockchain height. This functionality requires - // the dcrd transaction index to be enabled. - var g errgroup.Group - type extraTicketResult struct { - valid bool // unspent with known height - height int32 - } - extraTicketResults := make([]extraTicketResult, len(extraTickets)) - for i := range extraTickets { - i := i - g.Go(func() error { - // gettxout is used first as an optimization to check that output 0 - // of the ticket is unspent. - const index = 0 - const tree = 1 - txOut, err := rpc.GetTxOut(ctx, &extraTickets[i], index, tree, true) - if err != nil || txOut == nil { - return nil - } - blockHeight, err := rpc.GetConfirmationHeight(ctx, &extraTickets[i]) - if err != nil { - return nil - } - extraTicketResults[i] = extraTicketResult{true, blockHeight} - return nil - }) - } - err = g.Wait() - if err != nil { - return nil, err - } - for i := range extraTickets { - r := &extraTicketResults[i] - if !r.valid { - continue - } - // Same checks as above in the db view. - if ticketExpired(w.chainParams, r.height, tipHeight) { - continue - } - if !ticketMatured(w.chainParams, r.height, tipHeight) { - if includeImmature { - ticketHashes = append(ticketHashes, extraTickets[i]) - } - continue - } - maybeLive = append(maybeLive, &extraTickets[i]) - } - // If there are no possibly live tickets to check, ticketHashes contains all // of the results. if len(maybeLive) == 0 { diff --git a/wallet/udb/addressmanager_test.go b/wallet/udb/addressmanager_test.go index 2e964901c..813eddccc 100644 --- a/wallet/udb/addressmanager_test.go +++ b/wallet/udb/addressmanager_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2014 The btcsuite developers -// Copyright (c) 2015-2019 The Decred developers +// Copyright (c) 2015-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -432,7 +432,7 @@ func testImportScript(tc *testContext, wb walletdb.ReadWriteBucket) { func TestManagerImports(t *testing.T) { ctx := context.Background() - db, mgr, _, _, teardown, err := cloneDB(ctx, "imports.kv") + db, mgr, _, teardown, err := cloneDB(ctx, "imports.kv") defer teardown() if err != nil { t.Fatal(err) @@ -474,7 +474,7 @@ func TestManagerImports(t *testing.T) { // with the manager locked. func TestImportVotingAccount(t *testing.T) { ctx := context.Background() - db, mgr, _, _, teardown, err := cloneDB(ctx, "import_voting_account.kv") + db, mgr, _, teardown, err := cloneDB(ctx, "import_voting_account.kv") defer teardown() if err != nil { t.Fatal(err) @@ -607,7 +607,7 @@ func TestImportVotingAccount(t *testing.T) { // with the manager locked. func TestImportAccount(t *testing.T) { ctx := context.Background() - db, mgr, _, _, teardown, err := cloneDB(ctx, "import_account.kv") + db, mgr, _, teardown, err := cloneDB(ctx, "import_account.kv") defer teardown() if err != nil { t.Fatal(err) @@ -1073,7 +1073,7 @@ func testEncryptDecrypt(ctx context.Context, tc *testContext) { func TestManagerEncryptDecrypt(t *testing.T) { ctx := context.Background() - db, mgr, _, _, teardown, err := cloneDB(ctx, "encrypt_decrypt.kv") + db, mgr, _, teardown, err := cloneDB(ctx, "encrypt_decrypt.kv") defer teardown() if err != nil { t.Fatal(err) @@ -1095,7 +1095,7 @@ func TestManagerEncryptDecrypt(t *testing.T) { func TestChangePassphrase(t *testing.T) { ctx := context.Background() - db, mgr, _, _, teardown, err := cloneDB(ctx, "change_passphrase.kv") + db, mgr, _, teardown, err := cloneDB(ctx, "change_passphrase.kv") defer teardown() if err != nil { t.Fatal(err) @@ -1150,7 +1150,7 @@ func testManagerAPI(ctx context.Context, tc *testContext) { // copy as well as when it is opened from an existing namespace. func TestManagerWatchingOnly(t *testing.T) { ctx := context.Background() - db, mgr, _, _, teardown, err := cloneDB(ctx, "mgr_watching_only.kv") + db, mgr, _, teardown, err := cloneDB(ctx, "mgr_watching_only.kv") defer teardown() if err != nil { t.Fatal(err) @@ -1167,7 +1167,7 @@ func TestManagerWatchingOnly(t *testing.T) { }) mgr.Close() - mgr, _, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) + mgr, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -1195,7 +1195,7 @@ func TestManagerWatchingOnly(t *testing.T) { }) // Open the watching-only manager and run all the tests again. - mgr, _, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) + mgr, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -1220,7 +1220,7 @@ func TestManager(t *testing.T) { } ctx := context.Background() - db, mgr, _, _, teardown, err := cloneDB(ctx, "mgr_watching_only.kv") + db, mgr, _, teardown, err := cloneDB(ctx, "mgr_watching_only.kv") defer teardown() if err != nil { t.Fatal(err) @@ -1238,7 +1238,7 @@ func TestManager(t *testing.T) { // Open the manager and run all the tests again in open mode which // avoids reinserting new addresses like the create mode tests do. - mgr, _, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) + mgr, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) if err != nil { t.Fatalf("Open: unexpected error: %v", err) } diff --git a/wallet/udb/cointype_test.go b/wallet/udb/cointype_test.go index 84360daae..47b44226a 100644 --- a/wallet/udb/cointype_test.go +++ b/wallet/udb/cointype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 The Decred developers +// Copyright (c) 2017-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -70,7 +70,7 @@ func TestCoinTypeUpgrade(t *testing.T) { t.Fatal(err) } - m, _, _, err := Open(ctx, db, params, pubPass) + m, _, err := Open(ctx, db, params, pubPass) if err != nil { t.Fatal(err) } diff --git a/wallet/udb/common_test.go b/wallet/udb/common_test.go index a1d8d5ccf..4d244b42b 100644 --- a/wallet/udb/common_test.go +++ b/wallet/udb/common_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2014 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2015-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -64,27 +64,27 @@ func createEmptyDB(ctx context.Context) error { return nil } -// cloneDB makes a copy of an empty wallet db. It returns a wallet db, store, a -// stake store and a teardown function. -func cloneDB(ctx context.Context, cloneName string) (walletdb.DB, *Manager, *Store, *StakeStore, func(), error) { +// cloneDB makes a copy of an empty wallet db. It returns a wallet db, store, +// and a teardown function. +func cloneDB(ctx context.Context, cloneName string) (walletdb.DB, *Manager, *Store, func(), error) { file, err := os.ReadFile(emptyDbPath) if err != nil { - return nil, nil, nil, nil, nil, fmt.Errorf("unexpected error: %v", err) + return nil, nil, nil, nil, fmt.Errorf("unexpected error: %v", err) } err = os.WriteFile(cloneName, file, 0644) if err != nil { - return nil, nil, nil, nil, nil, fmt.Errorf("unexpected error: %v", err) + return nil, nil, nil, nil, fmt.Errorf("unexpected error: %v", err) } db, err := walletdb.Open("bdb", cloneName) if err != nil { - return nil, nil, nil, nil, nil, fmt.Errorf("unexpected error: %v", err) + return nil, nil, nil, nil, fmt.Errorf("unexpected error: %v", err) } - mgr, txStore, stkStore, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) + mgr, txStore, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) if err != nil { - return nil, nil, nil, nil, nil, fmt.Errorf("unexpected error: %v", err) + return nil, nil, nil, nil, fmt.Errorf("unexpected error: %v", err) } teardown := func() { @@ -92,5 +92,5 @@ func cloneDB(ctx context.Context, cloneName string) (walletdb.DB, *Manager, *Sto db.Close() } - return db, mgr, txStore, stkStore, teardown, err + return db, mgr, txStore, teardown, err } diff --git a/wallet/udb/initialize.go b/wallet/udb/initialize.go index c3a0d5409..315972fdb 100644 --- a/wallet/udb/initialize.go +++ b/wallet/udb/initialize.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2018 The Decred developers +// Copyright (c) 2017-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -25,12 +25,8 @@ func Initialize(ctx context.Context, db walletdb.DB, params *chaincfg.Params, se if err != nil { return errors.E(errors.IO, err) } - stakemgrNs, err := tx.CreateTopLevelBucket(wstakemgrBucketKey) - if err != nil { - return errors.E(errors.IO, err) - } - // Create the address manager, transaction store, and stake store. + // Create the address manager and transaction store. err = createAddressManager(addrmgrNs, seed, pubPass, privPass, params) if err != nil { return err @@ -39,10 +35,6 @@ func Initialize(ctx context.Context, db walletdb.DB, params *chaincfg.Params, se if err != nil { return err } - err = initializeEmpty(stakemgrNs) - if err != nil { - return err - } // Create the metadata bucket and write the current database version to // it. @@ -71,12 +63,8 @@ func InitializeWatchOnly(ctx context.Context, db walletdb.DB, params *chaincfg.P if err != nil { return errors.E(errors.IO, err) } - stakemgrNs, err := tx.CreateTopLevelBucket(wstakemgrBucketKey) - if err != nil { - return errors.E(errors.IO, err) - } - // Create the address manager, transaction store, and stake store. + // Create the address manager and transaction store. err = createWatchOnly(addrmgrNs, hdPubKey, pubPass, params) if err != nil { return err @@ -85,10 +73,6 @@ func InitializeWatchOnly(ctx context.Context, db walletdb.DB, params *chaincfg.P if err != nil { return err } - err = initializeEmpty(stakemgrNs) - if err != nil { - return err - } // Create the metadata bucket and write the current database version to // it. diff --git a/wallet/udb/migration.go b/wallet/udb/migration.go index ca9c709d6..f1cb0f7a9 100644 --- a/wallet/udb/migration.go +++ b/wallet/udb/migration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2018 The Decred developers +// Copyright (c) 2017-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -15,9 +15,8 @@ import ( // Old package namespace bucket keys. These are still used as of the very first // unified database layout. var ( - waddrmgrBucketKey = []byte("waddrmgr") - wtxmgrBucketKey = []byte("wtxmgr") - wstakemgrBucketKey = []byte("wstakemgr") + waddrmgrBucketKey = []byte("waddrmgr") + wtxmgrBucketKey = []byte("wtxmgr") ) // NeedsMigration checks whether the database needs to be converted to the @@ -39,9 +38,6 @@ func Migrate(ctx context.Context, db walletdb.DB, params *chaincfg.Params) error return walletdb.Update(ctx, db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrBucketKey) txmgrNs := tx.ReadWriteBucket(wtxmgrBucketKey) - stakemgrNs := tx.ReadWriteBucket(wstakemgrBucketKey) - - stakeStoreVersionName := []byte("stakestorever") // Perform any necessary upgrades for the old address manager. err := upgradeManager(addrmgrNs) @@ -68,10 +64,6 @@ func Migrate(ctx context.Context, db walletdb.DB, params *chaincfg.Params) error if err != nil { return errors.E(errors.IO, err) } - err = stakemgrNs.NestedReadWriteBucket(mainBucketName).Delete(stakeStoreVersionName) - if err != nil { - return errors.E(errors.IO, err) - } metadataBucket, err := tx.CreateTopLevelBucket(unifiedDBMetadata{}.rootBucketKey()) if err != nil { return errors.E(errors.IO, err) diff --git a/wallet/udb/open.go b/wallet/udb/open.go index b4dd8cd21..9d3c2773d 100644 --- a/wallet/udb/open.go +++ b/wallet/udb/open.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 The Decred developers +// Copyright (c) 2017-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -18,7 +18,7 @@ import ( // A NotExist error will be returned if the database has not been initialized. // The recorded database version must match exactly with DBVersion. If the // version does not match, an Invalid error is returned. -func Open(ctx context.Context, db walletdb.DB, params *chaincfg.Params, pubPass []byte) (addrMgr *Manager, txStore *Store, stakeStore *StakeStore, err error) { +func Open(ctx context.Context, db walletdb.DB, params *chaincfg.Params, pubPass []byte) (addrMgr *Manager, txStore *Store, err error) { err = walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { // Verify the database exists and the recorded version is supported by // this software version. @@ -38,7 +38,6 @@ func Open(ctx context.Context, db walletdb.DB, params *chaincfg.Params, pubPass } addrmgrNs := tx.ReadBucket(waddrmgrBucketKey) - stakemgrNs := tx.ReadBucket(wstakemgrBucketKey) addrMgr, err = loadManager(addrmgrNs, pubPass, params) if err != nil { @@ -49,7 +48,6 @@ func Open(ctx context.Context, db walletdb.DB, params *chaincfg.Params, pubPass acctLookupFunc: addrMgr.AddrAccount, manager: addrMgr, } - stakeStore, err = openStakeStore(stakemgrNs, addrMgr, params) return err }) return diff --git a/wallet/udb/stake.go b/wallet/udb/stake.go deleted file mode 100644 index 16939eab6..000000000 --- a/wallet/udb/stake.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) 2015-2024 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package udb - -import ( - "sync" - "time" - - "decred.org/dcrwallet/v5/errors" - "decred.org/dcrwallet/v5/wallet/walletdb" - "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/chaincfg/v3" - "github.com/decred/dcrd/dcrutil/v4" - "github.com/decred/dcrd/txscript/v4/stdaddr" - "github.com/decred/dcrd/wire" -) - -// sstxRecord is the structure for a stored SStx. -type sstxRecord struct { - tx *dcrutil.Tx - ts time.Time - voteBitsSet bool // Removed in version 3 - voteBits uint16 // Removed in version 3 - voteBitsExt []byte // Removed in version 3 -} - -// StakeStore represents a safely accessible database of -// stake transactions. -type StakeStore struct { - Params *chaincfg.Params - Manager *Manager - - ownedSStxs map[chainhash.Hash]struct{} - mtx sync.RWMutex // only protects ownedSStxs -} - -// checkHashInStore checks if a hash exists in ownedSStxs. -func (s *StakeStore) checkHashInStore(hash *chainhash.Hash) bool { - _, exists := s.ownedSStxs[*hash] - return exists -} - -// OwnTicket returns whether the ticket is tracked by the stake manager. -func (s *StakeStore) OwnTicket(hash *chainhash.Hash) bool { - s.mtx.RLock() - owned := s.checkHashInStore(hash) - s.mtx.RUnlock() - return owned -} - -// dumpSStxHashes dumps the hashes of all owned SStxs. Note -// that this doesn't use the DB. -func (s *StakeStore) dumpSStxHashes() []chainhash.Hash { - // Copy the hash list of sstxs. You could pass the pointer - // directly but you risk that the size of the internal - // ownedSStxs is later modified while the end user is - // working with the returned list. - ownedSStxs := make([]chainhash.Hash, len(s.ownedSStxs)) - - itr := 0 - for hash := range s.ownedSStxs { - ownedSStxs[itr] = hash - itr++ - } - - return ownedSStxs -} - -// DumpSStxHashes returns the hashes of all wallet ticket purchase transactions. -func (s *StakeStore) DumpSStxHashes() []chainhash.Hash { - defer s.mtx.RUnlock() - s.mtx.RLock() - - return s.dumpSStxHashes() -} - -// sstxAddress returns the address for a given ticket. -func (s *StakeStore) sstxAddress(ns walletdb.ReadBucket, hash *chainhash.Hash) (stdaddr.Address, error) { - // Access the database and store the result locally. - thisHash160, p2sh, err := fetchSStxRecordSStxTicketHash160(ns, hash) - if err != nil { - return nil, err - } - var addr stdaddr.Address - if p2sh { - addr, err = stdaddr.NewAddressScriptHashV0FromHash(thisHash160, s.Params) - } else { - addr, err = stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(thisHash160, s.Params) - } - if err != nil { - return nil, err - } - - return addr, nil -} - -// SStxAddress is the exported, concurrency safe version of sstxAddress. -func (s *StakeStore) SStxAddress(ns walletdb.ReadBucket, hash *chainhash.Hash) (stdaddr.Address, error) { - return s.sstxAddress(ns, hash) -} - -// TicketPurchase returns the ticket purchase transaction recorded in the "stake -// manager" portion of the DB. -// -// TODO: This is redundant and should be looked up in from the transaction -// manager. Left for now for compatibility. -func (s *StakeStore) TicketPurchase(dbtx walletdb.ReadTx, hash *chainhash.Hash) (*wire.MsgTx, error) { - ns := dbtx.ReadBucket(wstakemgrBucketKey) - - ticketRecord, err := fetchSStxRecord(ns, hash, DBVersion) - if err != nil { - return nil, err - } - return ticketRecord.tx.MsgTx(), nil -} - -// loadManager returns a new stake manager that results from loading it from -// the passed opened database. The public passphrase is required to decrypt the -// public keys. -func (s *StakeStore) loadOwnedSStxs(ns walletdb.ReadBucket) error { - // Regenerate the list of tickets. - // Perform all database lookups in a read-only view. - ticketList := make(map[chainhash.Hash]struct{}) - - // Open the sstx records database. - bucket := ns.NestedReadBucket(sstxRecordsBucketName) - - // Store each key sequentially. - err := bucket.ForEach(func(k []byte, v []byte) error { - var errNewHash error - var hash *chainhash.Hash - - hash, errNewHash = chainhash.NewHash(k) - if errNewHash != nil { - return errNewHash - } - ticketList[*hash] = struct{}{} - return nil - }) - if err != nil { - return err - } - - s.ownedSStxs = ticketList - return nil -} - -// newStakeStore initializes a new stake store with the given parameters. -func newStakeStore(params *chaincfg.Params, manager *Manager) *StakeStore { - return &StakeStore{ - Params: params, - Manager: manager, - ownedSStxs: make(map[chainhash.Hash]struct{}), - } -} - -// openStakeStore loads an existing stake manager from the given namespace, -// waddrmgr, and network parameters. -// -// A NotExist error is returned returned when the stake store is not written to -// the db. -func openStakeStore(ns walletdb.ReadBucket, manager *Manager, params *chaincfg.Params) (*StakeStore, error) { - // Return an error if the manager has NOT already been created in the - // given database namespace. - if !stakeStoreExists(ns) { - return nil, errors.E(errors.NotExist, "no stake store") - } - - ss := newStakeStore(params, manager) - - err := ss.loadOwnedSStxs(ns) - if err != nil { - return nil, err - } - - return ss, nil -} diff --git a/wallet/udb/stakedb.go b/wallet/udb/stakedb.go deleted file mode 100644 index e7ce72293..000000000 --- a/wallet/udb/stakedb.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) 2015-2024 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package udb - -import ( - "bytes" - "encoding/binary" - "io" - "time" - - "decred.org/dcrwallet/v5/errors" - "decred.org/dcrwallet/v5/wallet/walletdb" - "github.com/decred/dcrd/blockchain/stake/v5" - "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/dcrutil/v4" - "github.com/decred/dcrd/wire" -) - -const ( - // Size of various types in bytes. - int8Size = 1 - int16Size = 2 - int32Size = 4 - int64Size = 8 - hashSize = 32 -) - -var ( - // sstxTicket2PKHPrefix is the PkScript byte prefix for an SStx - // P2PKH ticket output. The entire prefix is 0xba76a914, but we - // only use the first 3 bytes. - sstxTicket2PKHPrefix = []byte{0xba, 0x76, 0xa9} - - // sstxTicket2SHPrefix is the PkScript byte prefix for an SStx - // P2SH ticket output. - sstxTicket2SHPrefix = []byte{0xba, 0xa9, 0x14} -) - -// Key names for various database fields. -// sstxRecords -// -// key: sstx tx hash -// val: sstxRecord -// -// ssgenRecords -// -// key: sstx tx hash -// val: serialized slice of ssgenRecords -var ( - // Bucket names. - sstxRecordsBucketName = []byte("sstxrecords") - ssgenRecordsBucketName = []byte("ssgenrecords") - ssrtxRecordsBucketName = []byte("ssrtxrecords") - - // Db related key names (main bucket). - stakeStoreCreateDateName = []byte("stakestorecreated") -) - -// deserializeSStxRecord deserializes the passed serialized tx record information. -func deserializeSStxRecord(serializedSStxRecord []byte, dbVersion uint32) (*sstxRecord, error) { - switch { - case dbVersion < 3: - record := new(sstxRecord) - - curPos := 0 - - // Read MsgTx size (as a uint64). - msgTxLen := int(binary.LittleEndian.Uint64( - serializedSStxRecord[curPos : curPos+int64Size])) - curPos += int64Size - - // Pretend to read the pkScrLoc for the 0th output pkScript. - curPos += int32Size - - // Read the intended voteBits and extended voteBits length (uint8). - record.voteBitsSet = false - voteBitsLen := int(serializedSStxRecord[curPos]) - if voteBitsLen != 0 { - record.voteBitsSet = true - } - curPos += int8Size - - // Read the assumed 2 byte VoteBits as well as the extended - // votebits (75 bytes max). - record.voteBits = binary.LittleEndian.Uint16( - serializedSStxRecord[curPos : curPos+int16Size]) - curPos += int16Size - if voteBitsLen != 0 { - record.voteBitsExt = make([]byte, voteBitsLen-int16Size) - copy(record.voteBitsExt, serializedSStxRecord[curPos:curPos+voteBitsLen-int16Size]) - } - curPos += stake.MaxSingleBytePushLength - int16Size - - // Prepare a buffer for the msgTx. - buf := bytes.NewBuffer(serializedSStxRecord[curPos : curPos+msgTxLen]) - curPos += msgTxLen - - // Deserialize transaction. - msgTx := new(wire.MsgTx) - err := msgTx.Deserialize(buf) - if err != nil { - if errors.Is(err, io.EOF) { - err = io.ErrUnexpectedEOF - } - return nil, err - } - - // Create and save the dcrutil.Tx of the read MsgTx and set its index. - tx := dcrutil.NewTx(msgTx) - tx.SetIndex(dcrutil.TxIndexUnknown) - tx.SetTree(wire.TxTreeStake) - record.tx = tx - - // Read received unix time (int64). - received := int64(binary.LittleEndian.Uint64( - serializedSStxRecord[curPos : curPos+int64Size])) - record.ts = time.Unix(received, 0) - - return record, nil - - case dbVersion >= 3: - // Don't need to read the pkscript location, so first four bytes are - // skipped. - serializedSStxRecord = serializedSStxRecord[4:] - - var tx wire.MsgTx - err := tx.Deserialize(bytes.NewReader(serializedSStxRecord)) - if err != nil { - return nil, err - } - unixTime := int64(binary.LittleEndian.Uint64(serializedSStxRecord[tx.SerializeSize():])) - return &sstxRecord{tx: dcrutil.NewTx(&tx), ts: time.Unix(unixTime, 0)}, nil - - default: - panic("unreachable") - } -} - -// deserializeSStxTicketHash160 deserializes and returns a 20 byte script -// hash for a ticket's 0th output. -func deserializeSStxTicketHash160(serializedSStxRecord []byte) (hash160 []byte, p2sh bool, err error) { - const pkscriptLocOffset = 0 - const txOffset = 4 - - pkscriptLoc := int(binary.LittleEndian.Uint32(serializedSStxRecord[pkscriptLocOffset:])) + txOffset - - // Pop off the script prefix, then pop off the 20 bytes - // HASH160 pubkey or script hash. - prefixBytes := serializedSStxRecord[pkscriptLoc : pkscriptLoc+3] - scriptHash := make([]byte, 20) - p2sh = false - switch { - case bytes.Equal(prefixBytes, sstxTicket2PKHPrefix): - scrHashLoc := pkscriptLoc + 4 - if scrHashLoc+20 >= len(serializedSStxRecord) { - return nil, false, errors.E(errors.IO, "bad sstx record size") - } - copy(scriptHash, serializedSStxRecord[scrHashLoc:scrHashLoc+20]) - case bytes.Equal(prefixBytes, sstxTicket2SHPrefix): - scrHashLoc := pkscriptLoc + 3 - if scrHashLoc+20 >= len(serializedSStxRecord) { - return nil, false, errors.E(errors.IO, "bad sstx record size") - } - copy(scriptHash, serializedSStxRecord[scrHashLoc:scrHashLoc+20]) - p2sh = true - } - - return scriptHash, p2sh, nil -} - -// serializeSSTxRecord returns the serialization of the passed txrecord row. -func serializeSStxRecord(record *sstxRecord) ([]byte, error) { - tx := record.tx.MsgTx() - txSize := tx.SerializeSize() - - buf := make([]byte, 4+txSize+8) // pkscript location + tx + unix timestamp - pkScrLoc := tx.PkScriptLocs() - binary.LittleEndian.PutUint32(buf, uint32(pkScrLoc[0])) - err := tx.Serialize(bytes.NewBuffer(buf[4:4])) - if err != nil { - return nil, err - } - binary.LittleEndian.PutUint64(buf[4+txSize:], uint64(record.ts.Unix())) - return buf, nil - -} - -// stakeStoreExists returns whether or not the stake store has already -// been created in the given database namespace. -func stakeStoreExists(ns walletdb.ReadBucket) bool { - mainBucket := ns.NestedReadBucket(mainBucketName) - return mainBucket != nil -} - -// fetchSStxRecord retrieves a tx record from the sstx records bucket -// with the given hash. -func fetchSStxRecord(ns walletdb.ReadBucket, hash *chainhash.Hash, dbVersion uint32) (*sstxRecord, error) { - bucket := ns.NestedReadBucket(sstxRecordsBucketName) - - key := hash[:] - val := bucket.Get(key) - if val == nil { - return nil, errors.E(errors.NotExist, errors.Errorf("no ticket purchase %v", hash)) - } - - return deserializeSStxRecord(val, dbVersion) -} - -// fetchSStxRecordSStxTicketHash160 retrieves a ticket 0th output script or -// pubkeyhash from the sstx records bucket with the given hash. -func fetchSStxRecordSStxTicketHash160(ns walletdb.ReadBucket, hash *chainhash.Hash) (hash160 []byte, p2sh bool, err error) { - bucket := ns.NestedReadBucket(sstxRecordsBucketName) - - key := hash[:] - val := bucket.Get(key) - if val == nil { - return nil, false, errors.E(errors.NotExist, errors.Errorf("no ticket purchase %v", hash)) - } - - return deserializeSStxTicketHash160(val) -} - -// putSStxRecord inserts a given SStx record to the SStxrecords bucket. -func putSStxRecord(ns walletdb.ReadWriteBucket, record *sstxRecord) error { - bucket := ns.NestedReadWriteBucket(sstxRecordsBucketName) - - // Write the serialized txrecord keyed by the tx hash. - serializedSStxRecord, err := serializeSStxRecord(record) - if err != nil { - return errors.E(errors.IO, err) - } - err = bucket.Put(record.tx.Hash()[:], serializedSStxRecord) - if err != nil { - return errors.E(errors.IO, err) - } - return nil -} - -// initialize creates the DB if it doesn't exist, and otherwise -// loads the database. -func initializeEmpty(ns walletdb.ReadWriteBucket) error { - // Initialize the buckets and main db fields as needed. - mainBucket, err := ns.CreateBucketIfNotExists(mainBucketName) - if err != nil { - return errors.E(errors.IO, err) - } - - _, err = ns.CreateBucketIfNotExists(sstxRecordsBucketName) - if err != nil { - return errors.E(errors.IO, err) - } - - _, err = ns.CreateBucketIfNotExists(ssgenRecordsBucketName) - if err != nil { - return errors.E(errors.IO, err) - } - - _, err = ns.CreateBucketIfNotExists(ssrtxRecordsBucketName) - if err != nil { - return errors.E(errors.IO, err) - } - - _, err = ns.CreateBucketIfNotExists(metaBucketName) - if err != nil { - return errors.E(errors.IO, err) - } - - createBytes := mainBucket.Get(stakeStoreCreateDateName) - if createBytes == nil { - createDate := uint64(time.Now().Unix()) - var buf [8]byte - binary.LittleEndian.PutUint64(buf[:], createDate) - err := mainBucket.Put(stakeStoreCreateDateName, buf[:]) - if err != nil { - return errors.E(errors.IO, err) - } - } - - return nil -} diff --git a/wallet/udb/stakevalidation_test.go b/wallet/udb/stakevalidation_test.go index 4893083e5..3978ec52d 100644 --- a/wallet/udb/stakevalidation_test.go +++ b/wallet/udb/stakevalidation_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 The Decred developers +// Copyright (c) 2016-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -41,7 +41,7 @@ func insertMainChainHeaders(s *Store, dbtx walletdb.ReadWriteTx, func TestStakeInvalidationOfTip(t *testing.T) { ctx := context.Background() - db, _, s, _, teardown, err := cloneDB(ctx, "stake_inv_of_tip.kv") + db, _, s, teardown, err := cloneDB(ctx, "stake_inv_of_tip.kv") defer teardown() if err != nil { t.Fatal(err) diff --git a/wallet/udb/tx_test.go b/wallet/udb/tx_test.go index 993d8002e..b1a4c4ad0 100644 --- a/wallet/udb/tx_test.go +++ b/wallet/udb/tx_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2015 The btcsuite developers -// Copyright (c) 2019 The Decred developers +// Copyright (c) 2019-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -19,7 +19,7 @@ import ( func TestInsertsCreditsDebitsRollbacks(t *testing.T) { ctx := context.Background() - db, _, s, _, teardown, err := cloneDB(ctx, "inserts_credits_debits_rollbacks.kv") + db, _, s, teardown, err := cloneDB(ctx, "inserts_credits_debits_rollbacks.kv") defer teardown() if err != nil { t.Fatal(err) @@ -353,7 +353,7 @@ func spendOutput(txHash *chainhash.Hash, index uint32, tree int8, outputValues . func TestCoinbases(t *testing.T) { ctx := context.Background() - db, _, s, _, teardown, err := cloneDB(ctx, "coinbases.kv") + db, _, s, teardown, err := cloneDB(ctx, "coinbases.kv") defer teardown() if err != nil { t.Fatal(err) diff --git a/wallet/udb/txmined_test.go b/wallet/udb/txmined_test.go index b82f9fd56..3f645449c 100644 --- a/wallet/udb/txmined_test.go +++ b/wallet/udb/txmined_test.go @@ -31,7 +31,7 @@ func randomHash() chainhash.Hash { func TestSetBirthState(t *testing.T) { ctx := context.Background() - db, _, _, _, teardown, err := cloneDB(ctx, "mgr_watching_only.kv") + db, _, _, teardown, err := cloneDB(ctx, "mgr_watching_only.kv") defer teardown() if err != nil { t.Fatal(err) diff --git a/wallet/udb/upgrades.go b/wallet/udb/upgrades.go index e7e489b6a..b8aa65b42 100644 --- a/wallet/udb/upgrades.go +++ b/wallet/udb/upgrades.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021 The Decred developers +// Copyright (c) 2017-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -406,8 +406,6 @@ func votingPreferencesUpgrade(tx walletdb.ReadWriteTx, publicPassphrase []byte, const newVersion = 3 metadataBucket := tx.ReadWriteBucket(unifiedDBMetadata{}.rootBucketKey()) - stakemgrBucket := tx.ReadWriteBucket(wstakemgrBucketKey) - ticketPurchasesBucket := stakemgrBucket.NestedReadWriteBucket(sstxRecordsBucketName) // Assert that this function is only called on version 2 databases. dbVersion, err := unifiedDBMetadata{}.getVersion(metadataBucket) @@ -418,26 +416,11 @@ func votingPreferencesUpgrade(tx walletdb.ReadWriteTx, publicPassphrase []byte, return errors.E(errors.Invalid, "votingPreferencesUpgrade inappropriately called") } - // Update every ticket purchase with the new database version. This removes - // all per-ticket vote bits. - ticketPurchases := make(map[chainhash.Hash]*sstxRecord) - c := ticketPurchasesBucket.ReadCursor() - defer c.Close() - for k, _ := c.First(); k != nil; k, _ = c.Next() { - var hash chainhash.Hash - copy(hash[:], k) - ticketPurchase, err := fetchSStxRecord(stakemgrBucket, &hash, oldVersion) - if err != nil { - return err - } - ticketPurchases[hash] = ticketPurchase - } - for _, ticketPurchase := range ticketPurchases { - err := putSStxRecord(stakemgrBucket, ticketPurchase) - if err != nil { - return err - } - } + // This upgrade originally updated every ticket in the "wstakemgr" bucket, + // however that bucket became redundant due to the removal of legacy + // stakepool functionality. Therefore, that part of the upgrade has been + // removed, and the only thing remaining is the creation of a new + // "ticketsagendaprefs" bucket. // Create the top level bucket for agenda preferences. _, err = tx.CreateTopLevelBucket(agendaPreferences.defaultBucketKey()) diff --git a/wallet/udb/upgrades_test.go b/wallet/udb/upgrades_test.go index 4535a1e5c..6a24c443d 100644 --- a/wallet/udb/upgrades_test.go +++ b/wallet/udb/upgrades_test.go @@ -1,14 +1,12 @@ -// Copyright (c) 2017-2023 The Decred developers +// Copyright (c) 2017-2024 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package udb import ( - "bytes" "compress/gzip" "context" - "encoding/hex" "fmt" "io" "os" @@ -91,7 +89,7 @@ func TestUpgrades(t *testing.T) { } func verifyV2Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { - amgr, _, _, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) + amgr, _, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) if err != nil { t.Fatalf("Open after Upgrade failed: %v", err) } @@ -160,61 +158,17 @@ func verifyV2Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { } func verifyV3Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { - _, _, smgr, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) + _, _, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) if err != nil { t.Fatalf("Open after Upgrade failed: %v", err) } err = walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(wstakemgrBucketKey) - - const ( - ticketHashStr = "4516ef1d548f3284c1a27b3e706c4677392031df7071ad2022050af376837033" - votingAddrStr = "Tcu5oEdEp1W93fRT9FGSwMin7LonfRjNYe4" - ticketPurchaseHex = "01000000024bf0a303a7e6d174833d9eb761815b61f8ba8c6fa8852a6bf51c703daefc0ef60400000000ffffffff4bf0a303a7e6d174833d9eb761815b61f8ba8c6fa8852a6bf51c703daefc0ef60500000000ffffffff056f78d37a00000000000018baa914ec97b165a5f028b50fb12ae717c5f6c1b9057b5f8700000000000000000000206a1e7f686bc0e548bbb92f487db6da070e43a34117288ed59100000000000058000000000000000000001abd76a914000000000000000000000000000000000000000088ac00000000000000000000206a1e9d8e8bdc618035be32a14ab752af2e331f9abf3651074a7a000000000058000000000000000000001abd76a914000000000000000000000000000000000000000088ac00000000ad480000028ed59100000000009c480000010000006b483045022100c240bdd6a656c20e9035b839fc91faae6c766772f76149adb91a1fdcf20faf9c02203d68038b83263293f864b173c8f3f00e4371b67bf36fb9ec9f5132bdf68d2858012102adc226dec4de09a18c5a522f8f00917fb6d4eb2361a105218ac3f87d802ae3d451074a7a000000009c480000010000006a47304402205af53185f2662a30a22014b0d19760c1bfde8ec8f065b19cacab6a7abcec76a202204a2614cfcb4db3fc1c86eb0b1ca577f9039ec6db29e9c44ddcca2fe6e3c8bd5d012102adc226dec4de09a18c5a522f8f00917fb6d4eb2361a105218ac3f87d802ae3d4" - - // Stored timestamp uses time.Now(). The generated database test - // artifact uses this time (2017-04-10 11:50:04 -0400 EDT). If the - // db is ever regenerated, this expected value be updated as well. - timeStamp = 1491839404 - ) - - // Verify ticket purchase is still present with correct info, and no - // vote bits. - ticketPurchaseHash, err := chainhash.NewHashFromStr(ticketHashStr) - if err != nil { - return err - } - rec, err := fetchSStxRecord(ns, ticketPurchaseHash, 3) - if err != nil { - return err - } - if rec.voteBitsSet || rec.voteBits != 0 || rec.voteBitsExt != nil { - t.Errorf("Ticket purchase record still has vote bits") - } - votingAddr, err := smgr.SStxAddress(ns, ticketPurchaseHash) - if err != nil { - return err - } - if votingAddr.String() != votingAddrStr { - t.Errorf("Unexpected voting address, got %v want %v", - votingAddr.String(), votingAddrStr) - } - if rec.ts.Unix() != timeStamp { - t.Errorf("Unexpected timestamp, got %v want %v", rec.ts.Unix(), timeStamp) - } - var buf bytes.Buffer - err = rec.tx.MsgTx().Serialize(&buf) - if err != nil { - return err - } - expectedBytes, err := hex.DecodeString(ticketPurchaseHex) - if err != nil { - return err - } - if !bytes.Equal(buf.Bytes(), expectedBytes) { - t.Errorf("Serialized transaction does not match expected") - } + // This upgrade originally updated every ticket in the "wstakemgr" bucket, + // however that bucket became redundant due to the removal of legacy + // stakepool functionality. Therefore, that part of the upgrade has been + // removed, and the only thing remaining is the creation of a new + // "ticketsagendaprefs" bucket. // Verify that the agenda preferences bucket was created. if tx.ReadBucket(agendaPreferences.defaultBucketKey()) == nil { @@ -422,7 +376,7 @@ func verifyV8Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { // See the v11.db.go file for an explanation of the database layout and test // plan. func verifyV12Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { - _, txmgr, _, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) + _, txmgr, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) if err != nil { t.Fatalf("Open after Upgrade failed: %v", err) } diff --git a/wallet/udb/vsp.go b/wallet/udb/vsp.go index 44aec49dc..1f472efbc 100644 --- a/wallet/udb/vsp.go +++ b/wallet/udb/vsp.go @@ -16,6 +16,8 @@ var ( vspPubKeyBucketKey = []byte("vsppubkey") ) +const hashSize = 32 + // FeeStatus represents the current fee status of a ticket. type FeeStatus int diff --git a/wallet/wallet.go b/wallet/wallet.go index d3e76b4ea..210a6ce10 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -104,10 +104,9 @@ type Wallet struct { disapprovePercent atomic.Uint32 // Data stores - db walletdb.DB - manager *udb.Manager - txStore *udb.Store - stakeMgr *udb.StakeStore + db walletdb.DB + manager *udb.Manager + txStore *udb.Store // Handlers for stake system. stakeSettingsLock sync.Mutex @@ -423,7 +422,6 @@ func (w *Wallet) AgendaChoices(ctx context.Context, ticketHash *chainhash.Hash) err = walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { if ticketHash != nil { ownTicket = w.txStore.OwnTicket(tx, ticketHash) - ownTicket = ownTicket || w.stakeMgr.OwnTicket(ticketHash) if !ownTicket { return nil } @@ -480,7 +478,7 @@ func (w *Wallet) SetAgendaChoices(ctx context.Context, ticketHash *chainhash.Has // validate ticket ownership var ownTicket bool err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { - ownTicket = w.txStore.OwnTicket(dbtx, ticketHash) || w.stakeMgr.OwnTicket(ticketHash) + ownTicket = w.txStore.OwnTicket(dbtx, ticketHash) return nil }) if err != nil { @@ -5428,7 +5426,7 @@ func Open(ctx context.Context, cfg *Config) (*Wallet, error) { } // Open database managers - w.manager, w.txStore, w.stakeMgr, err = udb.Open(ctx, db, params, cfg.PubPassphrase) + w.manager, w.txStore, err = udb.Open(ctx, db, params, cfg.PubPassphrase) if err != nil { return nil, errors.E(op, err) }