mirror of
https://github.com/aljazceru/breez-lnd.git
synced 2026-02-14 11:04:25 +01:00
Merge pull request #5781 from Crypt-iQ/accurate_dust0922
multi: replace DefaultDustLimit with accurate dust limit
This commit is contained in:
@@ -174,15 +174,6 @@ const (
|
||||
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
|
||||
// meant to be used when initially funding a Litecoin channel.
|
||||
var DefaultLtcChannelConstraints = channeldb.ChannelConstraints{
|
||||
@@ -235,6 +226,19 @@ type ChainControl struct {
|
||||
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
|
||||
// to the parameters in the passed configuration. Currently three
|
||||
// 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
|
||||
|
||||
// Select the default channel constraints for the primary chain.
|
||||
channelConstraints := DefaultBtcChannelConstraints
|
||||
channelConstraints := GenDefaultBtcConstraints()
|
||||
if cfg.PrimaryChain() == LitecoinChain {
|
||||
channelConstraints = DefaultLtcChannelConstraints
|
||||
}
|
||||
|
||||
9
docs/release-notes/release-notes-0.13.3.md
Normal file
9
docs/release-notes/release-notes-0.13.3.md
Normal file
@@ -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
|
||||
@@ -1340,7 +1340,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
|
||||
CsvDelay: msg.CsvDelay,
|
||||
}
|
||||
err = reservation.CommitConstraints(
|
||||
channelConstraints, f.cfg.MaxLocalCSVDelay,
|
||||
channelConstraints, f.cfg.MaxLocalCSVDelay, true,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Unacceptable channel constraints: %v", err)
|
||||
@@ -1386,7 +1386,19 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
|
||||
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 {
|
||||
chanReserve = acceptorResp.Reserve
|
||||
}
|
||||
@@ -1576,7 +1588,7 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
|
||||
CsvDelay: msg.CsvDelay,
|
||||
}
|
||||
err = resCtx.reservation.CommitConstraints(
|
||||
channelConstraints, resCtx.maxLocalCsv,
|
||||
channelConstraints, resCtx.maxLocalCsv, false,
|
||||
)
|
||||
if err != nil {
|
||||
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
|
||||
// here so we can properly commit their accepted constraints to the
|
||||
// reservation.
|
||||
chanReserve := f.cfg.RequiredRemoteChanReserve(resCtx.chanAmt, msg.DustLimit)
|
||||
// reservation. Also make sure that we re-generate the ChannelReserve
|
||||
// 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
|
||||
// contribution. At this point, we can process their contribution which
|
||||
@@ -3114,19 +3129,10 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
|
||||
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 "+
|
||||
"(subtract_fees=%v), push_amt=%v, chain_hash=%v, peer=%x, "+
|
||||
"dust_limit=%v, min_confs=%v)", localAmt, msg.SubtractFees,
|
||||
msg.PushAmt, msg.ChainHash, peerKey.SerializeCompressed(),
|
||||
ourDustLimit, msg.MinConfs)
|
||||
"min_confs=%v)", localAmt, msg.SubtractFees, msg.PushAmt,
|
||||
msg.ChainHash, peerKey.SerializeCompressed(), msg.MinConfs)
|
||||
|
||||
// We set the channel flags to indicate whether we want this channel to
|
||||
// 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.
|
||||
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
|
||||
// policy to determine of required commitment constraints for the
|
||||
// remote party.
|
||||
@@ -3313,7 +3325,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
|
||||
PendingChannelID: chanID,
|
||||
FundingAmount: capacity,
|
||||
PushAmount: msg.PushAmt,
|
||||
DustLimit: ourContribution.DustLimit,
|
||||
DustLimit: ourDustLimit,
|
||||
MaxValueInFlight: maxValue,
|
||||
ChannelReserve: chanReserve,
|
||||
HtlcMinimum: minHtlcIn,
|
||||
|
||||
@@ -276,7 +276,7 @@ func createTestWallet(cdb *channeldb.DB, netParams *chaincfg.Params,
|
||||
ChainIO: bio,
|
||||
FeeEstimator: estimator,
|
||||
NetParams: *netParams,
|
||||
DefaultConstraints: chainreg.DefaultBtcChannelConstraints,
|
||||
DefaultConstraints: chainreg.GenDefaultBtcConstraints(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
11
go.mod
11
go.mod
@@ -5,13 +5,14 @@ require (
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect
|
||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
|
||||
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/btcutil 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/wallet/txauthor v1.0.2-0.20210803004036-eebed51155ec
|
||||
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.1.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/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c
|
||||
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/utils v0.0.0-20180820210520-bf9cc5bdd62d // 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/protobuf-hex-display v1.4.3-hex-display
|
||||
github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1
|
||||
@@ -56,7 +57,7 @@ require (
|
||||
github.com/urfave/cli v1.20.0
|
||||
go.etcd.io/etcd/client/pkg/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/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect
|
||||
|
||||
19
go.sum
19
go.sum
@@ -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.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.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.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/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
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/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.2-0.20210803004036-eebed51155ec h1:nuO8goa4gbgDM4iegCztF7mTq8io9NT1DAMoPrEI6S4=
|
||||
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/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk=
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww=
|
||||
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.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.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.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
|
||||
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/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-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs=
|
||||
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.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
|
||||
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-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-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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
||||
@@ -45,6 +45,55 @@ func WitnessScriptHash(witnessScript []byte) ([]byte, error) {
|
||||
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
|
||||
// pubkeys.
|
||||
func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) {
|
||||
|
||||
@@ -41,11 +41,20 @@ const (
|
||||
// - P2WSHWitnessProgram: 34 bytes
|
||||
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
|
||||
// - value: 8 bytes
|
||||
// - var_int: 1 byte (pkscript_length)
|
||||
// - pkscript (p2pkh): 25 bytes
|
||||
P2PKHOutputSize = 8 + 1 + 25
|
||||
P2PKHOutputSize = 8 + 1 + P2PKHSize
|
||||
|
||||
// P2WKHOutputSize 31 bytes
|
||||
// - value: 8 bytes
|
||||
@@ -59,11 +68,14 @@ const (
|
||||
// - pkscript (p2wsh): 34 bytes
|
||||
P2WSHOutputSize = 8 + 1 + P2WSHSize
|
||||
|
||||
// P2SHSize 23 bytes
|
||||
P2SHSize = 23
|
||||
|
||||
// P2SHOutputSize 32 bytes
|
||||
// - value: 8 bytes
|
||||
// - var_int: 1 byte (pkscript_length)
|
||||
// - pkscript (p2sh): 23 bytes
|
||||
P2SHOutputSize = 8 + 1 + 23
|
||||
P2SHOutputSize = 8 + 1 + P2SHSize
|
||||
|
||||
// P2PKHScriptSigSize 108 bytes
|
||||
// - OP_DATA: 1 byte (signature length)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/funding"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
@@ -417,12 +418,15 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
// Check the returned response is correct.
|
||||
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
|
||||
// used to test against Alice's local channel constraints.
|
||||
defaultConstraints := &lnrpc.ChannelConstraints{
|
||||
CsvDelay: 4,
|
||||
ChanReserveSat: 1000,
|
||||
DustLimitSat: uint64(lnwallet.DefaultDustLimit()),
|
||||
DustLimitSat: uint64(dustLimit),
|
||||
MaxPendingAmtMsat: 99000000,
|
||||
MinHtlcMsat: 1,
|
||||
MaxAcceptedHtlcs: bobRemoteMaxHtlcs,
|
||||
@@ -438,7 +442,7 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
customizedConstraints := &lnrpc.ChannelConstraints{
|
||||
CsvDelay: 4,
|
||||
ChanReserveSat: 1000,
|
||||
DustLimitSat: uint64(lnwallet.DefaultDustLimit()),
|
||||
DustLimitSat: uint64(dustLimit),
|
||||
MaxPendingAmtMsat: 99000000,
|
||||
MinHtlcMsat: customizedMinHtlc,
|
||||
MaxAcceptedHtlcs: aliceRemoteMaxHtlcs,
|
||||
|
||||
@@ -219,7 +219,7 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
|
||||
|
||||
// If the the output is too small after subtracting the fee, the coin
|
||||
// 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 "+
|
||||
"subtracting fees(%v) below dust limit(%v)", outputAmt,
|
||||
requiredFeeNoChange, dustLimit)
|
||||
@@ -233,7 +233,7 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
|
||||
// If adding a change output leads to both outputs being above
|
||||
// the dust limit, we'll add the change output. Otherwise we'll
|
||||
// go with the no change tx we originally found.
|
||||
if newChange > dustLimit && newOutput > dustLimit {
|
||||
if newChange >= dustLimit && newOutput >= dustLimit {
|
||||
outputAmt = newOutput
|
||||
changeAmt = newChange
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ func TestCoinSelectSubtractFees(t *testing.T) {
|
||||
{
|
||||
TxOut: wire.TxOut{
|
||||
PkScript: p2wkhScript,
|
||||
Value: int64(fundingFee(feeRate, 1, false) + dustLimit),
|
||||
Value: int64(fundingFee(feeRate, 1, false) + dustLimit - 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -304,7 +304,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
||||
|
||||
// Sanity check: The addition of the outputs should not lead to the
|
||||
// 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 "+
|
||||
"select is below dust limit(%v)", changeAmt,
|
||||
w.cfg.DustLimit)
|
||||
|
||||
@@ -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
|
||||
// failed, but has not been committed by our commitment state.
|
||||
type ErrHtlcIndexAlreadyFailed uint64
|
||||
|
||||
@@ -1,13 +1,50 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/mempool"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet/txrules"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
)
|
||||
|
||||
// DefaultDustLimit is used to calculate the dust HTLC amount which will be
|
||||
// send to other node during funding process.
|
||||
func DefaultDustLimit() btcutil.Amount {
|
||||
return txrules.GetDustThreshold(input.P2WSHSize, txrules.DefaultRelayFeePerKb)
|
||||
// DustLimitForSize retrieves the dust limit for a given pkscript size. Given
|
||||
// the size, it automatically determines whether the script is a witness script
|
||||
// or not. It calls btcd's GetDustThreshold method under the hood. It must be
|
||||
// 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
lnwallet/parameters_test.go
Normal file
56
lnwallet/parameters_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -205,6 +205,9 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
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
|
||||
// no initial balance in the channel unless the remote party is pushing
|
||||
// some funds to us within the first commitment state.
|
||||
@@ -218,7 +221,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
if int64(theirBalance) < 0 {
|
||||
return nil, ErrFunderBalanceDust(
|
||||
int64(commitFee), int64(theirBalance.ToSatoshis()),
|
||||
int64(2*DefaultDustLimit()),
|
||||
int64(2*defaultDust),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -247,7 +250,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
if int64(ourBalance) < 0 {
|
||||
return nil, ErrFunderBalanceDust(
|
||||
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.
|
||||
//
|
||||
// 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(
|
||||
int64(commitFee),
|
||||
int64(ourBalance.ToSatoshis()),
|
||||
int64(2*DefaultDustLimit()),
|
||||
int64(2*defaultDust),
|
||||
)
|
||||
}
|
||||
|
||||
// Similarly we ensure their balance is reasonable if we are not the
|
||||
// initiator.
|
||||
if !initiator && theirBalance.ToSatoshis() <= 2*DefaultDustLimit() {
|
||||
if !initiator && theirBalance.ToSatoshis() <= 2*defaultDust {
|
||||
return nil, ErrFunderBalanceDust(
|
||||
int64(commitFee),
|
||||
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
|
||||
// if the parameters are seemed unsound.
|
||||
func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
||||
maxLocalCSVDelay uint16) error {
|
||||
maxLocalCSVDelay uint16, responder bool) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
@@ -406,6 +409,13 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
||||
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
|
||||
// currently fail if it is greater than 20% of the channel capacity.
|
||||
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
|
||||
// channel reserve.
|
||||
if r.ourContribution.DustLimit > c.ChanReserve {
|
||||
if responder && r.ourContribution.DustLimit > c.ChanReserve {
|
||||
r.ourContribution.DustLimit = c.ChanReserve
|
||||
}
|
||||
|
||||
@@ -459,6 +469,31 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
||||
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
|
||||
// pending payment channel. See 'ChannelContribution' for further details
|
||||
// regarding the contents of a contribution.
|
||||
|
||||
@@ -446,7 +446,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
}
|
||||
aliceChanReservation.SetNumConfsRequired(numReqConfs)
|
||||
channelConstraints := &channeldb.ChannelConstraints{
|
||||
DustLimit: lnwallet.DefaultDustLimit(),
|
||||
DustLimit: alice.Cfg.DefaultConstraints.DustLimit,
|
||||
ChanReserve: fundingAmount / 100,
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmount),
|
||||
MinHTLC: 1,
|
||||
@@ -454,7 +454,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
CsvDelay: csvDelay,
|
||||
}
|
||||
err = aliceChanReservation.CommitConstraints(
|
||||
channelConstraints, defaultMaxLocalCsvDelay,
|
||||
channelConstraints, defaultMaxLocalCsvDelay, false,
|
||||
)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
err = bobChanReservation.CommitConstraints(
|
||||
channelConstraints, defaultMaxLocalCsvDelay,
|
||||
channelConstraints, defaultMaxLocalCsvDelay, true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to verify constraints: %v", err)
|
||||
@@ -896,7 +896,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
}
|
||||
aliceChanReservation.SetNumConfsRequired(numReqConfs)
|
||||
channelConstraints := &channeldb.ChannelConstraints{
|
||||
DustLimit: lnwallet.DefaultDustLimit(),
|
||||
DustLimit: alice.Cfg.DefaultConstraints.DustLimit,
|
||||
ChanReserve: fundingAmt / 100,
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmt),
|
||||
MinHTLC: 1,
|
||||
@@ -904,7 +904,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
CsvDelay: csvDelay,
|
||||
}
|
||||
err = aliceChanReservation.CommitConstraints(
|
||||
channelConstraints, defaultMaxLocalCsvDelay,
|
||||
channelConstraints, defaultMaxLocalCsvDelay, false,
|
||||
)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
err = bobChanReservation.CommitConstraints(
|
||||
channelConstraints, defaultMaxLocalCsvDelay,
|
||||
channelConstraints, defaultMaxLocalCsvDelay, true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to verify constraints: %v", err)
|
||||
|
||||
@@ -542,16 +542,19 @@ func testSpendValidation(t *testing.T, tweakless bool) {
|
||||
Privkeys: []*btcec.PrivateKey{aliceKeyPriv},
|
||||
}
|
||||
|
||||
// Calculate the dust limit we'll use for the test.
|
||||
dustLimit := DustLimitForSize(input.UnknownWitnessSize)
|
||||
|
||||
aliceChanCfg := &channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: DefaultDustLimit(),
|
||||
DustLimit: dustLimit,
|
||||
CsvDelay: csvTimeout,
|
||||
},
|
||||
}
|
||||
|
||||
bobChanCfg := &channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: DefaultDustLimit(),
|
||||
DustLimit: dustLimit,
|
||||
CsvDelay: csvTimeout,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -689,12 +689,15 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
||||
// If no chanFunder was provided, then we'll assume the default
|
||||
// assembler, which is backed by the wallet's internal coin selection.
|
||||
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{
|
||||
CoinSource: &CoinSource{l},
|
||||
CoinSelectLocker: l,
|
||||
CoinLocker: l,
|
||||
Signer: l.Cfg.Signer,
|
||||
DustLimit: DefaultDustLimit(),
|
||||
DustLimit: DustLimitForSize(input.P2WSHSize),
|
||||
}
|
||||
req.ChanFunder = chanfunding.NewWalletAssembler(cfg)
|
||||
}
|
||||
@@ -1263,6 +1266,13 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||
theirContribution := req.contribution
|
||||
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 (
|
||||
chanPoint *wire.OutPoint
|
||||
err error
|
||||
@@ -1561,9 +1571,6 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
||||
pendingReservation.Lock()
|
||||
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
|
||||
// if it's set.
|
||||
shutdown := req.contribution.UpfrontShutdown
|
||||
@@ -1581,6 +1588,14 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
||||
theirContribution := pendingReservation.theirContribution
|
||||
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
|
||||
// revocation hash (we don't yet know the preimage so we can't add it
|
||||
// to the chain).
|
||||
|
||||
@@ -1227,8 +1227,7 @@ func (r *rpcServer) SendCoins(ctx context.Context,
|
||||
// pay to the change address created above if we needed to
|
||||
// reserve any value, the rest will go to targetAddr.
|
||||
sweepTxPkg, err := sweep.CraftSweepAllTx(
|
||||
feePerKw, lnwallet.DefaultDustLimit(),
|
||||
uint32(bestHeight), nil, targetAddr, wallet,
|
||||
feePerKw, uint32(bestHeight), nil, targetAddr, wallet,
|
||||
wallet, wallet.WalletController,
|
||||
r.server.cc.FeeEstimator, r.server.cc.Signer,
|
||||
minConfs,
|
||||
@@ -1280,9 +1279,9 @@ func (r *rpcServer) SendCoins(ctx context.Context,
|
||||
}
|
||||
|
||||
sweepTxPkg, err = sweep.CraftSweepAllTx(
|
||||
feePerKw, lnwallet.DefaultDustLimit(),
|
||||
uint32(bestHeight), outputs, targetAddr, wallet,
|
||||
wallet, wallet.WalletController,
|
||||
feePerKw, uint32(bestHeight), outputs,
|
||||
targetAddr, wallet, wallet,
|
||||
wallet.WalletController,
|
||||
r.server.cc.FeeEstimator, r.server.cc.Signer,
|
||||
minConfs,
|
||||
)
|
||||
|
||||
@@ -1150,7 +1150,7 @@ func (s *UtxoSweeper) getInputLists(cluster inputCluster,
|
||||
if len(retryInputs) > 0 {
|
||||
var err error
|
||||
allSets, err = generateInputPartitionings(
|
||||
append(retryInputs, newInputs...), s.relayFeeRate,
|
||||
append(retryInputs, newInputs...),
|
||||
cluster.sweepFeeRate, s.cfg.MaxInputsPerTx,
|
||||
s.cfg.Wallet,
|
||||
)
|
||||
@@ -1161,8 +1161,8 @@ func (s *UtxoSweeper) getInputLists(cluster inputCluster,
|
||||
|
||||
// Create sets for just the new inputs.
|
||||
newSets, err := generateInputPartitionings(
|
||||
newInputs, s.relayFeeRate, cluster.sweepFeeRate,
|
||||
s.cfg.MaxInputsPerTx, s.cfg.Wallet,
|
||||
newInputs, cluster.sweepFeeRate, s.cfg.MaxInputsPerTx,
|
||||
s.cfg.Wallet,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("input partitionings: %v", err)
|
||||
@@ -1193,7 +1193,7 @@ func (s *UtxoSweeper) sweep(inputs inputSet, feeRate chainfee.SatPerKWeight,
|
||||
// Create sweep tx.
|
||||
tx, err := createSweepTx(
|
||||
inputs, nil, s.currentOutputScript, uint32(currentHeight),
|
||||
feeRate, dustLimit(s.relayFeeRate), s.cfg.Signer,
|
||||
feeRate, s.cfg.Signer,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create sweep tx: %v", err)
|
||||
@@ -1488,7 +1488,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []input.Input, feePref FeePreference,
|
||||
|
||||
return createSweepTx(
|
||||
inputs, nil, pkScript, currentBlockHeight, feePerKw,
|
||||
dustLimit(s.relayFeeRate), s.cfg.Signer,
|
||||
s.cfg.Signer,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,8 @@ func createSweeperTestContext(t *testing.T) *sweeperTestContext {
|
||||
Store: store,
|
||||
Signer: &mock.DummySigner{},
|
||||
GenSweepScript: func() ([]byte, error) {
|
||||
script := []byte{outputScriptCount}
|
||||
script := make([]byte, input.P2WPKHSize)
|
||||
script[0] = outputScriptCount
|
||||
outputScriptCount++
|
||||
return script, nil
|
||||
},
|
||||
@@ -1801,6 +1802,24 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
locktime2 := uint32(52)
|
||||
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 {
|
||||
name string
|
||||
inputs []*testInput
|
||||
@@ -1815,7 +1834,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
{
|
||||
BaseInput: inputs[0],
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("aaa"),
|
||||
PkScript: aPkScript,
|
||||
Value: 100000,
|
||||
},
|
||||
},
|
||||
@@ -1836,7 +1855,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
// output must be the first one.
|
||||
require.Equal(t, 2, len(tx.TxOut))
|
||||
out := tx.TxOut[0]
|
||||
require.Equal(t, []byte("aaa"), out.PkScript)
|
||||
require.Equal(t, aPkScript, out.PkScript)
|
||||
require.Equal(t, int64(100000), out.Value)
|
||||
},
|
||||
},
|
||||
@@ -1848,13 +1867,13 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
{
|
||||
BaseInput: inputs[0],
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("aaa"),
|
||||
PkScript: aPkScript,
|
||||
|
||||
// Fee will be about 5340 sats.
|
||||
// Subtract a bit more to
|
||||
// ensure no dust change output
|
||||
// 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))
|
||||
out := tx.TxOut[0]
|
||||
require.Equal(t, []byte("aaa"), out.PkScript)
|
||||
require.Equal(t, aPkScript, out.PkScript)
|
||||
require.Equal(
|
||||
t,
|
||||
inputs[0].SignDesc().Output.Value-5600,
|
||||
inputs[0].SignDesc().Output.Value-6300,
|
||||
out.Value,
|
||||
)
|
||||
},
|
||||
@@ -1897,7 +1916,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
// The second input requires a TxOut.
|
||||
BaseInput: inputs[0],
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("aaa"),
|
||||
PkScript: aPkScript,
|
||||
Value: inputs[0].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
@@ -1916,7 +1935,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
|
||||
// The required TxOut should be the first one.
|
||||
out := tx.TxOut[0]
|
||||
require.Equal(t, []byte("aaa"), out.PkScript)
|
||||
require.Equal(t, aPkScript, out.PkScript)
|
||||
require.Equal(
|
||||
t, inputs[0].SignDesc().Output.Value,
|
||||
out.Value,
|
||||
@@ -1947,7 +1966,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
{
|
||||
BaseInput: inputs[0],
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("aaa"),
|
||||
PkScript: aPkScript,
|
||||
Value: inputs[0].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
@@ -1965,7 +1984,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
|
||||
require.Equal(t, 2, len(tx.TxOut))
|
||||
out := tx.TxOut[0]
|
||||
require.Equal(t, []byte("aaa"), out.PkScript)
|
||||
require.Equal(t, aPkScript, out.PkScript)
|
||||
require.Equal(
|
||||
t, inputs[0].SignDesc().Output.Value,
|
||||
out.Value,
|
||||
@@ -1980,21 +1999,21 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
{
|
||||
BaseInput: inputs[0],
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("aaa"),
|
||||
PkScript: aPkScript,
|
||||
Value: inputs[0].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
{
|
||||
BaseInput: inputs[1],
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("bbb"),
|
||||
PkScript: bPkScript,
|
||||
Value: inputs[1].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
{
|
||||
BaseInput: inputs[2],
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("ccc"),
|
||||
PkScript: cPkScript,
|
||||
Value: inputs[2].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
@@ -2041,7 +2060,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
BaseInput: inputs[0],
|
||||
locktime: &locktime1,
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("aaa"),
|
||||
PkScript: aPkScript,
|
||||
Value: inputs[0].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
@@ -2049,7 +2068,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
BaseInput: inputs[1],
|
||||
locktime: &locktime1,
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("bbb"),
|
||||
PkScript: bPkScript,
|
||||
Value: inputs[1].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
@@ -2057,7 +2076,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
BaseInput: inputs[2],
|
||||
locktime: &locktime2,
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("ccc"),
|
||||
PkScript: cPkScript,
|
||||
Value: inputs[2].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
@@ -2065,7 +2084,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
BaseInput: inputs[3],
|
||||
locktime: &locktime2,
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("ddd"),
|
||||
PkScript: dPkScript,
|
||||
Value: inputs[3].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
@@ -2073,7 +2092,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
BaseInput: inputs[4],
|
||||
locktime: &locktime3,
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("eee"),
|
||||
PkScript: ePkScript,
|
||||
Value: inputs[4].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
@@ -2081,7 +2100,7 @@ func TestRequiredTxOuts(t *testing.T) {
|
||||
BaseInput: inputs[5],
|
||||
locktime: &locktime3,
|
||||
reqTxOut: &wire.TxOut{
|
||||
PkScript: []byte("fff"),
|
||||
PkScript: fPkScript,
|
||||
Value: inputs[5].SignDesc().Output.Value,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet/txrules"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
@@ -109,9 +108,6 @@ func (t *txInputSetState) clone() txInputSetState {
|
||||
type txInputSet struct {
|
||||
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
|
||||
// the set.
|
||||
maxInputs int
|
||||
@@ -121,25 +117,15 @@ type txInputSet struct {
|
||||
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.
|
||||
func newTxInputSet(wallet Wallet, feePerKW,
|
||||
relayFee chainfee.SatPerKWeight, maxInputs int) *txInputSet {
|
||||
|
||||
dustLimit := dustLimit(relayFee)
|
||||
func newTxInputSet(wallet Wallet, feePerKW chainfee.SatPerKWeight,
|
||||
maxInputs int) *txInputSet {
|
||||
|
||||
state := txInputSetState{
|
||||
feeRate: feePerKW,
|
||||
}
|
||||
|
||||
b := txInputSet{
|
||||
dustLimit: dustLimit,
|
||||
maxInputs: maxInputs,
|
||||
wallet: wallet,
|
||||
txInputSetState: state,
|
||||
@@ -153,7 +139,7 @@ func newTxInputSet(wallet Wallet, feePerKW,
|
||||
func (t *txInputSet) enoughInput() bool {
|
||||
// If we have a change output above dust, then we certainly have enough
|
||||
// inputs to the transaction.
|
||||
if t.changeOutput >= t.dustLimit {
|
||||
if t.changeOutput >= lnwallet.DustLimitForSize(input.P2WPKHSize) {
|
||||
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
|
||||
// won't add it.
|
||||
reqOut := inp.RequiredTxOut()
|
||||
if reqOut != nil && btcutil.Amount(reqOut.Value) < t.dustLimit {
|
||||
return nil
|
||||
if reqOut != 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.
|
||||
|
||||
@@ -14,14 +14,9 @@ import (
|
||||
func TestTxInputSet(t *testing.T) {
|
||||
const (
|
||||
feeRate = 1000
|
||||
relayFee = 300
|
||||
maxInputs = 10
|
||||
)
|
||||
set := newTxInputSet(nil, feeRate, relayFee, maxInputs)
|
||||
|
||||
if set.dustLimit != 537 {
|
||||
t.Fatalf("incorrect dust limit")
|
||||
}
|
||||
set := newTxInputSet(nil, feeRate, maxInputs)
|
||||
|
||||
// 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
|
||||
@@ -66,16 +61,15 @@ func TestTxInputSet(t *testing.T) {
|
||||
func TestTxInputSetFromWallet(t *testing.T) {
|
||||
const (
|
||||
feeRate = 500
|
||||
relayFee = 300
|
||||
maxInputs = 10
|
||||
)
|
||||
|
||||
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.
|
||||
if !set.add(createP2WKHInput(700), constraintsRegular) {
|
||||
if !set.add(createP2WKHInput(500), constraintsRegular) {
|
||||
t.Fatal("expected add of positively yielding input to succeed")
|
||||
}
|
||||
if set.enoughInput() {
|
||||
@@ -138,13 +132,9 @@ func (r *reqInput) RequiredTxOut() *wire.TxOut {
|
||||
func TestTxInputSetRequiredOutput(t *testing.T) {
|
||||
const (
|
||||
feeRate = 1000
|
||||
relayFee = 300
|
||||
maxInputs = 10
|
||||
)
|
||||
set := newTxInputSet(nil, feeRate, relayFee, maxInputs)
|
||||
if set.dustLimit != 537 {
|
||||
t.Fatalf("incorrect dust limit")
|
||||
}
|
||||
set := newTxInputSet(nil, feeRate, maxInputs)
|
||||
|
||||
// Attempt to add an input with a required txout below the dust limit.
|
||||
// This should fail since we cannot trim such outputs.
|
||||
@@ -152,7 +142,7 @@ func TestTxInputSetRequiredOutput(t *testing.T) {
|
||||
Input: createP2WKHInput(500),
|
||||
txOut: &wire.TxOut{
|
||||
Value: 500,
|
||||
PkScript: make([]byte, 33),
|
||||
PkScript: make([]byte, input.P2PKHSize),
|
||||
},
|
||||
}
|
||||
require.False(t, set.add(inp, constraintsRegular),
|
||||
@@ -164,7 +154,7 @@ func TestTxInputSetRequiredOutput(t *testing.T) {
|
||||
Input: createP2WKHInput(1000),
|
||||
txOut: &wire.TxOut{
|
||||
Value: 1000,
|
||||
PkScript: make([]byte, 22),
|
||||
PkScript: make([]byte, input.P2WPKHSize),
|
||||
},
|
||||
}
|
||||
require.True(t, set.add(inp, constraintsRegular), "failed adding input")
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"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
|
||||
// dust limit are returned.
|
||||
func generateInputPartitionings(sweepableInputs []txInput,
|
||||
relayFeePerKW, feePerKW chainfee.SatPerKWeight,
|
||||
maxInputsPerTx int, wallet Wallet) ([]inputSet, error) {
|
||||
feePerKW chainfee.SatPerKWeight, maxInputsPerTx int,
|
||||
wallet Wallet) ([]inputSet, error) {
|
||||
|
||||
// Sort input by yield. We will start constructing input sets starting
|
||||
// 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
|
||||
// condition that the tx will be published with the specified
|
||||
// fee rate.
|
||||
txInputs := newTxInputSet(
|
||||
wallet, feePerKW, relayFeePerKW, maxInputsPerTx,
|
||||
)
|
||||
txInputs := newTxInputSet(wallet, feePerKW, maxInputsPerTx)
|
||||
|
||||
// From the set of sweepable inputs, keep adding inputs to 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
|
||||
// with an even lower output value.
|
||||
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 "+
|
||||
"limit of %v", txInputs.totalOutput(),
|
||||
txInputs.requiredOutput, txInputs.changeOutput,
|
||||
txInputs.dustLimit)
|
||||
dl)
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
@@ -135,8 +136,8 @@ func generateInputPartitionings(sweepableInputs []txInput,
|
||||
// sending any leftover change to the change script.
|
||||
func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
|
||||
changePkScript []byte, currentBlockHeight uint32,
|
||||
feePerKw chainfee.SatPerKWeight, dustLimit btcutil.Amount,
|
||||
signer input.Signer) (*wire.MsgTx, error) {
|
||||
feePerKw chainfee.SatPerKWeight, signer input.Signer) (*wire.MsgTx,
|
||||
error) {
|
||||
|
||||
inputs, estimator := getWeightEstimate(inputs, outputs, feePerKw)
|
||||
txFee := estimator.fee()
|
||||
@@ -227,16 +228,20 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
|
||||
// sweep tx has a change output.
|
||||
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
|
||||
// above.
|
||||
if changeAmt >= dustLimit {
|
||||
if changeAmt >= changeLimit {
|
||||
sweepTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: changePkScript,
|
||||
Value: int64(changeAmt),
|
||||
})
|
||||
} else {
|
||||
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
|
||||
|
||||
@@ -165,8 +165,8 @@ type DeliveryAddr struct {
|
||||
// output, as specified by the change address. The sweep transaction will be
|
||||
// crafted with the target fee rate, and will use the utxoSource and
|
||||
// outpointLocker as sources for wallet funds.
|
||||
func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount,
|
||||
blockHeight uint32, deliveryAddrs []DeliveryAddr, changeAddr btcutil.Address,
|
||||
func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, blockHeight uint32,
|
||||
deliveryAddrs []DeliveryAddr, changeAddr btcutil.Address,
|
||||
coinSelectLocker CoinSelectionLocker, utxoSource UtxoSource,
|
||||
outpointLocker OutpointLocker, feeEstimator chainfee.Estimator,
|
||||
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.
|
||||
sweepTx, err := createSweepTx(
|
||||
inputsToSweep, txOuts, changePkScript, blockHeight, feeRate,
|
||||
dustLimit, signer,
|
||||
signer,
|
||||
)
|
||||
if err != nil {
|
||||
unlockOutputs()
|
||||
|
||||
@@ -288,8 +288,8 @@ func TestCraftSweepAllTxCoinSelectFail(t *testing.T) {
|
||||
utxoLocker := newMockOutpointLocker()
|
||||
|
||||
_, err := CraftSweepAllTx(
|
||||
0, 100, 10, nil, nil, coinSelectLocker, utxoSource,
|
||||
utxoLocker, nil, nil, 0,
|
||||
0, 10, nil, nil, coinSelectLocker, utxoSource, utxoLocker, nil,
|
||||
nil, 0,
|
||||
)
|
||||
|
||||
// Since we instructed the coin select locker to fail above, we should
|
||||
@@ -314,8 +314,8 @@ func TestCraftSweepAllTxUnknownWitnessType(t *testing.T) {
|
||||
utxoLocker := newMockOutpointLocker()
|
||||
|
||||
_, err := CraftSweepAllTx(
|
||||
0, 100, 10, nil, nil, coinSelectLocker, utxoSource,
|
||||
utxoLocker, nil, nil, 0,
|
||||
0, 10, nil, nil, coinSelectLocker, utxoSource, utxoLocker, nil,
|
||||
nil, 0,
|
||||
)
|
||||
|
||||
// Since passed in a p2wsh output, which is unknown, we should fail to
|
||||
@@ -349,7 +349,7 @@ func TestCraftSweepAllTx(t *testing.T) {
|
||||
utxoLocker := newMockOutpointLocker()
|
||||
|
||||
sweepPkg, err := CraftSweepAllTx(
|
||||
0, 100, 10, nil, deliveryAddr, coinSelectLocker, utxoSource,
|
||||
0, 10, nil, deliveryAddr, coinSelectLocker, utxoSource,
|
||||
utxoLocker, feeEstimator, signer, 0,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -300,7 +300,7 @@ func TestBackupTask(t *testing.T) {
|
||||
expSweepCommitRewardLocal int64 = 197390
|
||||
expSweepCommitRewardRemote int64 = 98437
|
||||
sweepFeeRateNoRewardRemoteDust chainfee.SatPerKWeight = 227500
|
||||
sweepFeeRateRewardRemoteDust chainfee.SatPerKWeight = 175000
|
||||
sweepFeeRateRewardRemoteDust chainfee.SatPerKWeight = 175350
|
||||
)
|
||||
if chanType.HasAnchors() {
|
||||
expSweepCommitNoRewardBoth = 299236
|
||||
@@ -309,8 +309,8 @@ func TestBackupTask(t *testing.T) {
|
||||
expSweepCommitRewardBoth = 296112
|
||||
expSweepCommitRewardLocal = 197389
|
||||
expSweepCommitRewardRemote = 98433
|
||||
sweepFeeRateNoRewardRemoteDust = 225000
|
||||
sweepFeeRateRewardRemoteDust = 173750
|
||||
sweepFeeRateNoRewardRemoteDust = 225400
|
||||
sweepFeeRateRewardRemoteDust = 174100
|
||||
}
|
||||
|
||||
backupTaskTests = append(backupTaskTests, []backupTaskTest{
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/watchtower/blob"
|
||||
@@ -165,10 +166,9 @@ func (p *Policy) ComputeAltruistOutput(totalAmt btcutil.Amount,
|
||||
sweepAmt := totalAmt - txFee
|
||||
|
||||
// TODO(conner): replace w/ configurable dust limit
|
||||
dustLimit := lnwallet.DefaultDustLimit()
|
||||
|
||||
// Check that the created outputs won't be dusty.
|
||||
if sweepAmt <= dustLimit {
|
||||
// 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.
|
||||
if sweepAmt < lnwallet.DustLimitForSize(input.P2WPKHSize) {
|
||||
return 0, ErrCreatesDust
|
||||
}
|
||||
|
||||
@@ -199,10 +199,9 @@ func (p *Policy) ComputeRewardOutputs(totalAmt btcutil.Amount,
|
||||
sweepAmt := totalAmt - rewardAmt - txFee
|
||||
|
||||
// TODO(conner): replace w/ configurable dust limit
|
||||
dustLimit := lnwallet.DefaultDustLimit()
|
||||
|
||||
// Check that the created outputs won't be dusty.
|
||||
if sweepAmt <= dustLimit {
|
||||
// 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.
|
||||
if sweepAmt < lnwallet.DustLimitForSize(input.P2WPKHSize) {
|
||||
return 0, 0, ErrCreatesDust
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user