1
0
mirror of https://github.com/ACINQ/eclair.git synced 2025-01-19 05:33:59 +01:00

Update MinFinalCltvExpiryDelta default value and activate wumbo (#1483)

* Activate wumbo by default

This is safe as `max-funding-satoshis` is set to 16777215 sats, which is
the non-wumbo limit.

If users want to increase the maximum channel size, they can update this
configuration value.

* Update default minFinalCltvExpiryDelta

See https://github.com/lightningnetwork/lightning-rfc/pull/785

* Set minFinalCltvExpiryDelta in invoices

Our default fulfill-safety-window is now greater than the spec's default
min-final-expiry-delta in invoices, so we need to explicitly tell payers
what value they must use.

Otherwise we may end up closing channels if a block is produced while we're
waiting for our peer to accept an UpdateFulfillHtlc.
This commit is contained in:
Bastien Teinturier 2020-07-21 14:37:59 +02:00 committed by GitHub
parent aca7499df2
commit 625e996ae0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 225 additions and 189 deletions

View File

@ -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
# {
@ -76,6 +77,7 @@ eclair {
// NB: this number effectively reduces the expiry-delta-blocks, so you may want to take that into account and increase
// expiry-delta-blocks.
fulfill-safety-before-timeout-blocks = 24
min-final-expiry-delta-blocks = 30 // Bolt 11 invoice's min_final_cltv_expiry; must be strictly greater than fulfill-safety-before-timeout-blocks
fee-base-msat = 1000
fee-proportional-millionths = 100 // fee charged per transferred satoshi in millionths of a satoshi (100 = 0.01%)

View File

@ -55,11 +55,12 @@ case class NodeParams(keyManager: KeyManager,
onChainFeeConf: OnChainFeeConf,
maxHtlcValueInFlightMsat: UInt64,
maxAcceptedHtlcs: Int,
expiryDeltaBlocks: CltvExpiryDelta,
fulfillSafetyBeforeTimeoutBlocks: CltvExpiryDelta,
expiryDelta: CltvExpiryDelta,
fulfillSafetyBeforeTimeout: CltvExpiryDelta,
minFinalExpiryDelta: CltvExpiryDelta,
htlcMinimum: MilliSatoshi,
toRemoteDelayBlocks: CltvExpiryDelta,
maxToLocalDelayBlocks: CltvExpiryDelta,
toRemoteDelay: CltvExpiryDelta,
maxToLocalDelay: CltvExpiryDelta,
minDepthBlocks: Int,
feeBase: MilliSatoshi,
feeProportionalMillionth: Int,
@ -181,9 +182,11 @@ object NodeParams {
val offeredCLTV = CltvExpiryDelta(config.getInt("to-remote-delay-blocks"))
require(maxToLocalCLTV <= Channel.MAX_TO_SELF_DELAY && offeredCLTV <= Channel.MAX_TO_SELF_DELAY, s"CLTV delay values too high, max is ${Channel.MAX_TO_SELF_DELAY}")
val expiryDeltaBlocks = CltvExpiryDelta(config.getInt("expiry-delta-blocks"))
val fulfillSafetyBeforeTimeoutBlocks = CltvExpiryDelta(config.getInt("fulfill-safety-before-timeout-blocks"))
require(fulfillSafetyBeforeTimeoutBlocks * 2 < expiryDeltaBlocks, "fulfill-safety-before-timeout-blocks must be smaller than expiry-delta-blocks / 2 because it effectively reduces that delta; if you want to increase this value, you may want to increase expiry-delta-blocks as well")
val expiryDelta = CltvExpiryDelta(config.getInt("expiry-delta-blocks"))
val fulfillSafetyBeforeTimeout = CltvExpiryDelta(config.getInt("fulfill-safety-before-timeout-blocks"))
require(fulfillSafetyBeforeTimeout * 2 < expiryDelta, "fulfill-safety-before-timeout-blocks must be smaller than expiry-delta-blocks / 2 because it effectively reduces that delta; if you want to increase this value, you may want to increase expiry-delta-blocks as well")
val minFinalExpiryDelta = CltvExpiryDelta(config.getInt("min-final-expiry-delta-blocks"))
require(minFinalExpiryDelta > fulfillSafetyBeforeTimeout, "min-final-expiry-delta-blocks must be strictly greater than fulfill-safety-before-timeout-blocks; otherwise it may lead to undesired channel closure")
val nodeAlias = config.getString("node-alias")
require(nodeAlias.getBytes("UTF-8").length <= 32, "invalid alias, too long (max allowed 32 bytes)")
@ -255,11 +258,12 @@ object NodeParams {
),
maxHtlcValueInFlightMsat = UInt64(config.getLong("max-htlc-value-in-flight-msat")),
maxAcceptedHtlcs = maxAcceptedHtlcs,
expiryDeltaBlocks = expiryDeltaBlocks,
fulfillSafetyBeforeTimeoutBlocks = fulfillSafetyBeforeTimeoutBlocks,
expiryDelta = expiryDelta,
fulfillSafetyBeforeTimeout = fulfillSafetyBeforeTimeout,
minFinalExpiryDelta = minFinalExpiryDelta,
htlcMinimum = htlcMinimum,
toRemoteDelayBlocks = CltvExpiryDelta(config.getInt("to-remote-delay-blocks")),
maxToLocalDelayBlocks = CltvExpiryDelta(config.getInt("max-to-local-delay-blocks")),
toRemoteDelay = CltvExpiryDelta(config.getInt("to-remote-delay-blocks")),
maxToLocalDelay = CltvExpiryDelta(config.getInt("max-to-local-delay-blocks")),
minDepthBlocks = config.getInt("mindepth-blocks"),
feeBase = feeBase,
feeProportionalMillionth = config.getInt("fee-proportional-millionths"),

View File

@ -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
@ -246,7 +246,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, normal.channelUpdate.shortChannelId, None))
// we rebuild a new channel_update with values from the configuration because they may have changed while eclair was down
val candidateChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, normal.channelUpdate.shortChannelId, nodeParams.expiryDeltaBlocks,
val candidateChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, normal.channelUpdate.shortChannelId, nodeParams.expiryDelta,
normal.commitments.remoteParams.htlcMinimum, normal.channelUpdate.feeBaseMsat, normal.channelUpdate.feeProportionalMillionths, normal.commitments.localCommit.spec.totalFunds, enable = Announcements.isEnabled(normal.channelUpdate.channelFlags))
val channelUpdate1 = if (Announcements.areSame(candidateChannelUpdate, normal.channelUpdate)) {
// if there was no configuration change we keep the existing channel update
@ -596,7 +596,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED)
context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId, None))
// we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced
val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimum, nodeParams.feeBase, nodeParams.feeProportionalMillionth, commitments.localCommit.spec.totalFunds, enable = Helpers.aboveReserve(d.commitments))
val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDelta, d.commitments.remoteParams.htlcMinimum, nodeParams.feeBase, nodeParams.feeProportionalMillionth, commitments.localCommit.spec.totalFunds, enable = Helpers.aboveReserve(d.commitments))
// we need to periodically re-send channel updates, otherwise channel will be considered stale and get pruned by network
context.system.scheduler.schedule(initialDelay = REFRESH_CHANNEL_UPDATE_INTERVAL, interval = REFRESH_CHANNEL_UPDATE_INTERVAL, receiver = self, message = BroadcastChannelUpdate(PeriodicRefresh))
goto(NORMAL) using DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), shortChannelId, buried = false, None, initialChannelUpdate, None, None) storing()
@ -1948,7 +1948,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
def handleNewBlock(c: CurrentBlockCount, d: HasCommitments) = {
val timedOutOutgoing = d.commitments.timedOutOutgoingHtlcs(c.blockCount)
val almostTimedOutIncoming = d.commitments.almostTimedOutIncomingHtlcs(c.blockCount, nodeParams.fulfillSafetyBeforeTimeoutBlocks)
val almostTimedOutIncoming = d.commitments.almostTimedOutIncomingHtlcs(c.blockCount, nodeParams.fulfillSafetyBeforeTimeout)
if (timedOutOutgoing.nonEmpty) {
// Downstream timed out.
handleLocalError(HtlcsTimedoutDownstream(d.channelId, timedOutOutgoing), d, Some(c))

View File

@ -109,7 +109,7 @@ object Helpers {
if (open.pushMsat > open.fundingSatoshis) throw InvalidPushAmount(open.temporaryChannelId, open.pushMsat, open.fundingSatoshis.toMilliSatoshi)
// BOLT #2: The receiving node MUST fail the channel if: to_self_delay is unreasonably large.
if (open.toSelfDelay > Channel.MAX_TO_SELF_DELAY || open.toSelfDelay > nodeParams.maxToLocalDelayBlocks) throw ToSelfDelayTooHigh(open.temporaryChannelId, open.toSelfDelay, nodeParams.maxToLocalDelayBlocks)
if (open.toSelfDelay > Channel.MAX_TO_SELF_DELAY || open.toSelfDelay > nodeParams.maxToLocalDelay) throw ToSelfDelayTooHigh(open.temporaryChannelId, open.toSelfDelay, nodeParams.maxToLocalDelay)
// BOLT #2: The receiving node MUST fail the channel if: max_accepted_htlcs is greater than 483.
if (open.maxAcceptedHtlcs > Channel.MAX_ACCEPTED_HTLCS) throw InvalidMaxAcceptedHtlcs(open.temporaryChannelId, open.maxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS)
@ -156,7 +156,7 @@ object Helpers {
// if minimum_depth is unreasonably large:
// MAY reject the channel.
if (accept.toSelfDelay > Channel.MAX_TO_SELF_DELAY || accept.toSelfDelay > nodeParams.maxToLocalDelayBlocks) throw ToSelfDelayTooHigh(accept.temporaryChannelId, accept.toSelfDelay, nodeParams.maxToLocalDelayBlocks)
if (accept.toSelfDelay > Channel.MAX_TO_SELF_DELAY || accept.toSelfDelay > nodeParams.maxToLocalDelay) throw ToSelfDelayTooHigh(accept.temporaryChannelId, accept.toSelfDelay, nodeParams.maxToLocalDelay)
// if channel_reserve_satoshis is less than dust_limit_satoshis within the open_channel message:
// MUST reject the channel.

View File

@ -412,7 +412,7 @@ object Peer {
maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat,
channelReserve = (fundingAmount * nodeParams.reserveToFundingRatio).max(nodeParams.dustLimit), // BOLT #2: make sure that our reserve is above our dust limit
htlcMinimum = nodeParams.htlcMinimum,
toSelfDelay = nodeParams.toRemoteDelayBlocks, // we choose their delay
toSelfDelay = nodeParams.toRemoteDelay, // we choose their delay
maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs,
isFunder = isFunder,
defaultFinalScriptPubKey = defaultFinalScriptPubkey,

View File

@ -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)
@ -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)

View File

@ -20,13 +20,13 @@ 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
import fr.acinq.eclair.payment.{IncomingPacket, PaymentReceived, PaymentRequest}
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{CltvExpiry, Features, Logs, MilliSatoshi, NodeParams, randomBytes32}
import fr.acinq.eclair.{Features, Logs, MilliSatoshi, NodeParams, randomBytes32}
import scala.util.{Failure, Success, Try}
@ -66,7 +66,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, nodeParams.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
@ -78,7 +78,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)
@ -216,7 +216,8 @@ object MultiPartHandler {
}
}
private def validatePaymentCltv(payment: IncomingPacket.FinalPacket, minExpiry: CltvExpiry)(implicit log: LoggingAdapter): Boolean = {
private def validatePaymentCltv(nodeParams: NodeParams, payment: IncomingPacket.FinalPacket, record: IncomingPayment)(implicit log: LoggingAdapter): Boolean = {
val minExpiry = record.paymentRequest.minFinalCltvExpiryDelta.getOrElse(nodeParams.minFinalExpiryDelta).toCltvExpiry(nodeParams.currentBlockHeight)
if (payment.add.cltvExpiry < minExpiry) {
log.warning("received payment with expiry too small for amount={} totalAmount={}", payment.add.amountMsat, payment.payload.totalAmount)
false
@ -240,11 +241,11 @@ 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))
val paymentCltvOk = validatePaymentCltv(nodeParams, payment, record)
val paymentStatusOk = validatePaymentStatus(payment, record)
val paymentFeaturesOk = validateInvoiceFeatures(payment, record.paymentRequest)
if (paymentAmountOk && paymentCltvOk && paymentStatusOk && paymentFeaturesOk) None else Some(cmdFail)

View File

@ -235,7 +235,7 @@ object NodeRelayer {
val fee = nodeFee(nodeParams.feeBase, nodeParams.feeProportionalMillionth, payloadOut.amountToForward)
if (upstream.amountIn - payloadOut.amountToForward < fee) {
Some(TrampolineFeeInsufficient)
} else if (upstream.expiryIn - payloadOut.outgoingCltv < nodeParams.expiryDeltaBlocks) {
} else if (upstream.expiryIn - payloadOut.outgoingCltv < nodeParams.expiryDelta) {
Some(TrampolineExpiryTooSoon)
} else {
None
@ -244,7 +244,7 @@ object NodeRelayer {
/** Compute route params that honor our fee and cltv requirements. */
private def computeRouteParams(nodeParams: NodeParams, amountIn: MilliSatoshi, expiryIn: CltvExpiry, amountOut: MilliSatoshi, expiryOut: CltvExpiry): RouteParams = {
val routeMaxCltv = expiryIn - expiryOut - nodeParams.expiryDeltaBlocks
val routeMaxCltv = expiryIn - expiryOut - nodeParams.expiryDelta
val routeMaxFee = amountIn - amountOut - nodeFee(nodeParams.feeBase, nodeParams.feeProportionalMillionth, amountOut)
RouteCalculation.getDefaultRouteParams(nodeParams.routerConf).copy(
maxFeeBase = routeMaxFee,

View File

@ -92,7 +92,7 @@ class PaymentInitiator(nodeParams: NodeParams, router: ActorRef, register: Actor
case Nil =>
log.info("trampoline node couldn't find a route after all retries")
val trampolineRoute = Seq(
NodeHop(nodeParams.nodeId, pp.r.trampolineNodeId, nodeParams.expiryDeltaBlocks, 0 msat),
NodeHop(nodeParams.nodeId, pp.r.trampolineNodeId, nodeParams.expiryDelta, 0 msat),
NodeHop(pp.r.trampolineNodeId, pp.r.recipientNodeId, pp.r.trampolineAttempts.last._2, pp.r.trampolineAttempts.last._1)
)
val localFailure = pf.copy(failures = Seq(LocalFailure(trampolineRoute, RouteNotFound)))
@ -128,7 +128,7 @@ class PaymentInitiator(nodeParams: NodeParams, router: ActorRef, register: Actor
// We generate a random secret for the payment to the first trampoline node.
val trampolineSecret = r.trampolineSecret.getOrElse(randomBytes32)
sender ! SendPaymentToRouteResponse(paymentId, parentPaymentId, Some(trampolineSecret))
val (trampolineAmount, trampolineExpiry, trampolineOnion) = buildTrampolinePayment(SendTrampolinePaymentRequest(r.recipientAmount, r.paymentRequest, trampoline, Seq((r.trampolineFees, r.trampolineExpiryDelta)), r.finalExpiryDelta), r.trampolineFees, r.trampolineExpiryDelta)
val (trampolineAmount, trampolineExpiry, trampolineOnion) = buildTrampolinePayment(SendTrampolinePaymentRequest(r.recipientAmount, r.paymentRequest, trampoline, Seq((r.trampolineFees, r.trampolineExpiryDelta)), r.fallbackFinalExpiryDelta), r.trampolineFees, r.trampolineExpiryDelta)
payFsm forward SendPaymentToRoute(Left(r.route), Onion.createMultiPartPayload(r.amount, trampolineAmount, trampolineExpiry, trampolineSecret, Seq(OnionTlv.TrampolineOnion(trampolineOnion))), r.paymentRequest.routingInfo)
case Nil =>
sender ! SendPaymentToRouteResponse(paymentId, parentPaymentId, None)
@ -147,7 +147,7 @@ class PaymentInitiator(nodeParams: NodeParams, router: ActorRef, register: Actor
private def buildTrampolinePayment(r: SendTrampolinePaymentRequest, trampolineFees: MilliSatoshi, trampolineExpiryDelta: CltvExpiryDelta): (MilliSatoshi, CltvExpiry, OnionRoutingPacket) = {
val trampolineRoute = Seq(
NodeHop(nodeParams.nodeId, r.trampolineNodeId, nodeParams.expiryDeltaBlocks, 0 msat),
NodeHop(nodeParams.nodeId, r.trampolineNodeId, nodeParams.expiryDelta, 0 msat),
NodeHop(r.trampolineNodeId, r.recipientNodeId, trampolineExpiryDelta, trampolineFees) // for now we only use a single trampoline hop
)
val finalPayload = if (r.paymentRequest.features.allowMultiPart) {
@ -185,53 +185,53 @@ object PaymentInitiator {
* Once we have trampoline fee estimation built into the router, the decision to use Trampoline or not should be done
* automatically by the router instead of the caller.
*
* @param recipientAmount amount that should be received by the final recipient (usually from a Bolt 11 invoice).
* @param paymentRequest Bolt 11 invoice.
* @param trampolineNodeId id of the trampoline node.
* @param trampolineAttempts fees and expiry delta for the trampoline node. If this list contains multiple entries,
* 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 routeParams (optional) parameters to fine-tune the routing algorithm.
* @param recipientAmount amount that should be received by the final recipient (usually from a Bolt 11 invoice).
* @param paymentRequest Bolt 11 invoice.
* @param trampolineNodeId id of the trampoline node.
* @param trampolineAttempts fees and expiry delta for the trampoline node. If this list contains multiple entries,
* 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 fallbackFinalExpiryDelta 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,
paymentRequest: PaymentRequest,
trampolineNodeId: PublicKey,
trampolineAttempts: Seq[(MilliSatoshi, CltvExpiryDelta)],
finalExpiryDelta: CltvExpiryDelta = Channel.MIN_CLTV_EXPIRY_DELTA,
fallbackFinalExpiryDelta: CltvExpiryDelta = Channel.MIN_CLTV_EXPIRY_DELTA,
routeParams: Option[RouteParams] = None) {
val recipientNodeId = paymentRequest.nodeId
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(fallbackFinalExpiryDelta).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 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).
* @param routeParams (optional) parameters to fine-tune the routing algorithm.
* @param userCustomTlvs (optional) user-defined custom tlvs that will be added to the onion sent to the target node.
* @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 fallbackFinalExpiryDelta 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).
* @param routeParams (optional) parameters to fine-tune the routing algorithm.
* @param userCustomTlvs (optional) user-defined custom tlvs that will be added to the onion sent to the target node.
*/
case class SendPaymentRequest(recipientAmount: MilliSatoshi,
paymentHash: ByteVector32,
recipientNodeId: PublicKey,
maxAttempts: Int,
finalExpiryDelta: CltvExpiryDelta = Channel.MIN_CLTV_EXPIRY_DELTA,
fallbackFinalExpiryDelta: CltvExpiryDelta = Channel.MIN_CLTV_EXPIRY_DELTA,
paymentRequest: Option[PaymentRequest] = None,
externalId: Option[String] = None,
assistedRoutes: Seq[Seq[ExtraHop]] = Nil,
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(fallbackFinalExpiryDelta).toCltvExpiry(currentBlockHeight + 1)
}
/**
@ -247,33 +247,33 @@ object PaymentInitiator {
* SendPaymentToRouteRequest(250 msat, 600 msat, None, parentId, invoice, CltvExpiryDelta(9), Seq(alice, bob, dave), secret, 100 msat, CltvExpiryDelta(144), Seq(dave, peter))
* SendPaymentToRouteRequest(450 msat, 600 msat, None, parentId, invoice, CltvExpiryDelta(9), Seq(alice, carol, dave), secret, 100 msat, CltvExpiryDelta(144), Seq(dave, peter))
*
* @param amount amount that should be received by the last node in the route (should take trampoline
* fees into account).
* @param recipientAmount amount that should be received by the final recipient (usually from a Bolt 11 invoice).
* This amount may be split between multiple requests if using MPP.
* @param externalId (optional) externally-controlled identifier (to reconcile between application DB and eclair DB).
* @param parentId id of the whole payment. When manually sending a multi-part payment, you need to make
* 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 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
* all partial payments use the same trampolineSecret.
* @param trampolineFees if trampoline is used, fees for the first trampoline node. This value must be the same
* for all partial payments in the set.
* @param trampolineExpiryDelta if trampoline is used, expiry delta for the first trampoline node. This value must be
* the same for all partial payments in the set.
* @param trampolineNodes if trampoline is used, list of trampoline nodes to use (we currently support only a
* single trampoline node).
* @param amount amount that should be received by the last node in the route (should take trampoline
* fees into account).
* @param recipientAmount amount that should be received by the final recipient (usually from a Bolt 11 invoice).
* This amount may be split between multiple requests if using MPP.
* @param externalId (optional) externally-controlled identifier (to reconcile between application DB and eclair DB).
* @param parentId id of the whole payment. When manually sending a multi-part payment, you need to make
* 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 fallbackFinalExpiryDelta 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
* all partial payments use the same trampolineSecret.
* @param trampolineFees if trampoline is used, fees for the first trampoline node. This value must be the same
* for all partial payments in the set.
* @param trampolineExpiryDelta if trampoline is used, expiry delta for the first trampoline node. This value must be
* the same for all partial payments in the set.
* @param trampolineNodes if trampoline is used, list of trampoline nodes to use (we currently support only a
* single trampoline node).
*/
case class SendPaymentToRouteRequest(amount: MilliSatoshi,
recipientAmount: MilliSatoshi,
externalId: Option[String],
parentId: Option[UUID],
paymentRequest: PaymentRequest,
finalExpiryDelta: CltvExpiryDelta = Channel.MIN_CLTV_EXPIRY_DELTA,
fallbackFinalExpiryDelta: CltvExpiryDelta = Channel.MIN_CLTV_EXPIRY_DELTA,
route: Seq[PublicKey],
trampolineSecret: Option[ByteVector32],
trampolineFees: MilliSatoshi,
@ -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(fallbackFinalExpiryDelta).toCltvExpiry(currentBlockHeight + 1)
}
/**

View File

@ -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()
@ -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))
@ -133,7 +132,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
assert(send2.recipientAmount === 123.msat)
assert(send2.paymentHash === ByteVector32.Zeroes)
assert(send2.paymentRequest === Some(invoice2))
assert(send2.finalExpiryDelta === CltvExpiryDelta(96))
assert(send2.fallbackFinalExpiryDelta === CltvExpiryDelta(96))
// with custom route fees parameters
eclair.send(None, nodeId, 123 msat, ByteVector32.Zeroes, invoice_opt = None, feeThreshold_opt = Some(123 sat), maxFeePct_opt = Some(4.20))
@ -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))

View File

@ -63,6 +63,7 @@ object TestConstants {
}
sealed trait TestDatabases {
// @formatter:off
val connection: Connection
def network(): NetworkDb
def audit(): AuditDb
@ -72,9 +73,11 @@ object TestConstants {
def pendingRelay(): PendingRelayDb
def getVersion(statement: Statement, db_name: String, currentVersion: Int): Int
def close(): Unit
// @formatter:on
}
case class TestSqliteDatabases(connection: Connection = sqliteInMemory()) extends TestDatabases {
// @formatter:off
override def network(): NetworkDb = new SqliteNetworkDb(connection)
override def audit(): AuditDb = new SqliteAuditDb(connection)
override def channels(): ChannelsDb = new SqliteChannelsDb(connection)
@ -83,6 +86,7 @@ object TestConstants {
override def pendingRelay(): PendingRelayDb = new SqlitePendingRelayDb(connection)
override def getVersion(statement: Statement, db_name: String, currentVersion: Int): Int = SqliteUtils.getVersion(statement, db_name, currentVersion)
override def close(): Unit = ()
// @formatter:on
}
case class TestPgDatabases() extends TestDatabases {
@ -96,9 +100,9 @@ object TestConstants {
config.setDataSource(pg.getPostgresDatabase)
implicit val ds = new HikariDataSource(config)
implicit val lock = NoLock
// @formatter:off
override def network(): NetworkDb = new PgNetworkDb
override def audit(): AuditDb = new PgAuditDb
override def channels(): ChannelsDb = new PgChannelsDb
@ -107,14 +111,17 @@ object TestConstants {
override def pendingRelay(): PendingRelayDb = new PgPendingRelayDb
override def getVersion(statement: Statement, db_name: String, currentVersion: Int): Int = PgUtils.getVersion(statement, db_name, currentVersion)
override def close(): Unit = pg.close()
// @formatter:on
}
def sqliteInMemory(): Connection = DriverManager.getConnection("jdbc:sqlite::memory:")
def forAllDbs(f: TestDatabases => Unit): Unit = {
// @formatter:off
def using(dbs: TestDatabases)(g: TestDatabases => Unit): Unit = try g(dbs) finally dbs.close()
using(TestSqliteDatabases())(f)
using(TestPgDatabases())(f)
// @formatter:on
}
def inMemoryDb(connection: Connection = sqliteInMemory()): Databases = Databases.sqliteDatabaseByConnections(connection, connection, connection)
@ -148,12 +155,13 @@ object TestConstants {
),
maxHtlcValueInFlightMsat = UInt64(150000000),
maxAcceptedHtlcs = 100,
expiryDeltaBlocks = CltvExpiryDelta(144),
fulfillSafetyBeforeTimeoutBlocks = CltvExpiryDelta(6),
expiryDelta = CltvExpiryDelta(144),
fulfillSafetyBeforeTimeout = CltvExpiryDelta(6),
minFinalExpiryDelta = CltvExpiryDelta(18),
htlcMinimum = 0 msat,
minDepthBlocks = 3,
toRemoteDelayBlocks = CltvExpiryDelta(144),
maxToLocalDelayBlocks = CltvExpiryDelta(1000),
toRemoteDelay = CltvExpiryDelta(144),
maxToLocalDelay = CltvExpiryDelta(1000),
feeBase = 546000 msat,
feeProportionalMillionth = 10,
reserveToFundingRatio = 0.01, // note: not used (overridden below)
@ -205,7 +213,7 @@ object TestConstants {
nodeParams,
Script.write(Script.pay2wpkh(PrivateKey(randomBytes32).publicKey)),
None,
true,
isFunder = true,
fundingSatoshis
).copy(
channelReserve = 10000 sat // Bob will need to keep that much satoshis as direct payment
@ -235,12 +243,13 @@ object TestConstants {
),
maxHtlcValueInFlightMsat = UInt64.MaxValue, // Bob has no limit on the combined max value of in-flight htlcs
maxAcceptedHtlcs = 30,
expiryDeltaBlocks = CltvExpiryDelta(144),
fulfillSafetyBeforeTimeoutBlocks = CltvExpiryDelta(6),
expiryDelta = CltvExpiryDelta(144),
fulfillSafetyBeforeTimeout = CltvExpiryDelta(6),
minFinalExpiryDelta = CltvExpiryDelta(18),
htlcMinimum = 1000 msat,
minDepthBlocks = 3,
toRemoteDelayBlocks = CltvExpiryDelta(144),
maxToLocalDelayBlocks = CltvExpiryDelta(1000),
toRemoteDelay = CltvExpiryDelta(144),
maxToLocalDelay = CltvExpiryDelta(1000),
feeBase = 546000 msat,
feeProportionalMillionth = 10,
reserveToFundingRatio = 0.01, // note: not used (overridden below)
@ -292,7 +301,7 @@ object TestConstants {
nodeParams,
Script.write(Script.pay2wpkh(PrivateKey(randomBytes32).publicKey)),
None,
false,
isFunder = false,
fundingSatoshis).copy(
channelReserve = 20000 sat // Alice will need to keep that much satoshis as direct payment
)

View File

@ -113,7 +113,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
val delayTooHigh = CltvExpiryDelta(10000)
alice ! accept.copy(toSelfDelay = delayTooHigh)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, ToSelfDelayTooHigh(accept.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage))
assert(error === Error(accept.temporaryChannelId, ToSelfDelayTooHigh(accept.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelay).getMessage))
awaitCond(alice.stateName == CLOSED)
}

View File

@ -135,7 +135,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
val delayTooHigh = CltvExpiryDelta(10000)
bob ! open.copy(toSelfDelay = delayTooHigh)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, ToSelfDelayTooHigh(open.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage))
assert(error === Error(open.temporaryChannelId, ToSelfDelayTooHigh(open.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelay).getMessage))
awaitCond(bob.stateName == CLOSED)
}

View File

@ -1936,7 +1936,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = true))
sender.expectMsg(ChannelCommandResponse.Ok)
bob2alice.expectMsgType[UpdateFulfillHtlc]
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong))
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeout).toLong))
val ChannelErrorOccurred(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccurred]
assert(isFatal)
@ -1971,7 +1971,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = false))
sender.expectMsg(ChannelCommandResponse.Ok)
bob2alice.expectMsgType[UpdateFulfillHtlc]
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong))
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeout).toLong))
val ChannelErrorOccurred(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccurred]
assert(isFatal)
@ -2011,7 +2011,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
bob2alice.forward(alice)
alice2bob.expectMsgType[RevokeAndAck]
alice2bob.forward(bob)
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong))
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeout).toLong))
val ChannelErrorOccurred(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccurred]
assert(isFatal)

View File

@ -495,7 +495,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// We simulate a pending fulfill on that HTLC but not relayed.
// When it is close to expiring upstream, we should close the channel.
bob.underlyingActor.nodeParams.db.pendingRelay.addPendingRelay(initialState.channelId, CMD_FULFILL_HTLC(htlc.id, r, commit = true))
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong))
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeout).toLong))
val ChannelErrorOccurred(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccurred]
assert(isFatal)
@ -527,7 +527,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// We simulate a pending failure on that HTLC.
// Even if we get close to expiring upstream we shouldn't close the channel, because we have nothing to lose.
sender.send(bob, CMD_FAIL_HTLC(htlc.id, Right(IncorrectOrUnknownPaymentDetails(0 msat, 0))))
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong))
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeout).toLong))
bob2blockchain.expectNoMsg(250 millis)
alice2blockchain.expectNoMsg(250 millis)

View File

@ -79,7 +79,7 @@ class SqlitePaymentsDbSpec extends AnyFunSuite {
// add a few rows
val ps1 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), None, paymentHash1, PaymentType.Standard, 12345 msat, 12345 msat, alice, 1000, None, OutgoingPaymentStatus.Pending)
val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(500 msat), paymentHash1, davePriv, "Some invoice", expirySeconds = None, timestamp = 1)
val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(500 msat), paymentHash1, davePriv, "Some invoice", CltvExpiryDelta(18), expirySeconds = None, timestamp = 1)
val pr1 = IncomingPayment(i1, preimage1, PaymentType.Standard, i1.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(550 msat, 1100))
preMigrationDb.addOutgoingPayment(ps1)
@ -125,9 +125,9 @@ class SqlitePaymentsDbSpec extends AnyFunSuite {
val ps1 = OutgoingPayment(id1, id1, None, randomBytes32, PaymentType.Standard, 561 msat, 561 msat, PrivateKey(ByteVector32.One).publicKey, 1000, None, OutgoingPaymentStatus.Pending)
val ps2 = OutgoingPayment(id2, id2, None, randomBytes32, PaymentType.Standard, 1105 msat, 1105 msat, PrivateKey(ByteVector32.One).publicKey, 1010, None, OutgoingPaymentStatus.Failed(Nil, 1050))
val ps3 = OutgoingPayment(id3, id3, None, paymentHash1, PaymentType.Standard, 1729 msat, 1729 msat, PrivateKey(ByteVector32.One).publicKey, 1040, None, OutgoingPaymentStatus.Succeeded(preimage1, 0 msat, Nil, 1060))
val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash1, davePriv, "Some invoice", expirySeconds = None, timestamp = 1)
val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash1, davePriv, "Some invoice", CltvExpiryDelta(18), expirySeconds = None, timestamp = 1)
val pr1 = IncomingPayment(i1, preimage1, PaymentType.Standard, i1.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(12345678 msat, 1090))
val i2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash2, carolPriv, "Another invoice", expirySeconds = Some(30), timestamp = 1)
val i2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash2, carolPriv, "Another invoice", CltvExpiryDelta(18), expirySeconds = Some(30), timestamp = 1)
val pr2 = IncomingPayment(i2, preimage2, PaymentType.Standard, i2.timestamp.seconds.toMillis, IncomingPaymentStatus.Expired)
// Changes between version 2 and 3 to sent_payments:
@ -207,7 +207,7 @@ class SqlitePaymentsDbSpec extends AnyFunSuite {
assert(getVersion(statement, "payments", 4) == 4) // version still to 4
}
val i3 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), paymentHash3, alicePriv, "invoice #3", expirySeconds = Some(30))
val i3 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), paymentHash3, alicePriv, "invoice #3", CltvExpiryDelta(18), expirySeconds = Some(30))
val pr3 = IncomingPayment(i3, preimage3, PaymentType.Standard, i3.timestamp.seconds.toMillis, IncomingPaymentStatus.Pending)
postMigrationDb.addIncomingPayment(i3, pr3.paymentPreimage)
@ -251,7 +251,7 @@ class SqlitePaymentsDbSpec extends AnyFunSuite {
// Insert a bunch of old version 3 rows.
val (id1, id2, id3) = (UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID())
val parentId = UUID.randomUUID()
val invoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(2834 msat), paymentHash1, bobPriv, "invoice #1", expirySeconds = Some(30))
val invoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(2834 msat), paymentHash1, bobPriv, "invoice #1", CltvExpiryDelta(18), expirySeconds = Some(30))
val ps1 = OutgoingPayment(id1, id1, Some("42"), randomBytes32, PaymentType.Standard, 561 msat, 561 msat, alice, 1000, None, OutgoingPaymentStatus.Failed(Seq(FailureSummary(FailureType.REMOTE, "no candy for you", List(HopSummary(hop_ab), HopSummary(hop_bc)))), 1020))
val ps2 = OutgoingPayment(id2, parentId, Some("42"), paymentHash1, PaymentType.Standard, 1105 msat, 1105 msat, bob, 1010, Some(invoice1), OutgoingPaymentStatus.Pending)
val ps3 = OutgoingPayment(id3, parentId, None, paymentHash1, PaymentType.Standard, 1729 msat, 1729 msat, bob, 1040, None, OutgoingPaymentStatus.Succeeded(preimage1, 10 msat, Seq(HopSummary(hop_ab), HopSummary(hop_bc)), 1060))
@ -325,18 +325,18 @@ class SqlitePaymentsDbSpec extends AnyFunSuite {
// can't receive a payment without an invoice associated with it
assertThrows[IllegalArgumentException](db.receiveIncomingPayment(randomBytes32, 12345678 msat))
val expiredInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32, alicePriv, "invoice #1", timestamp = 1)
val expiredInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32, bobPriv, "invoice #2", timestamp = 2, expirySeconds = Some(30))
val expiredInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32, alicePriv, "invoice #1", CltvExpiryDelta(18), timestamp = 1)
val expiredInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32, bobPriv, "invoice #2", CltvExpiryDelta(18), timestamp = 2, expirySeconds = Some(30))
val expiredPayment1 = IncomingPayment(expiredInvoice1, randomBytes32, PaymentType.Standard, expiredInvoice1.timestamp.seconds.toMillis, IncomingPaymentStatus.Expired)
val expiredPayment2 = IncomingPayment(expiredInvoice2, randomBytes32, PaymentType.Standard, expiredInvoice2.timestamp.seconds.toMillis, IncomingPaymentStatus.Expired)
val pendingInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32, alicePriv, "invoice #3")
val pendingInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32, bobPriv, "invoice #4", expirySeconds = Some(30))
val pendingInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32, alicePriv, "invoice #3", CltvExpiryDelta(18))
val pendingInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32, bobPriv, "invoice #4", CltvExpiryDelta(18), expirySeconds = Some(30))
val pendingPayment1 = IncomingPayment(pendingInvoice1, randomBytes32, PaymentType.Standard, pendingInvoice1.timestamp.seconds.toMillis, IncomingPaymentStatus.Pending)
val pendingPayment2 = IncomingPayment(pendingInvoice2, randomBytes32, PaymentType.SwapIn, pendingInvoice2.timestamp.seconds.toMillis, IncomingPaymentStatus.Pending)
val paidInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32, alicePriv, "invoice #5")
val paidInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32, bobPriv, "invoice #6", expirySeconds = Some(60))
val paidInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32, alicePriv, "invoice #5", CltvExpiryDelta(18))
val paidInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32, bobPriv, "invoice #6", CltvExpiryDelta(18), expirySeconds = Some(60))
val receivedAt1 = System.currentTimeMillis + 1
val receivedAt2 = System.currentTimeMillis + 2
val payment1 = IncomingPayment(paidInvoice1, randomBytes32, PaymentType.Standard, paidInvoice1.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(561 msat, receivedAt2))
@ -376,7 +376,7 @@ class SqlitePaymentsDbSpec extends AnyFunSuite {
val db = dbs.payments()
val parentId = UUID.randomUUID()
val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(123 msat), paymentHash1, davePriv, "Some invoice", expirySeconds = None, timestamp = 0)
val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(123 msat), paymentHash1, davePriv, "Some invoice", CltvExpiryDelta(18), expirySeconds = None, timestamp = 0)
val s1 = OutgoingPayment(UUID.randomUUID(), parentId, None, paymentHash1, PaymentType.Standard, 123 msat, 600 msat, dave, 100, Some(i1), OutgoingPaymentStatus.Pending)
val s2 = OutgoingPayment(UUID.randomUUID(), parentId, Some("1"), paymentHash1, PaymentType.SwapOut, 456 msat, 600 msat, dave, 200, None, OutgoingPaymentStatus.Pending)
@ -432,14 +432,14 @@ class SqlitePaymentsDbSpec extends AnyFunSuite {
val db = new SqlitePaymentsDb(TestConstants.sqliteInMemory())
// -- feed db with incoming payments
val expiredInvoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(123 msat), randomBytes32, alicePriv, "incoming #1", timestamp = 1)
val expiredInvoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(123 msat), randomBytes32, alicePriv, "incoming #1", CltvExpiryDelta(18), timestamp = 1)
val expiredPayment = IncomingPayment(expiredInvoice, randomBytes32, PaymentType.Standard, 100, IncomingPaymentStatus.Expired)
val pendingInvoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(456 msat), randomBytes32, alicePriv, "incoming #2")
val pendingInvoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(456 msat), randomBytes32, alicePriv, "incoming #2", CltvExpiryDelta(18))
val pendingPayment = IncomingPayment(pendingInvoice, randomBytes32, PaymentType.Standard, 120, IncomingPaymentStatus.Pending)
val paidInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(789 msat), randomBytes32, alicePriv, "incoming #3")
val paidInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(789 msat), randomBytes32, alicePriv, "incoming #3", CltvExpiryDelta(18))
val receivedAt1 = 150
val receivedPayment1 = IncomingPayment(paidInvoice1, randomBytes32, PaymentType.Standard, 130, IncomingPaymentStatus.Received(561 msat, receivedAt1))
val paidInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(888 msat), randomBytes32, alicePriv, "incoming #4")
val paidInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(888 msat), randomBytes32, alicePriv, "incoming #4", CltvExpiryDelta(18))
val receivedAt2 = 160
val receivedPayment2 = IncomingPayment(paidInvoice2, randomBytes32, PaymentType.Standard, paidInvoice2.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(889 msat, receivedAt2))
db.addIncomingPayment(pendingInvoice, pendingPayment.paymentPreimage)
@ -452,7 +452,7 @@ class SqlitePaymentsDbSpec extends AnyFunSuite {
// -- feed db with outgoing payments
val parentId1 = UUID.randomUUID()
val parentId2 = UUID.randomUUID()
val invoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1337 msat), paymentHash1, davePriv, "outgoing #1", expirySeconds = None, timestamp = 0)
val invoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1337 msat), paymentHash1, davePriv, "outgoing #1", CltvExpiryDelta(18), expirySeconds = None, timestamp = 0)
// 1st attempt, pending -> failed
val outgoing1 = OutgoingPayment(UUID.randomUUID(), parentId1, None, paymentHash1, PaymentType.Standard, 123 msat, 123 msat, alice, 200, Some(invoice), OutgoingPaymentStatus.Pending)

View File

@ -88,6 +88,9 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
mpp = MultiPartParams(15000000 msat, 6)
))
// we need to provide a value higher than every node's fulfill-safety-before-timeout
val finalCltvExpiryDelta = CltvExpiryDelta(36)
val commonConfig = ConfigFactory.parseMap(Map(
"eclair.chain" -> "regtest",
"eclair.enable-db-backup" -> false,
@ -380,7 +383,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
// then we make the actual payment
sender.send(nodes("A").paymentInitiator, SendPaymentRequest(amountMsat, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 1))
sender.send(nodes("A").paymentInitiator, SendPaymentRequest(amountMsat, pr.paymentHash, nodes("D").nodeParams.nodeId, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams, maxAttempts = 1))
val paymentId = sender.expectMsgType[UUID](5 seconds)
val ps = sender.expectMsgType[PaymentSent](5 seconds)
assert(ps.id == paymentId)
@ -396,7 +399,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
sender.send(nodes("B").register, ForwardShortId(shortIdBC, CMD_GETINFO))
val commitmentBC = sender.expectMsgType[RES_GETINFO].data.asInstanceOf[DATA_NORMAL].commitments
// we then forge a new channel_update for B-C...
val channelUpdateBC = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, nodes("B").nodeParams.privateKey, nodes("C").nodeParams.nodeId, shortIdBC, nodes("B").nodeParams.expiryDeltaBlocks + 1, nodes("C").nodeParams.htlcMinimum, nodes("B").nodeParams.feeBase, nodes("B").nodeParams.feeProportionalMillionth, 500000000 msat)
val channelUpdateBC = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, nodes("B").nodeParams.privateKey, nodes("C").nodeParams.nodeId, shortIdBC, nodes("B").nodeParams.expiryDelta + 1, nodes("C").nodeParams.htlcMinimum, nodes("B").nodeParams.feeBase, nodes("B").nodeParams.feeProportionalMillionth, 500000000 msat)
// ...and notify B's relayer
sender.send(nodes("B").relayer, LocalChannelUpdate(system.deadLetters, commitmentBC.channelId, shortIdBC, commitmentBC.remoteParams.nodeId, None, channelUpdateBC, commitmentBC))
// we retrieve a payment hash from D
@ -404,7 +407,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
// then we make the actual payment, do not randomize the route to make sure we route through node B
val sendReq = SendPaymentRequest(amountMsat, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
val sendReq = SendPaymentRequest(amountMsat, pr.paymentHash, nodes("D").nodeParams.nodeId, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
// A will receive an error from B that include the updated channel update, then will retry the payment
val paymentId = sender.expectMsgType[UUID](5 seconds)
@ -429,11 +432,11 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
logger.info(s"channelUpdateBC=$channelUpdateBC")
logger.info(s"channelUpdateBC_new=$channelUpdateBC_new")
assert(channelUpdateBC_new.timestamp > channelUpdateBC.timestamp)
assert(channelUpdateBC_new.cltvExpiryDelta == nodes("B").nodeParams.expiryDeltaBlocks)
assert(channelUpdateBC_new.cltvExpiryDelta == nodes("B").nodeParams.expiryDelta)
awaitCond({
sender.send(nodes("A").router, Symbol("channelsMap"))
val u = updateFor(nodes("B").nodeParams.nodeId, sender.expectMsgType[Map[ShortChannelId, PublicChannel]](10 seconds).apply(channelUpdateBC.shortChannelId)).get
u.cltvExpiryDelta == nodes("B").nodeParams.expiryDeltaBlocks
u.cltvExpiryDelta == nodes("B").nodeParams.expiryDelta
}, max = 30 seconds, interval = 1 second)
}
@ -444,7 +447,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
// then we make the payment (B-C has a smaller capacity than A-B and C-D)
val sendReq = SendPaymentRequest(amountMsat, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
val sendReq = SendPaymentRequest(amountMsat, pr.paymentHash, nodes("D").nodeParams.nodeId, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
// A will first receive an error from C, then retry and route around C: A->B->E->C->D
sender.expectMsgType[UUID](5 seconds)
@ -473,7 +476,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val pr = sender.expectMsgType[PaymentRequest]
// A send payment of only 1 mBTC
val sendReq = SendPaymentRequest(100000000 msat, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
val sendReq = SendPaymentRequest(100000000 msat, pr.paymentHash, nodes("D").nodeParams.nodeId, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
// A will first receive an IncorrectPaymentAmount error from D
@ -493,7 +496,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val pr = sender.expectMsgType[PaymentRequest]
// A send payment of 6 mBTC
val sendReq = SendPaymentRequest(600000000 msat, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
val sendReq = SendPaymentRequest(600000000 msat, pr.paymentHash, nodes("D").nodeParams.nodeId, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
// A will first receive an IncorrectPaymentAmount error from D
@ -513,7 +516,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val pr = sender.expectMsgType[PaymentRequest]
// A send payment of 3 mBTC, more than asked but it should still be accepted
val sendReq = SendPaymentRequest(300000000 msat, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
val sendReq = SendPaymentRequest(300000000 msat, pr.paymentHash, nodes("D").nodeParams.nodeId, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
sender.expectMsgType[UUID]
}
@ -526,7 +529,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), "1 payment"))
val pr = sender.expectMsgType[PaymentRequest]
val sendReq = SendPaymentRequest(amountMsat, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
val sendReq = SendPaymentRequest(amountMsat, pr.paymentHash, nodes("D").nodeParams.nodeId, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
sender.expectMsgType[UUID]
sender.expectMsgType[PaymentSent] // the payment FSM will also reply to the sender after the payment is completed
@ -541,7 +544,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val pr = sender.expectMsgType[PaymentRequest](30 seconds)
// the payment is requesting to use a capacity-optimized route which will select node G even though it's a bit more expensive
sender.send(nodes("A").paymentInitiator, SendPaymentRequest(amountMsat, pr.paymentHash, nodes("C").nodeParams.nodeId, maxAttempts = 1, routeParams = integrationTestRouteParams.map(_.copy(ratios = Some(WeightRatios(0, 0, 1))))))
sender.send(nodes("A").paymentInitiator, SendPaymentRequest(amountMsat, pr.paymentHash, nodes("C").nodeParams.nodeId, maxAttempts = 1, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams.map(_.copy(ratios = Some(WeightRatios(0, 0, 1))))))
sender.expectMsgType[UUID](max = 60 seconds)
awaitCond({
@ -880,7 +883,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val pr = sender.expectMsgType[PaymentRequest]
// then we make the actual payment
sender.send(nodes("C").paymentInitiator, SendPaymentRequest(amountMsat, pr.paymentHash, nodes("F6").nodeParams.nodeId, maxAttempts = 1))
sender.send(nodes("C").paymentInitiator, SendPaymentRequest(amountMsat, pr.paymentHash, nodes("F6").nodeParams.nodeId, maxAttempts = 1, fallbackFinalExpiryDelta = finalCltvExpiryDelta))
val paymentId = sender.expectMsgType[UUID](5 seconds)
val ps = sender.expectMsgType[PaymentSent](5 seconds)
assert(ps.id == paymentId)
@ -948,7 +951,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val preimage = randomBytes32
val paymentHash = Crypto.sha256(preimage)
// A sends a payment to F
val paymentReq = SendPaymentRequest(100000000 msat, paymentHash, nodes("F1").nodeParams.nodeId, maxAttempts = 3, routeParams = integrationTestRouteParams)
val paymentReq = SendPaymentRequest(100000000 msat, paymentHash, nodes("F1").nodeParams.nodeId, maxAttempts = 3, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams)
val paymentSender = TestProbe()
paymentSender.send(nodes("A").paymentInitiator, paymentReq)
paymentSender.expectMsgType[UUID](30 seconds)
@ -1029,7 +1032,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val preimage = randomBytes32
val paymentHash = Crypto.sha256(preimage)
// A sends a payment to F
val paymentReq = SendPaymentRequest(100000000 msat, paymentHash, nodes("F2").nodeParams.nodeId, maxAttempts = 3, routeParams = integrationTestRouteParams)
val paymentReq = SendPaymentRequest(100000000 msat, paymentHash, nodes("F2").nodeParams.nodeId, maxAttempts = 3, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams)
val paymentSender = TestProbe()
paymentSender.send(nodes("A").paymentInitiator, paymentReq)
paymentSender.expectMsgType[UUID](30 seconds)
@ -1101,7 +1104,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val preimage: ByteVector = randomBytes32
val paymentHash = Crypto.sha256(preimage)
// A sends a payment to F
val paymentReq = SendPaymentRequest(100000000 msat, paymentHash, nodes("F3").nodeParams.nodeId, maxAttempts = 3, routeParams = integrationTestRouteParams)
val paymentReq = SendPaymentRequest(100000000 msat, paymentHash, nodes("F3").nodeParams.nodeId, maxAttempts = 3, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams)
val paymentSender = TestProbe()
paymentSender.send(nodes("A").paymentInitiator, paymentReq)
val paymentId = paymentSender.expectMsgType[UUID]
@ -1159,7 +1162,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val preimage: ByteVector = randomBytes32
val paymentHash = Crypto.sha256(preimage)
// A sends a payment to F
val paymentReq = SendPaymentRequest(100000000 msat, paymentHash, nodes("F4").nodeParams.nodeId, maxAttempts = 3, routeParams = integrationTestRouteParams)
val paymentReq = SendPaymentRequest(100000000 msat, paymentHash, nodes("F4").nodeParams.nodeId, maxAttempts = 3, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams)
val paymentSender = TestProbe()
paymentSender.send(nodes("A").paymentInitiator, paymentReq)
val paymentId = paymentSender.expectMsgType[UUID](30 seconds)
@ -1230,7 +1233,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val amountMsat = 300000000.msat
sender.send(paymentHandlerF, ReceivePayment(Some(amountMsat), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
val sendReq = SendPaymentRequest(300000000 msat, pr.paymentHash, pr.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 3)
val sendReq = SendPaymentRequest(300000000 msat, pr.paymentHash, pr.nodeId, maxAttempts = 3, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams)
sender.send(nodes("A").paymentInitiator, sendReq)
val paymentId = sender.expectMsgType[UUID]
// we forward the htlc to the payment handler
@ -1244,7 +1247,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
def send(amountMsat: MilliSatoshi, paymentHandler: ActorRef, paymentInitiator: ActorRef) = {
sender.send(paymentHandler, ReceivePayment(Some(amountMsat), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
val sendReq = SendPaymentRequest(amountMsat, pr.paymentHash, pr.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 1)
val sendReq = SendPaymentRequest(amountMsat, pr.paymentHash, pr.nodeId, maxAttempts = 1, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams)
sender.send(paymentInitiator, sendReq)
sender.expectMsgType[UUID]
}
@ -1274,7 +1277,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
val htlcSuccessTxs = localCommitF.htlcTxsAndSigs.collect { case h@HtlcTxAndSigs(_: Transactions.HtlcSuccessTx, _, _) => h }
assert(htlcTimeoutTxs.size === 2)
assert(htlcSuccessTxs.size === 2)
// we fulfill htlcs to get the preimagse
// we fulfill htlcs to get the preimages
buffer.expectMsgType[IncomingPacket.FinalPacket]
buffer.forward(paymentHandlerF)
sigListener.expectMsgType[ChannelSignatureReceived]
@ -1307,7 +1310,7 @@ class IntegrationSpec extends TestKitBaseClass with BitcoindService with AnyFunS
Transaction.correctlySpends(htlcSuccess, Seq(revokedCommitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
Transaction.correctlySpends(htlcTimeout, Seq(revokedCommitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// we then generate blocks to make the htlc timeout (nothing will happen in the channel because all of them have already been fulfilled)
generateBlocks(bitcoincli, 20)
generateBlocks(bitcoincli, 40)
// then we publish F's revoked transactions
sender.send(bitcoincli, BitcoinReq("sendrawtransaction", revokedCommitTx.toString()))
sender.expectMsgType[JValue](10000 seconds)

View File

@ -17,10 +17,10 @@
package fr.acinq.eclair.payment
import akka.actor.Status.Failure
import akka.testkit.{TestActorRef, TestKit, TestProbe}
import akka.testkit.{TestActorRef, TestProbe}
import fr.acinq.bitcoin.{ByteVector32, Crypto}
import fr.acinq.eclair.FeatureSupport.Optional
import fr.acinq.eclair.Features.{BasicMultiPartPayment, ChannelRangeQueries, ChannelRangeQueriesExtended, InitialRoutingSync, OptionDataLossProtect, PaymentSecret, VariableLengthOnion}
import fr.acinq.eclair.Features._
import fr.acinq.eclair.TestConstants.Alice
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, Register}
import fr.acinq.eclair.db.IncomingPaymentStatus
@ -63,7 +63,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val register = TestProbe()
val eventListener = TestProbe()
system.eventStream.subscribe(eventListener.ref, classOf[PaymentEvent])
withFixture(test.toNoArgTest(FixtureParam(nodeParams, CltvExpiryDelta(12).toCltvExpiry(nodeParams.currentBlockHeight), register, eventListener, TestProbe())))
withFixture(test.toNoArgTest(FixtureParam(nodeParams, nodeParams.minFinalExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight), register, eventListener, TestProbe())))
}
}
@ -160,10 +160,14 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
import f._
sender.send(normalHandler, ReceivePayment(Some(42000 msat), "1 coffee"))
assert(sender.expectMsgType[PaymentRequest].expiry === Some(Alice.nodeParams.paymentRequestExpiry.toSeconds))
val pr1 = sender.expectMsgType[PaymentRequest]
assert(pr1.minFinalCltvExpiryDelta === Some(nodeParams.minFinalExpiryDelta))
assert(pr1.expiry === Some(Alice.nodeParams.paymentRequestExpiry.toSeconds))
sender.send(normalHandler, ReceivePayment(Some(42000 msat), "1 coffee with custom expiry", expirySeconds_opt = Some(60)))
assert(sender.expectMsgType[PaymentRequest].expiry === Some(60))
val pr2 = sender.expectMsgType[PaymentRequest]
assert(pr2.minFinalCltvExpiryDelta === Some(nodeParams.minFinalExpiryDelta))
assert(pr2.expiry === Some(60))
}
test("Payment request generation with trampoline support") { _ =>
@ -272,7 +276,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val pr = sender.expectMsgType[PaymentRequest]
assert(pr.features.allowMultiPart)
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, pr.paymentHash, CltvExpiryDelta(1).toCltvExpiry(nodeParams.currentBlockHeight), TestConstants.emptyOnionPacket)
val lowCltvExpiry = nodeParams.fulfillSafetyBeforeTimeout.toCltvExpiry(nodeParams.currentBlockHeight)
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, pr.paymentHash, lowCltvExpiry, TestConstants.emptyOnionPacket)
sender.send(mppHandler, IncomingPacket.FinalPacket(add, Onion.createMultiPartPayload(add.amountMsat, 1000 msat, add.cltvExpiry, pr.paymentSecret.get)))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))

View File

@ -264,7 +264,7 @@ class NodeRelayerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
val fee = nodeFee(nodeParams.feeBase, nodeParams.feeProportionalMillionth, outgoingAmount)
assert(routeParams.maxFeePct === 0) // should be disabled
assert(routeParams.maxFeeBase === incomingAmount - outgoingAmount - fee) // we collect our fee and then use what remains for the rest of the route
assert(routeParams.routeMaxCltv === incomingSinglePart.add.cltvExpiry - outgoingExpiry - nodeParams.expiryDeltaBlocks) // we apply our cltv delta
assert(routeParams.routeMaxCltv === incomingSinglePart.add.cltvExpiry - outgoingExpiry - nodeParams.expiryDelta) // we apply our cltv delta
}
test("relay incoming multi-part payment") { f =>
@ -326,7 +326,7 @@ class NodeRelayerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
// Receive an upstream multi-part payment.
val hints = List(List(ExtraHop(outgoingNodeId, ShortChannelId(42), feeBase = 10 msat, feeProportionalMillionths = 1, cltvExpiryDelta = CltvExpiryDelta(12))))
val features = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.mandatory, BasicMultiPartPayment.optional)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(outgoingAmount * 3), paymentHash, randomKey, "Some invoice", extraHops = hints, features = Some(features))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(outgoingAmount * 3), paymentHash, randomKey, "Some invoice", CltvExpiryDelta(18), extraHops = hints, features = Some(features))
incomingMultiPart.foreach(incoming => relayer.send(nodeRelayer, incoming.copy(innerPayload = Onion.createNodeRelayToNonTrampolinePayload(
incoming.innerPayload.amountToForward, outgoingAmount * 3, outgoingExpiry, outgoingNodeId, pr
))))
@ -358,7 +358,7 @@ class NodeRelayerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
// Receive an upstream multi-part payment.
val hints = List(List(ExtraHop(outgoingNodeId, ShortChannelId(42), feeBase = 10 msat, feeProportionalMillionths = 1, cltvExpiryDelta = CltvExpiryDelta(12))))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(outgoingAmount), paymentHash, randomKey, "Some invoice", extraHops = hints, features = Some(PaymentRequestFeatures()))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(outgoingAmount), paymentHash, randomKey, "Some invoice", CltvExpiryDelta(18), extraHops = hints, features = Some(PaymentRequestFeatures()))
incomingMultiPart.foreach(incoming => relayer.send(nodeRelayer, incoming.copy(innerPayload = Onion.createNodeRelayToNonTrampolinePayload(
incoming.innerPayload.amountToForward, incoming.innerPayload.amountToForward, outgoingExpiry, outgoingNodeId, pr
))))

View File

@ -95,14 +95,14 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
payFsm.expectMsgType[SendPaymentConfig]
val FinalTlvPayload(tlvs) = payFsm.expectMsgType[SendPayment].finalPayload
assert(tlvs.get[AmountToForward].get.amount == finalAmount)
assert(tlvs.get[OutgoingCltv].get.cltv == req.finalExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight + 1))
assert(tlvs.get[OutgoingCltv].get.cltv == req.fallbackFinalExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight + 1))
assert(tlvs.unknown == keySendTlvRecords)
}
test("reject payment with unknown mandatory feature") { f =>
import f._
val unknownFeature = 42
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some invoice", features = Some(PaymentRequestFeatures(unknownFeature)))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some invoice", CltvExpiryDelta(18), features = Some(PaymentRequestFeatures(unknownFeature)))
val req = SendPaymentRequest(finalAmount + 100.msat, paymentHash, c, 1, CltvExpiryDelta(42), Some(pr))
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
@ -113,21 +113,25 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("forward payment with pre-defined route") { f =>
import f._
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some invoice", features = None)
sender.send(initiator, SendPaymentToRouteRequest(finalAmount, finalAmount, None, None, pr, Channel.MIN_CLTV_EXPIRY_DELTA, Seq(a, b, c), None, 0 msat, CltvExpiryDelta(0), Nil))
// we prioritize the invoice's finalExpiryDelta over the one from SendPaymentToRouteRequest
val ignoredFinalExpiryDelta = CltvExpiryDelta(18)
val finalExpiryDelta = CltvExpiryDelta(36)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some invoice", finalExpiryDelta, features = None)
sender.send(initiator, SendPaymentToRouteRequest(finalAmount, finalAmount, None, None, pr, ignoredFinalExpiryDelta, Seq(a, b, c), None, 0 msat, CltvExpiryDelta(0), Nil))
val payment = sender.expectMsgType[SendPaymentToRouteResponse]
payFsm.expectMsg(SendPaymentConfig(payment.paymentId, payment.parentId, None, paymentHash, finalAmount, c, Upstream.Local(payment.paymentId), Some(pr), storeInDb = true, publishEvent = true, Nil))
payFsm.expectMsg(SendPaymentToRoute(Left(Seq(a, b, c)), FinalLegacyPayload(finalAmount, Channel.MIN_CLTV_EXPIRY_DELTA.toCltvExpiry(nodeParams.currentBlockHeight + 1))))
payFsm.expectMsg(SendPaymentToRoute(Left(Seq(a, b, c)), FinalLegacyPayload(finalAmount, finalExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight + 1))))
}
test("forward legacy payment") { f =>
import f._
val finalExpiryDelta = CltvExpiryDelta(42)
val hints = Seq(Seq(ExtraHop(b, channelUpdate_bc.shortChannelId, feeBase = 10 msat, feeProportionalMillionths = 1, cltvExpiryDelta = CltvExpiryDelta(12))))
val routeParams = RouteParams(randomize = true, 15 msat, 1.5, 5, CltvExpiryDelta(561), None, MultiPartParams(10000 msat, 5))
sender.send(initiator, SendPaymentRequest(finalAmount, paymentHash, c, 1, CltvExpiryDelta(42), assistedRoutes = hints, routeParams = Some(routeParams)))
sender.send(initiator, SendPaymentRequest(finalAmount, paymentHash, c, 1, finalExpiryDelta, assistedRoutes = hints, routeParams = Some(routeParams)))
val id1 = sender.expectMsgType[UUID]
payFsm.expectMsg(SendPaymentConfig(id1, id1, None, paymentHash, finalAmount, c, Upstream.Local(id1), None, storeInDb = true, publishEvent = true, Nil))
payFsm.expectMsg(SendPayment(c, FinalLegacyPayload(finalAmount, CltvExpiryDelta(42).toCltvExpiry(nodeParams.currentBlockHeight + 1)), 1, hints, Some(routeParams)))
payFsm.expectMsg(SendPayment(c, FinalLegacyPayload(finalAmount, finalExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight + 1)), 1, hints, Some(routeParams)))
sender.send(initiator, SendPaymentRequest(finalAmount, paymentHash, e, 3))
val id2 = sender.expectMsgType[UUID]
@ -137,8 +141,10 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("forward single-part payment when multi-part deactivated", Tag("mpp_disabled")) { f =>
import f._
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some MPP invoice", features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)))
val req = SendPaymentRequest(finalAmount, paymentHash, c, 1, CltvExpiryDelta(42), Some(pr))
val finalExpiryDelta = CltvExpiryDelta(24)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some MPP invoice", finalExpiryDelta, features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)))
val req = SendPaymentRequest(finalAmount, paymentHash, c, 1, /* ignored since the invoice provides it */ CltvExpiryDelta(12), Some(pr))
assert(req.finalExpiry(nodeParams.currentBlockHeight) === (finalExpiryDelta + 1).toCltvExpiry(nodeParams.currentBlockHeight))
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
payFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, finalAmount, c, Upstream.Local(id), Some(pr), storeInDb = true, publishEvent = true, Nil))
@ -147,7 +153,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("forward multi-part payment") { f =>
import f._
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some invoice", features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some invoice", CltvExpiryDelta(18), features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)))
val req = SendPaymentRequest(finalAmount + 100.msat, paymentHash, c, 1, CltvExpiryDelta(42), Some(pr))
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
@ -157,7 +163,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("forward multi-part payment with pre-defined route") { f =>
import f._
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some invoice", features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some invoice", CltvExpiryDelta(18), features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)))
val req = SendPaymentToRouteRequest(finalAmount / 2, finalAmount, None, None, pr, Channel.MIN_CLTV_EXPIRY_DELTA, Seq(a, b, c), None, 0 msat, CltvExpiryDelta(0), Nil)
sender.send(initiator, req)
val payment = sender.expectMsgType[SendPaymentToRouteResponse]
@ -165,6 +171,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val msg = payFsm.expectMsgType[SendPaymentToRoute]
assert(msg.route === Left(Seq(a, b, c)))
assert(msg.finalPayload.amount === finalAmount / 2)
assert(msg.finalPayload.expiry === req.finalExpiry(nodeParams.currentBlockHeight))
assert(msg.finalPayload.paymentSecret === pr.paymentSecret)
assert(msg.finalPayload.totalAmount === finalAmount)
}
@ -173,9 +180,9 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
import f._
val features = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional, TrampolinePayment.optional)
val ignoredRoutingHints = List(List(ExtraHop(b, channelUpdate_bc.shortChannelId, feeBase = 10 msat, feeProportionalMillionths = 1, cltvExpiryDelta = CltvExpiryDelta(12))))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some phoenix invoice", features = Some(features), extraHops = ignoredRoutingHints)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some phoenix invoice", CltvExpiryDelta(9), features = Some(features), extraHops = ignoredRoutingHints)
val trampolineFees = 21000 msat
val req = SendTrampolinePaymentRequest(finalAmount, pr, b, Seq((trampolineFees, CltvExpiryDelta(12))), CltvExpiryDelta(9))
val req = SendTrampolinePaymentRequest(finalAmount, pr, b, Seq((trampolineFees, CltvExpiryDelta(12))), /* ignored since the invoice provides it */ CltvExpiryDelta(18))
sender.send(initiator, req)
sender.expectMsgType[UUID]
multiPartPayFsm.expectMsgType[SendPaymentConfig]
@ -212,9 +219,9 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("forward trampoline to legacy payment") { f =>
import f._
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some eclair-mobile invoice")
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some eclair-mobile invoice", CltvExpiryDelta(9))
val trampolineFees = 21000 msat
val req = SendTrampolinePaymentRequest(finalAmount, pr, b, Seq((trampolineFees, CltvExpiryDelta(12))), CltvExpiryDelta(9))
val req = SendTrampolinePaymentRequest(finalAmount, pr, b, Seq((trampolineFees, CltvExpiryDelta(12))))
sender.send(initiator, req)
sender.expectMsgType[UUID]
multiPartPayFsm.expectMsgType[SendPaymentConfig]
@ -244,7 +251,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
// This is disabled because it would let the trampoline node steal the whole payment (if malicious).
val routingHints = List(List(PaymentRequest.ExtraHop(b, channelUpdate_bc.shortChannelId, 10 msat, 100, CltvExpiryDelta(144))))
val features = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)
val pr = PaymentRequest(Block.RegtestGenesisBlock.hash, None, paymentHash, priv_a.privateKey, "#abittooreckless", None, None, routingHints, features = Some(features))
val pr = PaymentRequest(Block.RegtestGenesisBlock.hash, None, paymentHash, priv_a.privateKey, "#abittooreckless", CltvExpiryDelta(18), None, None, routingHints, features = Some(features))
val trampolineFees = 21000 msat
val req = SendTrampolinePaymentRequest(finalAmount, pr, b, Seq((trampolineFees, CltvExpiryDelta(12))), CltvExpiryDelta(9))
sender.send(initiator, req)
@ -260,7 +267,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("retry trampoline payment") { f =>
import f._
val features = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional, TrampolinePayment.optional)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some phoenix invoice", features = Some(features))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some phoenix invoice", CltvExpiryDelta(18), features = Some(features))
val trampolineAttempts = (21000 msat, CltvExpiryDelta(12)) :: (25000 msat, CltvExpiryDelta(24)) :: Nil
val req = SendTrampolinePaymentRequest(finalAmount, pr, b, trampolineAttempts, CltvExpiryDelta(9))
sender.send(initiator, req)
@ -290,7 +297,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("retry trampoline payment and fail") { f =>
import f._
val features = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional, TrampolinePayment.optional)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some phoenix invoice", features = Some(features))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some phoenix invoice", CltvExpiryDelta(18), features = Some(features))
val trampolineAttempts = (21000 msat, CltvExpiryDelta(12)) :: (25000 msat, CltvExpiryDelta(24)) :: Nil
val req = SendTrampolinePaymentRequest(finalAmount, pr, b, trampolineAttempts, CltvExpiryDelta(9))
sender.send(initiator, req)
@ -320,7 +327,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("retry trampoline payment and fail (route not found)") { f =>
import f._
val features = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional, TrampolinePayment.optional)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some phoenix invoice", features = Some(features))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some phoenix invoice", CltvExpiryDelta(18), features = Some(features))
val trampolineAttempts = (21000 msat, CltvExpiryDelta(12)) :: (25000 msat, CltvExpiryDelta(24)) :: Nil
val req = SendTrampolinePaymentRequest(finalAmount, pr, b, trampolineAttempts, CltvExpiryDelta(9))
sender.send(initiator, req)
@ -339,7 +346,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
multiPartPayFsm.send(initiator, failed)
val failure = sender.expectMsgType[PaymentFailed]
assert(failure.failures === Seq(LocalFailure(Seq(NodeHop(nodeParams.nodeId, b, nodeParams.expiryDeltaBlocks, 0 msat), NodeHop(b, c, CltvExpiryDelta(24), 25000 msat)), RouteNotFound)))
assert(failure.failures === Seq(LocalFailure(Seq(NodeHop(nodeParams.nodeId, b, nodeParams.expiryDelta, 0 msat), NodeHop(b, c, CltvExpiryDelta(24), 25000 msat)), RouteNotFound)))
eventListener.expectMsg(failure)
sender.expectNoMsg(100 millis)
eventListener.expectNoMsg(100 millis)
@ -347,7 +354,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("forward trampoline payment with pre-defined route") { f =>
import f._
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some invoice")
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, priv_c.privateKey, "Some invoice", CltvExpiryDelta(18))
val trampolineFees = 100 msat
val req = SendPaymentToRouteRequest(finalAmount + trampolineFees, finalAmount, None, None, pr, Channel.MIN_CLTV_EXPIRY_DELTA, Seq(a, b), None, trampolineFees, CltvExpiryDelta(144), Seq(b, c))
sender.send(initiator, req)

