sweep: allow specified outputs to sweep tx
We'll use this to attach an output for the value reserved for anchor commitments fee bumping if the user requests a send_all transaction.
This commit is contained in:
parent
eaf97418be
commit
185ba77f8e
@ -1256,7 +1256,7 @@ func (r *rpcServer) SendCoins(ctx context.Context,
|
|||||||
// safe manner, so no need to worry about locking.
|
// safe manner, so no need to worry about locking.
|
||||||
sweepTxPkg, err := sweep.CraftSweepAllTx(
|
sweepTxPkg, err := sweep.CraftSweepAllTx(
|
||||||
feePerKw, lnwallet.DefaultDustLimit(),
|
feePerKw, lnwallet.DefaultDustLimit(),
|
||||||
uint32(bestHeight), targetAddr, wallet,
|
uint32(bestHeight), nil, targetAddr, wallet,
|
||||||
wallet.WalletController, wallet.WalletController,
|
wallet.WalletController, wallet.WalletController,
|
||||||
r.server.cc.FeeEstimator, r.server.cc.Signer,
|
r.server.cc.FeeEstimator, r.server.cc.Signer,
|
||||||
)
|
)
|
||||||
|
@ -1192,8 +1192,8 @@ func (s *UtxoSweeper) sweep(inputs inputSet, feeRate chainfee.SatPerKWeight,
|
|||||||
|
|
||||||
// Create sweep tx.
|
// Create sweep tx.
|
||||||
tx, err := createSweepTx(
|
tx, err := createSweepTx(
|
||||||
inputs, s.currentOutputScript, uint32(currentHeight), feeRate,
|
inputs, nil, s.currentOutputScript, uint32(currentHeight),
|
||||||
dustLimit(s.relayFeeRate), s.cfg.Signer,
|
feeRate, dustLimit(s.relayFeeRate), s.cfg.Signer,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create sweep tx: %v", err)
|
return fmt.Errorf("create sweep tx: %v", err)
|
||||||
@ -1487,7 +1487,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []input.Input, feePref FeePreference,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return createSweepTx(
|
return createSweepTx(
|
||||||
inputs, pkScript, currentBlockHeight, feePerKw,
|
inputs, nil, pkScript, currentBlockHeight, feePerKw,
|
||||||
dustLimit(s.relayFeeRate), s.cfg.Signer,
|
dustLimit(s.relayFeeRate), s.cfg.Signer,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -354,7 +354,7 @@ func assertTxFeeRate(t *testing.T, tx *wire.MsgTx,
|
|||||||
outputAmt := tx.TxOut[0].Value
|
outputAmt := tx.TxOut[0].Value
|
||||||
|
|
||||||
fee := btcutil.Amount(inputAmt - outputAmt)
|
fee := btcutil.Amount(inputAmt - outputAmt)
|
||||||
_, estimator := getWeightEstimate(inputs, 0)
|
_, estimator := getWeightEstimate(inputs, nil, 0)
|
||||||
txWeight := estimator.weight()
|
txWeight := estimator.weight()
|
||||||
|
|
||||||
expectedFee := expectedFeeRate.FeeForWeight(int64(txWeight))
|
expectedFee := expectedFeeRate.FeeForWeight(int64(txWeight))
|
||||||
|
@ -131,12 +131,14 @@ func generateInputPartitionings(sweepableInputs []txInput,
|
|||||||
return sets, nil
|
return sets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSweepTx builds a signed tx spending the inputs to a the output script.
|
// createSweepTx builds a signed tx spending the inputs to the given outputs,
|
||||||
func createSweepTx(inputs []input.Input, outputPkScript []byte,
|
// sending any leftover change to the change script.
|
||||||
currentBlockHeight uint32, feePerKw chainfee.SatPerKWeight,
|
func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
|
||||||
dustLimit btcutil.Amount, signer input.Signer) (*wire.MsgTx, error) {
|
changePkScript []byte, currentBlockHeight uint32,
|
||||||
|
feePerKw chainfee.SatPerKWeight, dustLimit btcutil.Amount,
|
||||||
|
signer input.Signer) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
inputs, estimator := getWeightEstimate(inputs, feePerKw)
|
inputs, estimator := getWeightEstimate(inputs, outputs, feePerKw)
|
||||||
txFee := estimator.fee()
|
txFee := estimator.fee()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -210,6 +212,16 @@ func createSweepTx(inputs []input.Input, outputPkScript []byte,
|
|||||||
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
|
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the outputs given, if any.
|
||||||
|
for _, o := range outputs {
|
||||||
|
sweepTx.AddTxOut(o)
|
||||||
|
requiredOutput += btcutil.Amount(o.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if requiredOutput+txFee > totalInput {
|
||||||
|
return nil, fmt.Errorf("insufficient input to create sweep tx")
|
||||||
|
}
|
||||||
|
|
||||||
// The value remaining after the required output and fees, go to
|
// The value remaining after the required output and fees, go to
|
||||||
// change. Not that this fee is what we would have to pay in case the
|
// change. Not that this fee is what we would have to pay in case the
|
||||||
// sweep tx has a change output.
|
// sweep tx has a change output.
|
||||||
@ -219,7 +231,7 @@ func createSweepTx(inputs []input.Input, outputPkScript []byte,
|
|||||||
// above.
|
// above.
|
||||||
if changeAmt >= dustLimit {
|
if changeAmt >= dustLimit {
|
||||||
sweepTx.AddTxOut(&wire.TxOut{
|
sweepTx.AddTxOut(&wire.TxOut{
|
||||||
PkScript: outputPkScript,
|
PkScript: changePkScript,
|
||||||
Value: int64(changeAmt),
|
Value: int64(changeAmt),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -287,8 +299,8 @@ func createSweepTx(inputs []input.Input, outputPkScript []byte,
|
|||||||
|
|
||||||
// getWeightEstimate returns a weight estimate for the given inputs.
|
// getWeightEstimate returns a weight estimate for the given inputs.
|
||||||
// Additionally, it returns counts for the number of csv and cltv inputs.
|
// Additionally, it returns counts for the number of csv and cltv inputs.
|
||||||
func getWeightEstimate(inputs []input.Input, feeRate chainfee.SatPerKWeight) (
|
func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut,
|
||||||
[]input.Input, *weightEstimator) {
|
feeRate chainfee.SatPerKWeight) ([]input.Input, *weightEstimator) {
|
||||||
|
|
||||||
// We initialize a weight estimator so we can accurately asses the
|
// We initialize a weight estimator so we can accurately asses the
|
||||||
// amount of fees we need to pay for this sweep transaction.
|
// amount of fees we need to pay for this sweep transaction.
|
||||||
@ -297,13 +309,19 @@ func getWeightEstimate(inputs []input.Input, feeRate chainfee.SatPerKWeight) (
|
|||||||
// be more efficient on-chain.
|
// be more efficient on-chain.
|
||||||
weightEstimate := newWeightEstimator(feeRate)
|
weightEstimate := newWeightEstimator(feeRate)
|
||||||
|
|
||||||
// Our sweep transaction will pay to a single segwit p2wkh address,
|
// Our sweep transaction will always pay to the given set of outputs.
|
||||||
// ensure it contributes to our weight estimate. If the inputs we add
|
for _, o := range outputs {
|
||||||
// have required TxOuts, then this will be our change address. Note
|
weightEstimate.addOutput(o)
|
||||||
// that if we have required TxOuts, we might end up creating a sweep tx
|
}
|
||||||
// without a change output. It is okay to add the change output to the
|
|
||||||
// weight estimate regardless, since the estimated fee will just be
|
// If there is any leftover change after paying to the given outputs
|
||||||
// subtracted from this already dust output, and trimmed.
|
// and required outputs, it will go to a single segwit p2wkh address.
|
||||||
|
// This will be our change address, so ensure it contributes to our
|
||||||
|
// weight estimate. Note that if we have other outputs, we might end up
|
||||||
|
// creating a sweep tx without a change output. It is okay to add the
|
||||||
|
// change output to the weight estimate regardless, since the estimated
|
||||||
|
// fee will just be subtracted from this already dust output, and
|
||||||
|
// trimmed.
|
||||||
weightEstimate.addP2WKHOutput()
|
weightEstimate.addP2WKHOutput()
|
||||||
|
|
||||||
// For each output, use its witness type to determine the estimate
|
// For each output, use its witness type to determine the estimate
|
||||||
|
@ -39,7 +39,7 @@ func TestWeightEstimate(t *testing.T) {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, estimator := getWeightEstimate(inputs, 0)
|
_, estimator := getWeightEstimate(inputs, nil, 0)
|
||||||
weight := int64(estimator.weight())
|
weight := int64(estimator.weight())
|
||||||
if weight != expectedWeight {
|
if weight != expectedWeight {
|
||||||
t.Fatalf("unexpected weight. expected %d but got %d.",
|
t.Fatalf("unexpected weight. expected %d but got %d.",
|
||||||
|
@ -148,13 +148,24 @@ type WalletSweepPackage struct {
|
|||||||
CancelSweepAttempt func()
|
CancelSweepAttempt func()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeliveryAddr is a pair of (address, amount) used to craft a transaction
|
||||||
|
// paying to more than one specified address.
|
||||||
|
type DeliveryAddr struct {
|
||||||
|
// Addr is the address to pay to.
|
||||||
|
Addr btcutil.Address
|
||||||
|
|
||||||
|
// Amt is the amount to pay to the given address.
|
||||||
|
Amt btcutil.Amount
|
||||||
|
}
|
||||||
|
|
||||||
// CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the
|
// CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the
|
||||||
// caller to sweep ALL outputs within the wallet to a single UTXO, as specified
|
// caller to sweep ALL outputs within the wallet to a list of outputs. Any
|
||||||
// by the delivery address. The sweep transaction will be crafted with the
|
// leftover amount after these outputs and transaction fee, is sent to a single
|
||||||
// target fee rate, and will use the utxoSource and outpointLocker as sources
|
// output, as specified by the change address. The sweep transaction will be
|
||||||
// for wallet funds.
|
// crafted with the target fee rate, and will use the utxoSource and
|
||||||
|
// outpointLocker as sources for wallet funds.
|
||||||
func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount,
|
func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount,
|
||||||
blockHeight uint32, deliveryAddr btcutil.Address,
|
blockHeight uint32, deliveryAddrs []DeliveryAddr, changeAddr btcutil.Address,
|
||||||
coinSelectLocker CoinSelectionLocker, utxoSource UtxoSource,
|
coinSelectLocker CoinSelectionLocker, utxoSource UtxoSource,
|
||||||
outpointLocker OutpointLocker, feeEstimator chainfee.Estimator,
|
outpointLocker OutpointLocker, feeEstimator chainfee.Estimator,
|
||||||
signer input.Signer) (*WalletSweepPackage, error) {
|
signer input.Signer) (*WalletSweepPackage, error) {
|
||||||
@ -261,9 +272,25 @@ func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount,
|
|||||||
inputsToSweep = append(inputsToSweep, &input)
|
inputsToSweep = append(inputsToSweep, &input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, we'll convert the delivery addr to a pkScript that we can use
|
// Create a list of TxOuts from the given delivery addresses.
|
||||||
|
var txOuts []*wire.TxOut
|
||||||
|
for _, d := range deliveryAddrs {
|
||||||
|
pkScript, err := txscript.PayToAddrScript(d.Addr)
|
||||||
|
if err != nil {
|
||||||
|
unlockOutputs()
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txOuts = append(txOuts, &wire.TxOut{
|
||||||
|
PkScript: pkScript,
|
||||||
|
Value: int64(d.Amt),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, we'll convert the change addr to a pkScript that we can use
|
||||||
// to create the sweep transaction.
|
// to create the sweep transaction.
|
||||||
deliveryPkScript, err := txscript.PayToAddrScript(deliveryAddr)
|
changePkScript, err := txscript.PayToAddrScript(changeAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unlockOutputs()
|
unlockOutputs()
|
||||||
|
|
||||||
@ -273,7 +300,7 @@ func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount,
|
|||||||
// Finally, we'll ask the sweeper to craft a sweep transaction which
|
// Finally, we'll ask the sweeper to craft a sweep transaction which
|
||||||
// respects our fee preference and targets all the UTXOs of the wallet.
|
// respects our fee preference and targets all the UTXOs of the wallet.
|
||||||
sweepTx, err := createSweepTx(
|
sweepTx, err := createSweepTx(
|
||||||
inputsToSweep, deliveryPkScript, blockHeight, feeRate,
|
inputsToSweep, txOuts, changePkScript, blockHeight, feeRate,
|
||||||
dustLimit, signer,
|
dustLimit, signer,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -288,7 +288,8 @@ func TestCraftSweepAllTxCoinSelectFail(t *testing.T) {
|
|||||||
utxoLocker := newMockOutpointLocker()
|
utxoLocker := newMockOutpointLocker()
|
||||||
|
|
||||||
_, err := CraftSweepAllTx(
|
_, err := CraftSweepAllTx(
|
||||||
0, 100, 10, nil, coinSelectLocker, utxoSource, utxoLocker, nil, nil,
|
0, 100, 10, nil, nil, coinSelectLocker, utxoSource,
|
||||||
|
utxoLocker, nil, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Since we instructed the coin select locker to fail above, we should
|
// Since we instructed the coin select locker to fail above, we should
|
||||||
@ -313,7 +314,8 @@ func TestCraftSweepAllTxUnknownWitnessType(t *testing.T) {
|
|||||||
utxoLocker := newMockOutpointLocker()
|
utxoLocker := newMockOutpointLocker()
|
||||||
|
|
||||||
_, err := CraftSweepAllTx(
|
_, err := CraftSweepAllTx(
|
||||||
0, 100, 10, nil, coinSelectLocker, utxoSource, utxoLocker, nil, nil,
|
0, 100, 10, nil, nil, coinSelectLocker, utxoSource,
|
||||||
|
utxoLocker, nil, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Since passed in a p2wsh output, which is unknown, we should fail to
|
// Since passed in a p2wsh output, which is unknown, we should fail to
|
||||||
@ -347,8 +349,8 @@ func TestCraftSweepAllTx(t *testing.T) {
|
|||||||
utxoLocker := newMockOutpointLocker()
|
utxoLocker := newMockOutpointLocker()
|
||||||
|
|
||||||
sweepPkg, err := CraftSweepAllTx(
|
sweepPkg, err := CraftSweepAllTx(
|
||||||
0, 100, 10, deliveryAddr, coinSelectLocker, utxoSource, utxoLocker,
|
0, 100, 10, nil, deliveryAddr, coinSelectLocker, utxoSource,
|
||||||
feeEstimator, signer,
|
utxoLocker, feeEstimator, signer,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to make sweep tx: %v", err)
|
t.Fatalf("unable to make sweep tx: %v", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user