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

Update MinFinalCltvExpiryDelta default value and activate wumbo #1483

Merged
merged 8 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
1 change: 1 addition & 0 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ eclair {
var_onion_optin = optional
payment_secret = optional
basic_mpp = optional
option_support_large_channel = optional
}
override-features = [ // optional per-node features
# {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ object Channel {
val MAX_NEGOTIATION_ITERATIONS = 20

// this is defined in BOLT 11
val MIN_CLTV_EXPIRY_DELTA = CltvExpiryDelta(9)
val MIN_CLTV_EXPIRY_DELTA = CltvExpiryDelta(18)
val MAX_CLTV_EXPIRY_DELTA = CltvExpiryDelta(7 * 144) // one week

// since BOLT 1.1, there is a max value for the refund delay of the main commitment tx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,16 @@ object PaymentRequest {
Block.TestnetGenesisBlock.hash -> "lntb",
Block.LivenetGenesisBlock.hash -> "lnbc")

def apply(chainHash: ByteVector32, amount: Option[MilliSatoshi], paymentHash: ByteVector32, privateKey: PrivateKey,
description: String, fallbackAddress: Option[String] = None, expirySeconds: Option[Long] = None,
extraHops: List[List[ExtraHop]] = Nil, timestamp: Long = System.currentTimeMillis() / 1000L,
def apply(chainHash: ByteVector32,
amount: Option[MilliSatoshi],
paymentHash: ByteVector32,
privateKey: PrivateKey,
description: String,
minFinalCltvExpiryDelta: CltvExpiryDelta,
fallbackAddress: Option[String] = None,
expirySeconds: Option[Long] = None,
extraHops: List[List[ExtraHop]] = Nil,
timestamp: Long = System.currentTimeMillis() / 1000L,
features: Option[PaymentRequestFeatures] = Some(PaymentRequestFeatures(Features.VariableLengthOnion.optional, Features.PaymentSecret.optional))): PaymentRequest = {

val prefix = prefixes(chainHash)
Expand All @@ -136,6 +143,7 @@ object PaymentRequest {
Some(Description(description)),
fallbackAddress.map(FallbackAddress(_)),
expirySeconds.map(Expiry(_)),
Some(MinFinalCltvExpiry(minFinalCltvExpiryDelta.toInt)),
features).flatten
val paymentSecretTag = if (features.exists(_.allowPaymentSecret)) PaymentSecret(randomBytes32) :: Nil else Nil
val routingInfoTags = extraHops.map(RoutingInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import akka.actor.Actor.Receive
import akka.actor.{ActorContext, ActorRef, PoisonPill, Status}
import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter}
import fr.acinq.bitcoin.{ByteVector32, Crypto}
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, Channel, ChannelCommandResponse}
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, ChannelCommandResponse}
import fr.acinq.eclair.db._
import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
Expand Down Expand Up @@ -56,6 +56,10 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
Try {
val paymentPreimage = paymentPreimage_opt.getOrElse(randomBytes32)
val paymentHash = Crypto.sha256(paymentPreimage)
// We must set a `min_final_cltv_expiry` bigger than our `fulfill_safety_before_timeout`. Otherwise when we receive
// a payment, if a new block is produced while we're waiting for our peer to acknowledge our fulfill we'll end up
// closing the channel because it violates `fulfill_safety_before_timeout`.
val minFinalExpiryDelta = nodeParams.fulfillSafetyBeforeTimeoutBlocks + 3
val expirySeconds = expirySeconds_opt.getOrElse(nodeParams.paymentRequestExpiry.toSeconds)
// We currently only optionally support payment secrets (to allow legacy clients to pay invoices).
// Once we're confident most of the network has upgraded, we should switch to mandatory payment secrets.
Expand All @@ -66,7 +70,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
val f3 = if (nodeParams.enableTrampolinePayment) Seq(Features.TrampolinePayment.optional) else Nil
Some(PaymentRequest.PaymentRequestFeatures(f1 ++ f2 ++ f3: _*))
}
val paymentRequest = PaymentRequest(nodeParams.chainHash, amount_opt, paymentHash, nodeParams.privateKey, desc, fallbackAddress_opt, expirySeconds = Some(expirySeconds), extraHops = extraHops, features = features)
val paymentRequest = PaymentRequest(nodeParams.chainHash, amount_opt, paymentHash, nodeParams.privateKey, desc, minFinalExpiryDelta, fallbackAddress_opt, expirySeconds = Some(expirySeconds), extraHops = extraHops, features = features)
log.debug("generated payment request={} from amount={}", PaymentRequest.write(paymentRequest), amount_opt)
db.addIncomingPayment(paymentRequest, paymentPreimage, paymentType)
paymentRequest
Expand All @@ -78,7 +82,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
case p: IncomingPacket.FinalPacket if doHandle(p.add.paymentHash) =>
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(p.add.paymentHash))) {
db.getIncomingPayment(p.add.paymentHash) match {
case Some(record) => validatePayment(p, record, nodeParams.currentBlockHeight) match {
case Some(record) => validatePayment(nodeParams, p, record) match {
case Some(cmdFail) =>
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, Tags.FailureType(cmdFail)).increment()
PendingRelayDb.safeSend(register, nodeParams.db.pendingRelay, p.add.channelId, cmdFail)
Expand Down Expand Up @@ -240,11 +244,14 @@ object MultiPartHandler {
}
}

private def validatePayment(payment: IncomingPacket.FinalPacket, record: IncomingPayment, currentBlockHeight: Long)(implicit log: LoggingAdapter): Option[CMD_FAIL_HTLC] = {
private def validatePayment(nodeParams: NodeParams, payment: IncomingPacket.FinalPacket, record: IncomingPayment)(implicit log: LoggingAdapter): Option[CMD_FAIL_HTLC] = {
// We send the same error regardless of the failure to avoid probing attacks.
val cmdFail = CMD_FAIL_HTLC(payment.add.id, Right(IncorrectOrUnknownPaymentDetails(payment.payload.totalAmount, currentBlockHeight)), commit = true)
val cmdFail = CMD_FAIL_HTLC(payment.add.id, Right(IncorrectOrUnknownPaymentDetails(payment.payload.totalAmount, nodeParams.currentBlockHeight)), commit = true)
val paymentAmountOk = record.paymentRequest.amount.forall(a => validatePaymentAmount(payment, a))
val paymentCltvOk = validatePaymentCltv(payment, record.paymentRequest.minFinalCltvExpiryDelta.getOrElse(Channel.MIN_CLTV_EXPIRY_DELTA).toCltvExpiry(currentBlockHeight))
// The default `min_final_cltv_expiry` we accept is our `fulfill_safety_before_timeout` to which we add one block for
// safety (to handle races between our peer acknowledging the HTLC fulfill and a new block being produced).
val defaultMinFinalCltvExpiryDelta = nodeParams.fulfillSafetyBeforeTimeoutBlocks + 1
val paymentCltvOk = validatePaymentCltv(payment, record.paymentRequest.minFinalCltvExpiryDelta.getOrElse(defaultMinFinalCltvExpiryDelta).toCltvExpiry(nodeParams.currentBlockHeight))
val paymentStatusOk = validatePaymentStatus(payment, record)
val paymentFeaturesOk = validateInvoiceFeatures(payment, record.paymentRequest)
if (paymentAmountOk && paymentCltvOk && paymentStatusOk && paymentFeaturesOk) None else Some(cmdFail)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ object PaymentInitiator {
* the payment will automatically be retried in case of TrampolineFeeInsufficient errors.
* For example, [(10 msat, 144), (15 msat, 288)] will first send a payment with a fee of 10
* msat and cltv of 144, and retry with 15 msat and 288 in case an error occurs.
* @param finalExpiryDelta expiry delta for the final recipient.
* @param finalExpiryDelta expiry delta for the final recipient when the [[paymentRequest]] doesn't specify it.
* @param routeParams (optional) parameters to fine-tune the routing algorithm.
*/
case class SendTrampolinePaymentRequest(recipientAmount: MilliSatoshi,
Expand All @@ -205,15 +205,15 @@ object PaymentInitiator {
val paymentHash = paymentRequest.paymentHash

// We add one block in order to not have our htlcs fail when a new block has just been found.
def finalExpiry(currentBlockHeight: Long) = finalExpiryDelta.toCltvExpiry(currentBlockHeight + 1)
def finalExpiry(currentBlockHeight: Long) = paymentRequest.minFinalCltvExpiryDelta.getOrElse(finalExpiryDelta).toCltvExpiry(currentBlockHeight + 1)
}

/**
* @param recipientAmount amount that should be received by the final recipient (usually from a Bolt 11 invoice).
* @param paymentHash payment hash.
* @param recipientNodeId id of the final recipient.
* @param maxAttempts maximum number of retries.
* @param finalExpiryDelta expiry delta for the final recipient.
* @param finalExpiryDelta expiry delta for the final recipient when the [[paymentRequest]] doesn't specify it.
* @param paymentRequest (optional) Bolt 11 invoice.
* @param externalId (optional) externally-controlled identifier (to reconcile between application DB and eclair DB).
* @param assistedRoutes (optional) routing hints (usually from a Bolt 11 invoice).
Expand All @@ -231,7 +231,7 @@ object PaymentInitiator {
routeParams: Option[RouteParams] = None,
userCustomTlvs: Seq[GenericTlv] = Nil) {
// We add one block in order to not have our htlcs fail when a new block has just been found.
def finalExpiry(currentBlockHeight: Long) = finalExpiryDelta.toCltvExpiry(currentBlockHeight + 1)
def finalExpiry(currentBlockHeight: Long) = paymentRequest.flatMap(_.minFinalCltvExpiryDelta).getOrElse(finalExpiryDelta).toCltvExpiry(currentBlockHeight + 1)
}

/**
Expand All @@ -256,7 +256,7 @@ object PaymentInitiator {
* sure all partial payments use the same parentId. If not provided, a random parentId will
* be generated that can be used for the remaining partial payments.
* @param paymentRequest Bolt 11 invoice.
* @param finalExpiryDelta expiry delta for the final recipient.
* @param finalExpiryDelta expiry delta for the final recipient when the [[paymentRequest]] doesn't specify it.
* @param route route to use to reach either the final recipient or the first trampoline node.
* @param trampolineSecret if trampoline is used, this is a secret to protect the payment to the first trampoline
* node against probing. When manually sending a multi-part payment, you need to make sure
Expand All @@ -283,7 +283,7 @@ object PaymentInitiator {
val paymentHash = paymentRequest.paymentHash

// We add one block in order to not have our htlcs fail when a new block has just been found.
def finalExpiry(currentBlockHeight: Long) = finalExpiryDelta.toCltvExpiry(currentBlockHeight + 1)
def finalExpiry(currentBlockHeight: Long) = paymentRequest.minFinalCltvExpiryDelta.getOrElse(finalExpiryDelta).toCltvExpiry(currentBlockHeight + 1)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
val watcher = TestProbe()
val paymentHandler = TestProbe()
val register = TestProbe()
val commandBuffer = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val switchboard = TestProbe()
Expand Down Expand Up @@ -113,7 +112,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
// with assisted routes
val externalId1 = "030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87"
val hints = List(List(ExtraHop(Bob.nodeParams.nodeId, ShortChannelId("569178x2331x1"), feeBase = 10 msat, feeProportionalMillionths = 1, cltvExpiryDelta = CltvExpiryDelta(12))))
val invoice1 = PaymentRequest(Block.RegtestGenesisBlock.hash, Some(123 msat), ByteVector32.Zeroes, randomKey, "description", None, None, hints)
val invoice1 = PaymentRequest(Block.RegtestGenesisBlock.hash, Some(123 msat), ByteVector32.Zeroes, randomKey, "description", CltvExpiryDelta(18), None, None, hints)
eclair.send(Some(externalId1), nodeId, 123 msat, ByteVector32.Zeroes, invoice_opt = Some(invoice1))
val send1 = paymentInitiator.expectMsgType[SendPaymentRequest]
assert(send1.externalId === Some(externalId1))
Expand Down Expand Up @@ -382,7 +381,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
val trampolines = Seq(randomKey.publicKey, randomKey.publicKey)
val parentId = UUID.randomUUID()
val secret = randomBytes32
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(1234 msat), ByteVector32.One, randomKey, "Some invoice")
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(1234 msat), ByteVector32.One, randomKey, "Some invoice", CltvExpiryDelta(18))
eclair.sendToRoute(1000 msat, Some(1200 msat), Some("42"), Some(parentId), pr, CltvExpiryDelta(123), route, Some(secret), Some(100 msat), Some(CltvExpiryDelta(144)), trampolines)

paymentInitiator.expectMsg(SendPaymentToRouteRequest(1000 msat, 1200 msat, Some("42"), Some(parentId), pr, CltvExpiryDelta(123), route, Some(secret), 100 msat, CltvExpiryDelta(144), trampolines))
Expand Down
Loading