View File

@ -217,7 +217,7 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
val routingHints = List(List(PaymentRequest.ExtraHop(randomKey.publicKey, ShortChannelId(42), 10 msat, 100, CltvExpiryDelta(144))))
val invoiceFeatures = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)
val invoice = PaymentRequest(Block.RegtestGenesisBlock.hash, Some(finalAmount), paymentHash, priv_a.privateKey, "#reckless", None, None, routingHints, features = Some(invoiceFeatures))
val invoice = PaymentRequest(Block.RegtestGenesisBlock.hash, Some(finalAmount), paymentHash, priv_a.privateKey, "#reckless", CltvExpiryDelta(18), None, None, routingHints, features = Some(invoiceFeatures))
val (amount_ac, expiry_ac, trampolineOnion) = buildTrampolineToLegacyPacket(invoice, trampolineHops, FinalLegacyPayload(finalAmount, finalExpiry))
assert(amount_ac === amount_bc)
assert(expiry_ac === expiry_bc)
@ -263,7 +263,7 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
test("fail to build a trampoline payment when too much invoice data is provided") {
val routingHintOverflow = List(List.fill(7)(PaymentRequest.ExtraHop(randomKey.publicKey, ShortChannelId(1), 10 msat, 100, CltvExpiryDelta(12))))
val invoice = PaymentRequest(Block.RegtestGenesisBlock.hash, Some(finalAmount), paymentHash, priv_a.privateKey, "#reckless", None, None, routingHintOverflow)
val invoice = PaymentRequest(Block.RegtestGenesisBlock.hash, Some(finalAmount), paymentHash, priv_a.privateKey, "#reckless", CltvExpiryDelta(18), None, None, routingHintOverflow)
assertThrows[IllegalArgumentException](
buildTrampolineToLegacyPacket(invoice, trampolineHops, FinalLegacyPayload(finalAmount, finalExpiry))
)

View File

@ -310,10 +310,8 @@ class PaymentRequestSpec extends AnyFunSuite {
test("correctly serialize/deserialize variable-length tagged fields") {
val number = 123456
val codec = PaymentRequest.Codecs.dataCodec(scodec.codecs.bits).as[PaymentRequest.Expiry]
val field = PaymentRequest.Expiry(number)
assert(field.toLong == number)
val serializedExpiry = codec.encode(field).require
@ -321,8 +319,8 @@ class PaymentRequestSpec extends AnyFunSuite {
assert(field1 == field)
// Now with a payment request
val pr = PaymentRequest(chainHash = Block.LivenetGenesisBlock.hash, amount = Some(123 msat), paymentHash = ByteVector32(ByteVector.fill(32)(1)), privateKey = priv, description = "Some invoice", expirySeconds = Some(123456), timestamp = 12345)
val pr = PaymentRequest(chainHash = Block.LivenetGenesisBlock.hash, amount = Some(123 msat), paymentHash = ByteVector32(ByteVector.fill(32)(1)), privateKey = priv, description = "Some invoice", minFinalCltvExpiryDelta = CltvExpiryDelta(18), expirySeconds = Some(123456), timestamp = 12345)
assert(pr.minFinalCltvExpiryDelta === Some(CltvExpiryDelta(18)))
val serialized = PaymentRequest.write(pr)
val pr1 = PaymentRequest.read(serialized)
assert(pr == pr1)
@ -341,8 +339,8 @@ class PaymentRequestSpec extends AnyFunSuite {
),
signature = ByteVector.empty).sign(priv)
val serialized = PaymentRequest write pr
val pr1 = PaymentRequest read serialized
val serialized = PaymentRequest.write(pr)
val pr1 = PaymentRequest.read(serialized)
val Some(_) = pr1.tags.collectFirst { case u: UnknownTag21 => u }
}
@ -408,7 +406,7 @@ class PaymentRequestSpec extends AnyFunSuite {
)
for ((features, res) <- featureBits) {
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", features = Some(features))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", CltvExpiryDelta(18), features = Some(features))
assert(Result(pr.features.allowMultiPart, pr.features.requirePaymentSecret, pr.features.supported) === res)
assert(PaymentRequest.read(PaymentRequest.write(pr)) === pr)
}
@ -432,7 +430,7 @@ class PaymentRequestSpec extends AnyFunSuite {
}
test("payment secret") {
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice")
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", CltvExpiryDelta(18))
assert(pr.paymentSecret.isDefined)
assert(pr.features === PaymentRequestFeatures(PaymentSecret.optional, VariableLengthOnion.optional))
assert(!pr.features.requirePaymentSecret)
@ -451,19 +449,19 @@ class PaymentRequestSpec extends AnyFunSuite {
// A multi-part invoice must use a payment secret.
assertThrows[IllegalArgumentException](
PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "MPP without secrets", features = Some(PaymentRequestFeatures(BasicMultiPartPayment.optional, VariableLengthOnion.optional)))
PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "MPP without secrets", CltvExpiryDelta(18), features = Some(PaymentRequestFeatures(BasicMultiPartPayment.optional, VariableLengthOnion.optional)))
)
}
test("trampoline") {
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice")
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", CltvExpiryDelta(18))
assert(!pr.features.allowTrampoline)
val pr1 = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, TrampolinePayment.optional)))
val pr1 = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", CltvExpiryDelta(18), features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, TrampolinePayment.optional)))
assert(!pr1.features.allowMultiPart)
assert(pr1.features.allowTrampoline)
val pr2 = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional, TrampolinePayment.optional)))
val pr2 = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", CltvExpiryDelta(18), features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional, TrampolinePayment.optional)))
assert(pr2.features.allowMultiPart)
assert(pr2.features.allowTrampoline)

