utxos.go raw

   1  package wallet
   2  
   3  import (
   4  	"github.com/p9c/p9/pkg/btcaddr"
   5  	"github.com/p9c/p9/pkg/txscript"
   6  	"github.com/p9c/p9/pkg/walletdb"
   7  	"github.com/p9c/p9/pkg/wire"
   8  )
   9  
  10  // OutputSelectionPolicy describes the rules for selecting an output from the wallet.
  11  type OutputSelectionPolicy struct {
  12  	Account               uint32
  13  	RequiredConfirmations int32
  14  }
  15  
  16  func (p *OutputSelectionPolicy) meetsRequiredConfs(txHeight, curHeight int32) bool {
  17  	return confirmed(p.RequiredConfirmations, txHeight, curHeight)
  18  }
  19  
  20  // UnspentOutputs fetches all unspent outputs from the wallet that match rules described in the passed policy.
  21  func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOutput, error) {
  22  	var outputResults []*TransactionOutput
  23  	e := walletdb.View(
  24  		w.db, func(tx walletdb.ReadTx) (e error) {
  25  			addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
  26  			txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
  27  			syncBlock := w.Manager.SyncedTo()
  28  			// TODO: actually stream outputs from the db instead of fetching all of them at once.
  29  			outputs, e := w.TxStore.UnspentOutputs(txmgrNs)
  30  			if e != nil {
  31  				return e
  32  			}
  33  			for _, output := range outputs {
  34  				// Ignore outputs that haven't reached the required number of confirmations.
  35  				if !policy.meetsRequiredConfs(output.Height, syncBlock.Height) {
  36  					continue
  37  				}
  38  				// Ignore outputs that are not controlled by the account.
  39  				var addrs []btcaddr.Address
  40  				_, addrs, _, e = txscript.ExtractPkScriptAddrs(
  41  					output.PkScript,
  42  					w.chainParams,
  43  				)
  44  				if e != nil || len(addrs) == 0 {
  45  					// Cannot determine which account this belongs to without a valid address.
  46  					//
  47  					// TODO: Fix this by saving outputs per account, or accounts per output.
  48  					continue
  49  				}
  50  				var outputAcct uint32
  51  				_, outputAcct, e = w.Manager.AddrAccount(addrmgrNs, addrs[0])
  52  				if e != nil {
  53  					return e
  54  				}
  55  				if outputAcct != policy.Account {
  56  					continue
  57  				}
  58  				// Stakebase isn't exposed by wtxmgr so those will be OutputKindNormal for now.
  59  				outputSource := OutputKindNormal
  60  				if output.FromCoinBase {
  61  					outputSource = OutputKindCoinbase
  62  				}
  63  				result := &TransactionOutput{
  64  					OutPoint: output.OutPoint,
  65  					Output: wire.TxOut{
  66  						Value:    int64(output.Amount),
  67  						PkScript: output.PkScript,
  68  					},
  69  					OutputKind:      outputSource,
  70  					ContainingBlock: BlockIdentity(output.Block),
  71  					ReceiveTime:     output.Received,
  72  				}
  73  				outputResults = append(outputResults, result)
  74  			}
  75  			return nil
  76  		},
  77  	)
  78  	return outputResults, e
  79  }
  80