Merge pull request #1978 from joostjager/sweeper-prep
sweep: prepare for sweeper
This commit is contained in:
commit
c9e42a6ce0
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -748,33 +749,6 @@ func (b *breachArbiter) handleBreachHandoff(breachEvent *ContractBreachEvent) {
|
|||||||
go b.exactRetribution(cfChan, retInfo)
|
go b.exactRetribution(cfChan, retInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpendableOutput an interface which can be used by the breach arbiter to
|
|
||||||
// construct a transaction spending from outputs we control.
|
|
||||||
type SpendableOutput interface {
|
|
||||||
// Amount returns the number of satoshis contained within the output.
|
|
||||||
Amount() btcutil.Amount
|
|
||||||
|
|
||||||
// Outpoint returns the reference to the output being spent, used to
|
|
||||||
// construct the corresponding transaction input.
|
|
||||||
OutPoint() *wire.OutPoint
|
|
||||||
|
|
||||||
// WitnessType returns an enum specifying the type of witness that must
|
|
||||||
// be generated in order to spend this output.
|
|
||||||
WitnessType() lnwallet.WitnessType
|
|
||||||
|
|
||||||
// SignDesc returns a reference to a spendable output's sign descriptor,
|
|
||||||
// which is used during signing to compute a valid witness that spends
|
|
||||||
// this output.
|
|
||||||
SignDesc() *lnwallet.SignDescriptor
|
|
||||||
|
|
||||||
// BuildWitness returns a valid witness allowing this output to be
|
|
||||||
// spent, the witness should be attached to the transaction at the
|
|
||||||
// location determined by the given `txinIdx`.
|
|
||||||
BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
|
|
||||||
hashCache *txscript.TxSigHashes,
|
|
||||||
txinIdx int) ([][]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// breachedOutput contains all the information needed to sweep a breached
|
// breachedOutput contains all the information needed to sweep a breached
|
||||||
// output. A breached output is an output that we are now entitled to due to a
|
// output. A breached output is an output that we are now entitled to due to a
|
||||||
// revoked commitment transaction being broadcast.
|
// revoked commitment transaction being broadcast.
|
||||||
@ -850,9 +824,16 @@ func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
|
|||||||
return bo.witnessFunc(txn, hashCache, txinIdx)
|
return bo.witnessFunc(txn, hashCache, txinIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add compile-time constraint ensuring breachedOutput implements
|
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
|
||||||
// SpendableOutput.
|
// must be built on top of the confirmation height before the output can be
|
||||||
var _ SpendableOutput = (*breachedOutput)(nil)
|
// spent.
|
||||||
|
func (bo *breachedOutput) BlocksToMaturity() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add compile-time constraint ensuring breachedOutput implements the Input
|
||||||
|
// interface.
|
||||||
|
var _ sweep.Input = (*breachedOutput)(nil)
|
||||||
|
|
||||||
// retributionInfo encapsulates all the data needed to sweep all the contested
|
// retributionInfo encapsulates all the data needed to sweep all the contested
|
||||||
// funds within a channel whose contract has been breached by the prior
|
// funds within a channel whose contract has been breached by the prior
|
||||||
@ -963,13 +944,13 @@ func (b *breachArbiter) createJusticeTx(
|
|||||||
// outputs, while simultaneously computing the estimated weight of the
|
// outputs, while simultaneously computing the estimated weight of the
|
||||||
// transaction.
|
// transaction.
|
||||||
var (
|
var (
|
||||||
spendableOutputs []SpendableOutput
|
spendableOutputs []sweep.Input
|
||||||
weightEstimate lnwallet.TxWeightEstimator
|
weightEstimate lnwallet.TxWeightEstimator
|
||||||
)
|
)
|
||||||
|
|
||||||
// Allocate enough space to potentially hold each of the breached
|
// Allocate enough space to potentially hold each of the breached
|
||||||
// outputs in the retribution info.
|
// outputs in the retribution info.
|
||||||
spendableOutputs = make([]SpendableOutput, 0, len(r.breachedOutputs))
|
spendableOutputs = make([]sweep.Input, 0, len(r.breachedOutputs))
|
||||||
|
|
||||||
// The justice transaction we construct will be a segwit transaction
|
// The justice transaction we construct will be a segwit transaction
|
||||||
// that pays to a p2wkh output. Components such as the version,
|
// that pays to a p2wkh output. Components such as the version,
|
||||||
@ -1023,7 +1004,7 @@ func (b *breachArbiter) createJusticeTx(
|
|||||||
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
|
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
|
||||||
// spendable outputs by sweeping the funds into a single p2wkh output.
|
// spendable outputs by sweeping the funds into a single p2wkh output.
|
||||||
func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
|
func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
|
||||||
inputs ...SpendableOutput) (*wire.MsgTx, error) {
|
inputs ...sweep.Input) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
// First, we obtain a new public key script from the wallet which we'll
|
// First, we obtain a new public key script from the wallet which we'll
|
||||||
// sweep the funds to.
|
// sweep the funds to.
|
||||||
@ -1037,7 +1018,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
|
|||||||
// Compute the total amount contained in the inputs.
|
// Compute the total amount contained in the inputs.
|
||||||
var totalAmt btcutil.Amount
|
var totalAmt btcutil.Amount
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
totalAmt += input.Amount()
|
totalAmt += btcutil.Amount(input.SignDesc().Output.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll actually attempt to target inclusion within the next two
|
// We'll actually attempt to target inclusion within the next two
|
||||||
@ -1085,7 +1066,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
|
|||||||
// witness, and attaching it to the transaction. This function accepts
|
// witness, and attaching it to the transaction. This function accepts
|
||||||
// an integer index representing the intended txin index, and the
|
// an integer index representing the intended txin index, and the
|
||||||
// breached output from which it will spend.
|
// breached output from which it will spend.
|
||||||
addWitness := func(idx int, so SpendableOutput) error {
|
addWitness := func(idx int, so sweep.Input) error {
|
||||||
// First, we construct a valid witness for this outpoint and
|
// First, we construct a valid witness for this outpoint and
|
||||||
// transaction using the SpendableOutput's witness generation
|
// transaction using the SpendableOutput's witness generation
|
||||||
// function.
|
// function.
|
||||||
|
@ -3,6 +3,7 @@ package contractcourt
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@ -130,6 +131,9 @@ type ChainArbitratorConfig struct {
|
|||||||
// DisableChannel disables a channel, resulting in it not being able to
|
// DisableChannel disables a channel, resulting in it not being able to
|
||||||
// forward payments.
|
// forward payments.
|
||||||
DisableChannel func(wire.OutPoint) error
|
DisableChannel func(wire.OutPoint) error
|
||||||
|
|
||||||
|
// Sweeper allows resolvers to sweep their final outputs.
|
||||||
|
Sweeper *sweep.UtxoSweeper
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
||||||
|
@ -5,10 +5,10 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
@ -446,59 +446,19 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
|||||||
"incoming+remote htlc confirmed", h,
|
"incoming+remote htlc confirmed", h,
|
||||||
h.payHash[:])
|
h.payHash[:])
|
||||||
|
|
||||||
// In this case, we can sweep it directly from the
|
input := sweep.MakeHtlcSucceedInput(
|
||||||
// commitment output. We'll first grab a fresh address
|
&h.htlcResolution.ClaimOutpoint,
|
||||||
// from the wallet to sweep the output.
|
&h.htlcResolution.SweepSignDesc,
|
||||||
addr, err := h.NewSweepAddr()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// With our address obtained, we'll query for an
|
|
||||||
// estimate to be confirmed at ease.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): signal up if fee would be too large
|
|
||||||
// to sweep singly, need to batch
|
|
||||||
feePerKw, err := h.FeeEstimator.EstimateFeePerKW(6)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("%T(%x): using %v sat/kw to sweep htlc"+
|
|
||||||
"incoming+remote htlc confirmed", h,
|
|
||||||
h.payHash[:], int64(feePerKw))
|
|
||||||
|
|
||||||
// Using a weight estimator, we'll compute the total
|
|
||||||
// fee required, and from that the value we'll end up
|
|
||||||
// with.
|
|
||||||
totalWeight := (&lnwallet.TxWeightEstimator{}).
|
|
||||||
AddWitnessInput(lnwallet.OfferedHtlcSuccessWitnessSize).
|
|
||||||
AddP2WKHOutput().Weight()
|
|
||||||
totalFees := feePerKw.FeeForWeight(int64(totalWeight))
|
|
||||||
sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value -
|
|
||||||
int64(totalFees)
|
|
||||||
|
|
||||||
// With the fee computation finished, we'll now
|
|
||||||
// construct the sweep transaction.
|
|
||||||
htlcPoint := h.htlcResolution.ClaimOutpoint
|
|
||||||
h.sweepTx = wire.NewMsgTx(2)
|
|
||||||
h.sweepTx.AddTxIn(&wire.TxIn{
|
|
||||||
PreviousOutPoint: htlcPoint,
|
|
||||||
})
|
|
||||||
h.sweepTx.AddTxOut(&wire.TxOut{
|
|
||||||
PkScript: addr,
|
|
||||||
Value: sweepAmt,
|
|
||||||
})
|
|
||||||
|
|
||||||
// With the transaction fully assembled, we can now
|
|
||||||
// generate a valid witness for the transaction.
|
|
||||||
h.htlcResolution.SweepSignDesc.SigHashes = txscript.NewTxSigHashes(
|
|
||||||
h.sweepTx,
|
|
||||||
)
|
|
||||||
h.sweepTx.TxIn[0].Witness, err = lnwallet.SenderHtlcSpendRedeem(
|
|
||||||
h.Signer, &h.htlcResolution.SweepSignDesc, h.sweepTx,
|
|
||||||
h.htlcResolution.Preimage[:],
|
h.htlcResolution.Preimage[:],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// TODO: Set tx lock time to current block height instead of
|
||||||
|
// zero. Will be taken care of once sweeper implementation is
|
||||||
|
// complete.
|
||||||
|
h.sweepTx, err = h.Sweeper.CreateSweepTx(
|
||||||
|
[]sweep.Input{&input}, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1256,46 +1216,16 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
|
|||||||
// If the sweep transaction isn't already generated, and the remote
|
// If the sweep transaction isn't already generated, and the remote
|
||||||
// party broadcast the commitment transaction then we'll create it now.
|
// party broadcast the commitment transaction then we'll create it now.
|
||||||
case c.sweepTx == nil && !isLocalCommitTx:
|
case c.sweepTx == nil && !isLocalCommitTx:
|
||||||
// Now that the commitment transaction has confirmed, we'll
|
input := sweep.MakeBaseInput(
|
||||||
// craft a transaction to sweep this output into the wallet.
|
&c.commitResolution.SelfOutPoint,
|
||||||
signDesc := c.commitResolution.SelfOutputSignDesc
|
lnwallet.CommitmentNoDelay,
|
||||||
|
&c.commitResolution.SelfOutputSignDesc)
|
||||||
|
|
||||||
// First, we'll estimate the total weight so we can compute
|
// TODO: Set tx lock time to current block height instead of
|
||||||
// fees properly. We'll use a lax estimate, as this output is
|
// zero. Will be taken care of once sweeper implementation is
|
||||||
// in no immediate danger.
|
// complete.
|
||||||
feePerKw, err := c.FeeEstimator.EstimateFeePerKW(6)
|
c.sweepTx, err = c.Sweeper.CreateSweepTx(
|
||||||
if err != nil {
|
[]sweep.Input{&input}, 0)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("%T(%v): using %v sat/kw for sweep tx", c,
|
|
||||||
c.chanPoint, int64(feePerKw))
|
|
||||||
|
|
||||||
totalWeight := (&lnwallet.TxWeightEstimator{}).
|
|
||||||
AddP2WKHInput().
|
|
||||||
AddP2WKHOutput().Weight()
|
|
||||||
totalFees := feePerKw.FeeForWeight(int64(totalWeight))
|
|
||||||
sweepAmt := signDesc.Output.Value - int64(totalFees)
|
|
||||||
|
|
||||||
c.sweepTx = wire.NewMsgTx(2)
|
|
||||||
c.sweepTx.AddTxIn(&wire.TxIn{
|
|
||||||
PreviousOutPoint: c.commitResolution.SelfOutPoint,
|
|
||||||
})
|
|
||||||
sweepAddr, err := c.NewSweepAddr()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.sweepTx.AddTxOut(&wire.TxOut{
|
|
||||||
PkScript: sweepAddr,
|
|
||||||
Value: sweepAmt,
|
|
||||||
})
|
|
||||||
|
|
||||||
// With the transaction fully assembled, we can now generate a
|
|
||||||
// valid witness for the transaction.
|
|
||||||
signDesc.SigHashes = txscript.NewTxSigHashes(c.sweepTx)
|
|
||||||
c.sweepTx.TxIn[0].Witness, err = lnwallet.CommitSpendNoDelay(
|
|
||||||
c.Signer, &signDesc, c.sweepTx,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
10
log.go
10
log.go
@ -1,11 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/connmgr"
|
"github.com/btcsuite/btcd/connmgr"
|
||||||
@ -24,6 +22,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/routing"
|
"github.com/lightningnetwork/lnd/routing"
|
||||||
"github.com/lightningnetwork/lnd/signal"
|
"github.com/lightningnetwork/lnd/signal"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Loggers per subsystem. A single backend logger is created and all subsystem
|
// Loggers per subsystem. A single backend logger is created and all subsystem
|
||||||
@ -65,6 +64,7 @@ var (
|
|||||||
atplLog = build.NewSubLogger("ATPL", backendLog.Logger)
|
atplLog = build.NewSubLogger("ATPL", backendLog.Logger)
|
||||||
cnctLog = build.NewSubLogger("CNCT", backendLog.Logger)
|
cnctLog = build.NewSubLogger("CNCT", backendLog.Logger)
|
||||||
sphxLog = build.NewSubLogger("SPHX", backendLog.Logger)
|
sphxLog = build.NewSubLogger("SPHX", backendLog.Logger)
|
||||||
|
swprLog = build.NewSubLogger("SWPR", backendLog.Logger)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize package-global logger variables.
|
// Initialize package-global logger variables.
|
||||||
@ -81,6 +81,7 @@ func init() {
|
|||||||
contractcourt.UseLogger(cnctLog)
|
contractcourt.UseLogger(cnctLog)
|
||||||
sphinx.UseLogger(sphxLog)
|
sphinx.UseLogger(sphxLog)
|
||||||
signal.UseLogger(ltndLog)
|
signal.UseLogger(ltndLog)
|
||||||
|
sweep.UseLogger(swprLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
||||||
@ -103,6 +104,7 @@ var subsystemLoggers = map[string]btclog.Logger{
|
|||||||
"ATPL": atplLog,
|
"ATPL": atplLog,
|
||||||
"CNCT": cnctLog,
|
"CNCT": cnctLog,
|
||||||
"SPHX": sphxLog,
|
"SPHX": sphxLog,
|
||||||
|
"SWPR": swprLog,
|
||||||
}
|
}
|
||||||
|
|
||||||
// initLogRotator initializes the logging rotator to write logs to logFile and
|
// initLogRotator initializes the logging rotator to write logs to logFile and
|
||||||
|
21
server.go
21
server.go
@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
@ -58,6 +59,10 @@ const (
|
|||||||
// durations exceeding this value will be eligible to have their
|
// durations exceeding this value will be eligible to have their
|
||||||
// backoffs reduced.
|
// backoffs reduced.
|
||||||
defaultStableConnDuration = 10 * time.Minute
|
defaultStableConnDuration = 10 * time.Minute
|
||||||
|
|
||||||
|
// sweepTxConfirmationTarget assigns a confirmation target for sweep
|
||||||
|
// txes on which the fee calculation will be based.
|
||||||
|
sweepTxConfirmationTarget = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -581,19 +586,24 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sweeper := sweep.New(&sweep.UtxoSweeperConfig{
|
||||||
|
Estimator: cc.feeEstimator,
|
||||||
|
GenSweepScript: func() ([]byte, error) {
|
||||||
|
return newSweepPkScript(cc.wallet)
|
||||||
|
},
|
||||||
|
Signer: cc.wallet.Cfg.Signer,
|
||||||
|
ConfTarget: sweepTxConfirmationTarget,
|
||||||
|
})
|
||||||
|
|
||||||
s.utxoNursery = newUtxoNursery(&NurseryConfig{
|
s.utxoNursery = newUtxoNursery(&NurseryConfig{
|
||||||
ChainIO: cc.chainIO,
|
ChainIO: cc.chainIO,
|
||||||
ConfDepth: 1,
|
ConfDepth: 1,
|
||||||
FetchClosedChannels: chanDB.FetchClosedChannels,
|
FetchClosedChannels: chanDB.FetchClosedChannels,
|
||||||
FetchClosedChannel: chanDB.FetchClosedChannel,
|
FetchClosedChannel: chanDB.FetchClosedChannel,
|
||||||
Estimator: cc.feeEstimator,
|
|
||||||
GenSweepScript: func() ([]byte, error) {
|
|
||||||
return newSweepPkScript(cc.wallet)
|
|
||||||
},
|
|
||||||
Notifier: cc.chainNotifier,
|
Notifier: cc.chainNotifier,
|
||||||
PublishTransaction: cc.wallet.PublishTransaction,
|
PublishTransaction: cc.wallet.PublishTransaction,
|
||||||
Signer: cc.wallet.Cfg.Signer,
|
|
||||||
Store: utxnStore,
|
Store: utxnStore,
|
||||||
|
Sweeper: sweeper,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Construct a closure that wraps the htlcswitch's CloseLink method.
|
// Construct a closure that wraps the htlcswitch's CloseLink method.
|
||||||
@ -685,6 +695,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
DisableChannel: func(op wire.OutPoint) error {
|
DisableChannel: func(op wire.OutPoint) error {
|
||||||
return s.announceChanStatus(op, true)
|
return s.announceChanStatus(op, true)
|
||||||
},
|
},
|
||||||
|
Sweeper: sweeper,
|
||||||
}, chanDB)
|
}, chanDB)
|
||||||
|
|
||||||
s.breachArbiter = newBreachArbiter(&BreachConfig{
|
s.breachArbiter = newBreachArbiter(&BreachConfig{
|
||||||
|
154
sweep/input.go
Normal file
154
sweep/input.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package sweep
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Input contains all data needed to construct a sweep tx input.
|
||||||
|
type Input interface {
|
||||||
|
// Outpoint returns the reference to the output being spent, used to
|
||||||
|
// construct the corresponding transaction input.
|
||||||
|
OutPoint() *wire.OutPoint
|
||||||
|
|
||||||
|
// WitnessType returns an enum specifying the type of witness that must
|
||||||
|
// be generated in order to spend this output.
|
||||||
|
WitnessType() lnwallet.WitnessType
|
||||||
|
|
||||||
|
// SignDesc returns a reference to a spendable output's sign descriptor,
|
||||||
|
// which is used during signing to compute a valid witness that spends
|
||||||
|
// this output.
|
||||||
|
SignDesc() *lnwallet.SignDescriptor
|
||||||
|
|
||||||
|
// BuildWitness returns a valid witness allowing this output to be
|
||||||
|
// spent, the witness should be attached to the transaction at the
|
||||||
|
// location determined by the given `txinIdx`.
|
||||||
|
BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
|
||||||
|
hashCache *txscript.TxSigHashes,
|
||||||
|
txinIdx int) ([][]byte, error)
|
||||||
|
|
||||||
|
// BlocksToMaturity returns the relative timelock, as a number of
|
||||||
|
// blocks, that must be built on top of the confirmation height before
|
||||||
|
// the output can be spent. For non-CSV locked inputs this is always
|
||||||
|
// zero.
|
||||||
|
BlocksToMaturity() uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type inputKit struct {
|
||||||
|
outpoint wire.OutPoint
|
||||||
|
witnessType lnwallet.WitnessType
|
||||||
|
signDesc lnwallet.SignDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutPoint returns the breached output's identifier that is to be included as a
|
||||||
|
// transaction input.
|
||||||
|
func (i *inputKit) OutPoint() *wire.OutPoint {
|
||||||
|
return &i.outpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// WitnessType returns the type of witness that must be generated to spend the
|
||||||
|
// breached output.
|
||||||
|
func (i *inputKit) WitnessType() lnwallet.WitnessType {
|
||||||
|
return i.witnessType
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignDesc returns the breached output's SignDescriptor, which is used during
|
||||||
|
// signing to compute the witness.
|
||||||
|
func (i *inputKit) SignDesc() *lnwallet.SignDescriptor {
|
||||||
|
return &i.signDesc
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseInput contains all the information needed to sweep a basic output
|
||||||
|
// (CSV/CLTV/no time lock)
|
||||||
|
type BaseInput struct {
|
||||||
|
inputKit
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeBaseInput assembles a new BaseInput that can be used to construct a
|
||||||
|
// sweep transaction.
|
||||||
|
func MakeBaseInput(outpoint *wire.OutPoint,
|
||||||
|
witnessType lnwallet.WitnessType,
|
||||||
|
signDescriptor *lnwallet.SignDescriptor) BaseInput {
|
||||||
|
|
||||||
|
return BaseInput{
|
||||||
|
inputKit{
|
||||||
|
outpoint: *outpoint,
|
||||||
|
witnessType: witnessType,
|
||||||
|
signDesc: *signDescriptor,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildWitness computes a valid witness that allows us to spend from the
|
||||||
|
// breached output. It does so by generating the witness generation function,
|
||||||
|
// which is parameterized primarily by the witness type and sign descriptor. The
|
||||||
|
// method then returns the witness computed by invoking this function.
|
||||||
|
func (bi *BaseInput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
|
||||||
|
hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) {
|
||||||
|
|
||||||
|
witnessFunc := bi.witnessType.GenWitnessFunc(
|
||||||
|
signer, bi.SignDesc(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return witnessFunc(txn, hashCache, txinIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
|
||||||
|
// must be built on top of the confirmation height before the output can be
|
||||||
|
// spent. For non-CSV locked inputs this is always zero.
|
||||||
|
func (bi *BaseInput) BlocksToMaturity() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input
|
||||||
|
// is expected to reside on the commitment tx of the remote party and should not
|
||||||
|
// be a second level tx output.
|
||||||
|
type HtlcSucceedInput struct {
|
||||||
|
inputKit
|
||||||
|
|
||||||
|
preimage []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeHtlcSucceedInput assembles a new redeem input that can be used to
|
||||||
|
// construct a sweep transaction.
|
||||||
|
func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
|
||||||
|
signDescriptor *lnwallet.SignDescriptor,
|
||||||
|
preimage []byte) HtlcSucceedInput {
|
||||||
|
|
||||||
|
return HtlcSucceedInput{
|
||||||
|
inputKit: inputKit{
|
||||||
|
outpoint: *outpoint,
|
||||||
|
witnessType: lnwallet.HtlcAcceptedRemoteSuccess,
|
||||||
|
signDesc: *signDescriptor,
|
||||||
|
},
|
||||||
|
preimage: preimage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildWitness computes a valid witness that allows us to spend from the
|
||||||
|
// breached output. For HtlcSpendInput it will need to make the preimage part of
|
||||||
|
// the witness.
|
||||||
|
func (h *HtlcSucceedInput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
|
||||||
|
hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) {
|
||||||
|
|
||||||
|
desc := h.signDesc
|
||||||
|
desc.SigHashes = hashCache
|
||||||
|
desc.InputIndex = txinIdx
|
||||||
|
|
||||||
|
return lnwallet.SenderHtlcSpendRedeem(
|
||||||
|
signer, &desc, txn,
|
||||||
|
h.preimage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
|
||||||
|
// must be built on top of the confirmation height before the output can be
|
||||||
|
// spent.
|
||||||
|
func (h *HtlcSucceedInput) BlocksToMaturity() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add compile-time constraint ensuring input structs implement Input interface.
|
||||||
|
var _ Input = (*BaseInput)(nil)
|
||||||
|
var _ Input = (*HtlcSucceedInput)(nil)
|
45
sweep/log.go
Normal file
45
sweep/log.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package sweep
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btclog"
|
||||||
|
"github.com/lightningnetwork/lnd/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
// log is a logger that is initialized with no output filters. This
|
||||||
|
// means the package will not perform any logging by default until the caller
|
||||||
|
// requests it.
|
||||||
|
var log btclog.Logger
|
||||||
|
|
||||||
|
// The default amount of logging is none.
|
||||||
|
func init() {
|
||||||
|
UseLogger(build.NewSubLogger("SWPR", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableLog disables all library log output. Logging output is disabled
|
||||||
|
// by default until UseLogger is called.
|
||||||
|
func DisableLog() {
|
||||||
|
UseLogger(btclog.Disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger uses a specified Logger to output package logging info.
|
||||||
|
// This should be used in preference to SetLogWriter if the caller is also
|
||||||
|
// using btclog.
|
||||||
|
func UseLogger(logger btclog.Logger) {
|
||||||
|
log = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// logClosure is used to provide a closure over expensive logging operations so
|
||||||
|
// don't have to be performed when the logging level doesn't warrant it.
|
||||||
|
type logClosure func() string
|
||||||
|
|
||||||
|
// String invokes the underlying function and returns the result.
|
||||||
|
func (c logClosure) String() string {
|
||||||
|
return c()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLogClosure returns a new closure over a function that returns a string
|
||||||
|
// which itself provides a Stringer interface so that it can be used with the
|
||||||
|
// logging system.
|
||||||
|
func newLogClosure(c func() string) logClosure {
|
||||||
|
return logClosure(c)
|
||||||
|
}
|
234
sweep/sweeper.go
Normal file
234
sweep/sweeper.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
package sweep
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UtxoSweeper provides the functionality to generate sweep txes. The plan is to
|
||||||
|
// extend UtxoSweeper in the future to also manage the actual sweeping process
|
||||||
|
// by itself.
|
||||||
|
type UtxoSweeper struct {
|
||||||
|
cfg *UtxoSweeperConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// UtxoSweeperConfig contains dependencies of UtxoSweeper.
|
||||||
|
type UtxoSweeperConfig struct {
|
||||||
|
// GenSweepScript generates a P2WKH script belonging to the wallet where
|
||||||
|
// funds can be swept.
|
||||||
|
GenSweepScript func() ([]byte, error)
|
||||||
|
|
||||||
|
// Estimator is used when crafting sweep transactions to estimate the
|
||||||
|
// necessary fee relative to the expected size of the sweep transaction.
|
||||||
|
Estimator lnwallet.FeeEstimator
|
||||||
|
|
||||||
|
// Signer is used by the sweeper to generate valid witnesses at the
|
||||||
|
// time the incubated outputs need to be spent.
|
||||||
|
Signer lnwallet.Signer
|
||||||
|
|
||||||
|
// ConfTarget specifies a target for the number of blocks until an
|
||||||
|
// initial confirmation.
|
||||||
|
ConfTarget uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new UtxoSweeper instance.
|
||||||
|
func New(cfg *UtxoSweeperConfig) *UtxoSweeper {
|
||||||
|
return &UtxoSweeper{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSweepTx accepts a list of inputs and signs and generates a txn that
|
||||||
|
// spends from them. This method also makes an accurate fee estimate before
|
||||||
|
// generating the required witnesses.
|
||||||
|
//
|
||||||
|
// The created transaction has a single output sending all the funds back to the
|
||||||
|
// source wallet, after accounting for the fee estimate.
|
||||||
|
//
|
||||||
|
// The value of currentBlockHeight argument will be set as the tx locktime. This
|
||||||
|
// function assumes that all CLTV inputs will be unlocked after
|
||||||
|
// currentBlockHeight. Reasons not to use the maximum of all actual CLTV expiry
|
||||||
|
// values of the inputs:
|
||||||
|
//
|
||||||
|
// - Make handling re-orgs easier.
|
||||||
|
// - Thwart future possible fee sniping attempts.
|
||||||
|
// - Make us blend in with the bitcoind wallet.
|
||||||
|
func (s *UtxoSweeper) CreateSweepTx(inputs []Input,
|
||||||
|
currentBlockHeight uint32) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
|
// Generate the receiving script to which the funds will be swept.
|
||||||
|
pkScript, err := s.cfg.GenSweepScript()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txWeight, csvCount, cltvCount := s.getWeightEstimate(inputs)
|
||||||
|
|
||||||
|
// Using the txn weight estimate, compute the required txn fee.
|
||||||
|
feePerKw, err := s.cfg.Estimator.EstimateFeePerKW(s.cfg.ConfTarget)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Creating sweep transaction for %v inputs (%v CSV, %v CLTV) "+
|
||||||
|
"using %v sat/kw", len(inputs), csvCount, cltvCount,
|
||||||
|
int64(feePerKw))
|
||||||
|
|
||||||
|
txFee := feePerKw.FeeForWeight(txWeight)
|
||||||
|
|
||||||
|
// Sum up the total value contained in the inputs.
|
||||||
|
var totalSum btcutil.Amount
|
||||||
|
for _, o := range inputs {
|
||||||
|
totalSum += btcutil.Amount(o.SignDesc().Output.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sweep as much possible, after subtracting txn fees.
|
||||||
|
sweepAmt := int64(totalSum - txFee)
|
||||||
|
|
||||||
|
// Create the sweep transaction that we will be building. We use
|
||||||
|
// version 2 as it is required for CSV. The txn will sweep the amount
|
||||||
|
// after fees to the pkscript generated above.
|
||||||
|
sweepTx := wire.NewMsgTx(2)
|
||||||
|
sweepTx.AddTxOut(&wire.TxOut{
|
||||||
|
PkScript: pkScript,
|
||||||
|
Value: sweepAmt,
|
||||||
|
})
|
||||||
|
|
||||||
|
sweepTx.LockTime = currentBlockHeight
|
||||||
|
|
||||||
|
// Add all inputs to the sweep transaction. Ensure that for each
|
||||||
|
// csvInput, we set the sequence number properly.
|
||||||
|
|
||||||
|
for _, input := range inputs {
|
||||||
|
sweepTx.AddTxIn(&wire.TxIn{
|
||||||
|
PreviousOutPoint: *input.OutPoint(),
|
||||||
|
Sequence: input.BlocksToMaturity(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before signing the transaction, check to ensure that it meets some
|
||||||
|
// basic validity requirements.
|
||||||
|
// TODO(conner): add more control to sanity checks, allowing us to delay
|
||||||
|
// spending "problem" outputs, e.g. possibly batching with other classes
|
||||||
|
// if fees are too low.
|
||||||
|
btx := btcutil.NewTx(sweepTx)
|
||||||
|
if err := blockchain.CheckTransactionSanity(btx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hashCache := txscript.NewTxSigHashes(sweepTx)
|
||||||
|
|
||||||
|
// With all the inputs in place, use each output's unique witness
|
||||||
|
// function to generate the final witness required for spending.
|
||||||
|
addWitness := func(idx int, tso Input) error {
|
||||||
|
witness, err := tso.BuildWitness(
|
||||||
|
s.cfg.Signer, sweepTx, hashCache, idx,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sweepTx.TxIn[idx].Witness = witness
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally we'll attach a valid witness to each csv and cltv input
|
||||||
|
// within the sweeping transaction.
|
||||||
|
for i, input := range inputs {
|
||||||
|
if err := addWitness(i, input); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sweepTx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWeightEstimate returns a weight estimate for the given inputs.
|
||||||
|
// Additionally, it returns counts for the number of csv and cltv inputs.
|
||||||
|
func (s *UtxoSweeper) getWeightEstimate(inputs []Input) (int64, int, int) {
|
||||||
|
|
||||||
|
// Create a transaction which sweeps all the newly mature outputs into
|
||||||
|
// an output controlled by the wallet.
|
||||||
|
|
||||||
|
// TODO(roasbeef): can be more intelligent about buffering outputs to
|
||||||
|
// be more efficient on-chain.
|
||||||
|
|
||||||
|
var weightEstimate lnwallet.TxWeightEstimator
|
||||||
|
|
||||||
|
// Our sweep transaction will pay to a single segwit p2wkh address,
|
||||||
|
// ensure it contributes to our weight estimate.
|
||||||
|
weightEstimate.AddP2WKHOutput()
|
||||||
|
|
||||||
|
// For each output, use its witness type to determine the estimate
|
||||||
|
// weight of its witness, and add it to the proper set of spendable
|
||||||
|
// outputs.
|
||||||
|
csvCount := 0
|
||||||
|
cltvCount := 0
|
||||||
|
|
||||||
|
for i := range inputs {
|
||||||
|
input := inputs[i]
|
||||||
|
|
||||||
|
switch input.WitnessType() {
|
||||||
|
|
||||||
|
// Outputs on a remote commitment transaction that pay directly
|
||||||
|
// to us.
|
||||||
|
case lnwallet.CommitmentNoDelay:
|
||||||
|
weightEstimate.AddP2WKHInput()
|
||||||
|
|
||||||
|
// Outputs on a past commitment transaction that pay directly
|
||||||
|
// to us.
|
||||||
|
case lnwallet.CommitmentTimeLock:
|
||||||
|
weightEstimate.AddWitnessInput(
|
||||||
|
lnwallet.ToLocalTimeoutWitnessSize,
|
||||||
|
)
|
||||||
|
csvCount++
|
||||||
|
|
||||||
|
// Outgoing second layer HTLC's that have confirmed within the
|
||||||
|
// chain, and the output they produced is now mature enough to
|
||||||
|
// sweep.
|
||||||
|
case lnwallet.HtlcOfferedTimeoutSecondLevel:
|
||||||
|
weightEstimate.AddWitnessInput(
|
||||||
|
lnwallet.ToLocalTimeoutWitnessSize,
|
||||||
|
)
|
||||||
|
csvCount++
|
||||||
|
|
||||||
|
// Incoming second layer HTLC's that have confirmed within the
|
||||||
|
// chain, and the output they produced is now mature enough to
|
||||||
|
// sweep.
|
||||||
|
case lnwallet.HtlcAcceptedSuccessSecondLevel:
|
||||||
|
weightEstimate.AddWitnessInput(
|
||||||
|
lnwallet.ToLocalTimeoutWitnessSize,
|
||||||
|
)
|
||||||
|
csvCount++
|
||||||
|
|
||||||
|
// An HTLC on the commitment transaction of the remote party,
|
||||||
|
// that has had its absolute timelock expire.
|
||||||
|
case lnwallet.HtlcOfferedRemoteTimeout:
|
||||||
|
weightEstimate.AddWitnessInput(
|
||||||
|
lnwallet.AcceptedHtlcTimeoutWitnessSize,
|
||||||
|
)
|
||||||
|
cltvCount++
|
||||||
|
|
||||||
|
// An HTLC on the commitment transaction of the remote party,
|
||||||
|
// that can be swept with the preimage.
|
||||||
|
case lnwallet.HtlcAcceptedRemoteSuccess:
|
||||||
|
weightEstimate.AddWitnessInput(
|
||||||
|
lnwallet.OfferedHtlcSuccessWitnessSize,
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Warnf("kindergarten output in nursery store "+
|
||||||
|
"contains unexpected witness type: %v",
|
||||||
|
input.WitnessType())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txWeight := int64(weightEstimate.Weight())
|
||||||
|
|
||||||
|
return txWeight, csvCount, cltvCount
|
||||||
|
}
|
256
utxonursery.go
256
utxonursery.go
@ -4,11 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/blockchain"
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
@ -192,14 +192,6 @@ type NurseryConfig struct {
|
|||||||
FetchClosedChannel func(chanID *wire.OutPoint) (
|
FetchClosedChannel func(chanID *wire.OutPoint) (
|
||||||
*channeldb.ChannelCloseSummary, error)
|
*channeldb.ChannelCloseSummary, error)
|
||||||
|
|
||||||
// Estimator is used when crafting sweep transactions to estimate the
|
|
||||||
// necessary fee relative to the expected size of the sweep transaction.
|
|
||||||
Estimator lnwallet.FeeEstimator
|
|
||||||
|
|
||||||
// GenSweepScript generates a P2WKH script belonging to the wallet where
|
|
||||||
// funds can be swept.
|
|
||||||
GenSweepScript func() ([]byte, error)
|
|
||||||
|
|
||||||
// Notifier provides the utxo nursery the ability to subscribe to
|
// Notifier provides the utxo nursery the ability to subscribe to
|
||||||
// transaction confirmation events, which advance outputs through their
|
// transaction confirmation events, which advance outputs through their
|
||||||
// persistence state transitions.
|
// persistence state transitions.
|
||||||
@ -209,13 +201,13 @@ type NurseryConfig struct {
|
|||||||
// transaction to the appropriate network.
|
// transaction to the appropriate network.
|
||||||
PublishTransaction func(*wire.MsgTx) error
|
PublishTransaction func(*wire.MsgTx) error
|
||||||
|
|
||||||
// Signer is used by the utxo nursery to generate valid witnesses at the
|
|
||||||
// time the incubated outputs need to be spent.
|
|
||||||
Signer lnwallet.Signer
|
|
||||||
|
|
||||||
// Store provides access to and modification of the persistent state
|
// Store provides access to and modification of the persistent state
|
||||||
// maintained about the utxo nursery's incubating outputs.
|
// maintained about the utxo nursery's incubating outputs.
|
||||||
Store NurseryStore
|
Store NurseryStore
|
||||||
|
|
||||||
|
// Sweeper provides functionality to generate sweep transactions.
|
||||||
|
// Nursery uses this to sweep final outputs back into the wallet.
|
||||||
|
Sweeper *sweep.UtxoSweeper
|
||||||
}
|
}
|
||||||
|
|
||||||
// utxoNursery is a system dedicated to incubating time-locked outputs created
|
// utxoNursery is a system dedicated to incubating time-locked outputs created
|
||||||
@ -879,7 +871,15 @@ func (u *utxoNursery) graduateClass(classHeight uint32) error {
|
|||||||
// generated a sweep txn for this height. Generate one if there
|
// generated a sweep txn for this height. Generate one if there
|
||||||
// are kindergarten outputs or cltv crib outputs to be spent.
|
// are kindergarten outputs or cltv crib outputs to be spent.
|
||||||
if len(kgtnOutputs) > 0 {
|
if len(kgtnOutputs) > 0 {
|
||||||
finalTx, err = u.createSweepTx(kgtnOutputs, classHeight)
|
sweepInputs := make([]sweep.Input,
|
||||||
|
len(kgtnOutputs))
|
||||||
|
for i := range kgtnOutputs {
|
||||||
|
sweepInputs[i] = &kgtnOutputs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
finalTx, err = u.cfg.Sweeper.CreateSweepTx(
|
||||||
|
sweepInputs, classHeight)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utxnLog.Errorf("Failed to create sweep txn at "+
|
utxnLog.Errorf("Failed to create sweep txn at "+
|
||||||
"height=%d", classHeight)
|
"height=%d", classHeight)
|
||||||
@ -935,203 +935,6 @@ func (u *utxoNursery) graduateClass(classHeight uint32) error {
|
|||||||
return u.cfg.Store.GraduateHeight(classHeight)
|
return u.cfg.Store.GraduateHeight(classHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
// craftSweepTx accepts a list of kindergarten outputs, and baby
|
|
||||||
// outputs which don't require a second-layer claim, and signs and generates a
|
|
||||||
// signed txn that spends from them. This method also makes an accurate fee
|
|
||||||
// estimate before generating the required witnesses.
|
|
||||||
func (u *utxoNursery) createSweepTx(kgtnOutputs []kidOutput,
|
|
||||||
classHeight uint32) (*wire.MsgTx, error) {
|
|
||||||
|
|
||||||
// Create a transaction which sweeps all the newly mature outputs into
|
|
||||||
// an output controlled by the wallet.
|
|
||||||
|
|
||||||
// TODO(roasbeef): can be more intelligent about buffering outputs to
|
|
||||||
// be more efficient on-chain.
|
|
||||||
|
|
||||||
// Assemble the kindergarten class into a slice csv spendable outputs,
|
|
||||||
// and also a set of regular spendable outputs. The set of regular
|
|
||||||
// outputs are CLTV locked outputs that have had their timelocks
|
|
||||||
// expire.
|
|
||||||
var (
|
|
||||||
csvOutputs []CsvSpendableOutput
|
|
||||||
cltvOutputs []SpendableOutput
|
|
||||||
weightEstimate lnwallet.TxWeightEstimator
|
|
||||||
)
|
|
||||||
|
|
||||||
// Allocate enough room for both types of kindergarten outputs.
|
|
||||||
csvOutputs = make([]CsvSpendableOutput, 0, len(kgtnOutputs))
|
|
||||||
cltvOutputs = make([]SpendableOutput, 0, len(kgtnOutputs))
|
|
||||||
|
|
||||||
// Our sweep transaction will pay to a single segwit p2wkh address,
|
|
||||||
// ensure it contributes to our weight estimate.
|
|
||||||
weightEstimate.AddP2WKHOutput()
|
|
||||||
|
|
||||||
// For each kindergarten output, use its witness type to determine the
|
|
||||||
// estimate weight of its witness, and add it to the proper set of
|
|
||||||
// spendable outputs.
|
|
||||||
for i := range kgtnOutputs {
|
|
||||||
input := &kgtnOutputs[i]
|
|
||||||
|
|
||||||
switch input.WitnessType() {
|
|
||||||
|
|
||||||
// Outputs on a past commitment transaction that pay directly
|
|
||||||
// to us.
|
|
||||||
case lnwallet.CommitmentTimeLock:
|
|
||||||
weightEstimate.AddWitnessInput(
|
|
||||||
lnwallet.ToLocalTimeoutWitnessSize,
|
|
||||||
)
|
|
||||||
csvOutputs = append(csvOutputs, input)
|
|
||||||
|
|
||||||
// Outgoing second layer HTLC's that have confirmed within the
|
|
||||||
// chain, and the output they produced is now mature enough to
|
|
||||||
// sweep.
|
|
||||||
case lnwallet.HtlcOfferedTimeoutSecondLevel:
|
|
||||||
weightEstimate.AddWitnessInput(
|
|
||||||
lnwallet.ToLocalTimeoutWitnessSize,
|
|
||||||
)
|
|
||||||
csvOutputs = append(csvOutputs, input)
|
|
||||||
|
|
||||||
// Incoming second layer HTLC's that have confirmed within the
|
|
||||||
// chain, and the output they produced is now mature enough to
|
|
||||||
// sweep.
|
|
||||||
case lnwallet.HtlcAcceptedSuccessSecondLevel:
|
|
||||||
weightEstimate.AddWitnessInput(
|
|
||||||
lnwallet.ToLocalTimeoutWitnessSize,
|
|
||||||
)
|
|
||||||
csvOutputs = append(csvOutputs, input)
|
|
||||||
|
|
||||||
// An HTLC on the commitment transaction of the remote party,
|
|
||||||
// that has had its absolute timelock expire.
|
|
||||||
case lnwallet.HtlcOfferedRemoteTimeout:
|
|
||||||
weightEstimate.AddWitnessInput(
|
|
||||||
lnwallet.AcceptedHtlcTimeoutWitnessSize,
|
|
||||||
)
|
|
||||||
cltvOutputs = append(cltvOutputs, input)
|
|
||||||
|
|
||||||
default:
|
|
||||||
utxnLog.Warnf("kindergarten output in nursery store "+
|
|
||||||
"contains unexpected witness type: %v",
|
|
||||||
input.WitnessType())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
utxnLog.Infof("Creating sweep transaction for %v CSV inputs, %v CLTV "+
|
|
||||||
"inputs", len(csvOutputs), len(cltvOutputs))
|
|
||||||
|
|
||||||
txWeight := int64(weightEstimate.Weight())
|
|
||||||
return u.populateSweepTx(txWeight, classHeight, csvOutputs, cltvOutputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// populateSweepTx populate the final sweeping transaction with all witnesses
|
|
||||||
// in place for all inputs using the provided txn fee. The created transaction
|
|
||||||
// has a single output sending all the funds back to the source wallet, after
|
|
||||||
// accounting for the fee estimate.
|
|
||||||
func (u *utxoNursery) populateSweepTx(txWeight int64, classHeight uint32,
|
|
||||||
csvInputs []CsvSpendableOutput,
|
|
||||||
cltvInputs []SpendableOutput) (*wire.MsgTx, error) {
|
|
||||||
|
|
||||||
// Generate the receiving script to which the funds will be swept.
|
|
||||||
pkScript, err := u.cfg.GenSweepScript()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum up the total value contained in the inputs.
|
|
||||||
var totalSum btcutil.Amount
|
|
||||||
for _, o := range csvInputs {
|
|
||||||
totalSum += o.Amount()
|
|
||||||
}
|
|
||||||
for _, o := range cltvInputs {
|
|
||||||
totalSum += o.Amount()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Using the txn weight estimate, compute the required txn fee.
|
|
||||||
feePerKw, err := u.cfg.Estimator.EstimateFeePerKW(6)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
txFee := feePerKw.FeeForWeight(txWeight)
|
|
||||||
|
|
||||||
// Sweep as much possible, after subtracting txn fees.
|
|
||||||
sweepAmt := int64(totalSum - txFee)
|
|
||||||
|
|
||||||
// Create the sweep transaction that we will be building. We use
|
|
||||||
// version 2 as it is required for CSV. The txn will sweep the amount
|
|
||||||
// after fees to the pkscript generated above.
|
|
||||||
sweepTx := wire.NewMsgTx(2)
|
|
||||||
sweepTx.AddTxOut(&wire.TxOut{
|
|
||||||
PkScript: pkScript,
|
|
||||||
Value: sweepAmt,
|
|
||||||
})
|
|
||||||
|
|
||||||
// We'll also ensure that the transaction has the required lock time if
|
|
||||||
// we're sweeping any cltvInputs.
|
|
||||||
if len(cltvInputs) > 0 {
|
|
||||||
sweepTx.LockTime = classHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all inputs to the sweep transaction. Ensure that for each
|
|
||||||
// csvInput, we set the sequence number properly.
|
|
||||||
for _, input := range csvInputs {
|
|
||||||
sweepTx.AddTxIn(&wire.TxIn{
|
|
||||||
PreviousOutPoint: *input.OutPoint(),
|
|
||||||
Sequence: input.BlocksToMaturity(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, input := range cltvInputs {
|
|
||||||
sweepTx.AddTxIn(&wire.TxIn{
|
|
||||||
PreviousOutPoint: *input.OutPoint(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before signing the transaction, check to ensure that it meets some
|
|
||||||
// basic validity requirements.
|
|
||||||
// TODO(conner): add more control to sanity checks, allowing us to delay
|
|
||||||
// spending "problem" outputs, e.g. possibly batching with other classes
|
|
||||||
// if fees are too low.
|
|
||||||
btx := btcutil.NewTx(sweepTx)
|
|
||||||
if err := blockchain.CheckTransactionSanity(btx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hashCache := txscript.NewTxSigHashes(sweepTx)
|
|
||||||
|
|
||||||
// With all the inputs in place, use each output's unique witness
|
|
||||||
// function to generate the final witness required for spending.
|
|
||||||
addWitness := func(idx int, tso SpendableOutput) error {
|
|
||||||
witness, err := tso.BuildWitness(
|
|
||||||
u.cfg.Signer, sweepTx, hashCache, idx,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sweepTx.TxIn[idx].Witness = witness
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally we'll attach a valid witness to each csv and cltv input
|
|
||||||
// within the sweeping transaction.
|
|
||||||
for i, input := range csvInputs {
|
|
||||||
if err := addWitness(i, input); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add offset to relative indexes so cltv witnesses don't overwrite csv
|
|
||||||
// witnesses.
|
|
||||||
offset := len(csvInputs)
|
|
||||||
for i, input := range cltvInputs {
|
|
||||||
if err := addWitness(offset+i, input); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sweepTx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sweepMatureOutputs generates and broadcasts the transaction that transfers
|
// sweepMatureOutputs generates and broadcasts the transaction that transfers
|
||||||
// control of funds from a prior channel commitment transaction to the user's
|
// control of funds from a prior channel commitment transaction to the user's
|
||||||
// wallet. The outputs swept were previously time locked (either absolute or
|
// wallet. The outputs swept were previously time locked (either absolute or
|
||||||
@ -1635,29 +1438,6 @@ func newSweepPkScript(wallet lnwallet.WalletController) ([]byte, error) {
|
|||||||
return txscript.PayToAddrScript(sweepAddr)
|
return txscript.PayToAddrScript(sweepAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CsvSpendableOutput is a SpendableOutput that contains all of the information
|
|
||||||
// necessary to construct, sign, and sweep an output locked with a CSV delay.
|
|
||||||
type CsvSpendableOutput interface {
|
|
||||||
SpendableOutput
|
|
||||||
|
|
||||||
// ConfHeight returns the height at which this output was confirmed.
|
|
||||||
// A zero value indicates that the output has not been confirmed.
|
|
||||||
ConfHeight() uint32
|
|
||||||
|
|
||||||
// SetConfHeight marks the height at which the output is confirmed in
|
|
||||||
// the chain.
|
|
||||||
SetConfHeight(height uint32)
|
|
||||||
|
|
||||||
// BlocksToMaturity returns the relative timelock, as a number of
|
|
||||||
// blocks, that must be built on top of the confirmation height before
|
|
||||||
// the output can be spent.
|
|
||||||
BlocksToMaturity() uint32
|
|
||||||
|
|
||||||
// OriginChanPoint returns the outpoint of the channel from which this
|
|
||||||
// output is derived.
|
|
||||||
OriginChanPoint() *wire.OutPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
// babyOutput represents a two-stage CSV locked output, and is used to track
|
// babyOutput represents a two-stage CSV locked output, and is used to track
|
||||||
// htlc outputs through incubation. The first stage requires broadcasting a
|
// htlc outputs through incubation. The first stage requires broadcasting a
|
||||||
// presigned timeout txn that spends from the CLTV locked output on the
|
// presigned timeout txn that spends from the CLTV locked output on the
|
||||||
@ -1969,7 +1749,7 @@ func readTxOut(r io.Reader, txo *wire.TxOut) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile-time constraint to ensure kidOutput and babyOutput implement the
|
// Compile-time constraint to ensure kidOutput implements the
|
||||||
// CsvSpendableOutput interface.
|
// Input interface.
|
||||||
var _ CsvSpendableOutput = (*kidOutput)(nil)
|
|
||||||
var _ CsvSpendableOutput = (*babyOutput)(nil)
|
var _ sweep.Input = (*kidOutput)(nil)
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -431,6 +432,14 @@ func createNurseryTestContext(t *testing.T,
|
|||||||
|
|
||||||
notifier := newNurseryMockNotifier(t)
|
notifier := newNurseryMockNotifier(t)
|
||||||
|
|
||||||
|
sweeper := sweep.New(&sweep.UtxoSweeperConfig{
|
||||||
|
GenSweepScript: func() ([]byte, error) {
|
||||||
|
return []byte{}, nil
|
||||||
|
},
|
||||||
|
Estimator: &mockFeeEstimator{},
|
||||||
|
Signer: &nurseryMockSigner{},
|
||||||
|
})
|
||||||
|
|
||||||
cfg := NurseryConfig{
|
cfg := NurseryConfig{
|
||||||
Notifier: notifier,
|
Notifier: notifier,
|
||||||
FetchClosedChannels: func(pendingOnly bool) (
|
FetchClosedChannels: func(pendingOnly bool) (
|
||||||
@ -445,11 +454,7 @@ func createNurseryTestContext(t *testing.T,
|
|||||||
},
|
},
|
||||||
Store: storeIntercepter,
|
Store: storeIntercepter,
|
||||||
ChainIO: &mockChainIO{},
|
ChainIO: &mockChainIO{},
|
||||||
GenSweepScript: func() ([]byte, error) {
|
Sweeper: sweeper,
|
||||||
return []byte{}, nil
|
|
||||||
},
|
|
||||||
Estimator: &mockFeeEstimator{},
|
|
||||||
Signer: &nurseryMockSigner{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publishChan := make(chan wire.MsgTx, 1)
|
publishChan := make(chan wire.MsgTx, 1)
|
||||||
|
Loading…
Reference in New Issue
Block a user