View File

@ -33,7 +33,7 @@ import fr.acinq.eclair.router.Router.ChannelHop
import fr.acinq.eclair.transactions.{DirectedHtlc, IncomingHtlc, OutgoingHtlc}
import fr.acinq.eclair.wire.Onion.FinalLegacyPayload
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{CltvExpiry, LongToBtcAmount, NodeParams, TestConstants, TestKitBaseClass, randomBytes32}
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, LongToBtcAmount, NodeParams, TestConstants, TestKitBaseClass, randomBytes32}
import org.scalatest.Outcome
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import scodec.bits.ByteVector
@ -145,7 +145,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
val preimage = randomBytes32
val paymentHash = Crypto.sha256(preimage)
val invoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(500 msat), paymentHash, TestConstants.Bob.keyManager.nodeKey.privateKey, "Some invoice")
val invoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(500 msat), paymentHash, TestConstants.Bob.keyManager.nodeKey.privateKey, "Some invoice", CltvExpiryDelta(18))
nodeParams.db.payments.addIncomingPayment(invoice, preimage)
nodeParams.db.payments.receiveIncomingPayment(paymentHash, 5000 msat)

View File

@ -164,7 +164,7 @@ class RelayerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
relayer ! LocalChannelUpdate(null, channelId_bc, channelUpdate_bc.shortChannelId, c, None, channelUpdate_bc, makeCommitments(channelId_bc))
val totalAmount = finalAmount * 3 // we simulate a payment split between multiple trampoline routes.
val trampolineHops = NodeHop(a, b, channelUpdate_ab.cltvExpiryDelta, 0 msat) :: NodeHop(b, c, nodeParams.expiryDeltaBlocks, nodeFee(nodeParams.feeBase, nodeParams.feeProportionalMillionth, finalAmount)) :: Nil
val trampolineHops = NodeHop(a, b, channelUpdate_ab.cltvExpiryDelta, 0 msat) :: NodeHop(b, c, nodeParams.expiryDelta, nodeFee(nodeParams.feeBase, nodeParams.feeProportionalMillionth, finalAmount)) :: Nil
val (trampolineAmount, trampolineExpiry, trampolineOnion) = buildPacket(Sphinx.TrampolinePacket)(paymentHash, trampolineHops, Onion.createMultiPartPayload(finalAmount, totalAmount, finalExpiry, paymentSecret))
// A sends a multi-part payment to trampoline node B
@ -528,7 +528,7 @@ class RelayerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
// A sends a multi-payment to trampoline node B.
val preimage = randomBytes32
val paymentHash = Crypto.sha256(preimage)
val trampolineHops = NodeHop(a, b, channelUpdate_ab.cltvExpiryDelta, 0 msat) :: NodeHop(b, c, nodeParams.expiryDeltaBlocks, nodeFee(nodeParams.feeBase, nodeParams.feeProportionalMillionth, finalAmount)) :: Nil
val trampolineHops = NodeHop(a, b, channelUpdate_ab.cltvExpiryDelta, 0 msat) :: NodeHop(b, c, nodeParams.expiryDelta, nodeFee(nodeParams.feeBase, nodeParams.feeProportionalMillionth, finalAmount)) :: Nil
val (trampolineAmount, trampolineExpiry, trampolineOnion) = buildPacket(Sphinx.TrampolinePacket)(paymentHash, trampolineHops, Onion.createMultiPartPayload(finalAmount, finalAmount, finalExpiry, paymentSecret))
val secret_ab = randomBytes32
val (cmd1, _) = buildCommand(Upstream.Local(UUID.randomUUID()), paymentHash, ChannelHop(a, b, channelUpdate_ab) :: Nil, Onion.createTrampolinePayload(trampolineAmount - 10000000.msat, trampolineAmount, trampolineExpiry, secret_ab, trampolineOnion.packet))

