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:
parent
aca7499df2
commit
625e996ae0
@ -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%)
|
||||
|
@ -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"),
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
))))
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user