Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check funds in millisatoshi when sending/receiving an HTLC #1128

Merged
merged 2 commits into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ object Commitments {
// the HTLC we are about to create is outgoing, but from their point of view it is incoming
val outgoingHtlcs = reduced.htlcs.filter(_.direction == IN)

// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
// we look from remote's point of view, so if local is funder remote doesn't pay the fees
val fees = if (commitments1.localParams.isFunder) commitTxFee(commitments1.remoteParams.dustLimit, reduced) else 0.sat
val missing = reduced.toRemote - commitments1.remoteParams.channelReserve - fees
if (missing < 0.sat) {
pm47 marked this conversation as resolved.
Show resolved Hide resolved
return Left(InsufficientFunds(commitments.channelId, amount = cmd.amount, missing = -missing.truncateToSatoshi, reserve = commitments1.remoteParams.channelReserve, fees = fees))
}

val htlcValueInFlight = outgoingHtlcs.map(_.add.amountMsat).sum
if (commitments1.remoteParams.maxHtlcValueInFlightMsat < htlcValueInFlight) {
// TODO: this should be a specific UPDATE error
Expand All @@ -158,14 +166,6 @@ object Commitments {
return Left(TooManyAcceptedHtlcs(commitments.channelId, maximum = commitments1.remoteParams.maxAcceptedHtlcs))
}

// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
// we look from remote's point of view, so if local is funder remote doesn't pay the fees
val fees = if (commitments1.localParams.isFunder) commitTxFee(commitments1.remoteParams.dustLimit, reduced) else 0.sat
val missing = reduced.toRemote.truncateToSatoshi - commitments1.remoteParams.channelReserve - fees
if (missing < 0.sat) {
return Left(InsufficientFunds(commitments.channelId, amount = cmd.amount, missing = -missing, reserve = commitments1.remoteParams.channelReserve, fees = fees))
}

Right(commitments1, add)
}

Expand All @@ -183,6 +183,13 @@ object Commitments {
val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed)
val incomingHtlcs = reduced.htlcs.filter(_.direction == IN)

// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
val fees = if (commitments1.localParams.isFunder) 0.sat else Transactions.commitTxFee(commitments1.localParams.dustLimit, reduced)
val missing = reduced.toRemote - commitments1.localParams.channelReserve - fees
if (missing < 0.sat) {
pm47 marked this conversation as resolved.
Show resolved Hide resolved
throw InsufficientFunds(commitments.channelId, amount = add.amountMsat, missing = -missing.truncateToSatoshi, reserve = commitments1.localParams.channelReserve, fees = fees)
}

val htlcValueInFlight = incomingHtlcs.map(_.add.amountMsat).sum
if (commitments1.localParams.maxHtlcValueInFlightMsat < htlcValueInFlight) {
throw HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.localParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight)
Expand All @@ -192,13 +199,6 @@ object Commitments {
throw TooManyAcceptedHtlcs(commitments.channelId, maximum = commitments1.localParams.maxAcceptedHtlcs)
}

// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
val fees = if (commitments1.localParams.isFunder) 0.sat else Transactions.commitTxFee(commitments1.localParams.dustLimit, reduced)
val missing = reduced.toRemote.truncateToSatoshi - commitments1.localParams.channelReserve - fees
if (missing < 0.sat) {
throw InsufficientFunds(commitments.channelId, amount = add.amountMsat, missing = -missing, reserve = commitments1.localParams.channelReserve, fees = fees)
}

commitments1
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
alice2bob.expectNoMsg(200 millis)
}

test("recv CMD_ADD_HTLC (insufficient funds, missing 1 msat)") { f =>
import f._
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
val add = CMD_ADD_HTLC(initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))
sender.send(bob, add)

val error = InsufficientFunds(channelId(alice), amount = add.amount, missing = 0 sat, reserve = 10000 sat, fees = 0 sat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis)
}

test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs and 0 balance)") { f =>
import f._
val sender = TestProbe()
Expand Down