// NewUnsignedTransaction creates an unsigned transaction paying to one or more // non-change outputs. An appropriate transaction fee is included based on the // transaction size. // // Transaction inputs are chosen from repeated calls to fetchInputs with // increasing targets amounts. // // If any remaining output value can be returned to the wallet via a change // output without violating mempool dust rules, a P2PKH change output is // appended to the transaction outputs. Since the change output may not be // necessary, fetchChange is called zero or one times to generate this script. // This function must return a P2PKH script or smaller, otherwise fee estimation // will be incorrect. // // If successful, the transaction, total input value spent, and all previous // output scripts are returned. If the input source was unable to provide // enough input value to pay for every output any any necessary fees, an // InputSourceError is returned. // // BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs. func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) { targetAmount := h.SumOutputValues(outputs) estimatedSize := txsizes.EstimateSerializeSize(1, outputs, true) targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize) for { inputAmount, inputs, scripts, err := fetchInputs(targetAmount + targetFee) if err != nil { return nil, err } if inputAmount < targetAmount+targetFee { return nil, insufficientFundsError{} } maxSignedSize := txsizes.EstimateSerializeSize(len(inputs), outputs, true) maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize) remainingAmount := inputAmount - targetAmount if remainingAmount < maxRequiredFee { targetFee = maxRequiredFee continue } unsignedTransaction := &wire.MsgTx{ Version: wire.TxVersion, TxIn: inputs, TxOut: outputs, LockTime: 0, } changeIndex := -1 changeAmount := inputAmount - targetAmount - maxRequiredFee if changeAmount != 0 && !txrules.IsDustAmount(changeAmount, txsizes.P2PKHPkScriptSize, relayFeePerKb) { changeScript, err := fetchChange() if err != nil { return nil, err } if len(changeScript) > txsizes.P2PKHPkScriptSize { return nil, errors.New("fee estimation requires change " + "scripts no larger than P2PKH output scripts") } change := wire.NewTxOut(int64(changeAmount), changeScript) l := len(outputs) unsignedTransaction.TxOut = append(outputs[:l:l], change) changeIndex = l } return &AuthoredTx{ Tx: unsignedTransaction, PrevScripts: scripts, TotalInput: inputAmount, ChangeIndex: changeIndex, }, nil } }
func (w *LibbitcoinWallet) Spend(amount int64, addr btc.Address, feeLevel bitcoin.FeeLevel) error { // Check for dust script, _ := txscript.PayToAddrScript(addr) if txrules.IsDustAmount(btc.Amount(amount), len(script), txrules.DefaultRelayFeePerKb) { return errors.New("Amount is below dust threshold") } var additionalPrevScripts map[wire.OutPoint][]byte var additionalKeysByAddress map[string]*btc.WIF // Create input source coinMap := w.gatherCoins() coins := make([]coinset.Coin, 0, len(coinMap)) for k := range coinMap { coins = append(coins, k) } inputSource := func(target btc.Amount) (total btc.Amount, inputs []*wire.TxIn, scripts [][]byte, err error) { // TODO: maybe change the coin selection algorithm? We're using min coins right now because // TODO: we don't know the number of confirmations on each coin without querying the libbitcoin server. coinSelector := coinset.MinNumberCoinSelector{MaxInputs: 10000, MinChangeAmount: btc.Amount(10000)} coins, err := coinSelector.CoinSelect(target, coins) if err != nil { return total, inputs, scripts, errors.New("insuffient funds") } additionalPrevScripts = make(map[wire.OutPoint][]byte) additionalKeysByAddress = make(map[string]*btc.WIF) for _, c := range coins.Coins() { total += c.Value() outpoint := wire.NewOutPoint(c.Hash(), c.Index()) in := wire.NewTxIn(outpoint, []byte{}) in.Sequence = 0 // Opt-in RBF so we can bump fees inputs = append(inputs, in) additionalPrevScripts[*outpoint] = c.PkScript() key := coinMap[c] addr, _ := btc.NewAddressPubKey(key.PublicKey().Key, w.params) pk, _ := btcec.PrivKeyFromBytes(btcec.S256(), key.Key) wif, _ := btc.NewWIF(pk, w.params, true) additionalKeysByAddress[addr.AddressPubKeyHash().EncodeAddress()] = wif } return total, inputs, scripts, nil } // Get the fee per kilobyte feePerKB := int64(w.getFeePerByte(feeLevel)) * 1000 // outputs out := wire.NewTxOut(amount, script) // Create change source changeSource := func() ([]byte, error) { addr := w.GetCurrentAddress(bitcoin.CHANGE) script, err := txscript.PayToAddrScript(addr) if err != nil { return []byte{}, err } return script, nil } authoredTx, err := txauthor.NewUnsignedTransaction([]*wire.TxOut{out}, btc.Amount(feePerKB), inputSource, changeSource) if err != nil { return err } // BIP 69 sorting txsort.InPlaceSort(authoredTx.Tx) // Sign tx getKey := txscript.KeyClosure(func(addr btc.Address) ( *btcec.PrivateKey, bool, error) { addrStr := addr.EncodeAddress() wif := additionalKeysByAddress[addrStr] return wif.PrivKey, wif.CompressPubKey, nil }) getScript := txscript.ScriptClosure(func( addr btc.Address) ([]byte, error) { return []byte{}, nil }) for i, txIn := range authoredTx.Tx.TxIn { prevOutScript := additionalPrevScripts[txIn.PreviousOutPoint] script, err := txscript.SignTxOutput(w.params, authoredTx.Tx, i, prevOutScript, txscript.SigHashAll, getKey, getScript, txIn.SignatureScript) if err != nil { return errors.New("Failed to sign transaction") } txIn.SignatureScript = script } // Broadcast tx to bitcoin network serializedTx := new(bytes.Buffer) authoredTx.Tx.Serialize(serializedTx) w.Client.Broadcast(serializedTx.Bytes(), func(i interface{}, err error) { if err == nil { log.Infof("Broadcast tx %s to bitcoin network\n", authoredTx.Tx.TxSha().String()) } else { log.Errorf("Failed to broadcast tx, reason: %s\n", err) } }) // Update the db w.ProcessTransaction(btc.NewTx(authoredTx.Tx), 0) return nil }