createtx.go raw

   1  // Package wallet Copyright (c) 2015-2016 The btcsuite developers
   2  package wallet
   3  
   4  import (
   5  	"fmt"
   6  	"github.com/p9c/p9/pkg/amt"
   7  	"github.com/p9c/p9/pkg/btcaddr"
   8  	"github.com/p9c/p9/pkg/chainclient"
   9  	"sort"
  10  
  11  	ec "github.com/p9c/p9/pkg/ecc"
  12  	"github.com/p9c/p9/pkg/txauthor"
  13  	"github.com/p9c/p9/pkg/txscript"
  14  	"github.com/p9c/p9/pkg/waddrmgr"
  15  	"github.com/p9c/p9/pkg/walletdb"
  16  	"github.com/p9c/p9/pkg/wire"
  17  	"github.com/p9c/p9/pkg/wtxmgr"
  18  )
  19  
  20  // byAmount defines the methods needed to satisify sort.Interface to txsort credits by their output amount.
  21  type byAmount []wtxmgr.Credit
  22  
  23  func (s byAmount) Len() int           { return len(s) }
  24  func (s byAmount) Less(i, j int) bool { return s[i].Amount < s[j].Amount }
  25  func (s byAmount) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
  26  
  27  func makeInputSource(eligible []wtxmgr.Credit) txauthor.InputSource {
  28  	// Pick largest outputs first. This is only done for compatibility with previous
  29  	// tx creation code, not because it's a good idea.
  30  	sort.Sort(sort.Reverse(byAmount(eligible)))
  31  	// Current inputs and their total value. These are closed over by the returned
  32  	// input source and reused across multiple calls.
  33  	currentTotal := amt.Amount(0)
  34  	currentInputs := make([]*wire.TxIn, 0, len(eligible))
  35  	currentScripts := make([][]byte, 0, len(eligible))
  36  	currentInputValues := make([]amt.Amount, 0, len(eligible))
  37  	return func(target amt.Amount) (
  38  		amt.Amount, []*wire.TxIn,
  39  		[]amt.Amount, [][]byte, error,
  40  	) {
  41  		for currentTotal < target && len(eligible) != 0 {
  42  			nextCredit := &eligible[0]
  43  			eligible = eligible[1:]
  44  			nextInput := wire.NewTxIn(&nextCredit.OutPoint, nil, nil)
  45  			currentTotal += nextCredit.Amount
  46  			currentInputs = append(currentInputs, nextInput)
  47  			currentScripts = append(currentScripts, nextCredit.PkScript)
  48  			currentInputValues = append(currentInputValues, nextCredit.Amount)
  49  		}
  50  		return currentTotal, currentInputs, currentInputValues, currentScripts, nil
  51  	}
  52  }
  53  
  54  // secretSource is an implementation of txauthor.SecretSource for the wallet's address manager.
  55  type secretSource struct {
  56  	*waddrmgr.Manager
  57  	addrmgrNs walletdb.ReadBucket
  58  }
  59  
  60  // GetKey gets the private key for an address if it is available
  61  func (s secretSource) GetKey(addr btcaddr.Address) (privKey *ec.PrivateKey, cmpr bool, e error) {
  62  	var ma waddrmgr.ManagedAddress
  63  	ma, e = s.Address(s.addrmgrNs, addr)
  64  	if e != nil {
  65  		return nil, false, e
  66  	}
  67  	var ok bool
  68  	var mpka waddrmgr.ManagedPubKeyAddress
  69  	mpka, ok = ma.(waddrmgr.ManagedPubKeyAddress)
  70  	if !ok {
  71  		e = fmt.Errorf(
  72  			"managed address type for %v is `%T` but "+
  73  				"want waddrmgr.ManagedPubKeyAddress", addr, ma,
  74  		)
  75  		return nil, false, e
  76  	}
  77  	if privKey, e = mpka.PrivKey(); E.Chk(e) {
  78  		return nil, false, e
  79  	}
  80  	return privKey, ma.Compressed(), nil
  81  }
  82  
  83  // GetScript returns pay to script transaction
  84  func (s secretSource) GetScript(addr btcaddr.Address) ([]byte, error) {
  85  	ma, e := s.Address(s.addrmgrNs, addr)
  86  	if e != nil {
  87  		return nil, e
  88  	}
  89  	msa, ok := ma.(waddrmgr.ManagedScriptAddress)
  90  	if !ok {
  91  		e := fmt.Errorf(
  92  			"managed address type for %v is `%T` but "+
  93  				"want waddrmgr.ManagedScriptAddress", addr, ma,
  94  		)
  95  		return nil, e
  96  	}
  97  	return msa.Script()
  98  }
  99  
 100  // txToOutputs creates a signed transaction which includes each output from
 101  // outputs. Previous outputs to reedeem are chosen from the passed account's
 102  // UTXO set and minconf policy. An additional output may be added to return
 103  // change to the wallet. An appropriate fee is included based on the wallet's
 104  // current relay fee. The wallet must be unlocked to create the transaction.
 105  func (w *Wallet) txToOutputs(
 106  	outputs []*wire.TxOut, account uint32,
 107  	minconf int32, feeSatPerKb amt.Amount,
 108  ) (tx *txauthor.AuthoredTx, e error) {
 109  	var chainClient chainclient.Interface
 110  	if chainClient, e = w.requireChainClient(); E.Chk(e) {
 111  		return nil, e
 112  	}
 113  	e = walletdb.Update(
 114  		w.db, func(dbtx walletdb.ReadWriteTx) (e error) {
 115  			addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
 116  			// Get current block's height and hash.
 117  			var bs *waddrmgr.BlockStamp
 118  			if bs, e = chainClient.BlockStamp(); E.Chk(e) {
 119  				return
 120  			}
 121  			var eligible []wtxmgr.Credit
 122  			if eligible, e = w.findEligibleOutputs(dbtx, account, minconf, bs); E.Chk(e) {
 123  				return
 124  			}
 125  			inputSource := makeInputSource(eligible)
 126  			changeSource := func() (b []byte, e error) {
 127  				// Derive the change output script. As a hack to allow spending from the
 128  				// imported account, change addresses are created from account 0.
 129  				var changeAddr btcaddr.Address
 130  				if account == waddrmgr.ImportedAddrAccount {
 131  					changeAddr, e = w.newChangeAddress(addrmgrNs, 0)
 132  				} else {
 133  					changeAddr, e = w.newChangeAddress(addrmgrNs, account)
 134  				}
 135  				if E.Chk(e) {
 136  					return
 137  				}
 138  				return txscript.PayToAddrScript(changeAddr)
 139  			}
 140  			if tx, e = txauthor.NewUnsignedTransaction(outputs, feeSatPerKb, inputSource, changeSource); E.Chk(e) {
 141  				return
 142  			}
 143  			// Randomize change position, if change exists, before signing. This doesn't
 144  			// affect the serialize size, so the change amount will still be valid.
 145  			if tx.ChangeIndex >= 0 {
 146  				tx.RandomizeChangePosition()
 147  			}
 148  			return tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs})
 149  		},
 150  	)
 151  	if E.Chk(e) {
 152  		return
 153  	}
 154  	if e = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues); E.Chk(e) {
 155  		return
 156  	}
 157  	if tx.ChangeIndex >= 0 && account == waddrmgr.ImportedAddrAccount {
 158  		changeAmount := amt.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value)
 159  		W.F(
 160  			"spend from imported account produced change: moving %v from imported account into default account.",
 161  			changeAmount,
 162  		)
 163  	}
 164  	return
 165  }
 166  func (w *Wallet) findEligibleOutputs(
 167  	dbtx walletdb.ReadTx,
 168  	account uint32,
 169  	minconf int32,
 170  	bs *waddrmgr.BlockStamp,
 171  ) ([]wtxmgr.Credit, error) {
 172  	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
 173  	txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
 174  	unspent, e := w.TxStore.UnspentOutputs(txmgrNs)
 175  	if e != nil {
 176  		return nil, e
 177  	}
 178  	// TODO: Eventually all of these filters (except perhaps output locking) should
 179  	//  be handled by the call to UnspentOutputs (or similar). Because one of these
 180  	//  filters requires matching the output script to the desired
 181  	//  account, this change depends on making wtxmgr a waddrmgr dependancy and
 182  	//  requesting unspent outputs for a single account.
 183  	eligible := make([]wtxmgr.Credit, 0, len(unspent))
 184  	for i := range unspent {
 185  		output := &unspent[i]
 186  		// Only include this output if it meets the required number of confirmations.
 187  		// Coinbase transactions must have have reached maturity before their outputs
 188  		// may be spent.
 189  		if !confirmed(minconf, output.Height, bs.Height) {
 190  			continue
 191  		}
 192  		if output.FromCoinBase {
 193  			target := int32(w.chainParams.CoinbaseMaturity)
 194  			if !confirmed(target, output.Height, bs.Height) {
 195  				continue
 196  			}
 197  		}
 198  		// Locked unspent outputs are skipped.
 199  		if w.LockedOutpoint(output.OutPoint) {
 200  			continue
 201  		}
 202  		// Only include the output if it is associated with the passed account.
 203  		//
 204  		// TODO: Handle multisig outputs by determining if enough of the addresses are
 205  		//  controlled.
 206  		var addrs []btcaddr.Address
 207  		if _, addrs, _, e = txscript.ExtractPkScriptAddrs(
 208  			output.PkScript, w.chainParams,
 209  		); E.Chk(e) || len(addrs) != 1 {
 210  			continue
 211  		}
 212  		var addrAcct uint32
 213  		if _, addrAcct, e = w.Manager.AddrAccount(addrmgrNs, addrs[0]); E.Chk(e) ||
 214  			addrAcct != account {
 215  			continue
 216  		}
 217  		eligible = append(eligible, *output)
 218  	}
 219  	return eligible, nil
 220  }
 221  
 222  // validateMsgTx verifies transaction input scripts for tx. All previous output
 223  // scripts from outputs redeemed by the transaction, in the same order they are
 224  // spent, must be passed in the prevScripts slice.
 225  func validateMsgTx(tx *wire.MsgTx, prevScripts [][]byte, inputValues []amt.Amount) (e error) {
 226  	hashCache := txscript.NewTxSigHashes(tx)
 227  	for i, prevScript := range prevScripts {
 228  		var vm *txscript.Engine
 229  		vm, e = txscript.NewEngine(
 230  			prevScript, tx, i,
 231  			txscript.StandardVerifyFlags, nil, hashCache, int64(inputValues[i]),
 232  		)
 233  		if E.Chk(e) {
 234  			return fmt.Errorf("cannot create script engine: %s", e)
 235  		}
 236  		e = vm.Execute()
 237  		if E.Chk(e) {
 238  			return fmt.Errorf("cannot validate transaction: %s", e)
 239  		}
 240  	}
 241  	return nil
 242  }
 243