From 3a7b9c8367ff9c186f77d3d4163730b8c8866819 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Sat, 17 Nov 2018 21:10:32 -0800 Subject: [PATCH] rpc: implement new feature of sendcoins to sweep all wallet UTXOs In this commit, we implement the new feature which allows sendcoins to sweep all the coins in the wallet. We use the new sweep.CraftSweepAllTx method, and also use WithCoinSelectLock to ensure that we don't trigger any double-spend errors triggered by coin selection race conditions. --- rpcserver.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index a49fea0a..5664e24d 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -769,13 +769,89 @@ func (r *rpcServer) SendCoins(ctx context.Context, return nil, err } - rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/kw=%v", in.Addr, - btcutil.Amount(in.Amount), int64(feePerKw)) + rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/kw=%v, sweep_all=%v", + in.Addr, btcutil.Amount(in.Amount), int64(feePerKw), + in.SendAll) - paymentMap := map[string]int64{in.Addr: in.Amount} - txid, err := r.sendCoinsOnChain(paymentMap, feePerKw) - if err != nil { - return nil, err + var txid *chainhash.Hash + + wallet := r.server.cc.wallet + + // If the send all flag is active, then we'll attempt to sweep all the + // coins in the wallet in a single transaction (if possible), + // otherwise, we'll respect the amount, and attempt a regular 2-output + // send. + if in.SendAll { + // At this point, the amount shouldn't be set since we've been + // instructed to sweep all the coins from the wallet. + if in.Amount != 0 { + return nil, fmt.Errorf("amount set while SendAll is " + + "active") + } + + // Additionally, we'll need to convert the sweep address passed + // into a useable struct, and also query for the latest block + // height so we can properly construct the transaction. + sweepAddr, err := btcutil.DecodeAddress( + in.Addr, activeNetParams.Params, + ) + if err != nil { + return nil, err + } + _, bestHeight, err := r.server.cc.chainIO.GetBestBlock() + if err != nil { + return nil, err + } + + // With the sweeper instance created, we can now generate a + // transaction that will sweep ALL outputs from the wallet in a + // single transaction. This will be generated in a concurrent + // safe manner, so no need to worry about locking. + sweepTxPkg, err := sweep.CraftSweepAllTx( + feePerKw, uint32(bestHeight), sweepAddr, wallet, + wallet.WalletController, wallet.WalletController, + r.server.cc.feeEstimator, r.server.cc.signer, + ) + if err != nil { + return nil, err + } + + rpcsLog.Debugf("Sweeping all coins from wallet to addr=%v, "+ + "with tx=%v", in.Addr, spew.Sdump(sweepTxPkg.SweepTx)) + + // As our sweep transaction was created, successfully, we'll + // now attempt to publish it, cancelling the sweep pkg to + // return all outputs if it fails. + err = wallet.PublishTransaction(sweepTxPkg.SweepTx) + if err != nil { + sweepTxPkg.CancelSweepAttempt() + + return nil, fmt.Errorf("unable to broadcast sweep "+ + "transaction: %v", err) + } + + sweepTXID := sweepTxPkg.SweepTx.TxHash() + txid = &sweepTXID + } else { + + // We'll now construct out payment map, and use the wallet's + // coin selection synchronization method to ensure that no coin + // selection (funding, sweep alls, other sends) can proceed + // while we instruct the wallet to send this transaction. + paymentMap := map[string]int64{in.Addr: in.Amount} + err := wallet.WithCoinSelectLock(func() error { + newTXID, err := r.sendCoinsOnChain(paymentMap, feePerKw) + if err != nil { + return err + } + + txid = newTXID + + return nil + }) + if err != nil { + return nil, err + } } rpcsLog.Infof("[sendcoins] spend generated txid: %v", txid.String())