diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index ad0b2f54b..035bdf647 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -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 } diff --git a/funding/manager.go b/funding/manager.go index f60039044..9cbf77d5f 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -3114,19 +3114,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 +3291,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 +3310,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, diff --git a/funding/manager_test.go b/funding/manager_test.go index 97ee699f2..1a3f9905d 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -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 diff --git a/go.mod b/go.mod index c3e0c9370..2b249a91d 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 084797891..583a3757b 100644 --- a/go.sum +++ b/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= diff --git a/input/script_utils.go b/input/script_utils.go index 7ce6fff53..055fb7af2 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -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) { diff --git a/input/size.go b/input/size.go index 6ad55d87a..aa98d1da1 100644 --- a/input/size.go +++ b/input/size.go @@ -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) diff --git a/lntest/itest/lnd_misc_test.go b/lntest/itest/lnd_misc_test.go index 0046f0ab7..7de314e85 100644 --- a/lntest/itest/lnd_misc_test.go +++ b/lntest/itest/lnd_misc_test.go @@ -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, diff --git a/lnwallet/chanfunding/coin_select.go b/lnwallet/chanfunding/coin_select.go index 74a901ba8..e73a11b8d 100644 --- a/lnwallet/chanfunding/coin_select.go +++ b/lnwallet/chanfunding/coin_select.go @@ -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 } diff --git a/lnwallet/chanfunding/coin_select_test.go b/lnwallet/chanfunding/coin_select_test.go index 83c70dec5..d6cbd5ec7 100644 --- a/lnwallet/chanfunding/coin_select_test.go +++ b/lnwallet/chanfunding/coin_select_test.go @@ -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), }, }, }, diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index 504562853..c8c424a22 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -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) diff --git a/lnwallet/parameters.go b/lnwallet/parameters.go index 5cc6caf80..ccd5583d0 100644 --- a/lnwallet/parameters.go +++ b/lnwallet/parameters.go @@ -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 } diff --git a/lnwallet/parameters_test.go b/lnwallet/parameters_test.go new file mode 100644 index 000000000..021cb7bc6 --- /dev/null +++ b/lnwallet/parameters_test.go @@ -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) + }) + } +} diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 2b24178c4..ce9110c57 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -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), ) } diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index dd6bf1a95..40ae2db23 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -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, @@ -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, diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index 696328cdb..726996084 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -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, }, } diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index c1a2ca3c4..f0a2f57f3 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -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) } diff --git a/rpcserver.go b/rpcserver.go index 0e2770421..528412ba3 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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, ) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index f1f93aa6c..1219a2919 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -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, ) } diff --git a/sweep/sweeper_test.go b/sweep/sweeper_test.go index 2851a643b..e01544066 100644 --- a/sweep/sweeper_test.go +++ b/sweep/sweeper_test.go @@ -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, }, }, diff --git a/sweep/tx_input_set.go b/sweep/tx_input_set.go index dc2d6af2e..2f2ff9704 100644 --- a/sweep/tx_input_set.go +++ b/sweep/tx_input_set.go @@ -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. diff --git a/sweep/tx_input_set_test.go b/sweep/tx_input_set_test.go index ff21ff8e0..4dc31285a 100644 --- a/sweep/tx_input_set_test.go +++ b/sweep/tx_input_set_test.go @@ -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") diff --git a/sweep/txgenerator.go b/sweep/txgenerator.go index 10b3c7eef..35e5adfcb 100644 --- a/sweep/txgenerator.go +++ b/sweep/txgenerator.go @@ -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 diff --git a/sweep/walletsweep.go b/sweep/walletsweep.go index 4b10eea55..61c34178a 100644 --- a/sweep/walletsweep.go +++ b/sweep/walletsweep.go @@ -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() diff --git a/sweep/walletsweep_test.go b/sweep/walletsweep_test.go index f62fb900f..8f7d46877 100644 --- a/sweep/walletsweep_test.go +++ b/sweep/walletsweep_test.go @@ -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 { diff --git a/watchtower/wtclient/backup_task_internal_test.go b/watchtower/wtclient/backup_task_internal_test.go index db85472d7..e3183dca9 100644 --- a/watchtower/wtclient/backup_task_internal_test.go +++ b/watchtower/wtclient/backup_task_internal_test.go @@ -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{ diff --git a/watchtower/wtpolicy/policy.go b/watchtower/wtpolicy/policy.go index 40eb001b5..ba1c38459 100644 --- a/watchtower/wtpolicy/policy.go +++ b/watchtower/wtpolicy/policy.go @@ -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 }