예제 #1
0
파일: sync.go 프로젝트: jrick/btcwallet
// accountIsUsed checks if an account has ever been used by scanning the
// first acctSeekWidth many addresses for usage.
func (w *Wallet) accountIsUsed(ctx *discoveryContext, account uint32) (bool, error) {
	for branch := uint32(0); branch < 2; branch++ {
		for i := uint32(0); i < acctSeekWidth; i++ {
			var addr dcrutil.Address
			err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
				addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
				var err error
				addr, err = ctx.deriveAddr(addrmgrNs, i, account, branch)
				return err
			})
			// Skip erroneous keys, which happen rarely.
			if e, ok := err.(waddrmgr.ManagerError); ok && e.Err == hdkeychain.ErrInvalidChild {
				continue
			}
			if err != nil {
				return false, err
			}

			exists, err := ctx.chainClient.ExistsAddress(addr)
			if err != nil {
				return false, err
			}
			if exists {
				return true, nil
			}
		}
	}

	return false, nil
}
예제 #2
0
파일: rescan.go 프로젝트: decred/dcrwallet
// RescanFromHeight is an alternative to Rescan that takes a block height
// instead of a hash.  See Rescan for more details.
func (w *Wallet) RescanFromHeight(chainClient *chain.RPCClient, startHeight int32) <-chan error {
	errc := make(chan error)

	go func() (err error) {
		defer func() {
			select {
			case errc <- err:
			default:
				if err != nil {
					log.Errorf("Rescan failed: %v", err)
				}
				close(errc)
			}
		}()

		var startHash chainhash.Hash
		err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
			txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
			var err error
			startHash, err = w.TxStore.GetMainChainBlockHashForHeight(
				txmgrNs, startHeight)
			return err
		})
		if err != nil {
			return err
		}

		return w.rescan(chainClient, &startHash, startHeight, nil, nil)
	}()

	return errc
}
예제 #3
0
파일: rescan.go 프로젝트: decred/dcrwallet
// Rescan starts a rescan of the wallet for all blocks on the main chain
// beginning at startHash.
//
// An error channel is returned for consumers of this API, but it is not
// required to be read.  If the error can not be immediately written to the
// returned channel, the error will be logged and the channel will be closed.
func (w *Wallet) Rescan(chainClient *chain.RPCClient, startHash *chainhash.Hash) <-chan error {
	errc := make(chan error)

	go func() (err error) {
		defer func() {
			select {
			case errc <- err:
			default:
				if err != nil {
					log.Errorf("Rescan failed: %v", err)
				}
				close(errc)
			}
		}()

		var startHeight int32
		err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
			txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
			header, err := w.TxStore.GetSerializedBlockHeader(txmgrNs, startHash)
			if err != nil {
				return err
			}
			startHeight = wtxmgr.ExtractBlockHeaderHeight(header)
			return nil
		})
		if err != nil {
			return err
		}

		return w.rescan(chainClient, startHash, startHeight, nil, nil)
	}()

	return errc
}
예제 #4
0
// TicketHashesForVotingAddress returns the hashes of all tickets with voting
// rights delegated to votingAddr.  This function does not return the hashes of
// pruned tickets.
func (w *Wallet) TicketHashesForVotingAddress(votingAddr dcrutil.Address) ([]chainhash.Hash, error) {
	var ticketHashes []chainhash.Hash
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		stakemgrNs := tx.ReadBucket(wstakemgrNamespaceKey)
		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)

		var err error
		ticketHashes, err = w.StakeMgr.DumpSStxHashesForAddress(
			stakemgrNs, votingAddr)
		if err != nil {
			return err
		}

		// Exclude the hash if the transaction is not saved too.  No
		// promises of hash order are given (and at time of writing,
		// they are copies of iterators of a Go map in wstakemgr) so
		// when one must be removed, replace it with the last and
		// decrease the len.
		for i := 0; i < len(ticketHashes); {
			if w.TxStore.ExistsTx(txmgrNs, &ticketHashes[i]) {
				i++
				continue
			}

			ticketHashes[i] = ticketHashes[len(ticketHashes)-1]
			ticketHashes = ticketHashes[:len(ticketHashes)-1]
		}

		return nil
	})
	return ticketHashes, err
}
예제 #5
0
// FetchP2SHMultiSigOutput fetches information regarding a wallet's P2SH
// multi-signature output.
func (w *Wallet) FetchP2SHMultiSigOutput(outPoint *wire.OutPoint) (*P2SHMultiSigOutput, error) {
	var (
		mso          *wtxmgr.MultisigOut
		redeemScript []byte
	)
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
		var err error

		mso, err = w.TxStore.GetMultisigOutput(txmgrNs, outPoint)
		if err != nil {
			return err
		}

		redeemScript, err = w.TxStore.GetTxScript(txmgrNs, mso.ScriptHash[:])
		if err != nil {
			return err
		}
		// returns nil, nil when it successfully found no script.  That error is
		// only used to return early when the database is closed.
		if redeemScript == nil {
			return errors.New("script not found")
		}

		return nil
	})
	if err != nil {
		return nil, err
	}

	p2shAddr, err := dcrutil.NewAddressScriptHashFromHash(
		mso.ScriptHash[:], w.chainParams)
	if err != nil {
		return nil, err
	}

	multiSigOutput := P2SHMultiSigOutput{
		OutPoint:     *mso.OutPoint,
		OutputAmount: mso.Amount,
		ContainingBlock: BlockIdentity{
			Hash:   mso.BlockHash,
			Height: int32(mso.BlockHeight),
		},
		P2SHAddress:  p2shAddr,
		RedeemScript: redeemScript,
		M:            mso.M,
		N:            mso.N,
		Redeemer:     nil,
	}

	if mso.Spent {
		multiSigOutput.Redeemer = &OutputRedeemer{
			TxHash:     mso.SpentBy,
			InputIndex: mso.SpentByIndex,
		}
	}

	return &multiSigOutput, nil
}
예제 #6
0
// bisectLastAddrIndex is a helper function for search through addresses.
func (w *Wallet) bisectLastAddrIndex(hi, low int, account uint32, branch uint32) int {
	chainClient, err := w.requireChainClient()
	if err != nil {
		return 0
	}

	// Logarithmically scan address indexes to find the last used
	// address index. Each time the algorithm receives an end point,
	// scans a chunk of addresses at the end point, and if no
	// addresses are found, divides the address index by two and
	// repeats until it finds the last used index.
	offset := low
	for i := hi - low - 1; i > 0; i /= 2 {
		if i+offset+int(addrSeekWidth) < waddrmgr.MaxAddressesPerAccount {
			start := i + offset
			end := i + offset + int(addrSeekWidth)
			exists, idx, err := w.scanAddressRange(account, branch, start, end,
				chainClient)
			// Skip erroneous keys, which happen rarely. Don't skip
			// other errors.
			if err == errDerivation {
				continue
			}
			if err != nil {
				log.Warnf("unexpected error encountered during bisection "+
					"scan of account %v, branch %v: %s", account, branch,
					err.Error())
				return 0
			}
			if exists {
				return idx
			}
		} else {
			var addr dcrutil.Address
			err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
				addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
				var err error
				addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
					uint32(i+offset), account, branch)
				return err
			})
			// Skip erroneous keys, which happen rarely.
			if err != nil {
				continue
			}

			exists, err := chainClient.ExistsAddress(addr)
			if err != nil {
				return 0
			}
			if exists {
				return i + offset
			}
		}
	}

	return 0
}
예제 #7
0
// AddressPoolIndex returns the next to use address index for the passed
// branch of the passed account.
func (w *Wallet) AddressPoolIndex(account uint32, branch uint32) (uint32, error) {
	var index uint32
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		waddrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
		var err error
		index, err = w.addressPoolIndex(waddrmgrNs, account, branch)
		return err
	})
	return index, err
}
예제 #8
0
// TxDetails calls wtxmgr.Store.TxDetails under a single database view transaction.
func (u unstableAPI) TxDetails(txHash *chainhash.Hash) (*wtxmgr.TxDetails, error) {
	var details *wtxmgr.TxDetails
	err := walletdb.View(u.w.db, func(dbtx walletdb.ReadTx) error {
		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
		var err error
		details, err = u.w.TxStore.TxDetails(txmgrNs, txHash)
		return err
	})
	return details, err
}
예제 #9
0
// FetchAllRedeemScripts returns all P2SH redeem scripts saved by the wallet.
func (w *Wallet) FetchAllRedeemScripts() ([][]byte, error) {
	var redeemScripts [][]byte
	err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
		var err error
		redeemScripts, err = w.TxStore.StoredTxScripts(txmgrNs)
		return err
	})
	return redeemScripts, err
}
예제 #10
0
// UnspentMultisigCreditsForAddress calls
// wtxmgr.Store.UnspentMultisigCreditsForAddress under a single database view
// transaction.
func (u unstableAPI) UnspentMultisigCreditsForAddress(p2shAddr *dcrutil.AddressScriptHash) ([]*wtxmgr.MultisigCredit, error) {
	var multisigCredits []*wtxmgr.MultisigCredit
	err := walletdb.View(u.w.db, func(tx walletdb.ReadTx) error {
		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
		var err error
		multisigCredits, err = u.w.TxStore.UnspentMultisigCreditsForAddress(
			txmgrNs, p2shAddr)
		return err
	})
	return multisigCredits, err
}
예제 #11
0
// scanAddressRange scans backwards from end to start many addresses in the
// account branch, and return the first index that is found on the blockchain.
// If the address doesn't exist, false is returned as the first argument.
func (w *Wallet) scanAddressRange(account uint32, branch uint32, start int,
	end int, chainClient *chain.RPCClient) (bool, int, error) {

	var addresses []dcrutil.Address
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
		var err error
		addresses, err = w.Manager.AddressesDerivedFromDbAcct(addrmgrNs,
			uint32(start), uint32(end+1), account, branch)
		if err != nil {
			return errDerivation
		}
		return nil
	})
	if err != nil {
		return false, 0, err
	}

	// Whether or not the addresses exist is encoded as a binary
	// bitset.
	exists, err := chainClient.ExistsAddresses(addresses)
	if err != nil {
		return false, 0, err
	}
	existsB, err := hex.DecodeString(exists)
	if err != nil {
		return false, 0, err
	}
	set := bitset.Bytes(existsB)

	// Prevent a panic when an empty message is passed as a response.
	if len(set) == 0 {
		return false, 0, nil
	}

	// Scan backwards and return if we find an address exists.
	idx := end
	itr := len(addresses) - 1
	for idx >= start {
		// If the address exists in the mempool or blockchain according
		// to the bit set returned, return this index.
		if set.Get(itr) {
			return true, idx, nil
		}

		itr--
		idx--
	}

	return false, 0, nil
}
예제 #12
0
파일: sync.go 프로젝트: jrick/btcwallet
// bisectLastAddrIndex is a helper function for search through addresses.
func (w *Wallet) bisectLastAddrIndex(ctx *discoveryContext, hi, low uint32,
	account uint32, branch uint32) (uint32, error) {

	// Logarithmically scan address indexes to find the last used
	// address index. Each time the algorithm receives an end point,
	// scans a chunk of addresses at the end point, and if no
	// addresses are found, divides the address index by two and
	// repeats until it finds the last used index.
	offset := low
	for i := hi - low - 1; i > 0; i /= 2 {
		if i+offset+addrSeekWidth < waddrmgr.MaxAddressesPerAccount {
			start := i + offset
			end := i + offset + addrSeekWidth
			exists, idx, err := w.scanAddressRange(ctx, account, branch, start, end)
			// Skip erroneous keys, which happen rarely. Don't skip
			// other errors.
			if e, ok := err.(waddrmgr.ManagerError); ok && e.Err == hdkeychain.ErrInvalidChild {
				continue
			}
			if err != nil {
				return 0, err
			}
			if exists {
				return idx, nil
			}
		} else {
			var addr dcrutil.Address
			err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
				addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
				var err error
				addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
					i+offset, account, branch)
				return err
			})
			// Skip erroneous keys, which happen rarely.
			if err != nil {
				continue
			}

			exists, err := ctx.chainClient.ExistsAddress(addr)
			if err != nil {
				return 0, err
			}
			if exists {
				return i + offset, nil
			}
		}
	}

	return 0, nil
}
예제 #13
0
// VoteBitsForTicket returns the per-ticket vote bits, if any are saved, falling
// back to the wallet's default vote bits when missing.
func (w *Wallet) VoteBitsForTicket(ticketHash *chainhash.Hash) (stake.VoteBits, error) {
	var voteBits stake.VoteBits
	var ok bool
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		stakemgrNs := tx.ReadBucket(wstakemgrNamespaceKey)
		var err error
		ok, voteBits, err = w.StakeMgr.SStxVoteBits(stakemgrNs, ticketHash)
		return err
	})
	if !ok {
		voteBits = w.VoteBits
	}
	return voteBits, err
}
예제 #14
0
// StakePoolUserInfo returns the stake pool user information for a user
// identified by their P2SH voting address.
func (w *Wallet) StakePoolUserInfo(userAddress dcrutil.Address) (*wstakemgr.StakePoolUser, error) {
	switch userAddress.(type) {
	case *dcrutil.AddressPubKeyHash: // ok
	case *dcrutil.AddressScriptHash: // ok
	default:
		return nil, errors.New("stake pool user address must be P2PKH or P2SH")
	}

	var user *wstakemgr.StakePoolUser
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		stakemgrNs := tx.ReadBucket(wstakemgrNamespaceKey)
		var err error
		user, err = w.StakeMgr.StakePoolUserInfo(stakemgrNs, userAddress)
		return err
	})
	return user, err
}
예제 #15
0
파일: rescan.go 프로젝트: decred/dcrwallet
// RescanProgressFromHeight rescans for relevant transactions in all blocks in
// the main chain starting at startHeight.  Progress notifications and any
// errors are sent to the channel p.  This function blocks until the rescan
// completes or ends in an error.  p is closed before returning.
func (w *Wallet) RescanProgressFromHeight(chainClient *chain.RPCClient, startHeight int32, p chan<- RescanProgress, cancel <-chan struct{}) {
	defer close(p)

	var startHash chainhash.Hash
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
		var err error
		startHash, err = w.TxStore.GetMainChainBlockHashForHeight(
			txmgrNs, startHeight)
		return err
	})
	if err != nil {
		p <- RescanProgress{Err: err}
		return
	}

	err = w.rescan(chainClient, &startHash, startHeight, p, cancel)
	if err != nil {
		p <- RescanProgress{Err: err}
	}
}
예제 #16
0
// accountIsUsed checks if an account has ever been used by scanning the
// first acctSeekWidth many addresses for usage.
func (w *Wallet) accountIsUsed(account uint32, chainClient *chain.RPCClient) bool {
	// 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.
	addrFunc := w.Manager.AddressDerivedFromDbAcct
	if w.initiallyUnlocked {
		addrFunc = w.Manager.AddressDerivedFromCointype
	}

	for branch := uint32(0); branch < 2; branch++ {
		for i := uint32(0); i < acctSeekWidth; i++ {
			var addr dcrutil.Address
			err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
				addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
				var err error
				addr, err = addrFunc(addrmgrNs, i, account, branch)
				return err
			})
			if err != nil {
				// Skip erroneous keys, which happen rarely.
				continue
			}

			exists, err := chainClient.ExistsAddress(addr)
			if err != nil {
				return false
			}
			if exists {
				return true
			}
		}
	}

	return false
}
예제 #17
0
// RangeTransactions calls wtxmgr.Store.RangeTransactions under a single
// database view tranasction.
func (u unstableAPI) RangeTransactions(begin, end int32, f func([]wtxmgr.TxDetails) (bool, error)) error {
	return walletdb.View(u.w.db, func(dbtx walletdb.ReadTx) error {
		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
		return u.w.TxStore.RangeTransactions(txmgrNs, begin, end, f)
	})
}
예제 #18
0
파일: rescan.go 프로젝트: decred/dcrwallet
// 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
	}
}
예제 #19
0
파일: utxos.go 프로젝트: decred/dcrwallet
// UnspentOutputs fetches all unspent outputs from the wallet that match rules
// described in the passed policy.
func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOutput, error) {
	var outputResults []*TransactionOutput
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)

		_, tipHeight := w.TxStore.MainChainTip(txmgrNs)

		// TODO: actually stream outputs from the db instead of fetching
		// all of them at once.
		outputs, err := w.TxStore.UnspentOutputs(txmgrNs)
		if err != nil {
			return err
		}

		for _, output := range outputs {
			// Ignore outputs that haven't reached the required
			// number of confirmations.
			if !policy.meetsRequiredConfs(output.Height, tipHeight) {
				continue
			}

			// Ignore outputs that are not controlled by the account.
			_, addrs, _, err := txscript.ExtractPkScriptAddrs(
				txscript.DefaultScriptVersion, output.PkScript,
				w.chainParams)
			if err != nil || len(addrs) == 0 {
				// Cannot determine which account this belongs
				// to without a valid address.  TODO: Fix this
				// by saving outputs per account, or accounts
				// per output.
				continue
			}
			outputAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0])
			if err != nil {
				return err
			}
			if outputAcct != policy.Account {
				continue
			}

			// Stakebase isn't exposed by wtxmgr so those will be
			// OutputKindNormal for now.
			outputSource := OutputKindNormal
			if output.FromCoinBase {
				outputSource = OutputKindCoinbase
			}

			result := &TransactionOutput{
				OutPoint: output.OutPoint,
				Output: wire.TxOut{
					Value: int64(output.Amount),
					// TODO: version is bogus but there is
					// only version 0 at time of writing.
					Version:  txscript.DefaultScriptVersion,
					PkScript: output.PkScript,
				},
				OutputKind:      outputSource,
				ContainingBlock: BlockIdentity(output.Block),
				ReceiveTime:     output.Received,
			}
			outputResults = append(outputResults, result)
		}

		return nil
	})
	return outputResults, err
}
예제 #20
0
파일: sync.go 프로젝트: jrick/btcwallet
// scanAddressIndex identifies the last used address in an HD keychain of public
// keys. It returns the index of the last used key, along with the address of
// this key.
func (w *Wallet) scanAddressIndex(ctx *discoveryContext, start, end uint32,
	account uint32, branch uint32) (uint32, dcrutil.Address, error) {

	// Find the last used address. Scan from it to the end in case there was a
	// gap from that position, which is possible. Then, return the address
	// in that position.
	lastUsed, err := w.findAddrEnd(ctx, start, end, account, branch)
	if err != nil {
		return 0, nil, err
	}

	// If debug is on, do an exhaustive check and a graphical printout
	// of what the used addresses currently look like.
	if log.Level() <= btclog.DebugLvl {
		dbgStr, err := debugAccountAddrGapsString(ctx.chainClient,
			lastUsed+debugAddrScanLength, account, branch, w)
		if err != nil {
			log.Debugf("Failed to debug address gaps for account %v, "+
				"branch %v: %v", account, branch, err)
		} else {
			log.Debugf("%v", dbgStr)
		}
	}

	// If there was a last used index, do an exhaustive final scan that
	// reexamines the last used addresses and ensures that the final index
	// we have found is correct.
	if lastUsed != 0 {
		start := lastUsed
		end := lastUsed + uint32(w.addrIdxScanLen)
		exists, idx, err := w.scanAddressRange(ctx, account, branch, start, end)
		if err != nil {
			return 0, nil, err
		}

		if exists {
			lastUsed = idx
			var addr dcrutil.Address
			err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
				addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
				var err error
				addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
					lastUsed, account, branch)
				return err
			})
			if err != nil {
				return 0, nil, err
			}
			return lastUsed, addr, nil
		}
	}

	// In the case that 0 was returned as the last used address,
	// make sure the the 0th address was not used. If it was,
	// return this address to let the caller know that this
	// 0th address was used.
	if lastUsed == 0 {
		var addr dcrutil.Address
		err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
			addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
			var err error
			addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs, 0,
				account, branch)
			return err
		})
		// Skip erroneous keys.
		if err != nil {
			return 0, nil, err
		}

		exists, err := ctx.chainClient.ExistsAddress(addr)
		if err != nil {
			return 0, nil, fmt.Errorf("failed to access chain server: %v",
				err)
		}

		if exists {
			return 0, addr, nil
		}
	}

	// We can't find any used addresses for this account's
	// branch.
	return 0, nil, nil
}
예제 #21
0
파일: sync.go 프로젝트: jrick/btcwallet
// debugAccountAddrGapsString is a debug function that prints a graphical outlook
// of address usage to a string, from the perspective of the daemon.
func debugAccountAddrGapsString(chainClient *chain.RPCClient, scanBackFrom uint32,
	account uint32, branch uint32, w *Wallet) (string, error) {

	var buf bytes.Buffer
	str := fmt.Sprintf("Begin debug address scan scanning backwards from "+
		"idx %v, account %v, branch %v\n", scanBackFrom, account, branch)
	buf.WriteString(str)
	var firstUsedIndex uint32
	for i := scanBackFrom; i > 0; i-- {
		var addr dcrutil.Address
		err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
			addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
			var err error
			addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
				i, account, branch)
			return err
		})
		// Skip erroneous keys.
		if err != nil {
			continue
		}

		exists, err := chainClient.ExistsAddress(addr)
		if err != nil {
			return "", fmt.Errorf("failed to access chain server: %v",
				err)
		}

		if exists {
			firstUsedIndex = i
			break
		}
	}

	str = fmt.Sprintf("Last used index found: %v\n", firstUsedIndex)
	buf.WriteString(str)

	var batchSize uint32 = 50
	batches := (firstUsedIndex / batchSize) + 1
	var lastBatchSize uint32
	if firstUsedIndex%batchSize != 0 {
		lastBatchSize = firstUsedIndex - ((batches - 1) * batchSize)
	}

	for i := uint32(0); i < batches; i++ {
		str = fmt.Sprintf("%8v", i*batchSize)
		buf.WriteString(str)

		start := i * batchSize
		end := (i + 1) * batchSize
		if i == batches-1 {
			// Nothing to do because last batch empty.
			if lastBatchSize == 0 {
				break
			}
			end = (i*batchSize + lastBatchSize) + 1
		}

		for j := start; j < end; j++ {
			if j%10 == 0 {
				buf.WriteString("  ")
			}

			char := "_"
			var addr dcrutil.Address
			err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
				addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
				var err error
				addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
					j, account, branch)
				return err
			})
			if err != nil {
				char = "X"
			}

			exists, err := chainClient.ExistsAddress(addr)
			if err != nil {
				return "", fmt.Errorf("failed to access chain server: %v",
					err)
			}
			if exists {
				char = "#"
			}

			buf.WriteString(char)
		}

		buf.WriteString("\n")
	}

	return buf.String(), nil
}
예제 #22
0
// LiveTicketHashes returns the hashes of live tickets that have been purchased
// by the wallet.
func (w *Wallet) LiveTicketHashes(rpcClient *chain.RPCClient, includeImmature bool) ([]chainhash.Hash, error) {
	// This was mostly copied from an older version of the legacy RPC server
	// implementation, hence the overall weirdness, inefficiencies, and the
	// direct dependency on the consensus server RPC client.

	var blk waddrmgr.BlockStamp
	var ticketHashes []chainhash.Hash
	var stakeMgrTickets []chainhash.Hash
	err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)

		blk = w.Manager.SyncedTo()

		// UnspentTickets collects all the tickets that pay out to a
		// public key hash for a public key owned by this wallet.
		var err error
		ticketHashes, err = w.TxStore.UnspentTickets(txmgrNs, blk.Height,
			includeImmature)
		if err != nil {
			return err
		}

		// Access the stake manager and see if there are any extra tickets
		// there. Likely they were either pruned because they failed to get
		// into the blockchain or they are P2SH for some script we own.
		stakeMgrTickets, err = w.StakeMgr.DumpSStxHashes()
		return err
	})
	if err != nil {
		return nil, err
	}

	for _, h := range stakeMgrTickets {
		if sliceContainsHash(ticketHashes, h) {
			continue
		}

		// Get the raw transaction information from daemon and add
		// any relevant tickets. The ticket output is always the
		// zeroeth output.
		spent, err := rpcClient.GetTxOut(&h, 0, true)
		if err != nil {
			continue
		}
		// This returns nil if the output is spent.
		if spent == nil {
			continue
		}

		ticketTx, err := rpcClient.GetRawTransactionVerbose(&h)
		if err != nil {
			continue
		}

		txHeight := ticketTx.BlockHeight
		unconfirmed := (txHeight == 0)
		immature := (blk.Height-int32(txHeight) <
			int32(w.ChainParams().TicketMaturity))
		if includeImmature {
			ticketHashes = append(ticketHashes, h)
		} else {
			if !(unconfirmed || immature) {
				ticketHashes = append(ticketHashes, h)
			}
		}
	}

	return ticketHashes, nil
}
예제 #23
0
// initialize initializes an address pool for the passed account and branch
// to the address index given. It will automatically load a buffer of addresses
// from the address manager to use for upcoming calls.
func (a *addressPool) initialize(account uint32, branch uint32, index uint32, w *Wallet) error {
	a.mutex.Lock()
	defer a.mutex.Unlock()

	// Do not reinitialize an address pool that was already started.
	// This can happen if the RPC client dies due to a disconnect
	// from the daemon.
	if a.started {
		return nil
	}

	// 0 and 1 refer to the external and internal branches of the wallet.
	// Other branches are so far unused.
	if branch > waddrmgr.InternalBranch {
		return fmt.Errorf("unknown branch %v given when attempting to "+
			"initialize address pool for account %v", branch, account)
	}

	// Access the manager and get the synced to index, then insert all
	// the unused addresses into the address pool.
	var mgrIdx uint32
	err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
		waddrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)

		lastAddrFunc := w.Manager.LastExternalAddress
		if branch == waddrmgr.InternalBranch {
			lastAddrFunc = w.Manager.LastInternalAddress
		}
		var err error
		_, mgrIdx, err = lastAddrFunc(waddrmgrNs, account)
		if err != nil {
			return fmt.Errorf("failed to retrieve the last used addr index "+
				"from the address manager for branch %v, acct %v: %s", branch,
				account, err.Error())
		}

		if mgrIdx < index {
			return fmt.Errorf("manager is out of sync with the passed index "+
				"(index %v, mgr index %v)", index, mgrIdx)
		}

		if mgrIdx == index {
			a.addresses = make([]string, 0)
		} else {
			fetchNum := mgrIdx - index + 1
			a.addresses = make([]string, fetchNum)
			for i := uint32(0); i < fetchNum; i++ {
				addr, err := w.Manager.AddressDerivedFromDbAcct(waddrmgrNs,
					index+i, account, branch)
				if err != nil {
					return fmt.Errorf("failed to get the address at index %v "+
						"for account %v, branch %v: %s", index+i, account, branch,
						err.Error())
				}
				a.addresses[i] = addr.EncodeAddress()
			}
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to initialize address pool: %v", err)
	}

	a.wallet = w
	a.account = account
	a.branch = branch
	a.index = index

	log.Debugf("Address pool for account %v initialized to next "+
		"address index %v on branch %v", account, a.index, branch)
	log.Debugf("The address manager buffered address space is %v "+
		"many addresses (manager index: %v) for account %v, branch %v",
		len(a.addresses), mgrIdx, account, branch)

	a.cursor = 0
	a.started = true

	return nil
}