func sweep() error { rpcPassword, err := promptSecret("Wallet RPC password") if err != nil { return errContext(err, "failed to read RPC password") } // Open RPC client. rpcCertificate, err := ioutil.ReadFile(opts.RPCCertificateFile) if err != nil { return errContext(err, "failed to read RPC certificate") } rpcClient, err := btcrpcclient.New(&btcrpcclient.ConnConfig{ Host: opts.RPCConnect, User: opts.RPCUsername, Pass: rpcPassword, Certificates: rpcCertificate, HTTPPostMode: true, }, nil) if err != nil { return errContext(err, "failed to create RPC client") } defer rpcClient.Shutdown() // Fetch all unspent outputs, ignore those not from the source // account, and group by their destination address. Each grouping of // outputs will be used as inputs for a single transaction sending to a // new destination account address. unspentOutputs, err := rpcClient.ListUnspent() if err != nil { return errContext(err, "failed to fetch unspent outputs") } sourceOutputs := make(map[string][]btcjson.ListUnspentResult) for _, unspentOutput := range unspentOutputs { if !unspentOutput.Spendable { continue } if unspentOutput.Confirmations < opts.RequiredConfirmations { continue } if unspentOutput.Account != opts.SourceAccount { continue } sourceAddressOutputs := sourceOutputs[unspentOutput.Address] sourceOutputs[unspentOutput.Address] = append(sourceAddressOutputs, unspentOutput) } var privatePassphrase string if len(sourceOutputs) != 0 { privatePassphrase, err = promptSecret("Wallet private passphrase") if err != nil { return errContext(err, "failed to read private passphrase") } } var totalSwept btcutil.Amount var numErrors int var reportError = func(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, format, args...) os.Stderr.Write(newlineBytes) numErrors++ } for _, previousOutputs := range sourceOutputs { inputSource := makeInputSource(previousOutputs) destinationSource := makeDestinationScriptSource(rpcClient, opts.DestinationAccount) tx, err := txauthor.NewUnsignedTransaction(nil, opts.FeeRate.Amount, inputSource, destinationSource) if err != nil { if err != (noInputValue{}) { reportError("Failed to create unsigned transaction: %v", err) } continue } // Unlock the wallet, sign the transaction, and immediately lock. err = rpcClient.WalletPassphrase(privatePassphrase, 60) if err != nil { reportError("Failed to unlock wallet: %v", err) continue } signedTransaction, complete, err := rpcClient.SignRawTransaction(tx.Tx) _ = rpcClient.WalletLock() if err != nil { reportError("Failed to sign transaction: %v", err) continue } if !complete { reportError("Failed to sign every input") continue } // Publish the signed sweep transaction. txHash, err := rpcClient.SendRawTransaction(signedTransaction, false) if err != nil { reportError("Failed to publish transaction: %v", err) continue } outputAmount := btcutil.Amount(tx.Tx.TxOut[0].Value) fmt.Printf("Swept %v to destination account with transaction %v\n", outputAmount, txHash) totalSwept += outputAmount } numPublished := len(sourceOutputs) - numErrors transactionNoun := pickNoun(numErrors, "transaction", "transactions") if numPublished != 0 { fmt.Printf("Swept %v to destination account across %d %s\n", totalSwept, numPublished, transactionNoun) } if numErrors > 0 { return fmt.Errorf("Failed to publish %d %s", numErrors, transactionNoun) } return nil }
// txToOutputs creates a signed transaction which includes each output from // outputs. Previous outputs to reedeem are chosen from the passed account's // UTXO set and minconf policy. An additional output may be added to return // change to the wallet. An appropriate fee is included based on the wallet's // current relay fee. The wallet must be unlocked to create the transaction. func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int32) (*txauthor.AuthoredTx, error) { // Address manager must be unlocked to compose transaction. Grab // the unlock if possible (to prevent future unlocks), or return the // error if already locked. heldUnlock, err := w.HoldUnlock() if err != nil { return nil, err } defer heldUnlock.Release() chainClient, err := w.requireChainClient() if err != nil { return nil, err } // Get current block's height and hash. bs, err := chainClient.BlockStamp() if err != nil { return nil, err } eligible, err := w.findEligibleOutputs(account, minconf, bs) if err != nil { return nil, err } inputSource := makeInputSource(eligible) changeSource := func() ([]byte, error) { // Derive the change output script. As a hack to allow spending from // the imported account, change addresses are created from account 0. var changeAddr btcutil.Address if account == waddrmgr.ImportedAddrAccount { changeAddr, err = w.NewChangeAddress(0) } else { changeAddr, err = w.NewChangeAddress(account) } if err != nil { return nil, err } return txscript.PayToAddrScript(changeAddr) } tx, err := txauthor.NewUnsignedTransaction(outputs, w.RelayFee(), inputSource, changeSource) if err != nil { return nil, err } // Randomize change position, if change exists, before signing. This // doesn't affect the serialize size, so the change amount will still be // valid. if tx.ChangeIndex >= 0 { tx.RandomizeChangePosition() } err = tx.AddAllInputScripts(secretSource{w.Manager}) if err != nil { return nil, err } err = validateMsgTx(tx.Tx, tx.PrevScripts) if err != nil { return nil, err } if tx.ChangeIndex >= 0 && account == waddrmgr.ImportedAddrAccount { changeAmount := btcutil.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value) log.Warnf("Spend from imported account produced change: moving"+ " %v from imported account into default account.", changeAmount) } return tx, 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 }