func (w *Wallet) handleChainVotingNotifications() { chainClient, err := w.requireChainClient() if err != nil { log.Error(err) w.wg.Done() return } for n := range chainClient.NotificationsVoting() { var err error strErrType := "" switch n := n.(type) { case chain.WinningTickets: err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { return w.handleWinningTickets(dbtx, n.BlockHash, n.BlockHeight, n.Tickets) }) strErrType = "WinningTickets" case chain.MissedTickets: err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { return w.handleMissedTickets(dbtx, n.BlockHash, n.BlockHeight, n.Tickets) }) strErrType = "MissedTickets" default: err = fmt.Errorf("voting handler received unknown ntfn type") } if err != nil { log.Errorf("Cannot handle chain server voting "+ "notification %v: %v", strErrType, err) } } w.wg.Done() }
func (w *Wallet) handleConsensusRPCNotifications(chainClient *chain.RPCClient) { for n := range chainClient.Notifications() { var notificationName string var err error switch n := n.(type) { case chain.ClientConnected: log.Infof("The client has successfully connected to dcrd and " + "is now handling websocket notifications") case chain.BlockConnected: notificationName = "blockconnected" err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { return w.onBlockConnected(tx, n.BlockHeader, n.Transactions) }) case chain.Reorganization: notificationName = "reorganizing" err = w.handleReorganizing(n.OldHash, n.NewHash, n.OldHeight, n.NewHeight) case chain.RelevantTxAccepted: notificationName = "relevanttxaccepted" err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { return w.processTransaction(dbtx, n.Transaction, nil, nil) }) } if err != nil { log.Errorf("Failed to process consensus server notification "+ "(name: `%s`, detail: `%v`)", notificationName, err) } } }
// rescanProgressHandler handles notifications for partially and fully completed // rescans by marking each rescanned address as partially or fully synced. func (w *Wallet) rescanProgressHandler() { quit := w.quitChan() out: for { // These can't be processed out of order since both chans are // unbuffured and are sent from same context (the batch // handler). select { case msg := <-w.rescanProgress: n := msg.Notification log.Infof("Rescanned through block %v (height %d)", n.Hash, n.Height) bs := waddrmgr.BlockStamp{ Hash: *n.Hash, Height: n.Height, } err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) return w.Manager.SetSyncedTo(ns, &bs) }) if err != nil { log.Errorf("Failed to update address manager "+ "sync state for hash %v (height %d): %v", n.Hash, n.Height, err) } case msg := <-w.rescanFinished: n := msg.Notification addrs := msg.Addresses noun := pickNoun(len(addrs), "address", "addresses") log.Infof("Finished rescan for %d %s (synced to block "+ "%s, height %d)", len(addrs), noun, n.Hash, n.Height) bs := waddrmgr.BlockStamp{ Height: n.Height, Hash: *n.Hash, } err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) return w.Manager.SetSyncedTo(ns, &bs) }) if err != nil { log.Errorf("Failed to update address manager "+ "sync state for hash %v (height %d): %v", n.Hash, n.Height, err) continue } w.SetChainSynced(true) go w.resendUnminedTxs() case <-quit: break out } } w.wg.Done() }
// handleChainNotifications is the major chain notification handler that // receives websocket notifications about the blockchain. func (w *Wallet) handleChainNotifications() { chainClient, err := w.requireChainClient() if err != nil { log.Errorf("handleChainNotifications called without RPC client") w.wg.Done() return } // At the moment there is no recourse if the rescan fails for // some reason, however, the wallet will not be marked synced // and many methods will error early since the wallet is known // to be out of date. err = w.syncWithChain() if err != nil && !w.ShuttingDown() { log.Warnf("Unable to synchronize wallet to chain: %v", err) } for n := range chainClient.Notifications() { var err error strErrType := "" switch n := n.(type) { case chain.ClientConnected: log.Infof("The client has successfully connected to dcrd and " + "is now handling websocket notifications") case chain.BlockConnected: err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { return w.connectBlock(tx, wtxmgr.BlockMeta(n)) }) strErrType = "BlockConnected" case chain.BlockDisconnected: err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { return w.disconnectBlock(tx, wtxmgr.BlockMeta(n)) }) strErrType = "BlockDisconnected" case chain.Reorganization: w.handleReorganizing(n.OldHash, n.OldHeight, n.NewHash, n.NewHeight) case chain.RelevantTx: err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { return w.addRelevantTx(tx, n.TxRecord, n.Block) }) strErrType = "RelevantTx" // The following are handled by the wallet's rescan // goroutines, so just pass them there. case *chain.RescanProgress, *chain.RescanFinished: w.rescanNotifications <- n } if err != nil { log.Errorf("Cannot handle chain server "+ "notification %v: %v", strErrType, err) } } w.wg.Done() }
// ImportP2SHRedeemScript adds a P2SH redeem script to the wallet. func (w *Wallet) ImportP2SHRedeemScript(script []byte) (*dcrutil.AddressScriptHash, error) { var p2shAddr *dcrutil.AddressScriptHash err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey) err := w.TxStore.InsertTxScript(txmgrNs, script) if err != nil { return err } addrInfo, err := w.Manager.ImportScript(addrmgrNs, script) if err != nil { // Don't care if it's already there, but still have to // set the p2shAddr since the address manager didn't // return anything useful. if waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress) { // This function will never error as it always // hashes the script to the correct length. p2shAddr, _ = dcrutil.NewAddressScriptHash(script, w.chainParams) return nil } return err } p2shAddr = addrInfo.Address().(*dcrutil.AddressScriptHash) return nil }) return p2shAddr, err }
// NewAddress checks the address pools and then attempts to return a new // address for the account and branch requested. func (w *Wallet) NewAddress(account uint32, branch uint32) (dcrutil.Address, error) { var address dcrutil.Address err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { waddrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) err := w.CheckAddressPoolsInitialized(account) if err != nil { return err } var addrPool *addressPool switch branch { case waddrmgr.ExternalBranch: addrPool = w.getAddressPools(account).external case waddrmgr.InternalBranch: addrPool = w.getAddressPools(account).internal default: return fmt.Errorf("new address failed; unknown branch number %v", branch) } address, err = addrPool.GetNewAddress(waddrmgrNs) return err }) return address, err }
// SetVoteBitsForTickets sets vote bits for many given tickets. These vote bits // override the wallet's default vote bits. func (w *Wallet) SetVoteBitsForTickets(tickets []chainhash.Hash, voteBitsSlice []stake.VoteBits) error { if len(tickets) != len(voteBitsSlice) { return fmt.Errorf("number of tickets (%v) and number of vote bits "+ "(%v) not equal", len(tickets), len(voteBitsSlice)) } for i := range voteBitsSlice { // Sanity check for the extended voteBits length. if len(voteBitsSlice[i].ExtendedBits) > stake.MaxSingleBytePushLength-2 { return fmt.Errorf("bad extended votebits length (got %v, max %v)", len(voteBitsSlice[i].ExtendedBits), stake.MaxSingleBytePushLength-2) } } return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { stakemgrNs := tx.ReadWriteBucket(wstakemgrNamespaceKey) var err error for i := range tickets { err = w.StakeMgr.UpdateSStxVoteBits(stakemgrNs, &tickets[i], voteBitsSlice[i]) if err != nil { return err } } return nil }) }
// handleChainNotifications is the major chain notification handler that // receives websocket notifications about the blockchain. func (w *Wallet) handleChainNotifications() { chainClient, err := w.requireChainClient() if err != nil { log.Errorf("handleChainNotifications called without RPC client") w.wg.Done() return } // At the moment there is no recourse if the rescan fails for // some reason, however, the wallet will not be marked synced // and many methods will error early since the wallet is known // to be out of date. err = w.syncWithChain() if err != nil && !w.ShuttingDown() { log.Warnf("Unable to synchronize wallet to chain: %v", err) } for n := range chainClient.Notifications() { var notificationName string var err error switch n := n.(type) { case chain.ClientConnected: log.Infof("The client has successfully connected to dcrd and " + "is now handling websocket notifications") case chain.BlockConnected: notificationName = "blockconnected" err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { return w.onBlockConnected(tx, n.BlockHeader, n.Transactions) }) case chain.Reorganization: notificationName = "reorganizing" err = w.handleReorganizing(n.OldHash, n.NewHash, n.OldHeight, n.NewHeight) case chain.RelevantTxAccepted: notificationName = "relevanttxaccepted" err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { return w.processTransaction(dbtx, n.Transaction, nil, nil) }) } if err != nil { log.Errorf("Failed to process consensus server notification "+ "(name: `%s`, detail: `%v`)", notificationName, err) } } w.wg.Done() }
// SyncAddressPoolIndex synchronizes an account's branch to the given address // by iteratively calling getNewAddress on the respective address pool. func (w *Wallet) SyncAddressPoolIndex(account uint32, branch uint32, index uint32) error { // Sanity checks. err := w.CheckAddressPoolsInitialized(account) if err != nil { return err } return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { waddrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) defer func() { errNotify := w.notifyAccountAddrIdxs(waddrmgrNs, account) if errNotify != nil { log.Errorf("Failed to push account update notification "+ "for account %v", account) } }() var addrPool *addressPool switch branch { case waddrmgr.ExternalBranch: addrPool = w.getAddressPools(account).external addrPool.mutex.Lock() defer addrPool.mutex.Unlock() case waddrmgr.InternalBranch: addrPool = w.getAddressPools(account).internal addrPool.mutex.Lock() defer addrPool.mutex.Unlock() default: return fmt.Errorf("unknown branch number %v", branch) } if index < addrPool.index { return fmt.Errorf("the passed index, %v, is before the "+ "currently synced to address index %v", index, addrPool.index) } if index == addrPool.index { return nil } // Synchronize our address pool by calling getNewAddress // iteratively until the next to use index is synced to // where we need it. toFetch := index - addrPool.index for i := uint32(0); i < toFetch; i++ { _, err := addrPool.getNewAddress(waddrmgrNs) if err != nil { addrPool.BatchRollback() return err } } addrPool.BatchFinish(waddrmgrNs) return nil }) }
func mainInt() int { fmt.Println("Database path:", opts.DbPath) _, err := os.Stat(opts.DbPath) if os.IsNotExist(err) { fmt.Println("Database file does not exist") return 1 } for !opts.Force { fmt.Print("Drop all dcrwallet transaction history? [y/N] ") scanner := bufio.NewScanner(bufio.NewReader(os.Stdin)) if !scanner.Scan() { // Exit on EOF. return 0 } err := scanner.Err() if err != nil { fmt.Println() fmt.Println(err) return 1 } resp := scanner.Text() if yes(resp) { break } if no(resp) || resp == "" { return 0 } fmt.Println("Enter yes or no.") } db, err := walletdb.Open("bdb", opts.DbPath) if err != nil { fmt.Println("Failed to open database:", err) return 1 } defer db.Close() fmt.Println("Dropping wtxmgr namespace") err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { return tx.DeleteTopLevelBucket(wtxmgrNamespace) }) if err != nil && err != walletdb.ErrBucketNotFound { fmt.Println("Failed to drop namespace:", err) return 1 } return 0 }
// SetVoteBitsForTicket sets the per-ticket vote bits. These vote bits override // the wallet's default vote bits. func (w *Wallet) SetVoteBitsForTicket(ticket *chainhash.Hash, voteBits stake.VoteBits) error { // Sanity check for the extended voteBits length. if len(voteBits.ExtendedBits) > stake.MaxSingleBytePushLength-2 { return fmt.Errorf("bad extended votebits length (got %v, max %v)", len(voteBits.ExtendedBits), stake.MaxSingleBytePushLength-2) } return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { stakemgrNs := tx.ReadWriteBucket(wstakemgrNamespaceKey) return w.StakeMgr.UpdateSStxVoteBits(stakemgrNs, ticket, voteBits) }) }
// AddTicket adds a ticket transaction to the wallet. func (w *Wallet) AddTicket(ticket *dcrutil.Tx) error { return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { stakemgrNs := tx.ReadWriteBucket(wstakemgrNamespaceKey) // Insert the ticket to be tracked and voted. err := w.StakeMgr.InsertSStx(stakemgrNs, ticket, w.VoteBits) if err != nil { return err } if w.stakePoolEnabled { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) // Pluck the ticketaddress to indentify the stakepool user. pkVersion := ticket.MsgTx().TxOut[0].Version pkScript := ticket.MsgTx().TxOut[0].PkScript _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkVersion, pkScript, w.ChainParams()) if err != nil { return err } ticketHash := ticket.MsgTx().TxSha() chainClient, err := w.requireChainClient() if err != nil { return err } rawTx, err := chainClient.GetRawTransactionVerbose(&ticketHash) if err != nil { return err } // Update the pool ticket stake. This will include removing it from the // invalid slice and adding a ImmatureOrLive ticket to the valid ones. err = w.updateStakePoolInvalidTicket(stakemgrNs, addrmgrNs, addrs[0], &ticketHash, rawTx.BlockHeight) if err != nil { return err } } return nil }) }
// DiscoverActiveAddresses accesses the consensus RPC server to discover all the // addresses that have been used by an HD keychain stemming from this wallet. If // discoverAccts is true, used accounts will be discovered as well. This // feature requires the wallet to be unlocked in order to derive hardened // account extended pubkeys. // // A transaction filter (re)load and rescan should be performed after discovery. // // BUG(jrick): This function reassigns address pools, and if called multiple // times it would not be unlikely to see address reuse due to losing the address // pool's derivation index. I am punting on this for now. In the future, // address pools should be removed and all derivation should be done solely by // waddrmgr. Use with caution. func (w *Wallet) DiscoverActiveAddresses(chainClient *chain.RPCClient, discoverAccts bool) error { log.Infof("Beginning a rescan of active addresses using the daemon. " + "This may take a while.") // Search external branch then internal branch for a used address. We need // to set the address function to use based on whether or not this is the // initial sync. The function AddressDerivedFromCointype is able to see // addresses that exists in accounts that have not yet been created, while // AddressDerivedFromDbAcct can not. derive := w.Manager.AddressDerivedFromDbAcct if discoverAccts { derive = w.Manager.AddressDerivedFromCointype } ctx := &discoveryContext{chainClient: chainClient, deriveAddr: derive} // Start by rescanning the accounts and determining what the // current account index is. This scan should only ever be // performed if we're restoring our wallet from seed. var lastAcct uint32 if w.initiallyUnlocked { var err error lastAcct, err = w.scanAccountIndex(ctx, 0, waddrmgr.MaxAccountNum) if err != nil { return err } } err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) lastAcctMgr, err := w.Manager.LastAccount(addrmgrNs) if err != nil { return err } // The address manager is not synced (wallet has been restored // from seed?). In this case, spawn the accounts in the address // manager first. The accounts are named by their respective // index number, as strings. if lastAcctMgr < lastAcct { for i := lastAcctMgr + 1; i <= lastAcct; i++ { _, err := w.Manager.NewAccount( addrmgrNs, strconv.Itoa(int(i))) if err != nil { return err } } } // The account manager has a greater index than the rescan. // It is likely that the end user created a new account but // did not use it yet. Rescan it anyway so that the address // pool is created. if lastAcctMgr > lastAcct { lastAcct = lastAcctMgr } return nil }) if err != nil { return err } log.Infof("The last used account was %v. Beginning a rescan for "+ "all active addresses in known accounts.", lastAcct) // Rescan addresses for the both the internal and external // branches of the account. Insert a new address pool for // the respective account and initialize it. for acct := uint32(0); acct <= lastAcct; acct++ { var extIdx, intIdx uint32 // Do this for both external (0) and internal (1) branches. for branch := uint32(0); branch < 2; branch++ { idx, lastAddr, err := w.scanAddressIndex(ctx, 0, waddrmgr.MaxAddressesPerAccount, acct, branch) if err != nil { return err } err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) // If the account is unused, buffer the initial address pool // by syncing the address manager upstream. unusedAcct := (lastAddr == nil) if unusedAcct { _, err := w.Manager.SyncAccountToAddrIndex( addrmgrNs, acct, addressPoolBuffer, branch) if err != nil { // A ErrSyncToIndex error indicates that we're already // synced to beyond the end of the account in the // waddrmgr. errWaddrmgr, ok := err.(waddrmgr.ManagerError) if !ok || errWaddrmgr.ErrorCode != waddrmgr.ErrSyncToIndex { return fmt.Errorf("failed to create initial waddrmgr "+ "address buffer for the address pool, "+ "account %v, branch %v: %v", acct, branch, err) } } } branchString := "external" if branch == waddrmgr.InternalBranch { branchString = "internal" } // Fetch the address pool index for this account and // branch from the database meta bucket. isInternal := branch == waddrmgr.InternalBranch oldIdx, err := w.Manager.NextToUseAddrPoolIndex( addrmgrNs, isInternal, acct) unexpectedError := false if err != nil { mErr, ok := err.(waddrmgr.ManagerError) if !ok { unexpectedError = true } else { // Skip errors where the account's address index // has not been store. For this case, oldIdx will // be the special case 0 which will always be // skipped in the initialization step below. if mErr.ErrorCode != waddrmgr.ErrMetaPoolIdxNoExist { unexpectedError = true } } if unexpectedError { return fmt.Errorf("got unexpected error trying to "+ "retrieve last known addr index for acct %v, "+ "%s branch: %v", acct, branchString, err) } } // If the stored index is further along than the sync-to // index determined by the contents of daemon's addrindex, // use it to initialize the address pool instead. nextToUseIdx := idx if !unusedAcct { nextToUseIdx++ } if oldIdx > nextToUseIdx { nextToUseIdx = oldIdx } nextToUseAddr, err := w.Manager.AddressDerivedFromDbAcct( addrmgrNs, nextToUseIdx, acct, branch) if err != nil { return fmt.Errorf("failed to derive next address for "+ "account %v, branch %v: %v", acct, branch, err) } // Save these for the address pool startup later. if isInternal { intIdx = nextToUseIdx } else { extIdx = nextToUseIdx } // Synchronize the account manager to our address index plus // an extra chunk of addresses that are used as a buffer // in the address pool. _, err = w.Manager.SyncAccountToAddrIndex(addrmgrNs, acct, nextToUseIdx+addressPoolBuffer, branch) if err != nil { // A ErrSyncToIndex error indicates that we're already // synced to beyond the end of the account in the // waddrmgr. errWaddrmgr, ok := err.(waddrmgr.ManagerError) if !ok || errWaddrmgr.ErrorCode != waddrmgr.ErrSyncToIndex { return fmt.Errorf("couldn't sync %s addresses in "+ "address manager: %v", branchString, err) } } // Set the next address in the waddrmgr database so that the // address pool can synchronize properly after. err = w.Manager.StoreNextToUseAddress( addrmgrNs, isInternal, acct, nextToUseIdx) if err != nil { log.Errorf("Failed to store next to use pool idx for "+ "%s pool in the manager on init sync: %v", branchString, err.Error()) } log.Infof("Successfully synchronized the address manager to "+ "%s address %v (key index %v) for account %v", branchString, nextToUseAddr.String(), nextToUseIdx, acct) return nil }) if err != nil { return err } } pool, err := newAddressPools(acct, intIdx, extIdx, w) if err != nil { return err } w.addrPoolsMtx.Lock() w.addrPools[acct] = pool w.addrPoolsMtx.Unlock() } log.Infof("Successfully synchronized wallet accounts to account "+ "number %v.", lastAcct) return nil }
func TestCursorDeletions(t *testing.T) { t.Skip("Cursor deletion APIs have been removed until bolt issue #620 is resolved") db, _, teardown, err := setup() defer teardown() if err != nil { t.Fatal(err) } err = walletdb.Update(db, func(dbtx walletdb.ReadWriteTx) error { txHash := decodeHash("2b29213f06354455c235021e541604607ed738b1bc6217f2fe3ae5bffbfe1218") ks := [][]byte{ canonicalOutPoint(txHash, 0), canonicalOutPoint(txHash, 1), } vs := [][]byte{ valueUnminedCredit(1e8, false, 0, false, scriptTypeP2PKH, 0, 0, 0), valueUnminedCredit(2e8, true, 0, false, scriptTypeP2PKH, 0, 0, 0), } ns := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) for i := range ks { err = putRawUnminedCredit(ns, ks[i], vs[i]) if err != nil { return err } } iterations := 0 // Test the iterator iterates over each. it := makeUnminedCreditIterator(ns, txHash) for it.next() { iterations++ } if iterations != 2 { t.Errorf("Expected to iterate over two k/v pairs, but iterated %v time(s)", iterations) } iterations = 0 // Test the iterator can be used to delete each. it = makeUnminedCreditIterator(ns, txHash) for it.next() { //err = it.delete() if err != nil { return err } iterations++ } if iterations != 2 { t.Errorf("Expected to iterate over and delete two k/v pairs, but iterated %v time(s)", iterations) } it = makeUnminedCreditIterator(ns, txHash) for it.next() { t.Error("Did not delete every k/v pair from bucket using iterator") break } return nil }) if err != nil { t.Error(err) } }
func TestStakeInvalidationOfTip(t *testing.T) { db, s, teardown, err := setup() defer teardown() if err != nil { t.Fatal(err) } g := makeBlockGenerator() block1Header := g.generate(dcrutil.BlockValid) block2Header := g.generate(dcrutil.BlockValid) block3Header := g.generate(0) block1Tx := wire.MsgTx{ TxOut: []*wire.TxOut{{Value: 2e8}}, } block2Tx := wire.MsgTx{ TxIn: []*wire.TxIn{ {PreviousOutPoint: wire.OutPoint{Hash: block1Tx.TxSha(), Index: 0, Tree: 0}}, }, TxOut: []*wire.TxOut{{Value: 1e8}}, } block1TxRec, err := NewTxRecordFromMsgTx(&block1Tx, time.Time{}) if err != nil { t.Fatal(err) } block2TxRec, err := NewTxRecordFromMsgTx(&block2Tx, time.Time{}) if err != nil { t.Fatal(err) } const balanceFlag = BFBalanceSpendable err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(wtxmgrNamespaceKey) addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) err := s.InsertMemPoolTx(ns, block1TxRec) if err != nil { return err } err = s.AddCredit(ns, block1TxRec, nil, 0, false, 0) if err != nil { return err } err = s.InsertMemPoolTx(ns, block2TxRec) if err != nil { return err } err = s.AddCredit(ns, block2TxRec, nil, 0, false, 0) if err != nil { return err } bal, err := s.Balance(ns, addrmgrNs, 0, balanceFlag, false, 0) if err != nil { return err } if bal != 1e8 { t.Errorf("Wrong balance before mining either transaction: %v", bal) } headerData := makeHeaderDataSlice(block1Header, block2Header) err = s.InsertMainChainHeaders(ns, addrmgrNs, headerData) if err != nil { return err } err = s.InsertMinedTx(ns, addrmgrNs, block1TxRec, &headerData[0].BlockHash) if err != nil { return err } err = s.InsertMinedTx(ns, addrmgrNs, block2TxRec, &headerData[1].BlockHash) if err != nil { return err } // At this point there should only be one credit for the tx in block 2. bal, err = s.Balance(ns, addrmgrNs, 1, balanceFlag, false, 0) if err != nil { return err } if bal != dcrutil.Amount(block2Tx.TxOut[0].Value) { t.Errorf("Wrong balance: expected %v got %v", dcrutil.Amount(block2Tx.TxOut[0].Value), bal) } credits, err := s.UnspentOutputs(ns) if err != nil { return err } if len(credits) != 1 { t.Errorf("Expected only 1 credit, got %v", len(credits)) return nil } if credits[0].Hash != block2Tx.TxSha() { t.Errorf("Credit hash does match tx from block 2") return nil } if credits[0].Amount != dcrutil.Amount(block2Tx.TxOut[0].Value) { t.Errorf("Credit value does not match tx output 0 from block 2") return nil } // Add the next block header which invalidates the regular tx tree of // block 2. t.Log("Invalidating block 2") headerData = makeHeaderDataSlice(block3Header) err = s.InsertMainChainHeaders(ns, addrmgrNs, headerData) if err != nil { return err } /* d := makeHeaderData(block3Header) err = s.ExtendMainChain(ns, &d) if err != nil { return err } */ // Now the transaction in block 2 is invalidated. There should only be // one unspent output, from block 1. bal, err = s.Balance(ns, addrmgrNs, 1, balanceFlag, false, 0) if err != nil { return err } if bal != dcrutil.Amount(block1Tx.TxOut[0].Value) { t.Errorf("Wrong balance: expected %v got %v", dcrutil.Amount(block1Tx.TxOut[0].Value), bal) } credits, err = s.UnspentOutputs(ns) if err != nil { return err } if len(credits) != 1 { t.Errorf("Expected only 1 credit, got %v", len(credits)) return nil } if credits[0].Hash != block1Tx.TxSha() { t.Errorf("Credit hash does not match tx from block 1") return nil } if credits[0].Amount != dcrutil.Amount(block1Tx.TxOut[0].Value) { t.Errorf("Credit value does not match tx output 0 from block 1") return nil } return nil }) if err != nil { t.Error(err) } }
func TestStakeInvalidationTxInsert(t *testing.T) { db, s, teardown, err := setup() defer teardown() if err != nil { t.Fatal(err) } g := makeBlockGenerator() block1Header := g.generate(dcrutil.BlockValid) block2Header := g.generate(dcrutil.BlockValid) block3Header := g.generate(0) block1Tx := wire.MsgTx{ TxOut: []*wire.TxOut{{Value: 2e8}}, } block2Tx := wire.MsgTx{ TxIn: []*wire.TxIn{ {PreviousOutPoint: wire.OutPoint{Hash: block1Tx.TxSha(), Index: 0, Tree: 0}}, }, TxOut: []*wire.TxOut{{Value: 1e8}}, } block1TxRec, err := NewTxRecordFromMsgTx(&block1Tx, time.Time{}) if err != nil { t.Fatal(err) } block2TxRec, err := NewTxRecordFromMsgTx(&block2Tx, time.Time{}) if err != nil { t.Fatal(err) } err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(wtxmgrNamespaceKey) addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) headerData := makeHeaderDataSlice(block1Header, block2Header, block3Header) err = s.InsertMainChainHeaders(ns, addrmgrNs, headerData) if err != nil { return err } err = s.InsertMinedTx(ns, addrmgrNs, block1TxRec, &headerData[0].BlockHash) if err != nil { return err } err = s.AddCredit(ns, block1TxRec, makeBlockMeta(block1Header), 0, false, 0) if err != nil { return err } err = s.InsertMinedTx(ns, addrmgrNs, block2TxRec, &headerData[1].BlockHash) if err != nil { return err } err = s.AddCredit(ns, block2TxRec, makeBlockMeta(block2Header), 0, false, 0) if err != nil { return err } // The transaction in block 2 was inserted invalidated. There should // only be one unspent output, from block 1. bal, err := s.Balance(ns, addrmgrNs, 1, BFBalanceFullScan, true, 0) if err != nil { return err } if bal != dcrutil.Amount(block1Tx.TxOut[0].Value) { t.Errorf("Wrong balance: expected %v got %v", dcrutil.Amount(block1Tx.TxOut[0].Value), bal) } bal, err = s.Balance(ns, addrmgrNs, 1, BFBalanceSpendable, true, 0) if err != nil { return err } if bal != dcrutil.Amount(block1Tx.TxOut[0].Value) { t.Errorf("Wrong balance: expected %v got %v", dcrutil.Amount(block1Tx.TxOut[0].Value), bal) } credits, err := s.UnspentOutputs(ns) if err != nil { return err } if len(credits) != 1 { t.Errorf("Expected only 1 credit, got %v", len(credits)) return nil } if credits[0].Hash != block1Tx.TxSha() { t.Errorf("Credit hash does not match tx from block 1") return nil } if credits[0].Amount != dcrutil.Amount(block1Tx.TxOut[0].Value) { t.Errorf("Credit value does not match tx output 0 from block 1") return nil } return nil }) if err != nil { t.Error(err) } }
// rescan synchronously scans over all blocks on the main chain starting at // startHash and height up through the recorded main chain tip block. The // progress channel, if non-nil, is sent non-error progress notifications with // the heights the rescan has completed through, starting with the start height. func (w *Wallet) rescan(chainClient *chain.RPCClient, startHash *chainhash.Hash, height int32, p chan<- RescanProgress, cancel <-chan struct{}) error { blockHashStorage := make([]chainhash.Hash, maxBlocksPerRescan) rescanFrom := *startHash inclusive := true for { select { case <-cancel: return nil default: } var rescanBlocks []chainhash.Hash err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) var err error rescanBlocks, err = w.TxStore.GetMainChainBlockHashes(txmgrNs, &rescanFrom, inclusive, blockHashStorage) return err }) if err != nil { return err } if len(rescanBlocks) == 0 { return nil } scanningThrough := height + int32(len(rescanBlocks)) - 1 log.Infof("Rescanning blocks %v-%v...", height, scanningThrough) rescanResults, err := chainClient.Rescan(rescanBlocks) if err != nil { return err } var rawBlockHeader wtxmgr.RawBlockHeader err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) for _, r := range rescanResults.DiscoveredData { blockHash, err := chainhash.NewHashFromStr(r.Hash) if err != nil { return err } blockMeta, err := w.TxStore.GetBlockMetaForHash(txmgrNs, blockHash) if err != nil { return err } serHeader, err := w.TxStore.GetSerializedBlockHeader(txmgrNs, blockHash) if err != nil { return err } err = copyHeaderSliceToArray(&rawBlockHeader, serHeader) if err != nil { return err } for _, hexTx := range r.Transactions { serTx, err := hex.DecodeString(hexTx) if err != nil { return err } err = w.processTransaction(dbtx, serTx, &rawBlockHeader, &blockMeta) if err != nil { return err } } } return nil }) if err != nil { return err } if p != nil { p <- RescanProgress{ScannedThrough: scanningThrough} } rescanFrom = rescanBlocks[len(rescanBlocks)-1] height += int32(len(rescanBlocks)) inclusive = false } }