Merge pull request #5781 from Crypt-iQ/accurate_dust0922

multi: replace DefaultDustLimit with accurate dust limit
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2021-09-29 16:28:18 -07:00
commit aac824d6ef
29 files changed
+420 -168

No files matched your search

+14 -10
View File
@@ -174,15 +174,6 @@ const (
BtcToLtcConversionRate = 60 BtcToLtcConversionRate = 60
) )
// DefaultBtcChannelConstraints is the default set of channel constraints that are
// meant to be used when initially funding a Bitcoin channel.
//
// TODO(halseth): make configurable at startup?
var DefaultBtcChannelConstraints = channeldb.ChannelConstraints{
DustLimit: lnwallet.DefaultDustLimit(),
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
}
// DefaultLtcChannelConstraints is the default set of channel constraints that are // DefaultLtcChannelConstraints is the default set of channel constraints that are
// meant to be used when initially funding a Litecoin channel. // meant to be used when initially funding a Litecoin channel.
var DefaultLtcChannelConstraints = channeldb.ChannelConstraints{ var DefaultLtcChannelConstraints = channeldb.ChannelConstraints{
@@ -235,6 +226,19 @@ type ChainControl struct {
MinHtlcIn lnwire.MilliSatoshi MinHtlcIn lnwire.MilliSatoshi
} }
// GenDefaultBtcChannelConstraints generates the default set of channel
// constraints that are to be used when funding a Bitcoin channel.
func GenDefaultBtcConstraints() channeldb.ChannelConstraints {
// We use the dust limit for the maximally sized witness program with
// a 40-byte data push.
dustLimit := lnwallet.DustLimitForSize(input.UnknownWitnessSize)
return channeldb.ChannelConstraints{
DustLimit: dustLimit,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
}
}
// NewChainControl attempts to create a ChainControl instance according // NewChainControl attempts to create a ChainControl instance according
// to the parameters in the passed configuration. Currently three // to the parameters in the passed configuration. Currently three
// branches of ChainControl instances exist: one backed by a running btcd // branches of ChainControl instances exist: one backed by a running btcd
@@ -674,7 +678,7 @@ func NewChainControl(cfg *Config, blockCache *blockcache.BlockCache) (
cc.Wc = wc cc.Wc = wc
// Select the default channel constraints for the primary chain. // Select the default channel constraints for the primary chain.
channelConstraints := DefaultBtcChannelConstraints channelConstraints := GenDefaultBtcConstraints()
if cfg.PrimaryChain() == LitecoinChain { if cfg.PrimaryChain() == LitecoinChain {
channelConstraints = DefaultLtcChannelConstraints channelConstraints = DefaultLtcChannelConstraints
} }
@@ -0,0 +1,9 @@
# Release Notes
## Wallet
* [The `DefaultDustLimit` method has been removed in favor of `DustLimitForSize` which calculates the proper network dust limit for a given output size. This also fixes certain APIs like SendCoins to be able to send 294 sats to a P2WPKH script.](https://github.com/lightningnetwork/lnd/pull/5781)
# Contributors (Alphabetical Order)
* Eugene Siegel
+29 -17
View File
@@ -1340,7 +1340,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
CsvDelay: msg.CsvDelay, CsvDelay: msg.CsvDelay,
} }
err = reservation.CommitConstraints( err = reservation.CommitConstraints(
channelConstraints, f.cfg.MaxLocalCSVDelay, channelConstraints, f.cfg.MaxLocalCSVDelay, true,
) )
if err != nil { if err != nil {
log.Errorf("Unacceptable channel constraints: %v", err) log.Errorf("Unacceptable channel constraints: %v", err)
@@ -1386,7 +1386,19 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
remoteCsvDelay = acceptorResp.CSVDelay remoteCsvDelay = acceptorResp.CSVDelay
} }
chanReserve := f.cfg.RequiredRemoteChanReserve(amt, msg.DustLimit) // If our default dust limit was above their ChannelReserve, we change
// it to the ChannelReserve. We must make sure the ChannelReserve we
// send in the AcceptChannel message is above both dust limits.
// Therefore, take the maximum of msg.DustLimit and our dust limit.
//
// NOTE: Even with this bounding, the ChannelAcceptor may return an
// BOLT#02-invalid ChannelReserve.
maxDustLimit := reservation.OurContribution().DustLimit
if msg.DustLimit > maxDustLimit {
maxDustLimit = msg.DustLimit
}
chanReserve := f.cfg.RequiredRemoteChanReserve(amt, maxDustLimit)
if acceptorResp.Reserve != 0 { if acceptorResp.Reserve != 0 {
chanReserve = acceptorResp.Reserve chanReserve = acceptorResp.Reserve
} }
@@ -1576,7 +1588,7 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
CsvDelay: msg.CsvDelay, CsvDelay: msg.CsvDelay,
} }
err = resCtx.reservation.CommitConstraints( err = resCtx.reservation.CommitConstraints(
channelConstraints, resCtx.maxLocalCsv, channelConstraints, resCtx.maxLocalCsv, false,
) )
if err != nil { if err != nil {
log.Warnf("Unacceptable channel constraints: %v", err) log.Warnf("Unacceptable channel constraints: %v", err)
@@ -1586,8 +1598,11 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
// As they've accepted our channel constraints, we'll regenerate them // As they've accepted our channel constraints, we'll regenerate them
// here so we can properly commit their accepted constraints to the // here so we can properly commit their accepted constraints to the
// reservation. // reservation. Also make sure that we re-generate the ChannelReserve
chanReserve := f.cfg.RequiredRemoteChanReserve(resCtx.chanAmt, msg.DustLimit) // with our dust limit or we can get stuck channels.
chanReserve := f.cfg.RequiredRemoteChanReserve(
resCtx.chanAmt, resCtx.reservation.OurContribution().DustLimit,
)
// The remote node has responded with their portion of the channel // The remote node has responded with their portion of the channel
// contribution. At this point, we can process their contribution which // contribution. At this point, we can process their contribution which
@@ -3114,19 +3129,10 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
maxCSV = f.cfg.MaxLocalCSVDelay maxCSV = f.cfg.MaxLocalCSVDelay
} }
// We'll determine our dust limit depending on which chain is active.
var ourDustLimit btcutil.Amount
switch f.cfg.RegisteredChains.PrimaryChain() {
case chainreg.BitcoinChain:
ourDustLimit = lnwallet.DefaultDustLimit()
case chainreg.LitecoinChain:
ourDustLimit = chainreg.DefaultLitecoinDustLimit
}
log.Infof("Initiating fundingRequest(local_amt=%v "+ log.Infof("Initiating fundingRequest(local_amt=%v "+
"(subtract_fees=%v), push_amt=%v, chain_hash=%v, peer=%x, "+ "(subtract_fees=%v), push_amt=%v, chain_hash=%v, peer=%x, "+
"dust_limit=%v, min_confs=%v)", localAmt, msg.SubtractFees, "min_confs=%v)", localAmt, msg.SubtractFees, msg.PushAmt,
msg.PushAmt, msg.ChainHash, peerKey.SerializeCompressed(), msg.ChainHash, peerKey.SerializeCompressed(), msg.MinConfs)
ourDustLimit, msg.MinConfs)
// We set the channel flags to indicate whether we want this channel to // We set the channel flags to indicate whether we want this channel to
// be announced to the network. // be announced to the network.
@@ -3300,6 +3306,12 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
// request to the remote peer, kicking off the funding workflow. // request to the remote peer, kicking off the funding workflow.
ourContribution := reservation.OurContribution() ourContribution := reservation.OurContribution()
// Fetch our dust limit which is part of the default channel
// constraints, and log it.
ourDustLimit := ourContribution.DustLimit
log.Infof("Dust limit for pendingID(%x): %v", chanID, ourDustLimit)
// Finally, we'll use the current value of the channels and our default // Finally, we'll use the current value of the channels and our default
// policy to determine of required commitment constraints for the // policy to determine of required commitment constraints for the
// remote party. // remote party.
@@ -3313,7 +3325,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
PendingChannelID: chanID, PendingChannelID: chanID,
FundingAmount: capacity, FundingAmount: capacity,
PushAmount: msg.PushAmt, PushAmount: msg.PushAmt,
DustLimit: ourContribution.DustLimit, DustLimit: ourDustLimit,
MaxValueInFlight: maxValue, MaxValueInFlight: maxValue,
ChannelReserve: chanReserve, ChannelReserve: chanReserve,
HtlcMinimum: minHtlcIn, HtlcMinimum: minHtlcIn,
+1 -1
View File
@@ -276,7 +276,7 @@ func createTestWallet(cdb *channeldb.DB, netParams *chaincfg.Params,
ChainIO: bio, ChainIO: bio,
FeeEstimator: estimator, FeeEstimator: estimator,
NetParams: *netParams, NetParams: *netParams,
DefaultConstraints: chainreg.DefaultBtcChannelConstraints, DefaultConstraints: chainreg.GenDefaultBtcConstraints(),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
+6 -5
View File
@@ -5,13 +5,14 @@ require (
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2
github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4 github.com/btcsuite/btcd v0.22.0-beta.0.20210916191717-f8e6854197cd
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890
github.com/btcsuite/btcwallet v0.12.1-0.20210826004415-4ef582f76b02 github.com/btcsuite/btcwallet v0.12.1-0.20210826004415-4ef582f76b02
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.2-0.20210803004036-eebed51155ec github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 github.com/btcsuite/btcwallet/wallet/txrules v1.1.0
github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
@@ -37,7 +38,7 @@ require (
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d // indirect github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d // indirect
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec github.com/kkdai/bstream v1.0.0
github.com/lightninglabs/neutrino v0.12.1 github.com/lightninglabs/neutrino v0.12.1
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display
github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1 github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1
@@ -56,7 +57,7 @@ require (
github.com/urfave/cli v1.20.0 github.com/urfave/cli v1.20.0
go.etcd.io/etcd/client/pkg/v3 v3.5.0 go.etcd.io/etcd/client/pkg/v3 v3.5.0
go.etcd.io/etcd/client/v3 v3.5.0 go.etcd.io/etcd/client/v3 v3.5.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/net v0.0.0-20210913180222-943fd674d43e golang.org/x/net v0.0.0-20210913180222-943fd674d43e
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect
+12 -7
View File
@@ -76,8 +76,9 @@ github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcug
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs=
github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4 h1:EmyLrldY44jDVa3dQ2iscj1S6ExuVJhRzCZBOXo93r0=
github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
github.com/btcsuite/btcd v0.22.0-beta.0.20210916191717-f8e6854197cd h1:Nq1fLF6IA8XfW0HTpLaVZoDKazt05J1C2AAeswYloBE=
github.com/btcsuite/btcd v0.22.0-beta.0.20210916191717-f8e6854197cd/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@@ -92,13 +93,15 @@ github.com/btcsuite/btcwallet v0.12.1-0.20210826004415-4ef582f76b02 h1:Q8Scm1SXN
github.com/btcsuite/btcwallet v0.12.1-0.20210826004415-4ef582f76b02/go.mod h1:SdqXKJoEEi5LJq6zU67PcKiyqF97AcUOfBfyQHC7rqQ= github.com/btcsuite/btcwallet v0.12.1-0.20210826004415-4ef582f76b02/go.mod h1:SdqXKJoEEi5LJq6zU67PcKiyqF97AcUOfBfyQHC7rqQ=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.1-0.20210329233242-e0607006dce6/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.1-0.20210329233242-e0607006dce6/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.2-0.20210803004036-eebed51155ec h1:nuO8goa4gbgDM4iegCztF7mTq8io9NT1DAMoPrEI6S4= github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.2-0.20210803004036-eebed51155ec/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww=
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA= github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA=
github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 h1:Vg8G8zhNVjaCdwJg2QOmLoWn4RTP7K0J9xlwY8CJnLY=
github.com/btcsuite/btcwallet/wallet/txrules v1.1.0/go.mod h1:Zn9UTqpiTH+HOd5BLzSBzULzlOPmcoeyQIA0cp0WbQQ=
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.1-0.20210519225359-6ab9b615576f h1:bzrmHuQ3ZGWWhGDyTL0OqihQWXGXSXNuBPkDoDB8SS4=
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.1-0.20210519225359-6ab9b615576f/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/wallet/txsizes v1.0.1-0.20210519225359-6ab9b615576f/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8=
github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec h1:zcAU3Ij8SmqaE+ITtS76fua2Niq7DRNp46sJRhi8PiI= github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec h1:zcAU3Ij8SmqaE+ITtS76fua2Niq7DRNp46sJRhi8PiI=
@@ -408,8 +411,9 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs=
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8=
github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@@ -674,8 +678,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+49
View File
@@ -45,6 +45,55 @@ func WitnessScriptHash(witnessScript []byte) ([]byte, error) {
return bldr.Script() return bldr.Script()
} }
// WitnessPubKeyHash generates a pay-to-witness-pubkey-hash public key script
// paying to a version 0 witness program containing the passed serialized
// public key.
func WitnessPubKeyHash(pubkey []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_0)
pkhash := btcutil.Hash160(pubkey)
bldr.AddData(pkhash)
return bldr.Script()
}
// GenerateP2SH generates a pay-to-script-hash public key script paying to the
// passed redeem script.
func GenerateP2SH(script []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_HASH160)
scripthash := btcutil.Hash160(script)
bldr.AddData(scripthash)
bldr.AddOp(txscript.OP_EQUAL)
return bldr.Script()
}
// GenerateP2PKH generates a pay-to-public-key-hash public key script paying to
// the passed serialized public key.
func GenerateP2PKH(pubkey []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_DUP)
bldr.AddOp(txscript.OP_HASH160)
pkhash := btcutil.Hash160(pubkey)
bldr.AddData(pkhash)
bldr.AddOp(txscript.OP_EQUALVERIFY)
bldr.AddOp(txscript.OP_CHECKSIG)
return bldr.Script()
}
// GenerateUnknownWitness generates the maximum-sized witness public key script
// consisting of a version push and a 40-byte data push.
func GenerateUnknownWitness() ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_0)
witnessScript := make([]byte, 40)
bldr.AddData(witnessScript)
return bldr.Script()
}
// GenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2 // GenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2
// pubkeys. // pubkeys.
func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) { func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) {
+14 -2
View File
@@ -41,11 +41,20 @@ const (
// - P2WSHWitnessProgram: 34 bytes // - P2WSHWitnessProgram: 34 bytes
NestedP2WSHSize = 1 + P2WSHSize NestedP2WSHSize = 1 + P2WSHSize
// UnknownWitnessSize 42 bytes
// - OP_x: 1 byte
// - OP_DATA: 1 byte (max-size length)
// - max-size: 40 bytes
UnknownWitnessSize = 1 + 1 + 40
// P2PKHSize 25 bytes
P2PKHSize = 25
// P2PKHOutputSize 34 bytes // P2PKHOutputSize 34 bytes
// - value: 8 bytes // - value: 8 bytes
// - var_int: 1 byte (pkscript_length) // - var_int: 1 byte (pkscript_length)
// - pkscript (p2pkh): 25 bytes // - pkscript (p2pkh): 25 bytes
P2PKHOutputSize = 8 + 1 + 25 P2PKHOutputSize = 8 + 1 + P2PKHSize
// P2WKHOutputSize 31 bytes // P2WKHOutputSize 31 bytes
// - value: 8 bytes // - value: 8 bytes
@@ -59,11 +68,14 @@ const (
// - pkscript (p2wsh): 34 bytes // - pkscript (p2wsh): 34 bytes
P2WSHOutputSize = 8 + 1 + P2WSHSize P2WSHOutputSize = 8 + 1 + P2WSHSize
// P2SHSize 23 bytes
P2SHSize = 23
// P2SHOutputSize 32 bytes // P2SHOutputSize 32 bytes
// - value: 8 bytes // - value: 8 bytes
// - var_int: 1 byte (pkscript_length) // - var_int: 1 byte (pkscript_length)
// - pkscript (p2sh): 23 bytes // - pkscript (p2sh): 23 bytes
P2SHOutputSize = 8 + 1 + 23 P2SHOutputSize = 8 + 1 + P2SHSize
// P2PKHScriptSigSize 108 bytes // P2PKHScriptSigSize 108 bytes
// - OP_DATA: 1 byte (signature length) // - OP_DATA: 1 byte (signature length)
+6 -2
View File
@@ -16,6 +16,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
@@ -417,12 +418,15 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) {
// Check the returned response is correct. // Check the returned response is correct.
aliceChannel := resp.Channels[0] aliceChannel := resp.Channels[0]
// Calculate the dust limit we'll use for the test.
dustLimit := lnwallet.DustLimitForSize(input.UnknownWitnessSize)
// defaultConstraints is a ChannelConstraints with default values. It is // defaultConstraints is a ChannelConstraints with default values. It is
// used to test against Alice's local channel constraints. // used to test against Alice's local channel constraints.
defaultConstraints := &lnrpc.ChannelConstraints{ defaultConstraints := &lnrpc.ChannelConstraints{
CsvDelay: 4, CsvDelay: 4,
ChanReserveSat: 1000, ChanReserveSat: 1000,
DustLimitSat: uint64(lnwallet.DefaultDustLimit()), DustLimitSat: uint64(dustLimit),
MaxPendingAmtMsat: 99000000, MaxPendingAmtMsat: 99000000,
MinHtlcMsat: 1, MinHtlcMsat: 1,
MaxAcceptedHtlcs: bobRemoteMaxHtlcs, MaxAcceptedHtlcs: bobRemoteMaxHtlcs,
@@ -438,7 +442,7 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) {
customizedConstraints := &lnrpc.ChannelConstraints{ customizedConstraints := &lnrpc.ChannelConstraints{
CsvDelay: 4, CsvDelay: 4,
ChanReserveSat: 1000, ChanReserveSat: 1000,
DustLimitSat: uint64(lnwallet.DefaultDustLimit()), DustLimitSat: uint64(dustLimit),
MaxPendingAmtMsat: 99000000, MaxPendingAmtMsat: 99000000,
MinHtlcMsat: customizedMinHtlc, MinHtlcMsat: customizedMinHtlc,
MaxAcceptedHtlcs: aliceRemoteMaxHtlcs, MaxAcceptedHtlcs: aliceRemoteMaxHtlcs,
+2 -2
View File
@@ -219,7 +219,7 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
// If the the output is too small after subtracting the fee, the coin // If the the output is too small after subtracting the fee, the coin
// selection cannot be performed with an amount this small. // selection cannot be performed with an amount this small.
if outputAmt <= dustLimit { if outputAmt < dustLimit {
return nil, 0, 0, fmt.Errorf("output amount(%v) after "+ return nil, 0, 0, fmt.Errorf("output amount(%v) after "+
"subtracting fees(%v) below dust limit(%v)", outputAmt, "subtracting fees(%v) below dust limit(%v)", outputAmt,
requiredFeeNoChange, dustLimit) requiredFeeNoChange, dustLimit)
@@ -233,7 +233,7 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
// If adding a change output leads to both outputs being above // If adding a change output leads to both outputs being above
// the dust limit, we'll add the change output. Otherwise we'll // the dust limit, we'll add the change output. Otherwise we'll
// go with the no change tx we originally found. // go with the no change tx we originally found.
if newChange > dustLimit && newOutput > dustLimit { if newChange >= dustLimit && newOutput >= dustLimit {
outputAmt = newOutput outputAmt = newOutput
changeAmt = newChange changeAmt = newChange
} }
+1 -1
View File
@@ -383,7 +383,7 @@ func TestCoinSelectSubtractFees(t *testing.T) {
{ {
TxOut: wire.TxOut{ TxOut: wire.TxOut{
PkScript: p2wkhScript, PkScript: p2wkhScript,
Value: int64(fundingFee(feeRate, 1, false) + dustLimit), Value: int64(fundingFee(feeRate, 1, false) + dustLimit - 1),
}, },
}, },
}, },
+1 -1
View File
@@ -304,7 +304,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
// Sanity check: The addition of the outputs should not lead to the // Sanity check: The addition of the outputs should not lead to the
// creation of dust. // creation of dust.
if changeAmt != 0 && changeAmt <= w.cfg.DustLimit { if changeAmt != 0 && changeAmt < w.cfg.DustLimit {
return fmt.Errorf("change amount(%v) after coin "+ return fmt.Errorf("change amount(%v) after coin "+
"select is below dust limit(%v)", changeAmt, "select is below dust limit(%v)", changeAmt,
w.cfg.DustLimit) w.cfg.DustLimit)
+8
View File
@@ -152,6 +152,14 @@ func ErrChanTooLarge(chanSize, maxChanSize btcutil.Amount) ReservationError {
} }
} }
// ErrInvalidDustLimit returns an error indicating that a proposed DustLimit
// was rejected.
func ErrInvalidDustLimit(dustLimit btcutil.Amount) ReservationError {
return ReservationError{
fmt.Errorf("dust limit %v is invalid", dustLimit),
}
}
// ErrHtlcIndexAlreadyFailed is returned when the HTLC index has already been // ErrHtlcIndexAlreadyFailed is returned when the HTLC index has already been
// failed, but has not been committed by our commitment state. // failed, but has not been committed by our commitment state.
type ErrHtlcIndexAlreadyFailed uint64 type ErrHtlcIndexAlreadyFailed uint64
+42 -5
View File
@@ -1,13 +1,50 @@
package lnwallet package lnwallet
import ( import (
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
) )
// DefaultDustLimit is used to calculate the dust HTLC amount which will be // DustLimitForSize retrieves the dust limit for a given pkscript size. Given
// send to other node during funding process. // the size, it automatically determines whether the script is a witness script
func DefaultDustLimit() btcutil.Amount { // or not. It calls btcd's GetDustThreshold method under the hood. It must be
return txrules.GetDustThreshold(input.P2WSHSize, txrules.DefaultRelayFeePerKb) // called with a proper size parameter or else a panic occurs.
func DustLimitForSize(scriptSize int) btcutil.Amount {
var (
dustlimit btcutil.Amount
pkscript []byte
)
// With the size of the script, determine which type of pkscript to
// create. This will be used in the call to GetDustThreshold. We pass
// in an empty byte slice since the contents of the script itself don't
// matter.
switch scriptSize {
case input.P2WPKHSize:
pkscript, _ = input.WitnessPubKeyHash([]byte{})
case input.P2WSHSize:
pkscript, _ = input.WitnessScriptHash([]byte{})
case input.P2SHSize:
pkscript, _ = input.GenerateP2SH([]byte{})
case input.P2PKHSize:
pkscript, _ = input.GenerateP2PKH([]byte{})
case input.UnknownWitnessSize:
pkscript, _ = input.GenerateUnknownWitness()
default:
panic("invalid script size")
}
// Call GetDustThreshold with a TxOut containing the generated
// pkscript.
txout := &wire.TxOut{PkScript: pkscript}
dustlimit = btcutil.Amount(mempool.GetDustThreshold(txout))
return dustlimit
} }
+56
View File
@@ -0,0 +1,56 @@
package lnwallet
import (
"testing"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/stretchr/testify/require"
)
// TestDustLimitForSize tests that we receive the expected dust limits for
// various script types from btcd's GetDustThreshold function.
func TestDustLimitForSize(t *testing.T) {
t.Parallel()
tests := []struct {
name string
size int
expectedLimit btcutil.Amount
}{
{
name: "p2pkh dust limit",
size: input.P2PKHSize,
expectedLimit: btcutil.Amount(546),
},
{
name: "p2sh dust limit",
size: input.P2SHSize,
expectedLimit: btcutil.Amount(540),
},
{
name: "p2wpkh dust limit",
size: input.P2WPKHSize,
expectedLimit: btcutil.Amount(294),
},
{
name: "p2wsh dust limit",
size: input.P2WSHSize,
expectedLimit: btcutil.Amount(330),
},
{
name: "unknown witness limit",
size: input.UnknownWitnessSize,
expectedLimit: btcutil.Amount(354),
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
dustlimit := DustLimitForSize(test.size)
require.Equal(t, test.expectedLimit, dustlimit)
})
}
}
+43 -8
View File
@@ -205,6 +205,9 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize) feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize)
} }
// Used to cut down on verbosity.
defaultDust := wallet.Cfg.DefaultConstraints.DustLimit
// If we're the responder to a single-funder reservation, then we have // If we're the responder to a single-funder reservation, then we have
// no initial balance in the channel unless the remote party is pushing // no initial balance in the channel unless the remote party is pushing
// some funds to us within the first commitment state. // some funds to us within the first commitment state.
@@ -218,7 +221,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
if int64(theirBalance) < 0 { if int64(theirBalance) < 0 {
return nil, ErrFunderBalanceDust( return nil, ErrFunderBalanceDust(
int64(commitFee), int64(theirBalance.ToSatoshis()), int64(commitFee), int64(theirBalance.ToSatoshis()),
int64(2*DefaultDustLimit()), int64(2*defaultDust),
) )
} }
} else { } else {
@@ -247,7 +250,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
if int64(ourBalance) < 0 { if int64(ourBalance) < 0 {
return nil, ErrFunderBalanceDust( return nil, ErrFunderBalanceDust(
int64(commitFee), int64(ourBalance), int64(commitFee), int64(ourBalance),
int64(2*DefaultDustLimit()), int64(2*defaultDust),
) )
} }
} }
@@ -257,21 +260,21 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
// reject this channel creation request. // reject this channel creation request.
// //
// TODO(roasbeef): reject if 30% goes to fees? dust channel // TODO(roasbeef): reject if 30% goes to fees? dust channel
if initiator && ourBalance.ToSatoshis() <= 2*DefaultDustLimit() { if initiator && ourBalance.ToSatoshis() <= 2*defaultDust {
return nil, ErrFunderBalanceDust( return nil, ErrFunderBalanceDust(
int64(commitFee), int64(commitFee),
int64(ourBalance.ToSatoshis()), int64(ourBalance.ToSatoshis()),
int64(2*DefaultDustLimit()), int64(2*defaultDust),
) )
} }
// Similarly we ensure their balance is reasonable if we are not the // Similarly we ensure their balance is reasonable if we are not the
// initiator. // initiator.
if !initiator && theirBalance.ToSatoshis() <= 2*DefaultDustLimit() { if !initiator && theirBalance.ToSatoshis() <= 2*defaultDust {
return nil, ErrFunderBalanceDust( return nil, ErrFunderBalanceDust(
int64(commitFee), int64(commitFee),
int64(theirBalance.ToSatoshis()), int64(theirBalance.ToSatoshis()),
int64(2*DefaultDustLimit()), int64(2*defaultDust),
) )
} }
@@ -391,7 +394,7 @@ func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
// will also attempt to verify the constraints for sanity, returning an error // will also attempt to verify the constraints for sanity, returning an error
// if the parameters are seemed unsound. // if the parameters are seemed unsound.
func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints, func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
maxLocalCSVDelay uint16) error { maxLocalCSVDelay uint16, responder bool) error {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
@@ -406,6 +409,13 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
return ErrChanReserveTooSmall(c.ChanReserve, c.DustLimit) return ErrChanReserveTooSmall(c.ChanReserve, c.DustLimit)
} }
// Validate against the maximum-sized witness script dust limit, and
// also ensure that the DustLimit is not too large.
maxWitnessLimit := DustLimitForSize(input.UnknownWitnessSize)
if c.DustLimit < maxWitnessLimit || c.DustLimit > 3*maxWitnessLimit {
return ErrInvalidDustLimit(c.DustLimit)
}
// Fail if we consider the channel reserve to be too large. We // Fail if we consider the channel reserve to be too large. We
// currently fail if it is greater than 20% of the channel capacity. // currently fail if it is greater than 20% of the channel capacity.
maxChanReserve := r.partialState.Capacity / 5 maxChanReserve := r.partialState.Capacity / 5
@@ -446,7 +456,7 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
// Our dust limit should always be less than or equal to our proposed // Our dust limit should always be less than or equal to our proposed
// channel reserve. // channel reserve.
if r.ourContribution.DustLimit > c.ChanReserve { if responder && r.ourContribution.DustLimit > c.ChanReserve {
r.ourContribution.DustLimit = c.ChanReserve r.ourContribution.DustLimit = c.ChanReserve
} }
@@ -459,6 +469,31 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
return nil return nil
} }
// validateReserveBounds checks that both ChannelReserve values are above both
// DustLimit values. This not only avoids stuck channels, but is also mandated
// by BOLT#02 even if it's not explicit. This returns true if the bounds are
// valid. This function should be called with the lock held.
func (r *ChannelReservation) validateReserveBounds() bool {
ourDustLimit := r.ourContribution.DustLimit
ourRequiredReserve := r.ourContribution.ChanReserve
theirDustLimit := r.theirContribution.DustLimit
theirRequiredReserve := r.theirContribution.ChanReserve
// We take the smaller of the two ChannelReserves and compare it
// against the larger of the two DustLimits.
minChanReserve := ourRequiredReserve
if minChanReserve > theirRequiredReserve {
minChanReserve = theirRequiredReserve
}
maxDustLimit := ourDustLimit
if maxDustLimit < theirDustLimit {
maxDustLimit = theirDustLimit
}
return minChanReserve >= maxDustLimit
}
// OurContribution returns the wallet's fully populated contribution to the // OurContribution returns the wallet's fully populated contribution to the
// pending payment channel. See 'ChannelContribution' for further details // pending payment channel. See 'ChannelContribution' for further details
// regarding the contents of a contribution. // regarding the contents of a contribution.
+6 -6
View File
@@ -446,7 +446,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
} }
aliceChanReservation.SetNumConfsRequired(numReqConfs) aliceChanReservation.SetNumConfsRequired(numReqConfs)
channelConstraints := &channeldb.ChannelConstraints{ channelConstraints := &channeldb.ChannelConstraints{
DustLimit: lnwallet.DefaultDustLimit(), DustLimit: alice.Cfg.DefaultConstraints.DustLimit,
ChanReserve: fundingAmount / 100, ChanReserve: fundingAmount / 100,
MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmount), MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmount),
MinHTLC: 1, MinHTLC: 1,
@@ -454,7 +454,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
CsvDelay: csvDelay, CsvDelay: csvDelay,
} }
err = aliceChanReservation.CommitConstraints( err = aliceChanReservation.CommitConstraints(
channelConstraints, defaultMaxLocalCsvDelay, channelConstraints, defaultMaxLocalCsvDelay, false,
) )
if err != nil { if err != nil {
t.Fatalf("unable to verify constraints: %v", err) t.Fatalf("unable to verify constraints: %v", err)
@@ -490,7 +490,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
t.Fatalf("bob unable to init channel reservation: %v", err) t.Fatalf("bob unable to init channel reservation: %v", err)
} }
err = bobChanReservation.CommitConstraints( err = bobChanReservation.CommitConstraints(
channelConstraints, defaultMaxLocalCsvDelay, channelConstraints, defaultMaxLocalCsvDelay, true,
) )
if err != nil { if err != nil {
t.Fatalf("unable to verify constraints: %v", err) t.Fatalf("unable to verify constraints: %v", err)
@@ -896,7 +896,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
} }
aliceChanReservation.SetNumConfsRequired(numReqConfs) aliceChanReservation.SetNumConfsRequired(numReqConfs)
channelConstraints := &channeldb.ChannelConstraints{ channelConstraints := &channeldb.ChannelConstraints{
DustLimit: lnwallet.DefaultDustLimit(), DustLimit: alice.Cfg.DefaultConstraints.DustLimit,
ChanReserve: fundingAmt / 100, ChanReserve: fundingAmt / 100,
MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmt), MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmt),
MinHTLC: 1, MinHTLC: 1,
@@ -904,7 +904,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
CsvDelay: csvDelay, CsvDelay: csvDelay,
} }
err = aliceChanReservation.CommitConstraints( err = aliceChanReservation.CommitConstraints(
channelConstraints, defaultMaxLocalCsvDelay, channelConstraints, defaultMaxLocalCsvDelay, false,
) )
if err != nil { if err != nil {
t.Fatalf("unable to verify constraints: %v", err) t.Fatalf("unable to verify constraints: %v", err)
@@ -947,7 +947,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
t.Fatalf("unable to create bob reservation: %v", err) t.Fatalf("unable to create bob reservation: %v", err)
} }
err = bobChanReservation.CommitConstraints( err = bobChanReservation.CommitConstraints(
channelConstraints, defaultMaxLocalCsvDelay, channelConstraints, defaultMaxLocalCsvDelay, true,
) )
if err != nil { if err != nil {
t.Fatalf("unable to verify constraints: %v", err) t.Fatalf("unable to verify constraints: %v", err)
+5 -2
View File
@@ -542,16 +542,19 @@ func testSpendValidation(t *testing.T, tweakless bool) {
Privkeys: []*btcec.PrivateKey{aliceKeyPriv}, Privkeys: []*btcec.PrivateKey{aliceKeyPriv},
} }
// Calculate the dust limit we'll use for the test.
dustLimit := DustLimitForSize(input.UnknownWitnessSize)
aliceChanCfg := &channeldb.ChannelConfig{ aliceChanCfg := &channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{ ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: DefaultDustLimit(), DustLimit: dustLimit,
CsvDelay: csvTimeout, CsvDelay: csvTimeout,
}, },
} }
bobChanCfg := &channeldb.ChannelConfig{ bobChanCfg := &channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{ ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: DefaultDustLimit(), DustLimit: dustLimit,
CsvDelay: csvTimeout, CsvDelay: csvTimeout,
}, },
} }
+19 -4
View File
@@ -689,12 +689,15 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
// If no chanFunder was provided, then we'll assume the default // If no chanFunder was provided, then we'll assume the default
// assembler, which is backed by the wallet's internal coin selection. // assembler, which is backed by the wallet's internal coin selection.
if req.ChanFunder == nil { if req.ChanFunder == nil {
// We use the P2WSH dust limit since it is larger than the
// P2WPKH dust limit and to avoid threading through two
// different dust limits.
cfg := chanfunding.WalletConfig{ cfg := chanfunding.WalletConfig{
CoinSource: &CoinSource{l}, CoinSource: &CoinSource{l},
CoinSelectLocker: l, CoinSelectLocker: l,
CoinLocker: l, CoinLocker: l,
Signer: l.Cfg.Signer, Signer: l.Cfg.Signer,
DustLimit: DefaultDustLimit(), DustLimit: DustLimitForSize(input.P2WSHSize),
} }
req.ChanFunder = chanfunding.NewWalletAssembler(cfg) req.ChanFunder = chanfunding.NewWalletAssembler(cfg)
} }
@@ -1263,6 +1266,13 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
theirContribution := req.contribution theirContribution := req.contribution
ourContribution := pendingReservation.ourContribution ourContribution := pendingReservation.ourContribution
// Perform bounds-checking on both ChannelReserve and DustLimit
// parameters.
if !pendingReservation.validateReserveBounds() {
req.err <- fmt.Errorf("invalid reserve and dust bounds")
return
}
var ( var (
chanPoint *wire.OutPoint chanPoint *wire.OutPoint
err error err error
@@ -1561,9 +1571,6 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
pendingReservation.Lock() pendingReservation.Lock()
defer pendingReservation.Unlock() defer pendingReservation.Unlock()
// TODO(roasbeef): verify sanity of remote party's parameters, fail if
// disagree
// Validate that the remote's UpfrontShutdownScript is a valid script // Validate that the remote's UpfrontShutdownScript is a valid script
// if it's set. // if it's set.
shutdown := req.contribution.UpfrontShutdown shutdown := req.contribution.UpfrontShutdown
@@ -1581,6 +1588,14 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
theirContribution := pendingReservation.theirContribution theirContribution := pendingReservation.theirContribution
chanState := pendingReservation.partialState chanState := pendingReservation.partialState
// Perform bounds checking on both ChannelReserve and DustLimit
// parameters. The ChannelReserve may have been changed by the
// ChannelAcceptor RPC, so this is necessary.
if !pendingReservation.validateReserveBounds() {
req.err <- fmt.Errorf("invalid reserve and dust bounds")
return
}
// Initialize an empty sha-chain for them, tracking the current pending // Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the preimage so we can't add it // revocation hash (we don't yet know the preimage so we can't add it
// to the chain). // to the chain).
+4 -5
View File
@@ -1227,8 +1227,7 @@ func (r *rpcServer) SendCoins(ctx context.Context,
// pay to the change address created above if we needed to // pay to the change address created above if we needed to
// reserve any value, the rest will go to targetAddr. // reserve any value, the rest will go to targetAddr.
sweepTxPkg, err := sweep.CraftSweepAllTx( sweepTxPkg, err := sweep.CraftSweepAllTx(
feePerKw, lnwallet.DefaultDustLimit(), feePerKw, uint32(bestHeight), nil, targetAddr, wallet,
uint32(bestHeight), nil, targetAddr, wallet,
wallet, wallet.WalletController, wallet, wallet.WalletController,
r.server.cc.FeeEstimator, r.server.cc.Signer, r.server.cc.FeeEstimator, r.server.cc.Signer,
minConfs, minConfs,
@@ -1280,9 +1279,9 @@ func (r *rpcServer) SendCoins(ctx context.Context,
} }
sweepTxPkg, err = sweep.CraftSweepAllTx( sweepTxPkg, err = sweep.CraftSweepAllTx(
feePerKw, lnwallet.DefaultDustLimit(), feePerKw, uint32(bestHeight), outputs,
uint32(bestHeight), outputs, targetAddr, wallet, targetAddr, wallet, wallet,
wallet, wallet.WalletController, wallet.WalletController,
r.server.cc.FeeEstimator, r.server.cc.Signer, r.server.cc.FeeEstimator, r.server.cc.Signer,
minConfs, minConfs,
) )
+5 -5
View File
@@ -1150,7 +1150,7 @@ func (s *UtxoSweeper) getInputLists(cluster inputCluster,
if len(retryInputs) > 0 { if len(retryInputs) > 0 {
var err error var err error
allSets, err = generateInputPartitionings( allSets, err = generateInputPartitionings(
append(retryInputs, newInputs...), s.relayFeeRate, append(retryInputs, newInputs...),
cluster.sweepFeeRate, s.cfg.MaxInputsPerTx, cluster.sweepFeeRate, s.cfg.MaxInputsPerTx,
s.cfg.Wallet, s.cfg.Wallet,
) )
@@ -1161,8 +1161,8 @@ func (s *UtxoSweeper) getInputLists(cluster inputCluster,
// Create sets for just the new inputs. // Create sets for just the new inputs.
newSets, err := generateInputPartitionings( newSets, err := generateInputPartitionings(
newInputs, s.relayFeeRate, cluster.sweepFeeRate, newInputs, cluster.sweepFeeRate, s.cfg.MaxInputsPerTx,
s.cfg.MaxInputsPerTx, s.cfg.Wallet, s.cfg.Wallet,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("input partitionings: %v", err) return nil, fmt.Errorf("input partitionings: %v", err)
@@ -1193,7 +1193,7 @@ func (s *UtxoSweeper) sweep(inputs inputSet, feeRate chainfee.SatPerKWeight,
// Create sweep tx. // Create sweep tx.
tx, err := createSweepTx( tx, err := createSweepTx(
inputs, nil, s.currentOutputScript, uint32(currentHeight), inputs, nil, s.currentOutputScript, uint32(currentHeight),
feeRate, dustLimit(s.relayFeeRate), s.cfg.Signer, feeRate, 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)
@@ -1488,7 +1488,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []input.Input, feePref FeePreference,
return createSweepTx( return createSweepTx(
inputs, nil, pkScript, currentBlockHeight, feePerKw, inputs, nil, pkScript, currentBlockHeight, feePerKw,
dustLimit(s.relayFeeRate), s.cfg.Signer, s.cfg.Signer,
) )
} }
+39 -20
View File
@@ -136,7 +136,8 @@ func createSweeperTestContext(t *testing.T) *sweeperTestContext {
Store: store, Store: store,
Signer: &mock.DummySigner{}, Signer: &mock.DummySigner{},
GenSweepScript: func() ([]byte, error) { GenSweepScript: func() ([]byte, error) {
script := []byte{outputScriptCount} script := make([]byte, input.P2WPKHSize)
script[0] = outputScriptCount
outputScriptCount++ outputScriptCount++
return script, nil return script, nil
}, },
@@ -1801,6 +1802,24 @@ func TestRequiredTxOuts(t *testing.T) {
locktime2 := uint32(52) locktime2 := uint32(52)
locktime3 := uint32(53) locktime3 := uint32(53)
aPkScript := make([]byte, input.P2WPKHSize)
aPkScript[0] = 'a'
bPkScript := make([]byte, input.P2WSHSize)
bPkScript[0] = 'b'
cPkScript := make([]byte, input.P2PKHSize)
cPkScript[0] = 'c'
dPkScript := make([]byte, input.P2SHSize)
dPkScript[0] = 'd'
ePkScript := make([]byte, input.UnknownWitnessSize)
ePkScript[0] = 'e'
fPkScript := make([]byte, input.P2WSHSize)
fPkScript[0] = 'f'
testCases := []struct { testCases := []struct {
name string name string
inputs []*testInput inputs []*testInput
@@ -1815,7 +1834,7 @@ func TestRequiredTxOuts(t *testing.T) {
{ {
BaseInput: inputs[0], BaseInput: inputs[0],
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"), PkScript: aPkScript,
Value: 100000, Value: 100000,
}, },
}, },
@@ -1836,7 +1855,7 @@ func TestRequiredTxOuts(t *testing.T) {
// output must be the first one. // output must be the first one.
require.Equal(t, 2, len(tx.TxOut)) require.Equal(t, 2, len(tx.TxOut))
out := tx.TxOut[0] out := tx.TxOut[0]
require.Equal(t, []byte("aaa"), out.PkScript) require.Equal(t, aPkScript, out.PkScript)
require.Equal(t, int64(100000), out.Value) require.Equal(t, int64(100000), out.Value)
}, },
}, },
@@ -1848,13 +1867,13 @@ func TestRequiredTxOuts(t *testing.T) {
{ {
BaseInput: inputs[0], BaseInput: inputs[0],
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"), PkScript: aPkScript,
// Fee will be about 5340 sats. // Fee will be about 5340 sats.
// Subtract a bit more to // Subtract a bit more to
// ensure no dust change output // ensure no dust change output
// is manifested. // is manifested.
Value: inputs[0].SignDesc().Output.Value - 5600, Value: inputs[0].SignDesc().Output.Value - 6300,
}, },
}, },
}, },
@@ -1871,10 +1890,10 @@ func TestRequiredTxOuts(t *testing.T) {
require.Equal(t, 1, len(tx.TxOut)) require.Equal(t, 1, len(tx.TxOut))
out := tx.TxOut[0] out := tx.TxOut[0]
require.Equal(t, []byte("aaa"), out.PkScript) require.Equal(t, aPkScript, out.PkScript)
require.Equal( require.Equal(
t, t,
inputs[0].SignDesc().Output.Value-5600, inputs[0].SignDesc().Output.Value-6300,
out.Value, out.Value,
) )
}, },
@@ -1897,7 +1916,7 @@ func TestRequiredTxOuts(t *testing.T) {
// The second input requires a TxOut. // The second input requires a TxOut.
BaseInput: inputs[0], BaseInput: inputs[0],
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"), PkScript: aPkScript,
Value: inputs[0].SignDesc().Output.Value, Value: inputs[0].SignDesc().Output.Value,
}, },
}, },
@@ -1916,7 +1935,7 @@ func TestRequiredTxOuts(t *testing.T) {
// The required TxOut should be the first one. // The required TxOut should be the first one.
out := tx.TxOut[0] out := tx.TxOut[0]
require.Equal(t, []byte("aaa"), out.PkScript) require.Equal(t, aPkScript, out.PkScript)
require.Equal( require.Equal(
t, inputs[0].SignDesc().Output.Value, t, inputs[0].SignDesc().Output.Value,
out.Value, out.Value,
@@ -1947,7 +1966,7 @@ func TestRequiredTxOuts(t *testing.T) {
{ {
BaseInput: inputs[0], BaseInput: inputs[0],
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"), PkScript: aPkScript,
Value: inputs[0].SignDesc().Output.Value, Value: inputs[0].SignDesc().Output.Value,
}, },
}, },
@@ -1965,7 +1984,7 @@ func TestRequiredTxOuts(t *testing.T) {
require.Equal(t, 2, len(tx.TxOut)) require.Equal(t, 2, len(tx.TxOut))
out := tx.TxOut[0] out := tx.TxOut[0]
require.Equal(t, []byte("aaa"), out.PkScript) require.Equal(t, aPkScript, out.PkScript)
require.Equal( require.Equal(
t, inputs[0].SignDesc().Output.Value, t, inputs[0].SignDesc().Output.Value,
out.Value, out.Value,
@@ -1980,21 +1999,21 @@ func TestRequiredTxOuts(t *testing.T) {
{ {
BaseInput: inputs[0], BaseInput: inputs[0],
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"), PkScript: aPkScript,
Value: inputs[0].SignDesc().Output.Value, Value: inputs[0].SignDesc().Output.Value,
}, },
}, },
{ {
BaseInput: inputs[1], BaseInput: inputs[1],
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("bbb"), PkScript: bPkScript,
Value: inputs[1].SignDesc().Output.Value, Value: inputs[1].SignDesc().Output.Value,
}, },
}, },
{ {
BaseInput: inputs[2], BaseInput: inputs[2],
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("ccc"), PkScript: cPkScript,
Value: inputs[2].SignDesc().Output.Value, Value: inputs[2].SignDesc().Output.Value,
}, },
}, },
@@ -2041,7 +2060,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[0], BaseInput: inputs[0],
locktime: &locktime1, locktime: &locktime1,
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"), PkScript: aPkScript,
Value: inputs[0].SignDesc().Output.Value, Value: inputs[0].SignDesc().Output.Value,
}, },
}, },
@@ -2049,7 +2068,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[1], BaseInput: inputs[1],
locktime: &locktime1, locktime: &locktime1,
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("bbb"), PkScript: bPkScript,
Value: inputs[1].SignDesc().Output.Value, Value: inputs[1].SignDesc().Output.Value,
}, },
}, },
@@ -2057,7 +2076,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[2], BaseInput: inputs[2],
locktime: &locktime2, locktime: &locktime2,
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("ccc"), PkScript: cPkScript,
Value: inputs[2].SignDesc().Output.Value, Value: inputs[2].SignDesc().Output.Value,
}, },
}, },
@@ -2065,7 +2084,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[3], BaseInput: inputs[3],
locktime: &locktime2, locktime: &locktime2,
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("ddd"), PkScript: dPkScript,
Value: inputs[3].SignDesc().Output.Value, Value: inputs[3].SignDesc().Output.Value,
}, },
}, },
@@ -2073,7 +2092,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[4], BaseInput: inputs[4],
locktime: &locktime3, locktime: &locktime3,
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("eee"), PkScript: ePkScript,
Value: inputs[4].SignDesc().Output.Value, Value: inputs[4].SignDesc().Output.Value,
}, },
}, },
@@ -2081,7 +2100,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[5], BaseInput: inputs[5],
locktime: &locktime3, locktime: &locktime3,
reqTxOut: &wire.TxOut{ reqTxOut: &wire.TxOut{
PkScript: []byte("fff"), PkScript: fPkScript,
Value: inputs[5].SignDesc().Output.Value, Value: inputs[5].SignDesc().Output.Value,
}, },
}, },
+9 -19
View File
@@ -7,7 +7,6 @@ import (
"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"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -109,9 +108,6 @@ func (t *txInputSetState) clone() txInputSetState {
type txInputSet struct { type txInputSet struct {
txInputSetState txInputSetState
// dustLimit is the minimum output value of the tx.
dustLimit btcutil.Amount
// maxInputs is the maximum number of inputs that will be accepted in // maxInputs is the maximum number of inputs that will be accepted in
// the set. // the set.
maxInputs int maxInputs int
@@ -121,25 +117,15 @@ type txInputSet struct {
wallet Wallet wallet Wallet
} }
func dustLimit(relayFee chainfee.SatPerKWeight) btcutil.Amount {
return txrules.GetDustThreshold(
input.P2WPKHSize,
btcutil.Amount(relayFee.FeePerKVByte()),
)
}
// newTxInputSet constructs a new, empty input set. // newTxInputSet constructs a new, empty input set.
func newTxInputSet(wallet Wallet, feePerKW, func newTxInputSet(wallet Wallet, feePerKW chainfee.SatPerKWeight,
relayFee chainfee.SatPerKWeight, maxInputs int) *txInputSet { maxInputs int) *txInputSet {
dustLimit := dustLimit(relayFee)
state := txInputSetState{ state := txInputSetState{
feeRate: feePerKW, feeRate: feePerKW,
} }
b := txInputSet{ b := txInputSet{
dustLimit: dustLimit,
maxInputs: maxInputs, maxInputs: maxInputs,
wallet: wallet, wallet: wallet,
txInputSetState: state, txInputSetState: state,
@@ -153,7 +139,7 @@ func newTxInputSet(wallet Wallet, feePerKW,
func (t *txInputSet) enoughInput() bool { func (t *txInputSet) enoughInput() bool {
// If we have a change output above dust, then we certainly have enough // If we have a change output above dust, then we certainly have enough
// inputs to the transaction. // inputs to the transaction.
if t.changeOutput >= t.dustLimit { if t.changeOutput >= lnwallet.DustLimitForSize(input.P2WPKHSize) {
return true return true
} }
@@ -192,8 +178,12 @@ func (t *txInputSet) addToState(inp input.Input, constraints addConstraints) *tx
// If the input comes with a required tx out that is below dust, we // If the input comes with a required tx out that is below dust, we
// won't add it. // won't add it.
reqOut := inp.RequiredTxOut() reqOut := inp.RequiredTxOut()
if reqOut != nil && btcutil.Amount(reqOut.Value) < t.dustLimit { if reqOut != nil {
return nil // Fetch the dust limit for this output.
dustLimit := lnwallet.DustLimitForSize(len(reqOut.PkScript))
if btcutil.Amount(reqOut.Value) < dustLimit {
return nil
}
} }
// Clone the current set state. // Clone the current set state.
+7 -17
View File
@@ -14,14 +14,9 @@ import (
func TestTxInputSet(t *testing.T) { func TestTxInputSet(t *testing.T) {
const ( const (
feeRate = 1000 feeRate = 1000
relayFee = 300
maxInputs = 10 maxInputs = 10
) )
set := newTxInputSet(nil, feeRate, relayFee, maxInputs) set := newTxInputSet(nil, feeRate, maxInputs)
if set.dustLimit != 537 {
t.Fatalf("incorrect dust limit")
}
// Create a 300 sat input. The fee to sweep this input to a P2WKH output // Create a 300 sat input. The fee to sweep this input to a P2WKH output
// is 439 sats. That means that this input yields -139 sats and we // is 439 sats. That means that this input yields -139 sats and we
@@ -66,16 +61,15 @@ func TestTxInputSet(t *testing.T) {
func TestTxInputSetFromWallet(t *testing.T) { func TestTxInputSetFromWallet(t *testing.T) {
const ( const (
feeRate = 500 feeRate = 500
relayFee = 300
maxInputs = 10 maxInputs = 10
) )
wallet := &mockWallet{} wallet := &mockWallet{}
set := newTxInputSet(wallet, feeRate, relayFee, maxInputs) set := newTxInputSet(wallet, feeRate, maxInputs)
// Add a 700 sat input to the set. It yields positively, but doesn't // Add a 500 sat input to the set. It yields positively, but doesn't
// reach the output dust limit. // reach the output dust limit.
if !set.add(createP2WKHInput(700), constraintsRegular) { if !set.add(createP2WKHInput(500), constraintsRegular) {
t.Fatal("expected add of positively yielding input to succeed") t.Fatal("expected add of positively yielding input to succeed")
} }
if set.enoughInput() { if set.enoughInput() {
@@ -138,13 +132,9 @@ func (r *reqInput) RequiredTxOut() *wire.TxOut {
func TestTxInputSetRequiredOutput(t *testing.T) { func TestTxInputSetRequiredOutput(t *testing.T) {
const ( const (
feeRate = 1000 feeRate = 1000
relayFee = 300
maxInputs = 10 maxInputs = 10
) )
set := newTxInputSet(nil, feeRate, relayFee, maxInputs) set := newTxInputSet(nil, feeRate, maxInputs)
if set.dustLimit != 537 {
t.Fatalf("incorrect dust limit")
}
// Attempt to add an input with a required txout below the dust limit. // Attempt to add an input with a required txout below the dust limit.
// This should fail since we cannot trim such outputs. // This should fail since we cannot trim such outputs.
@@ -152,7 +142,7 @@ func TestTxInputSetRequiredOutput(t *testing.T) {
Input: createP2WKHInput(500), Input: createP2WKHInput(500),
txOut: &wire.TxOut{ txOut: &wire.TxOut{
Value: 500, Value: 500,
PkScript: make([]byte, 33), PkScript: make([]byte, input.P2PKHSize),
}, },
} }
require.False(t, set.add(inp, constraintsRegular), require.False(t, set.add(inp, constraintsRegular),
@@ -164,7 +154,7 @@ func TestTxInputSetRequiredOutput(t *testing.T) {
Input: createP2WKHInput(1000), Input: createP2WKHInput(1000),
txOut: &wire.TxOut{ txOut: &wire.TxOut{
Value: 1000, Value: 1000,
PkScript: make([]byte, 22), PkScript: make([]byte, input.P2WPKHSize),
}, },
} }
require.True(t, set.add(inp, constraintsRegular), "failed adding input") require.True(t, set.add(inp, constraintsRegular), "failed adding input")
+15 -10
View File
@@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
@@ -37,8 +38,8 @@ type inputSet []input.Input
// inputs are skipped. No input sets with a total value after fees below the // inputs are skipped. No input sets with a total value after fees below the
// dust limit are returned. // dust limit are returned.
func generateInputPartitionings(sweepableInputs []txInput, func generateInputPartitionings(sweepableInputs []txInput,
relayFeePerKW, feePerKW chainfee.SatPerKWeight, feePerKW chainfee.SatPerKWeight, maxInputsPerTx int,
maxInputsPerTx int, wallet Wallet) ([]inputSet, error) { wallet Wallet) ([]inputSet, error) {
// Sort input by yield. We will start constructing input sets starting // Sort input by yield. We will start constructing input sets starting
// with the highest yield inputs. This is to prevent the construction // with the highest yield inputs. This is to prevent the construction
@@ -85,9 +86,7 @@ func generateInputPartitionings(sweepableInputs []txInput,
// Start building a set of positive-yield tx inputs under the // Start building a set of positive-yield tx inputs under the
// condition that the tx will be published with the specified // condition that the tx will be published with the specified
// fee rate. // fee rate.
txInputs := newTxInputSet( txInputs := newTxInputSet(wallet, feePerKW, maxInputsPerTx)
wallet, feePerKW, relayFeePerKW, maxInputsPerTx,
)
// From the set of sweepable inputs, keep adding inputs to the // From the set of sweepable inputs, keep adding inputs to the
// input set until the tx output value no longer goes up or the // input set until the tx output value no longer goes up or the
@@ -111,10 +110,12 @@ func generateInputPartitionings(sweepableInputs []txInput,
// continuing with the remaining inputs will only lead to sets // continuing with the remaining inputs will only lead to sets
// with an even lower output value. // with an even lower output value.
if !txInputs.enoughInput() { if !txInputs.enoughInput() {
// The change output is always a p2wpkh here.
dl := lnwallet.DustLimitForSize(input.P2WPKHSize)
log.Debugf("Set value %v (r=%v, c=%v) below dust "+ log.Debugf("Set value %v (r=%v, c=%v) below dust "+
"limit of %v", txInputs.totalOutput(), "limit of %v", txInputs.totalOutput(),
txInputs.requiredOutput, txInputs.changeOutput, txInputs.requiredOutput, txInputs.changeOutput,
txInputs.dustLimit) dl)
return sets, nil return sets, nil
} }
@@ -135,8 +136,8 @@ func generateInputPartitionings(sweepableInputs []txInput,
// sending any leftover change to the change script. // sending any leftover change to the change script.
func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
changePkScript []byte, currentBlockHeight uint32, changePkScript []byte, currentBlockHeight uint32,
feePerKw chainfee.SatPerKWeight, dustLimit btcutil.Amount, feePerKw chainfee.SatPerKWeight, signer input.Signer) (*wire.MsgTx,
signer input.Signer) (*wire.MsgTx, error) { error) {
inputs, estimator := getWeightEstimate(inputs, outputs, feePerKw) inputs, estimator := getWeightEstimate(inputs, outputs, feePerKw)
txFee := estimator.fee() txFee := estimator.fee()
@@ -227,16 +228,20 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
// sweep tx has a change output. // sweep tx has a change output.
changeAmt := totalInput - requiredOutput - txFee changeAmt := totalInput - requiredOutput - txFee
// We'll calculate the dust limit for the given changePkScript since it
// is variable.
changeLimit := lnwallet.DustLimitForSize(len(changePkScript))
// The txn will sweep the amount after fees to the pkscript generated // The txn will sweep the amount after fees to the pkscript generated
// above. // above.
if changeAmt >= dustLimit { if changeAmt >= changeLimit {
sweepTx.AddTxOut(&wire.TxOut{ sweepTx.AddTxOut(&wire.TxOut{
PkScript: changePkScript, PkScript: changePkScript,
Value: int64(changeAmt), Value: int64(changeAmt),
}) })
} else { } else {
log.Infof("Change amt %v below dustlimit %v, not adding "+ log.Infof("Change amt %v below dustlimit %v, not adding "+
"change output", changeAmt, dustLimit) "change output", changeAmt, changeLimit)
} }
// We'll default to using the current block height as locktime, if none // We'll default to using the current block height as locktime, if none
+3 -3
View File
@@ -165,8 +165,8 @@ type DeliveryAddr struct {
// output, as specified by the change address. The sweep transaction will be // output, as specified by the change address. The sweep transaction will be
// crafted with the target fee rate, and will use the utxoSource and // crafted with the target fee rate, and will use the utxoSource and
// outpointLocker as sources for wallet funds. // outpointLocker as sources for wallet funds.
func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount, func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, blockHeight uint32,
blockHeight uint32, deliveryAddrs []DeliveryAddr, changeAddr btcutil.Address, 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, minConfs int32) (*WalletSweepPackage, error) { signer input.Signer, minConfs int32) (*WalletSweepPackage, error) {
@@ -302,7 +302,7 @@ func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount,
// 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, txOuts, changePkScript, blockHeight, feeRate, inputsToSweep, txOuts, changePkScript, blockHeight, feeRate,
dustLimit, signer, signer,
) )
if err != nil { if err != nil {
unlockOutputs() unlockOutputs()
+5 -5
View File
@@ -288,8 +288,8 @@ func TestCraftSweepAllTxCoinSelectFail(t *testing.T) {
utxoLocker := newMockOutpointLocker() utxoLocker := newMockOutpointLocker()
_, err := CraftSweepAllTx( _, err := CraftSweepAllTx(
0, 100, 10, nil, nil, coinSelectLocker, utxoSource, 0, 10, nil, nil, coinSelectLocker, utxoSource, utxoLocker, nil,
utxoLocker, nil, nil, 0, nil, 0,
) )
// Since we instructed the coin select locker to fail above, we should // Since we instructed the coin select locker to fail above, we should
@@ -314,8 +314,8 @@ func TestCraftSweepAllTxUnknownWitnessType(t *testing.T) {
utxoLocker := newMockOutpointLocker() utxoLocker := newMockOutpointLocker()
_, err := CraftSweepAllTx( _, err := CraftSweepAllTx(
0, 100, 10, nil, nil, coinSelectLocker, utxoSource, 0, 10, nil, nil, coinSelectLocker, utxoSource, utxoLocker, nil,
utxoLocker, nil, nil, 0, nil, 0,
) )
// 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
@@ -349,7 +349,7 @@ func TestCraftSweepAllTx(t *testing.T) {
utxoLocker := newMockOutpointLocker() utxoLocker := newMockOutpointLocker()
sweepPkg, err := CraftSweepAllTx( sweepPkg, err := CraftSweepAllTx(
0, 100, 10, nil, deliveryAddr, coinSelectLocker, utxoSource, 0, 10, nil, deliveryAddr, coinSelectLocker, utxoSource,
utxoLocker, feeEstimator, signer, 0, utxoLocker, feeEstimator, signer, 0,
) )
if err != nil { if err != nil {
@@ -300,7 +300,7 @@ func TestBackupTask(t *testing.T) {
expSweepCommitRewardLocal int64 = 197390 expSweepCommitRewardLocal int64 = 197390
expSweepCommitRewardRemote int64 = 98437 expSweepCommitRewardRemote int64 = 98437
sweepFeeRateNoRewardRemoteDust chainfee.SatPerKWeight = 227500 sweepFeeRateNoRewardRemoteDust chainfee.SatPerKWeight = 227500
sweepFeeRateRewardRemoteDust chainfee.SatPerKWeight = 175000 sweepFeeRateRewardRemoteDust chainfee.SatPerKWeight = 175350
) )
if chanType.HasAnchors() { if chanType.HasAnchors() {
expSweepCommitNoRewardBoth = 299236 expSweepCommitNoRewardBoth = 299236
@@ -309,8 +309,8 @@ func TestBackupTask(t *testing.T) {
expSweepCommitRewardBoth = 296112 expSweepCommitRewardBoth = 296112
expSweepCommitRewardLocal = 197389 expSweepCommitRewardLocal = 197389
expSweepCommitRewardRemote = 98433 expSweepCommitRewardRemote = 98433
sweepFeeRateNoRewardRemoteDust = 225000 sweepFeeRateNoRewardRemoteDust = 225400
sweepFeeRateRewardRemoteDust = 173750 sweepFeeRateRewardRemoteDust = 174100
} }
backupTaskTests = append(backupTaskTests, []backupTaskTest{ backupTaskTests = append(backupTaskTests, []backupTaskTest{
+7 -8
View File
@@ -6,6 +6,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/blob"
@@ -165,10 +166,9 @@ func (p *Policy) ComputeAltruistOutput(totalAmt btcutil.Amount,
sweepAmt := totalAmt - txFee sweepAmt := totalAmt - txFee
// TODO(conner): replace w/ configurable dust limit // TODO(conner): replace w/ configurable dust limit
dustLimit := lnwallet.DefaultDustLimit() // Check that the created outputs won't be dusty. The sweep pkscript is
// currently a p2wpkh, so we'll use that script's dust limit.
// Check that the created outputs won't be dusty. if sweepAmt < lnwallet.DustLimitForSize(input.P2WPKHSize) {
if sweepAmt <= dustLimit {
return 0, ErrCreatesDust return 0, ErrCreatesDust
} }
@@ -199,10 +199,9 @@ func (p *Policy) ComputeRewardOutputs(totalAmt btcutil.Amount,
sweepAmt := totalAmt - rewardAmt - txFee sweepAmt := totalAmt - rewardAmt - txFee
// TODO(conner): replace w/ configurable dust limit // TODO(conner): replace w/ configurable dust limit
dustLimit := lnwallet.DefaultDustLimit() // Check that the created outputs won't be dusty. The sweep pkscript is
// currently a p2wpkh, so we'll use that script's dust limit.
// Check that the created outputs won't be dusty. if sweepAmt < lnwallet.DustLimitForSize(input.P2WPKHSize) {
if sweepAmt <= dustLimit {
return 0, 0, ErrCreatesDust return 0, 0, ErrCreatesDust
} }