funding+lnwallet: validate ChannelReserve is above DustLimit
This is necessary and is implied by BOLT#02. Both ChannelReserve parameters should be above both DustLimit parameters. Otherwise, it is possible for one side to have nothing at stake.
This commit is contained in:
5 files changed
+81
-14
No files matched your search
+20
-5
@@ -1340,7 +1340,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
|
|||||||
CsvDelay: msg.CsvDelay,
|
CsvDelay: msg.CsvDelay,
|
||||||
}
|
}
|
||||||
err = reservation.CommitConstraints(
|
err = reservation.CommitConstraints(
|
||||||
channelConstraints, f.cfg.MaxLocalCSVDelay,
|
channelConstraints, f.cfg.MaxLocalCSVDelay, true,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unacceptable channel constraints: %v", err)
|
log.Errorf("Unacceptable channel constraints: %v", err)
|
||||||
@@ -1386,7 +1386,19 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
|
|||||||
remoteCsvDelay = acceptorResp.CSVDelay
|
remoteCsvDelay = acceptorResp.CSVDelay
|
||||||
}
|
}
|
||||||
|
|
||||||
chanReserve := f.cfg.RequiredRemoteChanReserve(amt, msg.DustLimit)
|
// If our default dust limit was above their ChannelReserve, we change
|
||||||
|
// it to the ChannelReserve. We must make sure the ChannelReserve we
|
||||||
|
// send in the AcceptChannel message is above both dust limits.
|
||||||
|
// Therefore, take the maximum of msg.DustLimit and our dust limit.
|
||||||
|
//
|
||||||
|
// NOTE: Even with this bounding, the ChannelAcceptor may return an
|
||||||
|
// BOLT#02-invalid ChannelReserve.
|
||||||
|
maxDustLimit := reservation.OurContribution().DustLimit
|
||||||
|
if msg.DustLimit > maxDustLimit {
|
||||||
|
maxDustLimit = msg.DustLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
chanReserve := f.cfg.RequiredRemoteChanReserve(amt, maxDustLimit)
|
||||||
if acceptorResp.Reserve != 0 {
|
if acceptorResp.Reserve != 0 {
|
||||||
chanReserve = acceptorResp.Reserve
|
chanReserve = acceptorResp.Reserve
|
||||||
}
|
}
|
||||||
@@ -1576,7 +1588,7 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
|
|||||||
CsvDelay: msg.CsvDelay,
|
CsvDelay: msg.CsvDelay,
|
||||||
}
|
}
|
||||||
err = resCtx.reservation.CommitConstraints(
|
err = resCtx.reservation.CommitConstraints(
|
||||||
channelConstraints, resCtx.maxLocalCsv,
|
channelConstraints, resCtx.maxLocalCsv, false,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Unacceptable channel constraints: %v", err)
|
log.Warnf("Unacceptable channel constraints: %v", err)
|
||||||
@@ -1586,8 +1598,11 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
|
|||||||
|
|
||||||
// As they've accepted our channel constraints, we'll regenerate them
|
// As they've accepted our channel constraints, we'll regenerate them
|
||||||
// here so we can properly commit their accepted constraints to the
|
// here so we can properly commit their accepted constraints to the
|
||||||
// reservation.
|
// reservation. Also make sure that we re-generate the ChannelReserve
|
||||||
chanReserve := f.cfg.RequiredRemoteChanReserve(resCtx.chanAmt, msg.DustLimit)
|
// with our dust limit or we can get stuck channels.
|
||||||
|
chanReserve := f.cfg.RequiredRemoteChanReserve(
|
||||||
|
resCtx.chanAmt, resCtx.reservation.OurContribution().DustLimit,
|
||||||
|
)
|
||||||
|
|
||||||
// The remote node has responded with their portion of the channel
|
// The remote node has responded with their portion of the channel
|
||||||
// contribution. At this point, we can process their contribution which
|
// contribution. At this point, we can process their contribution which
|
||||||
|
|||||||
@@ -152,6 +152,14 @@ func ErrChanTooLarge(chanSize, maxChanSize btcutil.Amount) ReservationError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrInvalidDustLimit returns an error indicating that a proposed DustLimit
|
||||||
|
// was rejected.
|
||||||
|
func ErrInvalidDustLimit(dustLimit btcutil.Amount) ReservationError {
|
||||||
|
return ReservationError{
|
||||||
|
fmt.Errorf("dust limit %v is invalid", dustLimit),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrHtlcIndexAlreadyFailed is returned when the HTLC index has already been
|
// ErrHtlcIndexAlreadyFailed is returned when the HTLC index has already been
|
||||||
// failed, but has not been committed by our commitment state.
|
// failed, but has not been committed by our commitment state.
|
||||||
type ErrHtlcIndexAlreadyFailed uint64
|
type ErrHtlcIndexAlreadyFailed uint64
|
||||||
|
|||||||
+34
-2
@@ -394,7 +394,7 @@ func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
|
|||||||
// will also attempt to verify the constraints for sanity, returning an error
|
// will also attempt to verify the constraints for sanity, returning an error
|
||||||
// if the parameters are seemed unsound.
|
// if the parameters are seemed unsound.
|
||||||
func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
||||||
maxLocalCSVDelay uint16) error {
|
maxLocalCSVDelay uint16, responder bool) error {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
defer r.Unlock()
|
||||||
|
|
||||||
@@ -409,6 +409,13 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
|||||||
return ErrChanReserveTooSmall(c.ChanReserve, c.DustLimit)
|
return ErrChanReserveTooSmall(c.ChanReserve, c.DustLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate against the maximum-sized witness script dust limit, and
|
||||||
|
// also ensure that the DustLimit is not too large.
|
||||||
|
maxWitnessLimit := DustLimitForSize(input.UnknownWitnessSize)
|
||||||
|
if c.DustLimit < maxWitnessLimit || c.DustLimit > 3*maxWitnessLimit {
|
||||||
|
return ErrInvalidDustLimit(c.DustLimit)
|
||||||
|
}
|
||||||
|
|
||||||
// Fail if we consider the channel reserve to be too large. We
|
// Fail if we consider the channel reserve to be too large. We
|
||||||
// currently fail if it is greater than 20% of the channel capacity.
|
// currently fail if it is greater than 20% of the channel capacity.
|
||||||
maxChanReserve := r.partialState.Capacity / 5
|
maxChanReserve := r.partialState.Capacity / 5
|
||||||
@@ -449,7 +456,7 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
|||||||
|
|
||||||
// Our dust limit should always be less than or equal to our proposed
|
// Our dust limit should always be less than or equal to our proposed
|
||||||
// channel reserve.
|
// channel reserve.
|
||||||
if r.ourContribution.DustLimit > c.ChanReserve {
|
if responder && r.ourContribution.DustLimit > c.ChanReserve {
|
||||||
r.ourContribution.DustLimit = c.ChanReserve
|
r.ourContribution.DustLimit = c.ChanReserve
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,6 +469,31 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateReserveBounds checks that both ChannelReserve values are above both
|
||||||
|
// DustLimit values. This not only avoids stuck channels, but is also mandated
|
||||||
|
// by BOLT#02 even if it's not explicit. This returns true if the bounds are
|
||||||
|
// valid. This function should be called with the lock held.
|
||||||
|
func (r *ChannelReservation) validateReserveBounds() bool {
|
||||||
|
ourDustLimit := r.ourContribution.DustLimit
|
||||||
|
ourRequiredReserve := r.ourContribution.ChanReserve
|
||||||
|
theirDustLimit := r.theirContribution.DustLimit
|
||||||
|
theirRequiredReserve := r.theirContribution.ChanReserve
|
||||||
|
|
||||||
|
// We take the smaller of the two ChannelReserves and compare it
|
||||||
|
// against the larger of the two DustLimits.
|
||||||
|
minChanReserve := ourRequiredReserve
|
||||||
|
if minChanReserve > theirRequiredReserve {
|
||||||
|
minChanReserve = theirRequiredReserve
|
||||||
|
}
|
||||||
|
|
||||||
|
maxDustLimit := ourDustLimit
|
||||||
|
if maxDustLimit < theirDustLimit {
|
||||||
|
maxDustLimit = theirDustLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
return minChanReserve >= maxDustLimit
|
||||||
|
}
|
||||||
|
|
||||||
// OurContribution returns the wallet's fully populated contribution to the
|
// OurContribution returns the wallet's fully populated contribution to the
|
||||||
// pending payment channel. See 'ChannelContribution' for further details
|
// pending payment channel. See 'ChannelContribution' for further details
|
||||||
// regarding the contents of a contribution.
|
// regarding the contents of a contribution.
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
|||||||
CsvDelay: csvDelay,
|
CsvDelay: csvDelay,
|
||||||
}
|
}
|
||||||
err = aliceChanReservation.CommitConstraints(
|
err = aliceChanReservation.CommitConstraints(
|
||||||
channelConstraints, defaultMaxLocalCsvDelay,
|
channelConstraints, defaultMaxLocalCsvDelay, false,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to verify constraints: %v", err)
|
t.Fatalf("unable to verify constraints: %v", err)
|
||||||
@@ -490,7 +490,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
|||||||
t.Fatalf("bob unable to init channel reservation: %v", err)
|
t.Fatalf("bob unable to init channel reservation: %v", err)
|
||||||
}
|
}
|
||||||
err = bobChanReservation.CommitConstraints(
|
err = bobChanReservation.CommitConstraints(
|
||||||
channelConstraints, defaultMaxLocalCsvDelay,
|
channelConstraints, defaultMaxLocalCsvDelay, true,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to verify constraints: %v", err)
|
t.Fatalf("unable to verify constraints: %v", err)
|
||||||
@@ -904,7 +904,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
|||||||
CsvDelay: csvDelay,
|
CsvDelay: csvDelay,
|
||||||
}
|
}
|
||||||
err = aliceChanReservation.CommitConstraints(
|
err = aliceChanReservation.CommitConstraints(
|
||||||
channelConstraints, defaultMaxLocalCsvDelay,
|
channelConstraints, defaultMaxLocalCsvDelay, false,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to verify constraints: %v", err)
|
t.Fatalf("unable to verify constraints: %v", err)
|
||||||
@@ -947,7 +947,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
|||||||
t.Fatalf("unable to create bob reservation: %v", err)
|
t.Fatalf("unable to create bob reservation: %v", err)
|
||||||
}
|
}
|
||||||
err = bobChanReservation.CommitConstraints(
|
err = bobChanReservation.CommitConstraints(
|
||||||
channelConstraints, defaultMaxLocalCsvDelay,
|
channelConstraints, defaultMaxLocalCsvDelay, true,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to verify constraints: %v", err)
|
t.Fatalf("unable to verify constraints: %v", err)
|
||||||
|
|||||||
+15
-3
@@ -1266,6 +1266,13 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
theirContribution := req.contribution
|
theirContribution := req.contribution
|
||||||
ourContribution := pendingReservation.ourContribution
|
ourContribution := pendingReservation.ourContribution
|
||||||
|
|
||||||
|
// Perform bounds-checking on both ChannelReserve and DustLimit
|
||||||
|
// parameters.
|
||||||
|
if !pendingReservation.validateReserveBounds() {
|
||||||
|
req.err <- fmt.Errorf("invalid reserve and dust bounds")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
chanPoint *wire.OutPoint
|
chanPoint *wire.OutPoint
|
||||||
err error
|
err error
|
||||||
@@ -1564,9 +1571,6 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
|||||||
pendingReservation.Lock()
|
pendingReservation.Lock()
|
||||||
defer pendingReservation.Unlock()
|
defer pendingReservation.Unlock()
|
||||||
|
|
||||||
// TODO(roasbeef): verify sanity of remote party's parameters, fail if
|
|
||||||
// disagree
|
|
||||||
|
|
||||||
// Validate that the remote's UpfrontShutdownScript is a valid script
|
// Validate that the remote's UpfrontShutdownScript is a valid script
|
||||||
// if it's set.
|
// if it's set.
|
||||||
shutdown := req.contribution.UpfrontShutdown
|
shutdown := req.contribution.UpfrontShutdown
|
||||||
@@ -1584,6 +1588,14 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
|||||||
theirContribution := pendingReservation.theirContribution
|
theirContribution := pendingReservation.theirContribution
|
||||||
chanState := pendingReservation.partialState
|
chanState := pendingReservation.partialState
|
||||||
|
|
||||||
|
// Perform bounds checking on both ChannelReserve and DustLimit
|
||||||
|
// parameters. The ChannelReserve may have been changed by the
|
||||||
|
// ChannelAcceptor RPC, so this is necessary.
|
||||||
|
if !pendingReservation.validateReserveBounds() {
|
||||||
|
req.err <- fmt.Errorf("invalid reserve and dust bounds")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize an empty sha-chain for them, tracking the current pending
|
// Initialize an empty sha-chain for them, tracking the current pending
|
||||||
// revocation hash (we don't yet know the preimage so we can't add it
|
// revocation hash (we don't yet know the preimage so we can't add it
|
||||||
// to the chain).
|
// to the chain).
|
||||||
|
|||||||
Reference in new issue
Block a user