Beispiel #1
0
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()
}
Beispiel #2
0
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)
		}
	}
}
Beispiel #3
0
// 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()
}
Beispiel #4
0
// 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()
}
Beispiel #5
0
// 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
}
Beispiel #6
0
// 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
}
Beispiel #7
0
// 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
	})
}
Beispiel #8
0
// 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()
}
Beispiel #9
0
// 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
	})
}
Beispiel #10
0
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
}
Beispiel #11
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)
	})
}
Beispiel #12
0
// 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
	})
}
Beispiel #13
0
// 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
}
Beispiel #14
0
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)
	}
}
Beispiel #17
0
// 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
	}
}