Merge pull request #2198 from Roasbeef/sendall-rpc
multi: add ability to sweep all coins in the the wallet to an addr to sendcoins
This commit is contained in:
22 files changed
+1761
-712
No files matched your search
+134
-64
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/lightningnetwork/lnd/signal"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
"github.com/tv42/zbase32"
|
||||
"golang.org/x/net/context"
|
||||
@@ -637,53 +638,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
|
||||
}
|
||||
|
||||
txHash := tx.TxHash()
|
||||
return &txHash, err
|
||||
}
|
||||
|
||||
// determineFeePerKw will determine the fee in sat/kw that should be paid given
|
||||
// an estimator, a confirmation target, and a manual value for sat/byte. A value
|
||||
// is chosen based on the two free parameters as one, or both of them can be
|
||||
// zero.
|
||||
func determineFeePerKw(feeEstimator lnwallet.FeeEstimator, targetConf int32,
|
||||
feePerByte int64) (lnwallet.SatPerKWeight, error) {
|
||||
|
||||
switch {
|
||||
// If the target number of confirmations is set, then we'll use that to
|
||||
// consult our fee estimator for an adequate fee.
|
||||
case targetConf != 0:
|
||||
feePerKw, err := feeEstimator.EstimateFeePerKW(
|
||||
uint32(targetConf),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to query fee "+
|
||||
"estimator: %v", err)
|
||||
}
|
||||
|
||||
return feePerKw, nil
|
||||
|
||||
// If a manual sat/byte fee rate is set, then we'll use that directly.
|
||||
// We'll need to convert it to sat/kw as this is what we use internally.
|
||||
case feePerByte != 0:
|
||||
feePerKW := lnwallet.SatPerKVByte(feePerByte * 1000).FeePerKWeight()
|
||||
if feePerKW < lnwallet.FeePerKwFloor {
|
||||
rpcsLog.Infof("Manual fee rate input of %d sat/kw is "+
|
||||
"too low, using %d sat/kw instead", feePerKW,
|
||||
lnwallet.FeePerKwFloor)
|
||||
feePerKW = lnwallet.FeePerKwFloor
|
||||
}
|
||||
return feePerKW, nil
|
||||
|
||||
// Otherwise, we'll attempt a relaxed confirmation target for the
|
||||
// transaction
|
||||
default:
|
||||
feePerKw, err := feeEstimator.EstimateFeePerKW(6)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to query fee estimator: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
return feePerKw, nil
|
||||
}
|
||||
return &txHash, nil
|
||||
}
|
||||
|
||||
// ListUnspent returns useful information about each unspent output owned by
|
||||
@@ -803,20 +758,100 @@ func (r *rpcServer) SendCoins(ctx context.Context,
|
||||
|
||||
// Based on the passed fee related parameters, we'll determine an
|
||||
// appropriate fee rate for this transaction.
|
||||
feePerKw, err := determineFeePerKw(
|
||||
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
|
||||
satPerKw := lnwallet.SatPerKVByte(in.SatPerByte * 1000).FeePerKWeight()
|
||||
feePerKw, err := sweep.DetermineFeePerKw(
|
||||
r.server.cc.feeEstimator, sweep.FeePreference{
|
||||
ConfTarget: uint32(in.TargetConf),
|
||||
FeeRate: satPerKw,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
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())
|
||||
@@ -831,8 +866,12 @@ func (r *rpcServer) SendMany(ctx context.Context,
|
||||
|
||||
// Based on the passed fee related parameters, we'll determine an
|
||||
// appropriate fee rate for this transaction.
|
||||
feePerKw, err := determineFeePerKw(
|
||||
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
|
||||
satPerKw := lnwallet.SatPerKVByte(in.SatPerByte * 1000).FeePerKWeight()
|
||||
feePerKw, err := sweep.DetermineFeePerKw(
|
||||
r.server.cc.feeEstimator, sweep.FeePreference{
|
||||
ConfTarget: uint32(in.TargetConf),
|
||||
FeeRate: satPerKw,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -841,7 +880,24 @@ func (r *rpcServer) SendMany(ctx context.Context,
|
||||
rpcsLog.Infof("[sendmany] outputs=%v, sat/kw=%v",
|
||||
spew.Sdump(in.AddrToAmount), int64(feePerKw))
|
||||
|
||||
txid, err := r.sendCoinsOnChain(in.AddrToAmount, feePerKw)
|
||||
var txid *chainhash.Hash
|
||||
|
||||
// We'll attempt to send to the target set of outputs, ensuring that we
|
||||
// synchronize with any other ongoing coin selection attempts which
|
||||
// happen to also be concurrently executing.
|
||||
wallet := r.server.cc.wallet
|
||||
err = wallet.WithCoinSelectLock(func() error {
|
||||
sendManyTXID, err := r.sendCoinsOnChain(
|
||||
in.AddrToAmount, feePerKw,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txid = sendManyTXID
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1170,8 +1226,12 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
|
||||
|
||||
// Based on the passed fee related parameters, we'll determine an
|
||||
// appropriate fee rate for the funding transaction.
|
||||
feeRate, err := determineFeePerKw(
|
||||
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
|
||||
satPerKw := lnwallet.SatPerKVByte(in.SatPerByte * 1000).FeePerKWeight()
|
||||
feeRate, err := sweep.DetermineFeePerKw(
|
||||
r.server.cc.feeEstimator, sweep.FeePreference{
|
||||
ConfTarget: uint32(in.TargetConf),
|
||||
FeeRate: satPerKw,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1317,8 +1377,12 @@ func (r *rpcServer) OpenChannelSync(ctx context.Context,
|
||||
|
||||
// Based on the passed fee related parameters, we'll determine an
|
||||
// appropriate fee rate for the funding transaction.
|
||||
feeRate, err := determineFeePerKw(
|
||||
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
|
||||
satPerKw := lnwallet.SatPerKVByte(in.SatPerByte * 1000).FeePerKWeight()
|
||||
feeRate, err := sweep.DetermineFeePerKw(
|
||||
r.server.cc.feeEstimator, sweep.FeePreference{
|
||||
ConfTarget: uint32(in.TargetConf),
|
||||
FeeRate: satPerKw,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1506,8 +1570,14 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
|
||||
// Based on the passed fee related parameters, we'll determine
|
||||
// an appropriate fee rate for the cooperative closure
|
||||
// transaction.
|
||||
feeRate, err := determineFeePerKw(
|
||||
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
|
||||
satPerKw := lnwallet.SatPerKVByte(
|
||||
in.SatPerByte * 1000,
|
||||
).FeePerKWeight()
|
||||
feeRate, err := sweep.DetermineFeePerKw(
|
||||
r.server.cc.feeEstimator, sweep.FeePreference{
|
||||
ConfTarget: uint32(in.TargetConf),
|
||||
FeeRate: satPerKw,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in new issue
Block a user