View File

@ -55,7 +55,7 @@ class AnnouncementsSpec extends AnyFunSuite {
}
test("create valid signed channel update announcement") {
val ann = makeChannelUpdate(Block.RegtestGenesisBlock.hash, Alice.nodeParams.privateKey, randomKey.publicKey, ShortChannelId(45561L), Alice.nodeParams.expiryDeltaBlocks, Alice.nodeParams.htlcMinimum, Alice.nodeParams.feeBase, Alice.nodeParams.feeProportionalMillionth, 500000000 msat)
val ann = makeChannelUpdate(Block.RegtestGenesisBlock.hash, Alice.nodeParams.privateKey, randomKey.publicKey, ShortChannelId(45561L), Alice.nodeParams.expiryDelta, Alice.nodeParams.htlcMinimum, Alice.nodeParams.feeBase, Alice.nodeParams.feeProportionalMillionth, 500000000 msat)
assert(checkSig(ann, Alice.nodeParams.nodeId))
assert(checkSig(ann, randomKey.publicKey) === false)
}

View File

@ -86,7 +86,7 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
kit <- fKit
sendPayment = req.minFinalCltvExpiryDelta match {
case None => SendPaymentRequest(MilliSatoshi(amountMsat), req.paymentHash, req.nodeId, kit.nodeParams.maxPaymentAttempts, assistedRoutes = req.routingInfo)
case Some(minFinalCltvExpiry) => SendPaymentRequest(MilliSatoshi(amountMsat), req.paymentHash, req.nodeId, kit.nodeParams.maxPaymentAttempts, assistedRoutes = req.routingInfo, finalExpiryDelta = minFinalCltvExpiry)
case Some(minFinalCltvExpiry) => SendPaymentRequest(MilliSatoshi(amountMsat), req.paymentHash, req.nodeId, kit.nodeParams.maxPaymentAttempts, assistedRoutes = req.routingInfo, fallbackFinalExpiryDelta = minFinalCltvExpiry)
}
res <- (kit.paymentInitiator ? sendPayment).mapTo[UUID]
} yield res).recover {

View File

@ -429,7 +429,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
val payment = SendPaymentToRouteResponse(UUID.fromString("487da196-a4dc-4b1e-92b4-3e5e905e9f3f"), UUID.fromString("2ad8c6d7-99cb-4238-8f67-89024b8eed0d"), None)
val expected = """{"paymentId":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","parentId":"2ad8c6d7-99cb-4238-8f67-89024b8eed0d"}"""
val externalId = UUID.randomUUID().toString
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(1234 msat), ByteVector32.Zeroes, randomKey, "Some invoice")
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(1234 msat), ByteVector32.Zeroes, randomKey, "Some invoice", CltvExpiryDelta(24))
val expectedRoute = List(PublicKey(hex"0217eb8243c95f5a3b7d4c5682d10de354b7007eb59b6807ae407823963c7547a9"), PublicKey(hex"0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3"), PublicKey(hex"026ac9fcd64fb1aa1c491fc490634dc33da41d4a17b554e0adf1b32fee88ee9f28"))
val csvNodes = "0217eb8243c95f5a3b7d4c5682d10de354b7007eb59b6807ae407823963c7547a9, 0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3, 026ac9fcd64fb1aa1c491fc490634dc33da41d4a17b554e0adf1b32fee88ee9f28"
val jsonNodes = serialization.write(expectedRoute)