Include the `node_id` of channel peers in payment events (#3243)
What changed, and why it matters
This commit is a refactoring and observability improvement for the Eclair Lightning node. It adds the node identifier (node_id) of channel peers to various payment events and restructures how payment events are represented in code. It does not fix a security vulnerability, nor does it introduce obvious new attack surface. The database schema is intentionally not yet updated, so some stored data uses a placeholder node_id. The change touches many files but is mostly internal plumbing for metrics and audit logging.
No immediate security action required. Reviewers should verify that the new remoteNodeId values are correctly populated at all event emission sites and that the codec backward-compatibility path preserves data integrity when loading old channel states. When the future database schema migration lands, ensure the placeholder dummy node_id is replaced with persisted values and that migration/rollback is handled.
Security signals we found
Large refactor across payment event pipeline
Codec format change for persisted upstream origins (new typecodes 0x04/0x05, backward-compatible readers for 0x02/0x03)
Database reads return placeholder public key for node_id fields not yet stored
No input validation, authorization, or cryptographic changes observed
Evidence from the diff
The commit refactors PaymentEvents.scala and related call sites to carry remoteNodeId in PaymentSent.PaymentPart, PaymentReceived parts, PaymentRelayed incoming/outgoing parts, and Upstream.Cold.Channel. RES_ADD_SETTLED now includes the downstream remote node id. Channel codecs are updated to serialize the new originNodeId field with backward-compatible typecodes. Audit DB reads currently synthesize a dummy public key because the schema has not been updated. The canSendAdd signature drops an unused reputationScore parameter. No cryptographic, consensus, or authorization logic is changed.
Changed components
eclair-core payment events (PaymentEvents.scala)channel state machine (Channel.scala, Commitments.scala)payment relay (ChannelRelay, NodeRelay, Relayer, PostRestartHtlcCleaner)payment send lifecycle (PaymentLifecycle, MultiPartPaymentLifecycle, TrampolinePaymentLifecycle)payment receive handler (MultiPartHandler, MultiPartPaymentFSM)audit database adapters (PgAuditDb, SqliteAuditDb, DbEventHandler)channel persistence codecs (ChannelCodecs5)JSON serializersInspect captured patch +462 / −396
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala
index 0c1dc2f..548ae94 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala
@@ -181,14 +181,14 @@ object Upstream {
object Cold {
def apply(hot: Hot): Cold = hot match {
case Local(id) => Local(id)
- case Hot.Channel(add, _, _, _) => Cold.Channel(add.channelId, add.id, add.amountMsat)
- case Hot.Trampoline(received) => Cold.Trampoline(received.map(r => Cold.Channel(r.add.channelId, r.add.id, r.add.amountMsat)))
+ case Hot.Channel(add, _, receivedFrom, _) => Cold.Channel(add.channelId, receivedFrom, add.id, add.amountMsat)
+ case Hot.Trampoline(received) => Cold.Trampoline(received.map(r => Cold.Channel(r.add.channelId, r.receivedFrom, r.add.id, r.add.amountMsat)))
}
/** Our node is forwarding a single incoming HTLC. */
- case class Channel(originChannelId: ByteVector32, originHtlcId: Long, amountIn: MilliSatoshi) extends Cold
+ case class Channel(originChannelId: ByteVector32, originNodeId: PublicKey, originHtlcId: Long, amountIn: MilliSatoshi) extends Cold
object Channel {
- def apply(add: UpdateAddHtlc): Channel = Channel(add.channelId, add.id, add.amountMsat)
+ def apply(add: UpdateAddHtlc, remoteNodeId: PublicKey): Channel = Channel(add.channelId, remoteNodeId, add.id, add.amountMsat)
}
/** Our node is forwarding a payment based on a set of HTLCs from potentially multiple upstream channels. */
@@ -313,7 +313,7 @@ object HtlcResult {
case object ChannelFailureBeforeSigned extends Fail
case class DisconnectedBeforeSigned(channelUpdate: ChannelUpdate) extends Fail { require(!channelUpdate.channelFlags.isEnabled, "channel update must have disabled flag set") }
}
-final case class RES_ADD_SETTLED[+O <: Origin, +R <: HtlcResult](origin: O, htlc: UpdateAddHtlc, result: R) extends CommandSuccess[CMD_ADD_HTLC]
+final case class RES_ADD_SETTLED[+O <: Origin, +R <: HtlcResult](origin: O, remoteNodeId: PublicKey, htlc: UpdateAddHtlc, result: R) extends CommandSuccess[CMD_ADD_HTLC]
/** other specific responses */
final case class RES_BUMP_FUNDING_FEE(rbfIndex: Int, fundingTxId: TxId, fee: Satoshi) extends CommandSuccess[CMD_BUMP_FUNDING_FEE]
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
index 383c031..901f45e 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
@@ -12,7 +12,6 @@ import fr.acinq.eclair.channel.fsm.Channel.ChannelConf
import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalCommitmentKeys, RemoteCommitmentKeys}
import fr.acinq.eclair.crypto.{NonceGenerator, ShaChain}
import fr.acinq.eclair.payment.OutgoingPaymentPacket
-import fr.acinq.eclair.reputation.Reputation
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions._
@@ -460,7 +459,7 @@ case class Commitment(fundingTxIndex: Long,
localCommit.spec.htlcs.collect(DirectedHtlc.incoming).filter(nearlyExpired)
}
- def canSendAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feeConf: OnChainFeeConf, reputationScore: Reputation.Score): Either[ChannelException, Unit] = {
+ def canSendAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = {
// let's compute the current commitments *as seen by them* with the additional htlc
// we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation
val remoteCommit1 = nextRemoteCommit_opt.getOrElse(remoteCommit)
@@ -901,7 +900,7 @@ case class Commitments(channelParams: ChannelParams,
val changes1 = changes.addLocalProposal(add).copy(localNextHtlcId = changes.localNextHtlcId + 1)
val originChannels1 = originChannels + (add.id -> cmd.origin)
// we verify that this htlc is allowed in every active commitment
- val failures = active.map(_.canSendAdd(add.amountMsat, channelParams, changes1, feeConf, cmd.reputationScore))
+ val failures = active.map(_.canSendAdd(add.amountMsat, channelParams, changes1, feeConf))
// and that we don't exceed the authorized channel occupancy (jamming)
.appended(cmd.reputationScore.checkIncomingChannelOccupancy(cmd.origin.upstream.incomingChannelOccupancy, channelId))
.collect { case Left(f) => f }
@@ -1144,12 +1143,12 @@ case class Commitments(channelParams: ChannelParams,
case fail: UpdateFailHtlc =>
val origin = originChannels(fail.id)
val add = remoteSpec.findIncomingHtlcById(fail.id).map(_.add).get
- RES_ADD_SETTLED(origin, add, HtlcResult.RemoteFail(fail))
+ RES_ADD_SETTLED(origin, remoteNodeId, add, HtlcResult.RemoteFail(fail))
// same as above
case fail: UpdateFailMalformedHtlc =>
val origin = originChannels(fail.id)
val add = remoteSpec.findIncomingHtlcById(fail.id).map(_.add).get
- RES_ADD_SETTLED(origin, add, HtlcResult.RemoteFailMalformed(fail))
+ RES_ADD_SETTLED(origin, remoteNodeId, add, HtlcResult.RemoteFailMalformed(fail))
}
val (acceptedHtlcs, rejectedHtlcs) = {
// the received htlcs have already been added to commitments (they've been signed by our peer), and may already
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
index c6e138a..699cd5a 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
@@ -510,7 +510,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
// We forward preimages as soon as possible to the upstream channel because it allows us to pull funds.
msg match {
case fulfill: UpdateFulfillHtlc => d.commitments.receiveFulfill(fulfill) match {
- case Right((_, origin, htlc)) => relayer ! RES_ADD_SETTLED(origin, htlc, HtlcResult.RemoteFulfill(fulfill))
+ case Right((_, origin, htlc)) => relayer ! RES_ADD_SETTLED(origin, remoteNodeId, htlc, HtlcResult.RemoteFulfill(fulfill))
case _ => ()
}
case _ => ()
@@ -562,7 +562,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
d.commitments.receiveFulfill(fulfill) match {
case Right((commitments1, origin, htlc)) =>
// we forward preimages as soon as possible to the upstream channel because it allows us to pull funds
- relayer ! RES_ADD_SETTLED(origin, htlc, HtlcResult.RemoteFulfill(fulfill))
+ relayer ! RES_ADD_SETTLED(origin, remoteNodeId, htlc, HtlcResult.RemoteFulfill(fulfill))
context.system.eventStream.publish(OutgoingHtlcFulfilled(fulfill))
log.info("OutgoingHtlcFulfilled: channelId={}, id={}", fulfill.channelId.toHex, fulfill.id)
stay() using d.copy(commitments = commitments1)
@@ -1562,7 +1562,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
val channelUpdate1 = Helpers.channelUpdate(nodeParams, scidForChannelUpdate(d), d.commitments, d.channelUpdate.relayFees, enable = false)
// NB: the htlcs stay in the commitments.localChange, they will be cleaned up after reconnection
d.commitments.changes.localChanges.proposed.collect {
- case add: UpdateAddHtlc => relayer ! RES_ADD_SETTLED(d.commitments.originChannels(add.id), add, HtlcResult.DisconnectedBeforeSigned(channelUpdate1))
+ case add: UpdateAddHtlc => relayer ! RES_ADD_SETTLED(d.commitments.originChannels(add.id), remoteNodeId, add, HtlcResult.DisconnectedBeforeSigned(channelUpdate1))
}
goto(OFFLINE) using d1.copy(channelUpdate = channelUpdate1) storing()
} else {
@@ -1599,7 +1599,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
d.commitments.receiveFulfill(fulfill) match {
case Right((commitments1, origin, htlc)) =>
// we forward preimages as soon as possible to the upstream channel because it allows us to pull funds
- relayer ! RES_ADD_SETTLED(origin, htlc, HtlcResult.RemoteFulfill(fulfill))
+ relayer ! RES_ADD_SETTLED(origin, remoteNodeId, htlc, HtlcResult.RemoteFulfill(fulfill))
stay() using d.copy(commitments = commitments1)
case Left(cause) => handleLocalError(cause, d, Some(fulfill))
}
@@ -2153,7 +2153,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
d.commitments.originChannels.get(add.id) match {
case Some(origin) =>
log.info("failing htlc #{} paymentHash={} origin={}: overridden by revoked remote commit", add.id, add.paymentHash, origin)
- relayer ! RES_ADD_SETTLED(origin, add, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(d.channelId, add)))
+ relayer ! RES_ADD_SETTLED(origin, remoteNodeId, add, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(d.channelId, add)))
case None => ()
}
}
@@ -2176,7 +2176,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
d.commitments.originChannels.get(htlc.id) match {
case Some(origin) =>
log.info("fulfilling htlc #{} paymentHash={} origin={}", htlc.id, htlc.paymentHash, origin)
- relayer ! RES_ADD_SETTLED(origin, htlc, HtlcResult.OnChainFulfill(preimage))
+ relayer ! RES_ADD_SETTLED(origin, remoteNodeId, htlc, HtlcResult.OnChainFulfill(preimage))
case None =>
// If we don't have the origin, it means that we already have forwarded the fulfill so that's not a big deal.
// This can happen if they send a signature containing the fulfill, then fail the channel before we have time to sign it.
@@ -2254,7 +2254,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
d.commitments.originChannels.get(add.id) match {
case Some(origin) =>
log.info("failing htlc #{} paymentHash={} origin={}: htlc timed out", add.id, add.paymentHash, origin)
- relayer ! RES_ADD_SETTLED(origin, add, HtlcResult.OnChainFail(HtlcsTimedoutDownstream(d.channelId, Set(add))))
+ relayer ! RES_ADD_SETTLED(origin, remoteNodeId, add, HtlcResult.OnChainFail(HtlcsTimedoutDownstream(d.channelId, Set(add))))
case None =>
// same as for fulfilling the htlc (no big deal)
log.info("cannot fail timed out htlc #{} paymentHash={} (origin not found)", add.id, add.paymentHash)
@@ -2266,7 +2266,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
d.commitments.originChannels.get(add.id) match {
case Some(origin) =>
log.info("failing htlc #{} paymentHash={} origin={}: overridden by local commit", add.id, add.paymentHash, origin)
- relayer ! RES_ADD_SETTLED(origin, add, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(d.channelId, add)))
+ relayer ! RES_ADD_SETTLED(origin, remoteNodeId, add, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(d.channelId, add)))
case None =>
// same as for fulfilling the htlc (no big deal)
log.info("cannot fail overridden htlc #{} paymentHash={} (origin not found)", add.id, add.paymentHash)
@@ -3189,7 +3189,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
nextStateData match {
case d: DATA_CLOSING =>
d.commitments.changes.localChanges.proposed.collect {
- case add: UpdateAddHtlc => relayer ! RES_ADD_SETTLED(d.commitments.originChannels(add.id), add, HtlcResult.ChannelFailureBeforeSigned)
+ case add: UpdateAddHtlc => relayer ! RES_ADD_SETTLED(d.commitments.originChannels(add.id), remoteNodeId, add, HtlcResult.ChannelFailureBeforeSigned)
}
case _ => ()
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/DbEventHandler.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/DbEventHandler.scala
index bc50eda..ba249a8 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/DbEventHandler.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/DbEventHandler.scala
@@ -63,7 +63,7 @@ class DbEventHandler(nodeParams: NodeParams) extends Actor with DiagnosticActorL
PaymentMetrics.PaymentFees.withTag(PaymentTags.Direction, PaymentTags.Directions.Sent).record(e.feesPaid.truncateToSatoshi.toLong)
PaymentMetrics.PaymentParts.withTag(PaymentTags.Direction, PaymentTags.Directions.Sent).record(e.parts.length)
auditDb.add(e)
- e.parts.foreach(p => channelsDb.updateChannelMeta(p.toChannelId, ChannelEvent.EventType.PaymentSent))
+ e.parts.foreach(p => channelsDb.updateChannelMeta(p.channelId, ChannelEvent.EventType.PaymentSent))
case _: PaymentFailed =>
PaymentMetrics.PaymentFailed.withTag(PaymentTags.Direction, PaymentTags.Directions.Sent).increment()
@@ -72,7 +72,7 @@ class DbEventHandler(nodeParams: NodeParams) extends Actor with DiagnosticActorL
PaymentMetrics.PaymentAmount.withTag(PaymentTags.Direction, PaymentTags.Directions.Received).record(e.amount.truncateToSatoshi.toLong)
PaymentMetrics.PaymentParts.withTag(PaymentTags.Direction, PaymentTags.Directions.Received).record(e.parts.length)
auditDb.add(e)
- e.parts.foreach(p => channelsDb.updateChannelMeta(p.fromChannelId, ChannelEvent.EventType.PaymentReceived))
+ e.parts.foreach(p => channelsDb.updateChannelMeta(p.channelId, ChannelEvent.EventType.PaymentReceived))
case e: PaymentRelayed =>
PaymentMetrics.PaymentAmount
@@ -89,9 +89,9 @@ class DbEventHandler(nodeParams: NodeParams) extends Actor with DiagnosticActorL
PaymentMetrics.PaymentParts.withTag(PaymentTags.Direction, PaymentTags.Directions.Sent).record(outgoing.length)
incoming.foreach(p => channelsDb.updateChannelMeta(p.channelId, ChannelEvent.EventType.PaymentReceived))
outgoing.foreach(p => channelsDb.updateChannelMeta(p.channelId, ChannelEvent.EventType.PaymentSent))
- case ChannelPaymentRelayed(_, _, _, fromChannelId, toChannelId, _, _) =>
- channelsDb.updateChannelMeta(fromChannelId, ChannelEvent.EventType.PaymentReceived)
- channelsDb.updateChannelMeta(toChannelId, ChannelEvent.EventType.PaymentSent)
+ case ChannelPaymentRelayed(_, incoming, outgoing) =>
+ channelsDb.updateChannelMeta(incoming.channelId, ChannelEvent.EventType.PaymentReceived)
+ channelsDb.updateChannelMeta(outgoing.channelId, ChannelEvent.EventType.PaymentSent)
case OnTheFlyFundingPaymentRelayed(_, incoming, outgoing) =>
incoming.foreach(p => channelsDb.updateChannelMeta(p.channelId, ChannelEvent.EventType.PaymentReceived))
outgoing.foreach(p => channelsDb.updateChannelMeta(p.channelId, ChannelEvent.EventType.PaymentSent))
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgAuditDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgAuditDb.scala
index 3623a0c..4caa243 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgAuditDb.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgAuditDb.scala
@@ -16,7 +16,7 @@
package fr.acinq.eclair.db.pg
-import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
+import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, SatoshiLong, TxId}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.db.AuditDb.{NetworkFee, PublishedTransaction, Stats}
@@ -206,7 +206,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
inTransaction { pg =>
using(pg.prepareStatement("INSERT INTO audit.sent VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) { statement =>
e.parts.foreach(p => {
- statement.setLong(1, p.amount.toLong)
+ statement.setLong(1, p.amountWithFees.toLong)
statement.setLong(2, p.feesPaid.toLong)
statement.setLong(3, e.recipientAmount.toLong)
statement.setString(4, p.id.toString)
@@ -214,7 +214,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
statement.setString(6, e.paymentHash.toHex)
statement.setString(7, e.paymentPreimage.toHex)
statement.setString(8, e.recipientNodeId.value.toHex)
- statement.setString(9, p.toChannelId.toHex)
+ statement.setString(9, p.payment.channelId.toHex)
statement.setTimestamp(10, p.settledAt.toSqlTimestamp)
statement.addBatch()
})
@@ -229,7 +229,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
e.parts.foreach(p => {
statement.setLong(1, p.amount.toLong)
statement.setString(2, e.paymentHash.toHex)
- statement.setString(3, p.fromChannelId.toHex)
+ statement.setString(3, p.channelId.toHex)
statement.setTimestamp(4, p.receivedAt.toSqlTimestamp)
statement.addBatch()
})
@@ -241,10 +241,10 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
override def add(e: PaymentRelayed): Unit = withMetrics("audit/add-payment-relayed", DbBackends.Postgres) {
inTransaction { pg =>
val payments = e match {
- case ChannelPaymentRelayed(amountIn, amountOut, _, fromChannelId, toChannelId, startedAt, settledAt) =>
+ case e: ChannelPaymentRelayed =>
// non-trampoline relayed payments have one input and one output
- val in = Seq(RelayedPart(fromChannelId, amountIn, "IN", "channel", startedAt))
- val out = Seq(RelayedPart(toChannelId, amountOut, "OUT", "channel", settledAt))
+ val in = Seq(RelayedPart(e.paymentIn.channelId, e.paymentIn.amount, "IN", "channel", e.startedAt))
+ val out = Seq(RelayedPart(e.paymentOut.channelId, e.paymentOut.amount, "OUT", "channel", e.settledAt))
in ++ out
case TrampolinePaymentRelayed(_, incoming, outgoing, nextTrampolineNodeId, nextTrampolineAmount) =>
using(pg.prepareStatement("INSERT INTO audit.relayed_trampoline VALUES (?, ?, ?, ?)")) { statement =>
@@ -356,15 +356,18 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
val result = statement.executeQuery()
.foldLeft(Map.empty[UUID, PaymentSent]) { (sentByParentId, rs) =>
val parentId = UUID.fromString(rs.getString("parent_payment_id"))
- val part = PaymentSent.PartialPayment(
- UUID.fromString(rs.getString("payment_id")),
- MilliSatoshi(rs.getLong("amount_msat")),
- MilliSatoshi(rs.getLong("fees_msat")),
- rs.getByteVector32FromHex("to_channel_id"),
- None, // we don't store the route in the audit DB
+ val part = PaymentSent.PaymentPart(
+ id = UUID.fromString(rs.getString("payment_id")),
+ payment = PaymentEvent.OutgoingPayment(
+ channelId = rs.getByteVector32FromHex("to_channel_id"),
+ remoteNodeId = PrivateKey(ByteVector32.One).publicKey, // we're not storing the remote node_id yet
+ amount = MilliSatoshi(rs.getLong("amount_msat")),
+ settledAt = TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp"))
+ ),
+ feesPaid = MilliSatoshi(rs.getLong("fees_msat")),
+ route = None, // we don't store the route in the audit DB
// TODO: store startedAt when updating the DB schema instead of duplicating settledAt.
- startedAt = TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp")),
- settledAt = TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp")))
+ startedAt = TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp")))
val sent = sentByParentId.get(parentId) match {
case Some(s) => s.copy(parts = s.parts :+ part)
case None => PaymentSent(
@@ -393,10 +396,11 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
val result = statement.executeQuery()
.foldLeft(Map.empty[ByteVector32, PaymentReceived]) { (receivedByHash, rs) =>
val paymentHash = rs.getByteVector32FromHex("payment_hash")
- val part = PaymentReceived.PartialPayment(
- MilliSatoshi(rs.getLong("amount_msat")),
- rs.getByteVector32FromHex("from_channel_id"),
- TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp")))
+ val part = PaymentEvent.IncomingPayment(
+ channelId = rs.getByteVector32FromHex("from_channel_id"),
+ remoteNodeId = PrivateKey(ByteVector32.One).publicKey, // we're not storing the remote node_id yet
+ amount = MilliSatoshi(rs.getLong("amount_msat")),
+ receivedAt = TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp")))
val received = receivedByHash.get(paymentHash) match {
case Some(r) => r.copy(parts = r.parts :+ part)
case None => PaymentReceived(paymentHash, Seq(part))
@@ -442,11 +446,11 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
case (paymentHash, parts) =>
// We may have been routing multiple payments for the same payment_hash (MPP) in both cases (trampoline and channel).
// NB: we may link the wrong in-out parts, but the overall sum will be correct: we sort by amounts to minimize the risk of mismatch.
- val incoming = parts.filter(_.direction == "IN").map(p => PaymentRelayed.IncomingPart(p.amount, p.channelId, p.timestamp)).sortBy(_.amount)
- val outgoing = parts.filter(_.direction == "OUT").map(p => PaymentRelayed.OutgoingPart(p.amount, p.channelId, p.timestamp)).sortBy(_.amount)
+ val incoming = parts.filter(_.direction == "IN").map(p => PaymentEvent.IncomingPayment(p.channelId, PrivateKey(ByteVector32.One).publicKey, p.amount, p.timestamp)).sortBy(_.amount)
+ val outgoing = parts.filter(_.direction == "OUT").map(p => PaymentEvent.OutgoingPayment(p.channelId, PrivateKey(ByteVector32.One).publicKey, p.amount, p.timestamp)).sortBy(_.amount)
parts.headOption match {
case Some(RelayedPart(_, _, _, "channel", _)) => incoming.zip(outgoing).map {
- case (in, out) => ChannelPaymentRelayed(in.amount, out.amount, paymentHash, in.channelId, out.channelId, in.receivedAt, out.settledAt)
+ case (in, out) => ChannelPaymentRelayed(paymentHash, in, out)
}
case Some(RelayedPart(_, _, _, "trampoline", _)) => trampolineByHash.get(paymentHash) match {
case Some((nextTrampolineAmount, nextTrampolineNodeId)) => TrampolinePaymentRelayed(paymentHash, incoming, outgoing, nextTrampolineNodeId, nextTrampolineAmount) :: Nil
@@ -483,7 +487,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
override def stats(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[Stats] = {
case class Relayed(amount: MilliSatoshi, fee: MilliSatoshi, direction: String)
- def aggregateRelayStats(previous: Map[ByteVector32, Seq[Relayed]], incoming: PaymentRelayed.Incoming, outgoing: PaymentRelayed.Outgoing): Map[ByteVector32, Seq[Relayed]] = {
+ def aggregateRelayStats(previous: Map[ByteVector32, Seq[Relayed]], incoming: Seq[PaymentEvent.IncomingPayment], outgoing: Seq[PaymentEvent.OutgoingPayment]): Map[ByteVector32, Seq[Relayed]] = {
// We ensure trampoline payments are counted only once per channel and per direction (if multiple HTLCs were sent
// from/to the same channel, we group them).
val amountIn = incoming.map(_.amount).sum
@@ -500,8 +504,8 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
// NB: we must avoid counting the fee twice: we associate it to the outgoing channels rather than the incoming ones.
val current = e match {
case c: ChannelPaymentRelayed => Map(
- c.fromChannelId -> (Relayed(c.amountIn, 0 msat, "IN") +: previous.getOrElse(c.fromChannelId, Nil)),
- c.toChannelId -> (Relayed(c.amountOut, c.amountIn - c.amountOut, "OUT") +: previous.getOrElse(c.toChannelId, Nil)),
+ c.paymentIn.channelId -> (Relayed(c.amountIn, 0 msat, "IN") +: previous.getOrElse(c.paymentIn.channelId, Nil)),
+ c.paymentOut.channelId -> (Relayed(c.amountOut, c.amountIn - c.amountOut, "OUT") +: previous.getOrElse(c.paymentOut.channelId, Nil)),
)
case t: TrampolinePaymentRelayed =>
aggregateRelayStats(previous, t.incoming, t.outgoing)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala
index b69d2d9..464d9da 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala
@@ -198,7 +198,7 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
override def add(e: PaymentSent): Unit = withMetrics("audit/add-payment-sent", DbBackends.Sqlite) {
using(sqlite.prepareStatement("INSERT INTO sent VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) { statement =>
e.parts.foreach(p => {
- statement.setLong(1, p.amount.toLong)
+ statement.setLong(1, p.amountWithFees.toLong)
statement.setLong(2, p.feesPaid.toLong)
statement.setLong(3, e.recipientAmount.toLong)
statement.setString(4, p.id.toString)
@@ -206,7 +206,7 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
statement.setBytes(6, e.paymentHash.toArray)
statement.setBytes(7, e.paymentPreimage.toArray)
statement.setBytes(8, e.recipientNodeId.value.toArray)
- statement.setBytes(9, p.toChannelId.toArray)
+ statement.setBytes(9, p.channelId.toArray)
statement.setLong(10, p.settledAt.toLong)
statement.addBatch()
})
@@ -219,7 +219,7 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
e.parts.foreach(p => {
statement.setLong(1, p.amount.toLong)
statement.setBytes(2, e.paymentHash.toArray)
- statement.setBytes(3, p.fromChannelId.toArray)
+ statement.setBytes(3, p.channelId.toArray)
statement.setLong(4, p.receivedAt.toLong)
statement.addBatch()
})
@@ -229,10 +229,10 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
override def add(e: PaymentRelayed): Unit = withMetrics("audit/add-payment-relayed", DbBackends.Sqlite) {
val payments = e match {
- case ChannelPaymentRelayed(amountIn, amountOut, _, fromChannelId, toChannelId, startedAt, settledAt) =>
+ case e: ChannelPaymentRelayed =>
// non-trampoline relayed payments have one input and one output
- val in = Seq(RelayedPart(fromChannelId, amountIn, "IN", "channel", startedAt))
- val out = Seq(RelayedPart(toChannelId, amountOut, "OUT", "channel", settledAt))
+ val in = Seq(RelayedPart(e.paymentIn.channelId, e.paymentIn.amount, "IN", "channel", e.startedAt))
+ val out = Seq(RelayedPart(e.paymentOut.channelId, e.paymentOut.amount, "OUT", "channel", e.settledAt))
in ++ out
case TrampolinePaymentRelayed(_, incoming, outgoing, nextTrampolineNodeId, nextTrampolineAmount) =>
using(sqlite.prepareStatement("INSERT INTO relayed_trampoline VALUES (?, ?, ?, ?)")) { statement =>
@@ -330,15 +330,18 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
val result = statement.executeQuery()
.foldLeft(Map.empty[UUID, PaymentSent]) { (sentByParentId, rs) =>
val parentId = UUID.fromString(rs.getString("parent_payment_id"))
- val part = PaymentSent.PartialPayment(
- UUID.fromString(rs.getString("payment_id")),
- MilliSatoshi(rs.getLong("amount_msat")),
- MilliSatoshi(rs.getLong("fees_msat")),
- rs.getByteVector32("to_channel_id"),
- None, // we don't store the route in the audit DB
+ val part = PaymentSent.PaymentPart(
+ id = UUID.fromString(rs.getString("payment_id")),
+ payment = PaymentEvent.OutgoingPayment(
+ channelId = rs.getByteVector32("to_channel_id"),
+ remoteNodeId = PrivateKey(ByteVector32.One).publicKey, // we're not storing the remote node_id yet
+ amount = MilliSatoshi(rs.getLong("amount_msat")),
+ settledAt = TimestampMilli(rs.getLong("timestamp"))
+ ),
+ feesPaid = MilliSatoshi(rs.getLong("fees_msat")),
+ route = None, // we don't store the route in the audit DB
// TODO: store startedAt when updating the DB schema instead of duplicating settledAt.
- startedAt = TimestampMilli(rs.getLong("timestamp")),
- settledAt = TimestampMilli(rs.getLong("timestamp")))
+ startedAt = TimestampMilli(rs.getLong("timestamp")))
val sent = sentByParentId.get(parentId) match {
case Some(s) => s.copy(parts = s.parts :+ part)
case None => PaymentSent(
@@ -365,10 +368,11 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
val result = statement.executeQuery()
.foldLeft(Map.empty[ByteVector32, PaymentReceived]) { (receivedByHash, rs) =>
val paymentHash = rs.getByteVector32("payment_hash")
- val part = PaymentReceived.PartialPayment(
- MilliSatoshi(rs.getLong("amount_msat")),
- rs.getByteVector32("from_channel_id"),
- TimestampMilli(rs.getLong("timestamp")))
+ val part = PaymentEvent.IncomingPayment(
+ channelId = rs.getByteVector32("from_channel_id"),
+ remoteNodeId = PrivateKey(ByteVector32.One).publicKey, // we're not storing the remote node_id yet
+ amount = MilliSatoshi(rs.getLong("amount_msat")),
+ receivedAt = TimestampMilli(rs.getLong("timestamp")))
val received = receivedByHash.get(paymentHash) match {
case Some(r) => r.copy(parts = r.parts :+ part)
case None => PaymentReceived(paymentHash, Seq(part))
@@ -413,11 +417,11 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
case (paymentHash, parts) =>
// We may have been routing multiple payments for the same payment_hash (MPP) in both cases (trampoline and channel).
// NB: we may link the wrong in-out parts, but the overall sum will be correct: we sort by amounts to minimize the risk of mismatch.
- val incoming = parts.filter(_.direction == "IN").map(p => PaymentRelayed.IncomingPart(p.amount, p.channelId, p.timestamp)).sortBy(_.amount)
- val outgoing = parts.filter(_.direction == "OUT").map(p => PaymentRelayed.OutgoingPart(p.amount, p.channelId, p.timestamp)).sortBy(_.amount)
+ val incoming = parts.filter(_.direction == "IN").map(p => PaymentEvent.IncomingPayment(p.channelId, PrivateKey(ByteVector32.One).publicKey, p.amount, p.timestamp)).sortBy(_.amount)
+ val outgoing = parts.filter(_.direction == "OUT").map(p => PaymentEvent.OutgoingPayment(p.channelId, PrivateKey(ByteVector32.One).publicKey, p.amount, p.timestamp)).sortBy(_.amount)
parts.headOption match {
case Some(RelayedPart(_, _, _, "channel", _)) => incoming.zip(outgoing).map {
- case (in, out) => ChannelPaymentRelayed(in.amount, out.amount, paymentHash, in.channelId, out.channelId, in.receivedAt, out.settledAt)
+ case (in, out) => ChannelPaymentRelayed(paymentHash, in, out)
}
case Some(RelayedPart(_, _, _, "trampoline", _)) => trampolineByHash.get(paymentHash) match {
case Some((nextTrampolineAmount, nextTrampolineNodeId)) => TrampolinePaymentRelayed(paymentHash, incoming, outgoing, nextTrampolineNodeId, nextTrampolineAmount) :: Nil
@@ -453,7 +457,7 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
override def stats(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[Stats] = {
case class Relayed(amount: MilliSatoshi, fee: MilliSatoshi, direction: String)
- def aggregateRelayStats(previous: Map[ByteVector32, Seq[Relayed]], incoming: PaymentRelayed.Incoming, outgoing: PaymentRelayed.Outgoing): Map[ByteVector32, Seq[Relayed]] = {
+ def aggregateRelayStats(previous: Map[ByteVector32, Seq[Relayed]], incoming: Seq[PaymentEvent.IncomingPayment], outgoing: Seq[PaymentEvent.OutgoingPayment]): Map[ByteVector32, Seq[Relayed]] = {
// We ensure trampoline payments are counted only once per channel and per direction (if multiple HTLCs were sent
// from/to the same channel, we group them).
val amountIn = incoming.map(_.amount).sum
@@ -470,8 +474,8 @@ class SqliteAuditDb(val sqlite: Connection) extends AuditDb with Logging {
// NB: we must avoid counting the fee twice: we associate it to the outgoing channels rather than the incoming ones.
val current = e match {
case c: ChannelPaymentRelayed => Map(
- c.fromChannelId -> (Relayed(c.amountIn, 0 msat, "IN") +: previous.getOrElse(c.fromChannelId, Nil)),
- c.toChannelId -> (Relayed(c.amountOut, c.amountIn - c.amountOut, "OUT") +: previous.getOrElse(c.toChannelId, Nil)),
+ c.paymentIn.channelId -> (Relayed(c.amountIn, 0 msat, "IN") +: previous.getOrElse(c.paymentIn.channelId, Nil)),
+ c.paymentOut.channelId -> (Relayed(c.amountOut, c.amountIn - c.amountOut, "OUT") +: previous.getOrElse(c.paymentOut.channelId, Nil)),
)
case t: TrampolinePaymentRelayed =>
aggregateRelayStats(previous, t.incoming, t.outgoing)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
index c782a1f..7529e07 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
@@ -41,7 +41,7 @@ import fr.acinq.eclair.io.OpenChannelInterceptor.{OpenChannelInitiator, OpenChan
import fr.acinq.eclair.io.PeerConnection.KillReason
import fr.acinq.eclair.message.OnionMessages
import fr.acinq.eclair.payment.relay.OnTheFlyFunding
-import fr.acinq.eclair.payment.{OnTheFlyFundingPaymentRelayed, PaymentRelayed}
+import fr.acinq.eclair.payment.{OnTheFlyFundingPaymentRelayed, PaymentEvent}
import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes
import fr.acinq.eclair.router.Router
import fr.acinq.eclair.wire.protocol
@@ -814,12 +814,12 @@ class Peer(val nodeParams: NodeParams,
case OnTheFlyFunding.Proposal(htlc, upstream, _) => upstream match {
case _: Upstream.Local => ()
case u: Upstream.Hot.Channel =>
- val incoming = PaymentRelayed.IncomingPart(u.add.amountMsat, u.add.channelId, u.receivedAt)
- val outgoing = PaymentRelayed.OutgoingPart(htlc.amount, success.channelId, TimestampMilli.now())
+ val incoming = PaymentEvent.IncomingPayment(u.add.channelId, u.receivedFrom, u.add.amountMsat, u.receivedAt)
+ val outgoing = PaymentEvent.OutgoingPayment(success.channelId, remoteNodeId, htlc.amount, TimestampMilli.now())
context.system.eventStream.publish(OnTheFlyFundingPaymentRelayed(htlc.paymentHash, Seq(incoming), Seq(outgoing)))
case u: Upstream.Hot.Trampoline =>
- val incoming = u.received.map(r => PaymentRelayed.IncomingPart(r.add.amountMsat, r.add.channelId, r.receivedAt))
- val outgoing = PaymentRelayed.OutgoingPart(htlc.amount, success.channelId, TimestampMilli.now())
+ val incoming = u.received.map(r => PaymentEvent.IncomingPayment(r.add.channelId, r.receivedFrom, r.add.amountMsat, r.receivedAt))
+ val outgoing = PaymentEvent.OutgoingPayment(success.channelId, remoteNodeId, htlc.amount, TimestampMilli.now())
context.system.eventStream.publish(OnTheFlyFundingPaymentRelayed(htlc.paymentHash, incoming, Seq(outgoing)))
}
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
index d81e9b4..bea4040 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
@@ -383,8 +383,12 @@ object PaymentFailedSummarySerializer extends ConvertClassSerializer[PaymentFail
))
// @formatter:on
-private case class PaymentSentJson(id: UUID, paymentHash: ByteVector32, paymentPreimage: ByteVector32, recipientAmount: MilliSatoshi, recipientNodeId: PublicKey, parts: Seq[PaymentSent.PartialPayment], fees: MilliSatoshi, startedAt: TimestampMilli, settledAt: TimestampMilli)
-object PaymentSentSerializer extends ConvertClassSerializer[PaymentSent](p => PaymentSentJson(p.id, p.paymentHash, p.paymentPreimage, p.recipientAmount, p.recipientNodeId, p.parts, p.feesPaid, p.startedAt, p.settledAt))
+private case class PaymentPartJson(id: UUID, channelId: ByteVector32, nextNodeId: PublicKey, amountWithFees: MilliSatoshi, fees: MilliSatoshi, route: Option[Seq[Hop]], startedAt: TimestampMilli, settledAt: TimestampMilli)
+private case class PaymentSentJson(id: UUID, paymentHash: ByteVector32, paymentPreimage: ByteVector32, recipientAmount: MilliSatoshi, recipientNodeId: PublicKey, parts: Seq[PaymentPartJson], fees: MilliSatoshi, startedAt: TimestampMilli, settledAt: TimestampMilli)
+object PaymentSentSerializer extends ConvertClassSerializer[PaymentSent](p => {
+ val parts = p.parts.map(pp => PaymentPartJson(pp.id, pp.channelId, pp.remoteNodeId, pp.amountWithFees, pp.feesPaid, pp.route, pp.startedAt, pp.settledAt))
+ PaymentSentJson(p.id, p.paymentHash, p.paymentPreimage, p.recipientAmount, p.recipientNodeId, parts, p.feesPaid, p.startedAt, p.settledAt)
+})
object ThrowableSerializer extends MinimalSerializer({
case t: Throwable if t.getMessage != null => JString(t.getMessage)
@@ -591,6 +595,7 @@ object OriginSerializer extends MinimalSerializer({
case u: Upstream.Local => JObject(JField("paymentId", JString(u.id.toString)))
case u: Upstream.Hot.Channel => JObject(
JField("channelId", JString(u.add.channelId.toHex)),
+ JField("remoteNodeId", JString(u.receivedFrom.toHex)),
JField("htlcId", JLong(u.add.id)),
JField("amount", JLong(u.add.amountMsat.toLong)),
JField("expiry", JLong(u.add.cltvExpiry.toLong)),
@@ -599,6 +604,7 @@ object OriginSerializer extends MinimalSerializer({
case u: Upstream.Hot.Trampoline => JArray(u.received.map { htlc =>
JObject(
JField("channelId", JString(htlc.add.channelId.toHex)),
+ JField("remoteNodeId", JString(htlc.receivedFrom.toHex)),
JField("htlcId", JLong(htlc.add.id)),
JField("amount", JLong(htlc.add.amountMsat.toLong)),
JField("expiry", JLong(htlc.add.cltvExpiry.toLong)),
@@ -607,12 +613,14 @@ object OriginSerializer extends MinimalSerializer({
})
case o: Upstream.Cold.Channel => JObject(
JField("channelId", JString(o.originChannelId.toHex)),
+ JField("remoteNodeId", JString(o.originNodeId.toHex)),
JField("htlcId", JLong(o.originHtlcId)),
JField("amount", JLong(o.amountIn.toLong)),
)
case o: Upstream.Cold.Trampoline => JArray(o.originHtlcs.map { htlc =>
JObject(
JField("channelId", JString(htlc.originChannelId.toHex)),
+ JField("remoteNodeId", JString(htlc.originNodeId.toHex)),
JField("htlcId", JLong(htlc.originHtlcId)),
JField("amount", JLong(htlc.amountIn.toLong)),
)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala
index e1a9477..2315cf3 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala
@@ -47,18 +47,39 @@ sealed trait PaymentEvent {
// @formatter:on
}
+object PaymentEvent {
+ /**
+ * An incoming payment that has been received through one of our channels.
+ *
+ * @param channelId the incoming channelId.
+ * @param remoteNodeId the nodeId of our channel peer.
+ * @param amount amount received.
+ * @param receivedAt absolute time in milliseconds since UNIX epoch when the payment was received.
+ */
+ case class IncomingPayment(channelId: ByteVector32, remoteNodeId: PublicKey, amount: MilliSatoshi, receivedAt: TimestampMilli)
+
+ /**
+ * An outgoing payment that was sent through one of our channels.
+ *
+ * @param channelId the outgoing channelId.
+ * @param remoteNodeId the nodeId of our channel peer.
+ * @param amount amount sent.
+ * @param settledAt absolute time in milliseconds since UNIX epoch when the payment was settled (fulfilled or failed).
+ */
+ case class OutgoingPayment(channelId: ByteVector32, remoteNodeId: PublicKey, amount: MilliSatoshi, settledAt: TimestampMilli)
+}
+
/**
* A payment was successfully sent and fulfilled.
*
- * @param id id of the whole payment attempt (if using multi-part, there will be multiple parts,
- * each with a different id).
+ * @param id id of the whole payment attempt (if using multi-part, there will be multiple parts, each with a different id).
* @param paymentPreimage payment preimage (proof of payment).
* @param recipientAmount amount that has been received by the final recipient.
* @param recipientNodeId id of the final recipient.
* @param parts child payments (actual outgoing HTLCs).
* @param remainingAttribution_opt for relayed trampoline payments, the attribution data that needs to be sent upstream
*/
-case class PaymentSent(id: UUID, paymentPreimage: ByteVector32, recipientAmount: MilliSatoshi, recipientNodeId: PublicKey, parts: Seq[PaymentSent.PartialPayment], remainingAttribution_opt: Option[ByteVector], startedAt: TimestampMilli) extends PaymentEvent {
+case class PaymentSent(id: UUID, paymentPreimage: ByteVector32, recipientAmount: MilliSatoshi, recipientNodeId: PublicKey, parts: Seq[PaymentSent.PaymentPart], remainingAttribution_opt: Option[ByteVector], startedAt: TimestampMilli) extends PaymentEvent {
require(parts.nonEmpty, "must have at least one payment part")
val paymentHash: ByteVector32 = Crypto.sha256(paymentPreimage)
val amountWithFees: MilliSatoshi = parts.map(_.amountWithFees).sum
@@ -67,74 +88,63 @@ case class PaymentSent(id: UUID, paymentPreimage: ByteVector32, recipientAmount:
}
object PaymentSent {
-
/**
* A successfully sent partial payment (single outgoing HTLC).
*
- * @param id id of the outgoing payment.
- * @param amount amount received by the target node.
- * @param feesPaid fees paid to route to the target node.
- * @param toChannelId id of the channel used.
- * @param route payment route used.
- * @param startedAt absolute time in milliseconds since UNIX epoch when the payment was started.
- * @param settledAt absolute time in milliseconds since UNIX epoch when the payment was fulfilled.
+ * @param id id of the outgoing payment.
+ * @param payment payment sent to the target node through one of our channels (including fees).
+ * @param feesPaid fees paid to route to the target node.
+ * @param route payment route used.
+ * @param startedAt absolute time in milliseconds since UNIX epoch when the payment was started.
*/
- case class PartialPayment(id: UUID, amount: MilliSatoshi, feesPaid: MilliSatoshi, toChannelId: ByteVector32, route: Option[Seq[Hop]], startedAt: TimestampMilli, settledAt: TimestampMilli) {
+ case class PaymentPart(id: UUID, payment: PaymentEvent.OutgoingPayment, feesPaid: MilliSatoshi, route: Option[Seq[Hop]], startedAt: TimestampMilli) {
require(route.isEmpty || route.get.nonEmpty, "route must be None or contain at least one hop")
- val amountWithFees: MilliSatoshi = amount + feesPaid
- val duration: FiniteDuration = settledAt - startedAt
+ val channelId: ByteVector32 = payment.channelId
+ val remoteNodeId: PublicKey = payment.remoteNodeId
+ /** Amount received by the final recipient. */
+ val amount: MilliSatoshi = payment.amount - feesPaid
+ /** Amount we paid, which includes routing fees for the route. */
+ val amountWithFees: MilliSatoshi = payment.amount
+ val settledAt: TimestampMilli = payment.settledAt
+ val duration: FiniteDuration = payment.settledAt - startedAt
}
-
}
+/** A payment that we tried to send was failed and aborted. */
case class PaymentFailed(id: UUID, paymentHash: ByteVector32, failures: Seq[PaymentFailure], startedAt: TimestampMilli, settledAt: TimestampMilli = TimestampMilli.now()) extends PaymentEvent
+/** A payment was relayed and fulfilled. */
sealed trait PaymentRelayed extends PaymentEvent {
- val amountIn: MilliSatoshi
- val amountOut: MilliSatoshi
- val startedAt: TimestampMilli
- val settledAt: TimestampMilli
-}
-
-case class ChannelPaymentRelayed(amountIn: MilliSatoshi, amountOut: MilliSatoshi, paymentHash: ByteVector32, fromChannelId: ByteVector32, toChannelId: ByteVector32, startedAt: TimestampMilli, settledAt: TimestampMilli) extends PaymentRelayed
-
-case class TrampolinePaymentRelayed(paymentHash: ByteVector32, incoming: PaymentRelayed.Incoming, outgoing: PaymentRelayed.Outgoing, nextTrampolineNodeId: PublicKey, nextTrampolineAmount: MilliSatoshi) extends PaymentRelayed {
- override val amountIn: MilliSatoshi = incoming.map(_.amount).sum
- override val amountOut: MilliSatoshi = outgoing.map(_.amount).sum
- override val startedAt: TimestampMilli = incoming.map(_.receivedAt).minOption.getOrElse(TimestampMilli.now())
- override val settledAt: TimestampMilli = outgoing.map(_.settledAt).maxOption.getOrElse(TimestampMilli.now())
+ // @formatter:off
+ def incoming: Seq[PaymentEvent.IncomingPayment]
+ def outgoing: Seq[PaymentEvent.OutgoingPayment]
+ def amountIn: MilliSatoshi = incoming.map(_.amount).sum
+ def amountOut: MilliSatoshi = outgoing.map(_.amount).sum
+ override def startedAt: TimestampMilli = incoming.map(_.receivedAt).minOption.getOrElse(TimestampMilli.now())
+ override def settledAt: TimestampMilli = outgoing.map(_.settledAt).maxOption.getOrElse(TimestampMilli.now())
+ // @formatter:on
}
-case class OnTheFlyFundingPaymentRelayed(paymentHash: ByteVector32, incoming: PaymentRelayed.Incoming, outgoing: PaymentRelayed.Outgoing) extends PaymentRelayed {
- override val amountIn: MilliSatoshi = incoming.map(_.amount).sum
- override val amountOut: MilliSatoshi = outgoing.map(_.amount).sum
- override val startedAt: TimestampMilli = incoming.map(_.receivedAt).minOption.getOrElse(TimestampMilli.now())
- override val settledAt: TimestampMilli = outgoing.map(_.settledAt).maxOption.getOrElse(TimestampMilli.now())
+/** A payment was successfully relayed from a single incoming channel to a single outgoing channel. */
+case class ChannelPaymentRelayed(paymentHash: ByteVector32, paymentIn: PaymentEvent.IncomingPayment, paymentOut: PaymentEvent.OutgoingPayment) extends PaymentRelayed {
+ override val incoming: Seq[PaymentEvent.IncomingPayment] = Seq(paymentIn)
+ override val outgoing: Seq[PaymentEvent.OutgoingPayment] = Seq(paymentOut)
}
-object PaymentRelayed {
-
- case class IncomingPart(amount: MilliSatoshi, channelId: ByteVector32, receivedAt: TimestampMilli)
- case class OutgoingPart(amount: MilliSatoshi, channelId: ByteVector32, settledAt: TimestampMilli)
+/** A trampoline payment was successfully relayed, using potentially multiple incoming and outgoing channels. */
+case class TrampolinePaymentRelayed(paymentHash: ByteVector32, incoming: Seq[PaymentEvent.IncomingPayment], outgoing: Seq[PaymentEvent.OutgoingPayment], nextTrampolineNodeId: PublicKey, nextTrampolineAmount: MilliSatoshi) extends PaymentRelayed
- type Incoming = Seq[IncomingPart]
- type Outgoing = Seq[OutgoingPart]
+/** A payment (potentially using MPP and/or trampoline) was successfully relayed after funding an outgoing channel (liquidity purchase). */
+case class OnTheFlyFundingPaymentRelayed(paymentHash: ByteVector32, incoming: Seq[PaymentEvent.IncomingPayment], outgoing: Seq[PaymentEvent.OutgoingPayment]) extends PaymentRelayed
-}
-
-case class PaymentReceived(paymentHash: ByteVector32, parts: Seq[PaymentReceived.PartialPayment]) extends PaymentEvent {
+/** A payment has been received through some of our channels. */
+case class PaymentReceived(paymentHash: ByteVector32, parts: Seq[PaymentEvent.IncomingPayment]) extends PaymentEvent {
require(parts.nonEmpty, "must have at least one payment part")
val amount: MilliSatoshi = parts.map(_.amount).sum
val startedAt: TimestampMilli = parts.map(_.receivedAt).min // we use min here (when we receive the first payment part)
val settledAt: TimestampMilli = parts.map(_.receivedAt).max // we use max here because we fulfill the payment only once we received all the parts
}
-object PaymentReceived {
-
- case class PartialPayment(amount: MilliSatoshi, fromChannelId: ByteVector32, receivedAt: TimestampMilli = TimestampMilli.now())
-
-}
-
case class PaymentMetadataReceived(paymentHash: ByteVector32, paymentMetadata: ByteVector)
case class PaymentSettlingOnChain(id: UUID, channelId: ByteVector32, amount: MilliSatoshi, paymentHash: ByteVector32, timestamp: TimestampMilli = TimestampMilli.now())
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala
index b837838..6c9f271 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala
@@ -22,7 +22,7 @@ import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.adapter.ClassicActorContextOps
import akka.actor.{ActorContext, ActorRef, PoisonPill, typed}
import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter}
-import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
+import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto}
import fr.acinq.eclair.EncodedNodeId.ShortChannelIdDir
import fr.acinq.eclair.Logs.LogCategory
@@ -53,13 +53,13 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
// NB: this is safe because this handler will be called from within an actor
private var pendingPayments: Map[ByteVector32, (IncomingPayment, ActorRef)] = Map.empty
- private def addHtlcPart(ctx: ActorContext, add: UpdateAddHtlc, payload: FinalPayload, payment: IncomingPayment, receivedAt: TimestampMilli): Unit = {
+ private def addHtlcPart(ctx: ActorContext, add: UpdateAddHtlc, payload: FinalPayload, remoteNodeId: PublicKey, payment: IncomingPayment, receivedAt: TimestampMilli): Unit = {
pendingPayments.get(add.paymentHash) match {
case Some((_, handler)) =>
- handler ! MultiPartPaymentFSM.HtlcPart(payload.totalAmount, add, receivedAt)
+ handler ! MultiPartPaymentFSM.HtlcPart(payload.totalAmount, add, remoteNodeId, receivedAt)
case None =>
val handler = ctx.actorOf(MultiPartPaymentFSM.props(nodeParams, add.paymentHash, payload.totalAmount, ctx.self))
- handler ! MultiPartPaymentFSM.HtlcPart(payload.totalAmount, add, receivedAt)
+ handler ! MultiPartPaymentFSM.HtlcPart(payload.totalAmount, add, remoteNodeId, receivedAt)
pendingPayments = pendingPayments + (add.paymentHash -> (payment, handler))
}
}
@@ -82,11 +82,11 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
val child = ctx.spawnAnonymous(CreateInvoiceActor(nodeParams))
child ! CreateInvoiceActor.CreateBolt12Invoice(receivePayment)
- case p: IncomingPaymentPacket.FinalPacket if doHandle(p.add.paymentHash) =>
- val child = ctx.spawnAnonymous(GetIncomingPaymentActor(nodeParams, p, offerManager))
+ case receivePayment: ReceivePacket if doHandle(receivePayment.packet.add.paymentHash) =>
+ val child = ctx.spawnAnonymous(GetIncomingPaymentActor(nodeParams, receivePayment.packet, receivePayment.remoteNodeId, offerManager))
child ! GetIncomingPaymentActor.GetIncomingPayment(ctx.self)
- case ProcessPacket(add, payload, payment_opt, receivedAt) if doHandle(add.paymentHash) =>
+ case ProcessPacket(add, payload, remoteNodeId, payment_opt, receivedAt) if doHandle(add.paymentHash) =>
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(add.paymentHash))) {
payment_opt match {
case Some(payment) => validateStandardPayment(nodeParams, add, payload, payment, receivedAt) match {
@@ -100,7 +100,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
log.debug("received payment for amount={} totalAmount={} paymentMetadata={}", add.amountMsat, payload.totalAmount, payload.paymentMetadata.map(_.toHex).getOrElse("none"))
Metrics.PaymentHtlcReceived.withTag(Tags.PaymentMetadataIncluded, payload.paymentMetadata.nonEmpty).increment()
payload.paymentMetadata.foreach(metadata => ctx.system.eventStream.publish(PaymentMetadataReceived(add.paymentHash, metadata)))
- addHtlcPart(ctx, add, payload, payment, receivedAt)
+ addHtlcPart(ctx, add, payload, remoteNodeId, payment, receivedAt)
}
case None => payload.paymentPreimage match {
case Some(paymentPreimage) if nodeParams.features.hasFeature(Features.KeySend) =>
@@ -116,7 +116,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
val invoice = Bolt11Invoice(nodeParams.chainHash, amount, paymentHash, nodeParams.privateKey, desc, nodeParams.channelConf.minFinalExpiryDelta, paymentSecret = payload.paymentSecret, features = features)
log.debug("generated fake invoice={} from amount={} (KeySend)", invoice.toString, amount)
db.addIncomingPayment(invoice, paymentPreimage, PaymentType.KeySend)
- ctx.self ! ProcessPacket(add, payload, Some(IncomingStandardPayment(invoice, paymentPreimage, PaymentType.KeySend, TimestampMilli.now(), IncomingPaymentStatus.Pending)), receivedAt)
+ ctx.self ! ProcessPacket(add, payload, remoteNodeId, Some(IncomingStandardPayment(invoice, paymentPreimage, PaymentType.KeySend, TimestampMilli.now(), IncomingPaymentStatus.Pending)), receivedAt)
case _ =>
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, "InvoiceNotFound").increment()
val attribution = FailureAttributionData(htlcReceivedAt = receivedAt, trampolineReceivedAt_opt = None)
@@ -126,7 +126,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
}
}
- case ProcessBlindedPacket(add, payload, payment, maxRecipientPathFees, receivedAt) if doHandle(add.paymentHash) =>
+ case ProcessBlindedPacket(add, payload, remoteNodeId, payment, maxRecipientPathFees, receivedAt) if doHandle(add.paymentHash) =>
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(add.paymentHash))) {
validateBlindedPayment(nodeParams, add, payload, payment, maxRecipientPathFees, receivedAt) match {
case Some(cmdFail) =>
@@ -135,7 +135,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
case None =>
val recipientPathFees = payload.amount - add.amountMsat
log.debug("received payment for amount={} recipientPathFees={} totalAmount={}", add.amountMsat, recipientPathFees, payload.totalAmount)
- addHtlcPart(ctx, add, payload, payment, receivedAt)
+ addHtlcPart(ctx, add, payload, remoteNodeId, payment, receivedAt)
if (recipientPathFees > 0.msat) {
// We've opted into deducing the blinded paths fees from the amount we receive for this payment.
// We add an artificial payment part for those fees, otherwise we will never reach the total amount.
@@ -187,7 +187,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
// NB: this case shouldn't happen unless the sender violated the spec, so it's ok that we take a slightly more
// expensive code path by fetching the preimage from DB.
case p: MultiPartPaymentFSM.HtlcPart => db.getIncomingPayment(paymentHash).foreach(record => {
- val received = PaymentReceived(paymentHash, PaymentReceived.PartialPayment(p.amount, p.htlc.channelId, p.receivedAt) :: Nil)
+ val received = PaymentReceived(paymentHash, PaymentEvent.IncomingPayment(p.htlc.channelId, p.remoteNodeId, p.amount, p.receivedAt) :: Nil)
if (db.receiveIncomingPayment(paymentHash, p.amount, received.settledAt)) {
val attribution = FulfillAttributionData(htlcReceivedAt = p.receivedAt, trampolineReceivedAt_opt = None, downstreamAttribution_opt = None)
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, CMD_FULFILL_HTLC(p.htlc.id, record.paymentPreimage, Some(attribution), commit = true))
@@ -207,7 +207,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(paymentHash))) {
log.debug("fulfilling payment for amount={}", parts.map(_.amount).sum)
val received = PaymentReceived(paymentHash, parts.flatMap {
- case p: MultiPartPaymentFSM.HtlcPart => Some(PaymentReceived.PartialPayment(p.amount, p.htlc.channelId, p.receivedAt))
+ case p: MultiPartPaymentFSM.HtlcPart => Some(PaymentEvent.IncomingPayment(p.htlc.channelId, p.remoteNodeId, p.amount, p.receivedAt))
case _: MultiPartPaymentFSM.RecipientBlindedPathFeePart => None
})
val recordedInDb = payment match {
@@ -248,8 +248,9 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
object MultiPartHandler {
// @formatter:off
- private case class ProcessPacket(add: UpdateAddHtlc, payload: FinalPayload.Standard, payment_opt: Option[IncomingStandardPayment], receivedAt: TimestampMilli)
- private case class ProcessBlindedPacket(add: UpdateAddHtlc, payload: FinalPayload.Blinded, payment: IncomingBlindedPayment, maxRecipientPathFees: MilliSatoshi, receivedAt: TimestampMilli)
+ case class ReceivePacket(packet: IncomingPaymentPacket.FinalPacket, remoteNodeId: PublicKey)
+ private case class ProcessPacket(add: UpdateAddHtlc, payload: FinalPayload.Standard, remoteNodeId: PublicKey, payment_opt: Option[IncomingStandardPayment], receivedAt: TimestampMilli)
+ private case class ProcessBlindedPacket(add: UpdateAddHtlc, payload: FinalPayload.Blinded, remoteNodeId: PublicKey, payment: IncomingBlindedPayment, maxRecipientPathFees: MilliSatoshi, receivedAt: TimestampMilli)
private case class RejectPacket(add: UpdateAddHtlc, failure: FailureMessage, receivedAt: TimestampMilli)
case class DoFulfill(payment: IncomingPayment, success: MultiPartPaymentFSM.MultiPartPaymentSucceeded)
@@ -372,7 +373,7 @@ object MultiPartHandler {
case class RejectPayment(reason: String) extends Command
// @formatter:on
- def apply(nodeParams: NodeParams, packet: IncomingPaymentPacket.FinalPacket, offerManager: typed.ActorRef[OfferManager.ReceivePayment]): Behavior[Command] = {
+ def apply(nodeParams: NodeParams, packet: IncomingPaymentPacket.FinalPacket, remoteNodeId: PublicKey, offerManager: typed.ActorRef[OfferManager.ReceivePayment]): Behavior[Command] = {
Behaviors.setup { context =>
Behaviors.withMdc(Logs.mdc(category_opt = Some(LogCategory.PAYMENT), paymentHash_opt = Some(packet.add.paymentHash))) {
Behaviors.receiveMessagePartial {
@@ -383,23 +384,23 @@ object MultiPartHandler {
case Some(_: IncomingBlindedPayment) =>
context.log.info("rejecting non-blinded htlc #{} from channel {}: expected a blinded payment", packet.add.id, packet.add.channelId)
replyTo ! RejectPacket(packet.add, IncorrectOrUnknownPaymentDetails(payload.totalAmount, nodeParams.currentBlockHeight), packet.receivedAt)
- case Some(payment: IncomingStandardPayment) => replyTo ! ProcessPacket(packet.add, payload, Some(payment), packet.receivedAt)
- case None => replyTo ! ProcessPacket(packet.add, payload, None, packet.receivedAt)
+ case Some(payment: IncomingStandardPayment) => replyTo ! ProcessPacket(packet.add, payload, remoteNodeId, Some(payment), packet.receivedAt)
+ case None => replyTo ! ProcessPacket(packet.add, payload, remoteNodeId, None, packet.receivedAt)
}
Behaviors.stopped
case payload: FinalPayload.Blinded =>
offerManager ! OfferManager.ReceivePayment(context.self, packet.add.paymentHash, payload, packet.add.amountMsat)
- waitForPayment(context, nodeParams, replyTo, packet.add, payload, packet.receivedAt)
+ waitForPayment(context, nodeParams, replyTo, packet.add, payload, remoteNodeId, packet.receivedAt)
}
}
}
}
}
- private def waitForPayment(context: typed.scaladsl.ActorContext[Command], nodeParams: NodeParams, replyTo: ActorRef, add: UpdateAddHtlc, payload: FinalPayload.Blinded, packetReceivedAt: TimestampMilli): Behavior[Command] = {
+ private def waitForPayment(context: typed.scaladsl.ActorContext[Command], nodeParams: NodeParams, replyTo: ActorRef, add: UpdateAddHtlc, payload: FinalPayload.Blinded, remoteNodeId: PublicKey, packetReceivedAt: TimestampMilli): Behavior[Command] = {
Behaviors.receiveMessagePartial {
case ProcessPayment(payment, maxRecipientPathFees) =>
- replyTo ! ProcessBlindedPacket(add, payload, payment, maxRecipientPathFees, packetReceivedAt)
+ replyTo ! ProcessBlindedPacket(add, payload, remoteNodeId, payment, maxRecipientPathFees, packetReceivedAt)
Behaviors.stopped
case RejectPayment(reason) =>
context.log.info("rejecting blinded htlc #{} from channel {}: {}", add.id, add.channelId, reason)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala
index 5e8f96f..b48bb74 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala
@@ -19,6 +19,7 @@ package fr.acinq.eclair.payment.receive
import akka.actor.{ActorRef, Props}
import akka.event.Logging.MDC
import fr.acinq.bitcoin.scalacompat.ByteVector32
+import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.wire.protocol
import fr.acinq.eclair.wire.protocol.{FailureMessage, IncorrectOrUnknownPaymentDetails, UpdateAddHtlc}
@@ -41,7 +42,7 @@ class MultiPartPaymentFSM(nodeParams: NodeParams, paymentHash: ByteVector32, tot
import MultiPartPaymentFSM._
- val start = TimestampMilli.now()
+ val start: TimestampMilli = TimestampMilli.now()
startSingleTimer(PaymentTimeout.toString, PaymentTimeout, nodeParams.multiPartPaymentExpiry)
@@ -134,7 +135,7 @@ object MultiPartPaymentFSM {
def totalAmount: MilliSatoshi
}
/** An incoming HTLC. */
- case class HtlcPart(totalAmount: MilliSatoshi, htlc: UpdateAddHtlc, receivedAt: TimestampMilli) extends PaymentPart {
+ case class HtlcPart(totalAmount: MilliSatoshi, htlc: UpdateAddHtlc, remoteNodeId: PublicKey, receivedAt: TimestampMilli) extends PaymentPart {
override def paymentHash: ByteVector32 = htlc.paymentHash
override def amount: MilliSatoshi = htlc.amountMsat
}
@@ -158,9 +159,9 @@ object MultiPartPaymentFSM {
// @formatter:off
sealed trait Data {
def parts: Queue[PaymentPart]
- lazy val paidAmount = parts.map(_.amount).sum
+ lazy val paidAmount: MilliSatoshi = parts.map(_.amount).sum
}
- case class WaitingForHtlc(parts: Queue[PaymentPart]) extends Data
+ private case class WaitingForHtlc(parts: Queue[PaymentPart]) extends Data
case class PaymentSucceeded(parts: Queue[PaymentPart]) extends Data
case class PaymentFailed(failure: FailureMessage, parts: Queue[PaymentPart]) extends Data
// @formatter:on
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala
index 167f477..130e4b7 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala
@@ -30,7 +30,7 @@ import fr.acinq.eclair.io.Peer.ProposeOnTheFlyFundingResponse
import fr.acinq.eclair.io.{Peer, PeerReadyNotifier}
import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.payment.relay.Relayer.{OutgoingChannel, OutgoingChannelParams}
-import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket}
+import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket, PaymentEvent}
import fr.acinq.eclair.reputation.Reputation
import fr.acinq.eclair.reputation.ReputationRecorder.GetConfidence
import fr.acinq.eclair.wire.protocol.FailureMessageCodecs.createBadOnionFailure
@@ -244,7 +244,7 @@ class ChannelRelay private(nodeParams: NodeParams,
private def waitForAddSettled(): Behavior[Command] =
Behaviors.receiveMessagePartial {
- case WrappedAddResponse(RES_ADD_SETTLED(_, htlc, fulfill: HtlcResult.Fulfill)) =>
+ case WrappedAddResponse(RES_ADD_SETTLED(_, remoteNodeId, htlc, fulfill: HtlcResult.Fulfill)) =>
val now = TimestampMilli.now()
context.log.info("relaying fulfill to upstream, receivedAt={}, endedAt={}, confidence={}, originNode={}, outgoingChannel={}", upstream.receivedAt, now, reputationScore.outgoingConfidence, upstream.receivedFrom, htlc.channelId)
Metrics.relayFulfill(reputationScore.outgoingConfidence)
@@ -254,10 +254,12 @@ class ChannelRelay private(nodeParams: NodeParams,
}
val attribution = FulfillAttributionData(htlcReceivedAt = upstream.receivedAt, trampolineReceivedAt_opt = None, downstreamAttribution_opt = downstreamAttribution_opt)
val cmd = CMD_FULFILL_HTLC(upstream.add.id, fulfill.paymentPreimage, Some(attribution), commit = true)
- context.system.eventStream ! EventStream.Publish(ChannelPaymentRelayed(upstream.amountIn, htlc.amountMsat, htlc.paymentHash, upstream.add.channelId, htlc.channelId, upstream.receivedAt, now))
+ val incoming = PaymentEvent.IncomingPayment(upstream.add.channelId, upstream.receivedFrom, upstream.amountIn, upstream.receivedAt)
+ val outgoing = PaymentEvent.OutgoingPayment(htlc.channelId, remoteNodeId, htlc.amountMsat, now)
+ context.system.eventStream ! EventStream.Publish(ChannelPaymentRelayed(htlc.paymentHash, incoming, outgoing))
recordRelayDuration(isSuccess = true)
safeSendAndStop(upstream.add.channelId, cmd)
- case WrappedAddResponse(RES_ADD_SETTLED(_, htlc, fail: HtlcResult.Fail)) =>
+ case WrappedAddResponse(RES_ADD_SETTLED(_, _, htlc, fail: HtlcResult.Fail)) =>
val now = TimestampMilli.now()
context.log.info("relaying fail to upstream, receivedAt={}, endedAt={}, confidence={}, originNode={}, outgoingChannel={}", upstream.receivedAt, now, reputationScore.outgoingConfidence, upstream.receivedFrom, htlc.channelId)
Metrics.relayFail(reputationScore.outgoingConfidence)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala
index 5e12162..3f0da94 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala
@@ -226,7 +226,7 @@ class NodeRelay private(nodeParams: NodeParams,
case Relay(packet: IncomingPaymentPacket.NodeRelayPacket, originNode, incomingChannelOccupancy) =>
require(packet.outerPayload.paymentSecret == paymentSecret, "payment secret mismatch")
context.log.debug("forwarding incoming htlc #{} from channel {} to the payment FSM", packet.add.id, packet.add.channelId)
- handler ! MultiPartPaymentFSM.HtlcPart(packet.outerPayload.totalAmount, packet.add, packet.receivedAt)
+ handler ! MultiPartPaymentFSM.HtlcPart(packet.outerPayload.totalAmount, packet.add, originNode, packet.receivedAt)
receiving(htlcs :+ Upstream.Hot.Channel(packet.add.removeUnknownTlvs(), packet.receivedAt, originNode, incomingChannelOccupancy), nextPayload, nextPacket_opt, handler, upgradeAccountability && packet.innerPayload.upgradeAccountability)
case WrappedMultiPartPaymentFailed(MultiPartPaymentFSM.MultiPartPaymentFailed(_, failure, parts)) =>
context.log.warn("could not complete incoming multi-part payment (parts={} paidAmount={} failure={})", parts.size, parts.map(_.amount).sum, failure)
@@ -515,8 +515,8 @@ class NodeRelay private(nodeParams: NodeParams,
if (!fulfilledUpstream) {
fulfillPayment(upstream, paymentSent.paymentPreimage, paymentSent.remainingAttribution_opt)
}
- val incoming = upstream.received.map(r => PaymentRelayed.IncomingPart(r.add.amountMsat, r.add.channelId, r.receivedAt))
- val outgoing = paymentSent.parts.map(part => PaymentRelayed.OutgoingPart(part.amountWithFees, part.toChannelId, part.settledAt))
+ val incoming = upstream.received.map(r => PaymentEvent.IncomingPayment(r.add.channelId, r.receivedFrom, r.add.amountMsat, r.receivedAt))
+ val outgoing = paymentSent.parts.map(p => PaymentEvent.OutgoingPayment(p.channelId, p.remoteNodeId, p.amountWithFees, p.settledAt))
context.system.eventStream ! EventStream.Publish(TrampolinePaymentRelayed(paymentHash, incoming, outgoing, paymentSent.recipientNodeId, paymentSent.recipientAmount))
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala
index cdc8fb1..7952335 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala
@@ -21,12 +21,12 @@ import akka.actor.{Actor, ActorRef, DiagnosticActorLogging, Props, Stash}
import akka.event.Logging.MDC
import akka.event.LoggingAdapter
import fr.acinq.bitcoin.scalacompat.ByteVector32
-import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
+import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.eclair.channel.Helpers.Closing
import fr.acinq.eclair.channel._
import fr.acinq.eclair.db._
import fr.acinq.eclair.payment.Monitoring.Tags
-import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket, PaymentFailed, PaymentSent}
+import fr.acinq.eclair.payment._
import fr.acinq.eclair.transactions.DirectedHtlc.outgoing
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CustomCommitmentsPlugin, Feature, Features, Logs, MilliSatoshiLong, NodeParams, TimestampMilli}
@@ -155,36 +155,36 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
case _: ChannelStateChanged => // ignore other channel state changes
- case RES_ADD_SETTLED(o: Origin.Cold, htlc, fulfill: HtlcResult.Fulfill) =>
- log.info("htlc #{} from channelId={} fulfilled downstream", htlc.id, htlc.channelId)
- handleDownstreamFulfill(brokenHtlcs, o, htlc, fulfill.paymentPreimage)
+ case RES_ADD_SETTLED(o: Origin.Cold, remoteNodeId, htlc, fulfill: HtlcResult.Fulfill) =>
+ log.info("htlc #{} from channelId={} fulfilled downstream by {}", htlc.id, htlc.channelId, remoteNodeId)
+ handleDownstreamFulfill(brokenHtlcs, o, remoteNodeId, htlc, fulfill.paymentPreimage)
- case RES_ADD_SETTLED(o: Origin.Cold, htlc, fail: HtlcResult.Fail) =>
+ case RES_ADD_SETTLED(o: Origin.Cold, remoteNodeId, htlc, fail: HtlcResult.Fail) =>
if (htlc.fundingFee_opt.nonEmpty) {
- log.info("htlc #{} from channelId={} failed downstream but has a pending on-the-fly funding", htlc.id, htlc.channelId)
+ log.info("htlc #{} from channelId={} failed downstream by {} but has a pending on-the-fly funding", htlc.id, htlc.channelId, remoteNodeId)
// We don't fail upstream: we haven't been paid our funding fee yet, so we will try relaying again.
} else {
- log.info("htlc #{} from channelId={} failed downstream: {}", htlc.id, htlc.channelId, fail.getClass.getSimpleName)
+ log.info("htlc #{} from channelId={} failed downstream by {}: {}", htlc.id, htlc.channelId, remoteNodeId, fail.getClass.getSimpleName)
handleDownstreamFailure(brokenHtlcs, o, htlc, fail)
}
case GetBrokenHtlcs => sender() ! brokenHtlcs
}
- private def handleDownstreamFulfill(brokenHtlcs: BrokenHtlcs, origin: Origin.Cold, fulfilledHtlc: UpdateAddHtlc, paymentPreimage: ByteVector32): Unit =
+ private def handleDownstreamFulfill(brokenHtlcs: BrokenHtlcs, origin: Origin.Cold, downstreamNodeId: PublicKey, fulfilledHtlc: UpdateAddHtlc, paymentPreimage: ByteVector32): Unit =
brokenHtlcs.relayedOut.get(origin) match {
case Some(relayedOut) => origin.upstream match {
case Upstream.Local(id) =>
val feesPaid = 0.msat // fees are unknown since we lost the reference to the payment
nodeParams.db.payments.getOutgoingPayment(id) match {
case Some(p) =>
- nodeParams.db.payments.updateOutgoingPayment(PaymentSent(p.parentId, paymentPreimage, p.recipientAmount, p.recipientNodeId, PaymentSent.PartialPayment(id, fulfilledHtlc.amountMsat, feesPaid, fulfilledHtlc.channelId, None, startedAt = p.createdAt, settledAt = TimestampMilli.now()) :: Nil, None, p.createdAt))
+ nodeParams.db.payments.updateOutgoingPayment(PaymentSent(p.parentId, paymentPreimage, p.recipientAmount, p.recipientNodeId, PaymentSent.PaymentPart(id, PaymentEvent.OutgoingPayment(fulfilledHtlc.channelId, downstreamNodeId, fulfilledHtlc.amountMsat, settledAt = TimestampMilli.now()), feesPaid, None, startedAt = p.createdAt) :: Nil, None, p.createdAt))
// If all downstream HTLCs are now resolved, we can emit the payment event.
val payments = nodeParams.db.payments.listOutgoingPayments(p.parentId)
if (!payments.exists(p => p.status == OutgoingPaymentStatus.Pending)) {
val succeeded = payments.collect {
case OutgoingPayment(id, _, _, _, _, amount, _, _, createdAt, _, _, OutgoingPaymentStatus.Succeeded(_, feesPaid, _, completedAt)) =>
- PaymentSent.PartialPayment(id, amount, feesPaid, ByteVector32.Zeroes, None, createdAt, completedAt)
+ PaymentSent.PaymentPart(id, PaymentEvent.OutgoingPayment(ByteVector32.Zeroes, downstreamNodeId, amount, completedAt), feesPaid, None, createdAt)
}
val sent = PaymentSent(p.parentId, paymentPreimage, p.recipientAmount, p.recipientNodeId, succeeded, None, p.createdAt)
log.info(s"payment id=${sent.id} paymentHash=${sent.paymentHash} successfully sent (amount=${sent.recipientAmount})")
@@ -198,27 +198,27 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
val dummyNodeId = nodeParams.nodeId
val now = TimestampMilli.now()
nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id, id, None, fulfilledHtlc.paymentHash, PaymentType.Standard, fulfilledHtlc.amountMsat, dummyFinalAmount, dummyNodeId, now, None, None, OutgoingPaymentStatus.Pending))
- nodeParams.db.payments.updateOutgoingPayment(PaymentSent(id, paymentPreimage, dummyFinalAmount, dummyNodeId, PaymentSent.PartialPayment(id, fulfilledHtlc.amountMsat, feesPaid, fulfilledHtlc.channelId, None, startedAt = now, settledAt = now) :: Nil, None, startedAt = now))
+ nodeParams.db.payments.updateOutgoingPayment(PaymentSent(id, paymentPreimage, dummyFinalAmount, dummyNodeId, PaymentSent.PaymentPart(id, PaymentEvent.OutgoingPayment(fulfilledHtlc.channelId, downstreamNodeId, fulfilledHtlc.amountMsat, settledAt = now), feesPaid, None, startedAt = now) :: Nil, None, startedAt = now))
}
// There can never be more than one pending downstream HTLC for a given local origin (a multi-part payment is
// instead spread across multiple local origins) so we can now forget this origin.
Metrics.PendingRelayedOut.decrement()
context become main(brokenHtlcs.copy(relayedOut = brokenHtlcs.relayedOut - origin))
- case Upstream.Cold.Channel(originChannelId, originHtlcId, amountIn) =>
+ case u: Upstream.Cold.Channel =>
log.info(s"received preimage for paymentHash=${fulfilledHtlc.paymentHash}: fulfilling 1 HTLC upstream")
if (relayedOut != Set((fulfilledHtlc.channelId, fulfilledHtlc.id))) {
log.error(s"unexpected channel relay downstream HTLCs: expected (${fulfilledHtlc.channelId},${fulfilledHtlc.id}), found $relayedOut")
}
- PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, originChannelId, CMD_FULFILL_HTLC(originHtlcId, paymentPreimage, None, commit = true))
+ PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, u.originChannelId, CMD_FULFILL_HTLC(u.originHtlcId, paymentPreimage, None, commit = true))
// We don't know when we received this HTLC so we just pretend that we received it just now.
- context.system.eventStream.publish(ChannelPaymentRelayed(amountIn, fulfilledHtlc.amountMsat, fulfilledHtlc.paymentHash, originChannelId, fulfilledHtlc.channelId, TimestampMilli.now(), TimestampMilli.now()))
+ context.system.eventStream.publish(ChannelPaymentRelayed(fulfilledHtlc.paymentHash, PaymentEvent.IncomingPayment(u.originChannelId, u.originNodeId, u.amountIn, TimestampMilli.now()), PaymentEvent.OutgoingPayment(fulfilledHtlc.channelId, downstreamNodeId, fulfilledHtlc.amountMsat, TimestampMilli.now())))
Metrics.PendingRelayedOut.decrement()
context become main(brokenHtlcs.copy(relayedOut = brokenHtlcs.relayedOut - origin))
- case Upstream.Cold.Trampoline(originHtlcs) =>
+ case u: Upstream.Cold.Trampoline =>
// We fulfill upstream as soon as we have the payment preimage available.
if (!brokenHtlcs.settledUpstream.contains(origin)) {
- log.info(s"received preimage for paymentHash=${fulfilledHtlc.paymentHash}: fulfilling ${originHtlcs.length} HTLCs upstream")
- originHtlcs.foreach { case Upstream.Cold.Channel(channelId, htlcId, _) =>
+ log.info("received preimage for paymentHash={}: fulfilling {} HTLCs upstream", fulfilledHtlc.paymentHash, u.originHtlcs.length)
+ u.originHtlcs.foreach { case Upstream.Cold.Channel(channelId, _, htlcId, _) =>
Metrics.Resolved.withTag(Tags.Success, value = true).withTag(Metrics.Relayed, value = true).increment()
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, CMD_FULFILL_HTLC(htlcId, paymentPreimage, None, commit = true))
}
@@ -260,8 +260,8 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
context.system.eventStream.publish(PaymentFailed(p.parentId, failedHtlc.paymentHash, Nil, p.createdAt, settledAt = TimestampMilli.now()))
}
})
- case Upstream.Cold.Channel(originChannelId, originHtlcId, _) =>
- log.warning(s"payment failed for paymentHash=${failedHtlc.paymentHash}: failing 1 HTLC upstream")
+ case u: Upstream.Cold.Channel =>
+ log.warning("payment failed for paymentHash={}: failing 1 HTLC upstream", failedHtlc.paymentHash)
Metrics.Resolved.withTag(Tags.Success, value = false).withTag(Metrics.Relayed, value = true).increment()
val cmd = failedHtlc.pathKey_opt match {
case Some(_) =>
@@ -269,14 +269,14 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
// we don't have access to the incoming onion: to avoid leaking information, we act as if we were an
// intermediate node and send invalid_onion_blinding in an update_fail_malformed_htlc message.
val failure = InvalidOnionBlinding(ByteVector32.Zeroes)
- CMD_FAIL_MALFORMED_HTLC(originHtlcId, failure.onionHash, failure.code, commit = true)
+ CMD_FAIL_MALFORMED_HTLC(u.originHtlcId, failure.onionHash, failure.code, commit = true)
case None =>
- ChannelRelay.translateRelayFailure(originHtlcId, fail, None)
+ ChannelRelay.translateRelayFailure(u.originHtlcId, fail, None)
}
- PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, originChannelId, cmd)
- case Upstream.Cold.Trampoline(originHtlcs) =>
- log.warning(s"payment failed for paymentHash=${failedHtlc.paymentHash}: failing ${originHtlcs.length} HTLCs upstream")
- originHtlcs.foreach { case Upstream.Cold.Channel(channelId, htlcId, _) =>
+ PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, u.originChannelId, cmd)
+ case u: Upstream.Cold.Trampoline =>
+ log.warning("payment failed for paymentHash={}: failing {} HTLCs upstream", failedHtlc.paymentHash, u.originHtlcs.length)
+ u.originHtlcs.foreach { case Upstream.Cold.Channel(channelId, _, htlcId, _) =>
Metrics.Resolved.withTag(Tags.Success, value = false).withTag(Metrics.Relayed, value = true).increment()
// We don't bother decrypting the downstream failure to forward a more meaningful error upstream, it's
// very likely that it won't be actionable anyway because of our node restart.
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala
index 7f46092..fe44843 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala
@@ -28,6 +28,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.eclair.channel._
import fr.acinq.eclair.db.PendingCommandsDb
import fr.acinq.eclair.payment._
+import fr.acinq.eclair.payment.receive.MultiPartHandler
import fr.acinq.eclair.reputation.{Reputation, ReputationRecorder}
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiryDelta, Logs, MilliSatoshi, NodeParams, RealShortChannelId, TimestampMilli}
@@ -68,7 +69,7 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym
IncomingPaymentPacket.decrypt(add, nodeParams.privateKey, nodeParams.features) match {
case Right(p: IncomingPaymentPacket.FinalPacket) =>
log.debug(s"forwarding htlc #${add.id} to payment-handler")
- paymentHandler forward p
+ paymentHandler forward MultiPartHandler.ReceivePacket(p, originNode)
case Right(r: IncomingPaymentPacket.ChannelRelayPacket) =>
channelRelayer ! ChannelRelayer.Relay(r, originNode, incomingChannelOccupancy)
case Right(r: IncomingPaymentPacket.NodeRelayPacket) =>
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala
index a25cf1b..1937346 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala
@@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.ByteVector32
import fr.acinq.eclair.channel.{HtlcOverriddenByLocalCommit, HtlcsTimedoutDownstream, HtlcsWillTimeoutUpstream, Upstream}
import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus}
import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags}
-import fr.acinq.eclair.payment.PaymentSent.PartialPayment
+import fr.acinq.eclair.payment.PaymentSent.PaymentPart
import fr.acinq.eclair.payment._
import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentConfig
import fr.acinq.eclair.payment.send.PaymentLifecycle.SendPaymentToRoute
@@ -255,7 +255,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig,
// in case of a relayed payment, we need to take into account the fee of the first channels
paymentSent.parts.collect {
// NB: the route attribute will always be defined here
- case p@PartialPayment(_, _, _, _, Some(route), _, _) => route.head.fee(p.amountWithFees)
+ case p@PaymentPart(_, _, _, Some(route), _) => route.head.fee(p.amountWithFees)
}.sum
}
paymentSent.feesPaid + localFees
@@ -369,7 +369,7 @@ object MultiPartPaymentLifecycle {
* @param parts fulfilled child payments.
* @param pending pending child payments (we are waiting for them to be fulfilled downstream).
*/
- case class PaymentSucceeded(request: SendMultiPartPayment, preimage: ByteVector32, parts: Seq[PartialPayment], pending: Set[UUID], remainingAttribution_opt: Option[ByteVector]) extends Data
+ case class PaymentSucceeded(request: SendMultiPartPayment, preimage: ByteVector32, parts: Seq[PaymentPart], pending: Set[UUID], remainingAttribution_opt: Option[ByteVector]) extends Data
private def createRouteRequest(replyTo: ActorRef, nodeParams: NodeParams, routeParams: RouteParams, d: PaymentProgress, cfg: SendPaymentConfig): RouteRequest = {
RouteRequest(replyTo.toTyped, nodeParams.nodeId, d.request.recipient, routeParams, d.ignore, allowMultiPart = true, d.pending.values.toSeq, Some(cfg.paymentContext))
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala
index 6fdb12d..041c50c 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala
@@ -336,7 +336,7 @@ object PaymentInitiator {
case _ => PaymentType.Standard
}
- def createPaymentSent(recipient: Recipient, preimage: ByteVector32, parts: Seq[PaymentSent.PartialPayment], remainingAttribution_opt: Option[ByteVector], startedAt: TimestampMilli): PaymentSent = {
+ def createPaymentSent(recipient: Recipient, preimage: ByteVector32, parts: Seq[PaymentSent.PaymentPart], remainingAttribution_opt: Option[ByteVector], startedAt: TimestampMilli): PaymentSent = {
PaymentSent(parentId, preimage, recipient.totalAmount, recipient.nodeId, parts, remainingAttribution_opt, startedAt)
}
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala
index f571a01..c165a46 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala
@@ -26,7 +26,7 @@ import fr.acinq.eclair.crypto.{Sphinx, TransportHandler}
import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus}
import fr.acinq.eclair.payment.Invoice.ExtraEdge
import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags}
-import fr.acinq.eclair.payment.PaymentSent.PartialPayment
+import fr.acinq.eclair.payment.PaymentSent.PaymentPart
import fr.acinq.eclair.payment._
import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentConfig
import fr.acinq.eclair.payment.send.PaymentLifecycle._
@@ -111,10 +111,10 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
case Event(_: Register.ForwardShortIdFailure[_], d: WaitingForComplete) =>
handleLocalFail(d, DisconnectedException, isFatal = false)
- case Event(RES_ADD_SETTLED(_, htlc, fulfill: HtlcResult.Fulfill), d: WaitingForComplete) =>
+ case Event(RES_ADD_SETTLED(_, remoteNodeId, htlc, fulfill: HtlcResult.Fulfill), d: WaitingForComplete) =>
router ! Router.RouteDidRelay(d.route)
Metrics.PaymentAttempt.withTag(Tags.MultiPart, value = false).record(d.failures.size + 1)
- val p = PartialPayment(id, d.request.amount, d.cmd.amount - d.request.amount, htlc.channelId, Some(d.route.fullRoute), startedAt = d.sentAt, settledAt = TimestampMilli.now())
+ val p = PaymentPart(id, PaymentEvent.OutgoingPayment(htlc.channelId, remoteNodeId, d.cmd.amount, settledAt = TimestampMilli.now()), d.cmd.amount - d.request.amount, Some(d.route.fullRoute), startedAt = d.sentAt)
val remainingAttribution_opt = fulfill match {
case HtlcResult.RemoteFulfill(updateFulfill) =>
updateFulfill.attribution_opt match {
@@ -130,7 +130,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
}
myStop(d.request, Right(cfg.createPaymentSent(d.recipient, fulfill.paymentPreimage, p :: Nil, remainingAttribution_opt, start)))
- case Event(RES_ADD_SETTLED(_, _, fail: HtlcResult.Fail), d: WaitingForComplete) =>
+ case Event(RES_ADD_SETTLED(_, _, _, fail: HtlcResult.Fail), d: WaitingForComplete) =>
fail match {
case HtlcResult.RemoteFail(fail) => handleRemoteFail(d, fail)
case HtlcResult.RemoteFailMalformed(fail) =>
@@ -418,7 +418,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
// in case of a relayed payment, we need to take into account the fee of the first channels
paymentSent.parts.collect {
// NB: the route attribute will always be defined here
- case p@PartialPayment(_, _, _, _, Some(route), _, _) => route.head.fee(p.amountWithFees)
+ case p@PaymentPart(_, _, _, Some(route), _) => route.head.fee(p.amountWithFees)
}.sum
}
paymentSent.feesPaid + localFees
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/TrampolinePaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/TrampolinePaymentLifecycle.scala
index 22cc576..0a31610 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/TrampolinePaymentLifecycle.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/TrampolinePaymentLifecycle.scala
@@ -26,7 +26,7 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.io.Peer
import fr.acinq.eclair.payment.OutgoingPaymentPacket.{NodePayload, buildOnion}
-import fr.acinq.eclair.payment.PaymentSent.PartialPayment
+import fr.acinq.eclair.payment.PaymentSent.PaymentPart
import fr.acinq.eclair.payment._
import fr.acinq.eclair.payment.send.TrampolinePayment.{buildOutgoingPayment, computeFees}
import fr.acinq.eclair.reputation.Reputation
@@ -59,7 +59,7 @@ object TrampolinePaymentLifecycle {
}
private case class TrampolinePeerNotFound(trampolineNodeId: PublicKey) extends Command
private case class CouldntAddHtlc(failure: Throwable) extends Command
- private case class HtlcSettled(result: HtlcResult, part: PartialPayment, holdTimes: Seq[Sphinx.HoldTime]) extends Command
+ private case class HtlcSettled(result: HtlcResult, part: PaymentPart, holdTimes: Seq[Sphinx.HoldTime]) extends Command
private case class WrappedPeerChannels(channels: Seq[Peer.ChannelInfo]) extends Command
// @formatter:on
@@ -113,11 +113,12 @@ object TrampolinePaymentLifecycle {
val add = CMD_ADD_HTLC(addHtlcAdapter.toClassic, outgoing.trampolineAmount, paymentHash, outgoing.trampolineExpiry, outgoing.onion.packet, None, Reputation.Score.max(accountable = false), None, origin, commit = true)
channelInfo.channel ! add
val channelId = channelInfo.data.asInstanceOf[DATA_NORMAL].channelId
- val part = PartialPayment(cmd.paymentId, amount, computeFees(amount, attemptNumber), channelId, None, startedAt = TimestampMilli.now(), settledAt = TimestampMilli.now()) // we will update settledAt below
+ val trampolineFees = computeFees(amount, attemptNumber)
+ val part = PaymentPart(cmd.paymentId, PaymentEvent.OutgoingPayment(channelId, cmd.trampolineNodeId, amount + trampolineFees, settledAt = TimestampMilli.now()), trampolineFees, None, startedAt = TimestampMilli.now()) // we will update settledAt below
waitForSettlement(part, outgoing.onion.sharedSecrets, outgoing.trampolineOnion.sharedSecrets)
}
- def waitForSettlement(part: PartialPayment, outerOnionSecrets: Seq[Sphinx.SharedSecret], trampolineOnionSecrets: Seq[Sphinx.SharedSecret]): Behavior[PartHandler.Command] = {
+ def waitForSettlement(part: PaymentPart, outerOnionSecrets: Seq[Sphinx.SharedSecret], trampolineOnionSecrets: Seq[Sphinx.SharedSecret]): Behavior[PartHandler.Command] = {
Behaviors.receiveMessagePartial {
case WrappedAddHtlcResponse(response) => response match {
case _: CommandSuccess[_] =>
@@ -137,7 +138,7 @@ object TrampolinePaymentLifecycle {
}
case _: HtlcResult.OnChainFulfill => Nil
}
- parent ! HtlcSettled(fulfill, part.copy(settledAt = TimestampMilli.now()), holdTimes)
+ parent ! HtlcSettled(fulfill, part.copy(payment = part.payment.copy(settledAt = TimestampMilli.now())), holdTimes)
Behaviors.stopped
case fail: HtlcResult.Fail =>
val holdTimes = fail match {
@@ -145,7 +146,7 @@ object TrampolinePaymentLifecycle {
Sphinx.FailurePacket.decrypt(updateFail.reason, updateFail.attribution_opt, outerOnionSecrets).holdTimes
case _ => Nil
}
- parent ! HtlcSettled(fail, part.copy(settledAt = TimestampMilli.now()), holdTimes)
+ parent ! HtlcSettled(fail, part.copy(payment = part.payment.copy(settledAt = TimestampMilli.now())), holdTimes)
Behaviors.stopped
}
}
@@ -212,7 +213,7 @@ class TrampolinePaymentLifecycle private(nodeParams: NodeParams,
}
}
- private def waitForSettlement(remaining: Int, attemptNumber: Int, fulfilledParts: Seq[PartialPayment]): Behavior[Command] = {
+ private def waitForSettlement(remaining: Int, attemptNumber: Int, fulfilledParts: Seq[PaymentPart]): Behavior[Command] = {
Behaviors.receiveMessagePartial {
case CouldntAddHtlc(failure) =>
context.log.warn("HTLC could not be sent: {}", failure.getMessage)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala
index 82a9cd3..dbf45f2 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala
@@ -16,8 +16,9 @@
package fr.acinq.eclair.wire.internal.channel.version5
+import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
-import fr.acinq.bitcoin.scalacompat.{OutPoint, ScriptWitness, Transaction, TxOut}
+import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, ScriptWitness, Transaction, TxOut}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession}
import fr.acinq.eclair.crypto.ShaChain
@@ -335,15 +336,24 @@ private[channel] object ChannelCodecs5 {
("localNextHtlcId" | uint64overflow) ::
("remoteNextHtlcId" | uint64overflow)).as[CommitmentChanges]
+ private val upstreamChannelCodecWithoutNodeId: Codec[Upstream.Cold.Channel] = (
+ ("originChannelId" | bytes32) ::
+ ("originNodeId" | provide(PrivateKey(ByteVector32.One).publicKey)) ::
+ ("originHtlcId" | int64) ::
+ ("amountIn" | millisatoshi)).as[Upstream.Cold.Channel]
+
private val upstreamChannelCodec: Codec[Upstream.Cold.Channel] = (
("originChannelId" | bytes32) ::
+ ("originNodeId" | publicKey) ::
("originHtlcId" | int64) ::
("amountIn" | millisatoshi)).as[Upstream.Cold.Channel]
private val coldUpstreamCodec: Codec[Upstream.Cold] = discriminated[Upstream.Cold].by(uint16)
// NB: order matters!
- .typecase(0x03, upstreamChannelCodec)
- .typecase(0x02, listOfN(uint16, upstreamChannelCodec).as[Upstream.Cold.Trampoline])
+ .typecase(0x05, listOfN(uint16, upstreamChannelCodec).as[Upstream.Cold.Trampoline])
+ .typecase(0x04, upstreamChannelCodec)
+ .typecase(0x03, upstreamChannelCodecWithoutNodeId)
+ .typecase(0x02, listOfN(uint16, upstreamChannelCodecWithoutNodeId).as[Upstream.Cold.Trampoline])
.typecase(0x01, ("id" | uuid).as[Upstream.Local])
private val originCodec: Codec[Origin] = coldUpstreamCodec.xmap[Origin](
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala
index 86c3d4b..9946f03 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala
@@ -134,10 +134,10 @@ class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Channe
sendChannel ! buildCmdAdd(invoice)
context become {
case RES_SUCCESS(_: CMD_ADD_HTLC, _) => ()
- case RES_ADD_SETTLED(_, htlc, _: HtlcResult.Fulfill) =>
+ case RES_ADD_SETTLED(_, _, htlc, _: HtlcResult.Fulfill) =>
log.info(s"successfully sent htlc #${htlc.id}")
initiatePaymentOrStop(remaining - 1)
- case RES_ADD_SETTLED(_, htlc, _: HtlcResult.Fail) =>
+ case RES_ADD_SETTLED(_, _, htlc, _: HtlcResult.Fail) =>
log.warning(s"htlc failed: ${htlc.id}")
initiatePaymentOrStop(remaining - 1)
case RES_ADD_FAILED(_, t: Throwable, _) =>
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala
index 730303e..652a60f 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala
@@ -795,7 +795,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val origin = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, aliceCommitTx)
// so she fails it
- alice2relayer.expectMsg(RES_ADD_SETTLED(origin, htlc, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId(alice), htlc))))
+ alice2relayer.expectMsg(RES_ADD_SETTLED(origin, bob.nodeParams.nodeId, htlc, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId(alice), htlc))))
// the htlc will not settle on chain
listener.expectNoMessage(100 millis)
alice2relayer.expectNoMessage(100 millis)
@@ -909,7 +909,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val origin = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, closingState.commitTx)
// so she fails it
- alice2relayer.expectMsg(RES_ADD_SETTLED(origin, htlc, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId(alice), htlc))))
+ alice2relayer.expectMsg(RES_ADD_SETTLED(origin, bob.nodeParams.nodeId, htlc, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId(alice), htlc))))
// the htlc will not settle on chain
listener.expectNoMessage(100 millis)
alice2relayer.expectNoMessage(100 millis)
@@ -1269,7 +1269,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val origin = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx)
// so she fails it
- alice2relayer.expectMsg(RES_ADD_SETTLED(origin, htlc, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId(alice), htlc))))
+ alice2relayer.expectMsg(RES_ADD_SETTLED(origin, bob.nodeParams.nodeId, htlc, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId(alice), htlc))))
// the htlc will not settle on chain
listener.expectNoMessage(100 millis)
alice2relayer.expectNoMessage(100 millis)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala
index 88bdd53..8f2b4ae 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala
@@ -16,7 +16,7 @@
package fr.acinq.eclair.db
-import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
+import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, SatoshiLong, Script, Transaction, TxOut}
import fr.acinq.eclair.TestDatabases.{TestPgDatabases, TestSqliteDatabases}
import fr.acinq.eclair.TestUtils.randomTxId
@@ -61,36 +61,39 @@ class AuditDbSpec extends AnyFunSuite {
test("add/list events") {
forAllDbs { dbs =>
val db = dbs.audit
+ // We don't yet store the remote node_id in our DB: we use this placeholder instead.
+ // TODO: update this test once we store the remote node_id for incoming/outgoing payments.
+ val dummyRemoteNodeId = PrivateKey(ByteVector32.One).publicKey
val now = TimestampMilli.now()
- val e1 = PaymentSent(ZERO_UUID, randomBytes32(), 40000 msat, randomKey().publicKey, PaymentSent.PartialPayment(ZERO_UUID, 42000 msat, 1000 msat, randomBytes32(), None, now, now) :: Nil, None, now)
- val pp2a = PaymentReceived.PartialPayment(42000 msat, randomBytes32())
- val pp2b = PaymentReceived.PartialPayment(42100 msat, randomBytes32())
+ val e1 = PaymentSent(ZERO_UUID, randomBytes32(), 40000 msat, randomKey().publicKey, PaymentSent.PaymentPart(ZERO_UUID, PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 42000 msat, now), 1000 msat, None, now) :: Nil, None, now)
+ val pp2a = PaymentEvent.IncomingPayment(randomBytes32(), dummyRemoteNodeId, 42000 msat, now)
+ val pp2b = PaymentEvent.IncomingPayment(randomBytes32(), dummyRemoteNodeId, 42100 msat, now)
val e2 = PaymentReceived(randomBytes32(), pp2a :: pp2b :: Nil)
- val e3 = ChannelPaymentRelayed(42000 msat, 1000 msat, randomBytes32(), randomBytes32(), randomBytes32(), now - 3.seconds, now)
+ val e3 = ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(randomBytes32(), dummyRemoteNodeId, 42000 msat, now - 3.seconds), PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 1000 msat, now))
val e4a = TransactionPublished(randomBytes32(), randomKey().publicKey, Transaction(0, Seq.empty, Seq.empty, 0), 42 sat, "mutual")
val e4b = TransactionConfirmed(e4a.channelId, e4a.remoteNodeId, e4a.tx)
val e4c = TransactionConfirmed(randomBytes32(), randomKey().publicKey, Transaction(2, Nil, TxOut(500 sat, hex"1234") :: Nil, 0))
- val pp5a = PaymentSent.PartialPayment(UUID.randomUUID(), 42000 msat, 1000 msat, randomBytes32(), None, startedAt = 0 unixms, settledAt = 0 unixms)
- val pp5b = PaymentSent.PartialPayment(UUID.randomUUID(), 42100 msat, 900 msat, randomBytes32(), None, startedAt = 1 unixms, settledAt = 1 unixms)
+ val pp5a = PaymentSent.PaymentPart(UUID.randomUUID(), PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 42000 msat, 0 unixms), 1000 msat, None, startedAt = 0 unixms)
+ val pp5b = PaymentSent.PaymentPart(UUID.randomUUID(), PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 42100 msat, 1 unixms), 900 msat, None, startedAt = 1 unixms)
val e5 = PaymentSent(UUID.randomUUID(), randomBytes32(), 84100 msat, randomKey().publicKey, pp5a :: pp5b :: Nil, None, startedAt = 0 unixms)
- val pp6 = PaymentSent.PartialPayment(UUID.randomUUID(), 42000 msat, 1000 msat, randomBytes32(), None, startedAt = now + 10.minutes, settledAt = now + 10.minutes)
+ val pp6 = PaymentSent.PaymentPart(UUID.randomUUID(), PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 42000 msat, settledAt = now + 10.minutes), 1000 msat, None, startedAt = now + 10.minutes)
val e6 = PaymentSent(UUID.randomUUID(), randomBytes32(), 42000 msat, randomKey().publicKey, pp6 :: Nil, None, startedAt = now + 10.minutes)
val e7 = ChannelEvent(randomBytes32(), randomKey().publicKey, randomTxId(), 456123000 sat, isChannelOpener = true, isPrivate = false, ChannelEvent.EventType.Closed(MutualClose(null)))
val e10 = TrampolinePaymentRelayed(randomBytes32(),
Seq(
- PaymentRelayed.IncomingPart(20000 msat, randomBytes32(), now - 7.seconds),
- PaymentRelayed.IncomingPart(22000 msat, randomBytes32(), now - 5.seconds)
+ PaymentEvent.IncomingPayment(randomBytes32(), dummyRemoteNodeId, 20000 msat, now - 7.seconds),
+ PaymentEvent.IncomingPayment(randomBytes32(), dummyRemoteNodeId, 22000 msat, now - 5.seconds)
),
Seq(
- PaymentRelayed.OutgoingPart(10000 msat, randomBytes32(), now + 1.milli),
- PaymentRelayed.OutgoingPart(12000 msat, randomBytes32(), now + 2.milli),
- PaymentRelayed.OutgoingPart(15000 msat, randomBytes32(), now + 3.milli)
+ PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 10000 msat, now + 1.milli),
+ PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 12000 msat, now + 2.milli),
+ PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 15000 msat, now + 3.milli)
),
randomKey().publicKey, 30000 msat)
val multiPartPaymentHash = randomBytes32()
- val e11 = ChannelPaymentRelayed(13000 msat, 11000 msat, multiPartPaymentHash, randomBytes32(), randomBytes32(), now - 5.seconds, now + 4.milli)
- val e12 = ChannelPaymentRelayed(15000 msat, 12500 msat, multiPartPaymentHash, randomBytes32(), randomBytes32(), now - 4.seconds, now + 5.milli)
+ val e11 = ChannelPaymentRelayed(multiPartPaymentHash, PaymentEvent.IncomingPayment(randomBytes32(), dummyRemoteNodeId, 13000 msat, now - 5.seconds), PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 11000 msat, now + 4.milli))
+ val e12 = ChannelPaymentRelayed(multiPartPaymentHash, PaymentEvent.IncomingPayment(randomBytes32(), dummyRemoteNodeId, 15000 msat, now - 4.seconds), PaymentEvent.OutgoingPayment(randomBytes32(), dummyRemoteNodeId, 12500 msat, now + 5.milli))
db.add(e1)
db.add(e2)
@@ -141,13 +144,13 @@ class AuditDbSpec extends AnyFunSuite {
val c5 = c1.copy(bytes = 0x05b +: c1.tail)
val c6 = c1.copy(bytes = 0x06b +: c1.tail)
- db.add(ChannelPaymentRelayed(46000 msat, 44000 msat, randomBytes32(), c6, c1, 1000 unixms, 1001 unixms))
- db.add(ChannelPaymentRelayed(41000 msat, 40000 msat, randomBytes32(), c6, c1, 1002 unixms, 1003 unixms))
- db.add(ChannelPaymentRelayed(43000 msat, 42000 msat, randomBytes32(), c5, c1, 1004 unixms, 1005 unixms))
- db.add(ChannelPaymentRelayed(42000 msat, 40000 msat, randomBytes32(), c5, c2, 1006 unixms, 1007 unixms))
- db.add(ChannelPaymentRelayed(45000 msat, 40000 msat, randomBytes32(), c5, c6, 1008 unixms, 1009 unixms))
- db.add(TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.IncomingPart(25000 msat, c6, 1010 unixms)), Seq(PaymentRelayed.OutgoingPart(20000 msat, c4, 1011 unixms)), randomKey().publicKey, 15000 msat))
- db.add(TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.IncomingPart(46000 msat, c6, 1012 unixms)), Seq(PaymentRelayed.OutgoingPart(16000 msat, c2, 1013 unixms), PaymentRelayed.OutgoingPart(10000 msat, c4, 1014 unixms), PaymentRelayed.OutgoingPart(14000 msat, c4, 1015 unixms)), randomKey().publicKey, 37000 msat))
+ db.add(ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(c6, randomKey().publicKey, 46000 msat, 1000 unixms), PaymentEvent.OutgoingPayment(c1, randomKey().publicKey, 44000 msat, 1001 unixms)))
+ db.add(ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(c6, randomKey().publicKey, 41000 msat, 1002 unixms), PaymentEvent.OutgoingPayment(c1, randomKey().publicKey, 40000 msat, 1003 unixms)))
+ db.add(ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(c5, randomKey().publicKey, 43000 msat, 1004 unixms), PaymentEvent.OutgoingPayment(c1, randomKey().publicKey, 42000 msat, 1005 unixms)))
+ db.add(ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(c5, randomKey().publicKey, 42000 msat, 1006 unixms), PaymentEvent.OutgoingPayment(c2, randomKey().publicKey, 40000 msat, 1007 unixms)))
+ db.add(ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(c5, randomKey().publicKey, 45000 msat, 1008 unixms), PaymentEvent.OutgoingPayment(c6, randomKey().publicKey, 40000 msat, 1009 unixms)))
+ db.add(TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentEvent.IncomingPayment(c6, randomKey().publicKey, 25000 msat, 1010 unixms)), Seq(PaymentEvent.OutgoingPayment(c4, randomKey().publicKey, 20000 msat, 1011 unixms)), randomKey().publicKey, 15000 msat))
+ db.add(TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentEvent.IncomingPayment(c6, randomKey().publicKey, 46000 msat, 1012 unixms)), Seq(PaymentEvent.OutgoingPayment(c2, randomKey().publicKey, 16000 msat, 1013 unixms), PaymentEvent.OutgoingPayment(c4, randomKey().publicKey, 10000 msat, 1014 unixms), PaymentEvent.OutgoingPayment(c4, randomKey().publicKey, 14000 msat, 1015 unixms)), randomKey().publicKey, 37000 msat))
// The following confirmed txs will be taken into account.
db.add(TransactionPublished(c2, n2, Transaction(0, Seq.empty, Seq(TxOut(5000 sat, hex"12345")), 0), 200 sat, "funding"))
@@ -209,12 +212,12 @@ class AuditDbSpec extends AnyFunSuite {
// 25% trampoline relays.
if (Random.nextInt(4) == 0) {
val outgoingCount = 1 + Random.nextInt(4)
- val incoming = Seq(PaymentRelayed.IncomingPart(10000 msat, randomBytes32(), TimestampMilli.now() - 3.seconds))
- val outgoing = (1 to outgoingCount).map(_ => PaymentRelayed.OutgoingPart(Random.nextInt(2000).msat, channelIds(Random.nextInt(channelCount)), TimestampMilli.now()))
+ val incoming = Seq(PaymentEvent.IncomingPayment(randomBytes32(), randomKey().publicKey, 10000 msat, TimestampMilli.now() - 3.seconds))
+ val outgoing = (1 to outgoingCount).map(_ => PaymentEvent.OutgoingPayment(channelIds(Random.nextInt(channelCount)), randomKey().publicKey, Random.nextInt(2000).msat, TimestampMilli.now()))
db.add(TrampolinePaymentRelayed(randomBytes32(), incoming, outgoing, randomKey().publicKey, 5000 msat))
} else {
val toChannelId = channelIds(Random.nextInt(channelCount))
- db.add(ChannelPaymentRelayed(10000 msat, Random.nextInt(10000).msat, randomBytes32(), randomBytes32(), toChannelId, TimestampMilli.now() - 2.seconds, TimestampMilli.now()))
+ db.add(ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(randomBytes32(), randomKey().publicKey, 10000 msat, TimestampMilli.now() - 2.seconds), PaymentEvent.OutgoingPayment(toChannelId, randomKey().publicKey, Random.nextInt(10000).msat, TimestampMilli.now())))
}
})
// Test starts here.
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala
index 238e6a2..9700767 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala
@@ -200,7 +200,7 @@ class PaymentsDbSpec extends AnyFunSuite {
val ps6 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), Some("3"), randomBytes32(), PaymentType.Standard, 789 msat, 789 msat, bob, 1250 unixms, None, None, OutgoingPaymentStatus.Failed(Nil, 1300 unixms))
db.addOutgoingPayment(ps4)
db.addOutgoingPayment(ps5.copy(status = OutgoingPaymentStatus.Pending))
- db.updateOutgoingPayment(PaymentSent(ps5.parentId, preimage1, ps5.amount, ps5.recipientNodeId, Seq(PaymentSent.PartialPayment(ps5.id, ps5.amount, 42 msat, randomBytes32(), None, 1000 unixms, 1180 unixms)), None, 900 unixms))
+ db.updateOutgoingPayment(PaymentSent(ps5.parentId, preimage1, ps5.amount, ps5.recipientNodeId, Seq(PaymentSent.PaymentPart(ps5.id, PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, ps5.amount, 1180 unixms), 42 msat, None, 1000 unixms)), None, 900 unixms))
db.addOutgoingPayment(ps6.copy(status = OutgoingPaymentStatus.Pending))
db.updateOutgoingPayment(PaymentFailed(ps6.id, ps6.paymentHash, Nil, 1100 unixms, 1300 unixms))
@@ -772,11 +772,11 @@ class PaymentsDbSpec extends AnyFunSuite {
assert(db.getOutgoingPayment(s4.id).contains(ss4))
// can't update again once it's in a final state
- assertThrows[IllegalArgumentException](db.updateOutgoingPayment(PaymentSent(parentId, preimage1, s3.recipientAmount, s3.recipientNodeId, Seq(PaymentSent.PartialPayment(s3.id, s3.amount, 42 msat, randomBytes32(), None, startedAt = 100 unixms, settledAt = 500 unixms)), None, 100 unixms)))
+ assertThrows[IllegalArgumentException](db.updateOutgoingPayment(PaymentSent(parentId, preimage1, s3.recipientAmount, s3.recipientNodeId, Seq(PaymentSent.PaymentPart(s3.id, PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, s3.amount, settledAt = 500 unixms), 42 msat, None, startedAt = 100 unixms)), None, startedAt = 100 unixms)))
val paymentSent = PaymentSent(parentId, preimage1, 600 msat, carol, Seq(
- PaymentSent.PartialPayment(s1.id, s1.amount, 15 msat, randomBytes32(), None, startedAt = 200 unixms, settledAt = 400 unixms),
- PaymentSent.PartialPayment(s2.id, s2.amount, 20 msat, randomBytes32(), Some(Seq(hop_ab, hop_bc)), startedAt = 210 unixms, settledAt = 410 unixms)
+ PaymentSent.PaymentPart(s1.id, PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, s1.amount, settledAt = 400 unixms), 15 msat, None, startedAt = 200 unixms),
+ PaymentSent.PaymentPart(s2.id, PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, s2.amount, settledAt = 410 unixms), 20 msat, Some(Seq(hop_ab, hop_bc)), startedAt = 210 unixms)
), None, startedAt = 100 unixms)
val ss1 = s1.copy(status = OutgoingPaymentStatus.Succeeded(preimage1, 15 msat, Nil, 400 unixms))
val ss2 = s2.copy(status = OutgoingPaymentStatus.Succeeded(preimage1, 20 msat, Seq(HopSummary(alice, bob, Some(ShortChannelId(42))), HopSummary(bob, carol, None)), 410 unixms))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/PgUtilsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/PgUtilsSpec.scala
index 3ce41af..89bfd76 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/db/PgUtilsSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/PgUtilsSpec.scala
@@ -6,7 +6,7 @@ import fr.acinq.eclair.db.DbEventHandler.ChannelEvent
import fr.acinq.eclair.db.pg.PgUtils.ExtendedResultSet._
import fr.acinq.eclair.db.pg.PgUtils.PgLock.{LeaseLock, LockFailure, LockFailureHandler}
import fr.acinq.eclair.db.pg.PgUtils.{migrateTable, using}
-import fr.acinq.eclair.payment.ChannelPaymentRelayed
+import fr.acinq.eclair.payment.{ChannelPaymentRelayed, PaymentEvent}
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec
import fr.acinq.eclair.wire.protocol.Color
@@ -173,7 +173,7 @@ class PgUtilsSpec extends TestKitBaseClass with AnyFunSuiteLike with Eventually
db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-A", Color(50, 99, -80), Nil, Features.empty, TimestampSecond.now() - 45.days))
db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-B", Color(50, 99, -80), Nil, Features.empty, TimestampSecond.now() - 3.days))
db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-C", Color(50, 99, -80), Nil, Features.empty, TimestampSecond.now() - 7.minutes))
- db.audit.add(ChannelPaymentRelayed(421 msat, 400 msat, randomBytes32(), randomBytes32(), randomBytes32(), TimestampMilli.now() - 5.seconds, TimestampMilli.now() - 3.seconds))
+ db.audit.add(ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(randomBytes32(), randomKey().publicKey, 421 msat, TimestampMilli.now() - 5.seconds), PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, 400 msat, TimestampMilli.now() - 3.seconds)))
db.dataSource.close()
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala
index 4af0971..761da2b 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala
@@ -31,7 +31,7 @@ import fr.acinq.eclair.crypto.Sphinx.DecryptedFailurePacket
import fr.acinq.eclair.io.{Peer, Switchboard}
import fr.acinq.eclair.payment._
import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceiveStandardPayment
-import fr.acinq.eclair.payment.receive.{ForwardHandler, PaymentHandler}
+import fr.acinq.eclair.payment.receive.{ForwardHandler, MultiPartHandler, PaymentHandler}
import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentToNode
import fr.acinq.eclair.router.Router
import fr.acinq.eclair.transactions.{OutgoingHtlc, Scripts, Transactions}
@@ -143,7 +143,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
paymentSender.send(nodes("A").paymentInitiator, paymentReq)
val paymentId = paymentSender.expectMsgType[UUID]
// F gets the htlc
- val htlc = htlcReceiver.expectMsgType[IncomingPaymentPacket.FinalPacket](max = 60 seconds).add
+ val htlc = htlcReceiver.expectMsgType[MultiPartHandler.ReceivePacket](max = 60 seconds).packet.add
// now that we have the channel id, we retrieve channels default final addresses
sender.send(nodes("C").register, Register.Forward(sender.ref.toTyped[Any], htlc.channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender)))
val dataC = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data
@@ -372,19 +372,19 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
val buffer = TestProbe()
send(100000000 msat, paymentHandlerF, nodes("C").paymentInitiator)
- forwardHandlerF.expectMsgType[IncomingPaymentPacket.FinalPacket](max = 60 seconds)
+ forwardHandlerF.expectMsgType[MultiPartHandler.ReceivePacket](max = 60 seconds)
forwardHandlerF.forward(buffer.ref)
sigListener.expectMsgType[ChannelSignatureReceived]
send(110000000 msat, paymentHandlerF, nodes("C").paymentInitiator)
- forwardHandlerF.expectMsgType[IncomingPaymentPacket.FinalPacket](max = 60 seconds)
+ forwardHandlerF.expectMsgType[MultiPartHandler.ReceivePacket](max = 60 seconds)
forwardHandlerF.forward(buffer.ref)
sigListener.expectMsgType[ChannelSignatureReceived]
send(120000000 msat, paymentHandlerC, nodes("F").paymentInitiator)
- forwardHandlerC.expectMsgType[IncomingPaymentPacket.FinalPacket](max = 60 seconds)
+ forwardHandlerC.expectMsgType[MultiPartHandler.ReceivePacket](max = 60 seconds)
forwardHandlerC.forward(buffer.ref)
sigListener.expectMsgType[ChannelSignatureReceived]
send(130000000 msat, paymentHandlerC, nodes("F").paymentInitiator)
- forwardHandlerC.expectMsgType[IncomingPaymentPacket.FinalPacket](max = 60 seconds)
+ forwardHandlerC.expectMsgType[MultiPartHandler.ReceivePacket](max = 60 seconds)
forwardHandlerC.forward(buffer.ref)
val commitmentsF = sigListener.expectMsgType[ChannelSignatureReceived].commitments
sigListener.expectNoMessage(1 second)
@@ -402,19 +402,19 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
assert(htlcTimeoutTxs.size == 2)
assert(htlcSuccessTxs.size == 2)
// we fulfill htlcs to get the preimages
- buffer.expectMsgType[IncomingPaymentPacket.FinalPacket]
+ buffer.expectMsgType[MultiPartHandler.ReceivePacket]
buffer.forward(paymentHandlerF)
sigListener.expectMsgType[ChannelSignatureReceived]
val preimage1 = sender.expectMsgType[PaymentSent].paymentPreimage
- buffer.expectMsgType[IncomingPaymentPacket.FinalPacket]
+ buffer.expectMsgType[MultiPartHandler.ReceivePacket]
buffer.forward(paymentHandlerF)
sigListener.expectMsgType[ChannelSignatureReceived]
val preimage2 = sender.expectMsgType[PaymentSent].paymentPreimage
- buffer.expectMsgType[IncomingPaymentPacket.FinalPacket]
+ buffer.expectMsgType[MultiPartHandler.ReceivePacket]
buffer.forward(paymentHandlerC)
sigListener.expectMsgType[ChannelSignatureReceived]
sender.expectMsgType[PaymentSent]
- buffer.expectMsgType[IncomingPaymentPacket.FinalPacket]
+ buffer.expectMsgType[MultiPartHandler.ReceivePacket]
buffer.forward(paymentHandlerC)
sigListener.expectMsgType[ChannelSignatureReceived]
sender.expectMsgType[PaymentSent]
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
index cad805f..3e57e05 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
@@ -383,7 +383,7 @@ class PaymentIntegrationSpec extends IntegrationSpec {
assert(sent.length == 1, sent)
val paymentSent1 = paymentSent.copy(
// We don't store the route in the DB, and don't store the startedAt timestamp yet (we set it to the same value as settledAt).
- parts = paymentSent.parts.map(p => p.copy(route = None, startedAt = p.settledAt)).sortBy(_.settledAt),
+ parts = paymentSent.parts.map(p => p.copy(payment = p.payment.copy(remoteNodeId = PrivateKey(ByteVector32.One).publicKey), route = None, startedAt = p.settledAt)).sortBy(_.settledAt),
// We don't store attribution data in the DB.
remainingAttribution_opt = None,
startedAt = 0 unixms,
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala
index 857eb29..ffdd099 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala
@@ -30,7 +30,7 @@ import fr.acinq.eclair.crypto.{ShaChain, Sphinx}
import fr.acinq.eclair.db.OfferData
import fr.acinq.eclair.io.Peer
import fr.acinq.eclair.io.Peer.PeerInfo
-import fr.acinq.eclair.payment.{ChannelPaymentRelayed, Invoice}
+import fr.acinq.eclair.payment.{ChannelPaymentRelayed, Invoice, PaymentEvent}
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions.{CommitmentSpec, IncomingHtlc, OutgoingHtlc, Transactions}
import fr.acinq.eclair.wire.internal.channel.ChannelCodecs
@@ -282,16 +282,16 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat
val expectedLocalOrigin = """{"paymentId":"11111111-1111-1111-1111-111111111111"}"""
JsonSerializers.serialization.write(localOrigin)(org.json4s.DefaultFormats + OriginSerializer) shouldBe expectedLocalOrigin
- val channelOrigin = Origin.Cold(Upstream.Cold.Channel(ByteVector32(hex"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), 7, 500 msat))
- val expectedChannelOrigin = """{"channelId":"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f","htlcId":7,"amount":500}"""
+ val channelOrigin = Origin.Cold(Upstream.Cold.Channel(ByteVector32(hex"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), PublicKey(hex"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), 7, 500 msat))
+ val expectedChannelOrigin = """{"channelId":"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f","remoteNodeId":"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","htlcId":7,"amount":500}"""
JsonSerializers.serialization.write(channelOrigin)(org.json4s.DefaultFormats + OriginSerializer) shouldBe expectedChannelOrigin
val relayedHtlcs = List(
- Upstream.Cold.Channel(ByteVector32(hex"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692"), 3, 600 msat),
- Upstream.Cold.Channel(ByteVector32(hex"70685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), 7, 500 msat),
+ Upstream.Cold.Channel(ByteVector32(hex"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692"), PublicKey(hex"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), 3, 600 msat),
+ Upstream.Cold.Channel(ByteVector32(hex"70685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), 7, 500 msat),
)
val trampolineOrigin = Origin.Cold(Upstream.Cold.Trampoline(relayedHtlcs))
- val expectedTrampolineOrigin = """[{"channelId":"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692","htlcId":3,"amount":600},{"channelId":"70685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","htlcId":7,"amount":500}]"""
+ val expectedTrampolineOrigin = """[{"channelId":"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692","remoteNodeId":"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","htlcId":3,"amount":600},{"channelId":"70685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","remoteNodeId":"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f","htlcId":7,"amount":500}]"""
JsonSerializers.serialization.write(trampolineOrigin)(org.json4s.DefaultFormats + OriginSerializer) shouldBe expectedTrampolineOrigin
}
@@ -404,7 +404,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat
}
test("type hints") {
- val e1 = ChannelPaymentRelayed(110 msat, 100 msat, randomBytes32(), randomBytes32(), randomBytes32(), 100 unixms, 150 unixms)
+ val e1 = ChannelPaymentRelayed(randomBytes32(), PaymentEvent.IncomingPayment(randomBytes32(), randomKey().publicKey, 110 msat, 100 unixms), PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, 100 msat, 150 unixms))
assert(JsonSerializers.serialization.writePretty(e1)(JsonSerializers.formats).contains("\"type\" : \"payment-relayed\""))
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala
index 0557333..715a241 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala
@@ -25,7 +25,6 @@ import fr.acinq.eclair.TestConstants.Alice
import fr.acinq.eclair.channel._
import fr.acinq.eclair.db.{IncomingBlindedPayment, IncomingPaymentStatus, IncomingStandardPayment, PaymentType}
import fr.acinq.eclair.payment.Bolt11Invoice.ExtraHop
-import fr.acinq.eclair.payment.PaymentReceived.PartialPayment
import fr.acinq.eclair.payment.offer.OfferManager
import fr.acinq.eclair.payment.receive.MultiPartHandler._
import fr.acinq.eclair.payment.receive.MultiPartPaymentFSM.HtlcPart
@@ -105,6 +104,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
import f._
val amountMsat = 42000 msat
+ val receivedFrom = randomKey().publicKey
val receivedAt = TimestampMilli.now()
{
@@ -117,11 +117,11 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(Crypto.sha256(incoming.get.paymentPreimage) == invoice.paymentHash)
val add = UpdateAddHtlc(ByteVector32.One, 1, amountMsat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt))
+ sender.send(handlerWithoutMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt), receivedFrom))
assert(register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]].message.id == add.id)
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
- assert(paymentReceived == PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, receivedAt) :: Nil))
+ assert(paymentReceived == PaymentReceived(add.paymentHash, PaymentEvent.IncomingPayment(add.channelId, receivedFrom, amountMsat, receivedAt) :: Nil))
val received = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received] == IncomingPaymentStatus.Received(amountMsat, receivedAt))
@@ -133,11 +133,11 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt11Invoice]
val add = UpdateAddHtlc(ByteVector32.One, 1, 75_000 msat, invoice.paymentHash, defaultExpiry + CltvExpiryDelta(12), TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(70_000 msat, 70_000 msat, defaultExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt))
+ sender.send(handlerWithoutMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(70_000 msat, 70_000 msat, defaultExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt), receivedFrom))
assert(register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]].message.id == add.id)
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
- assert(paymentReceived == PaymentReceived(add.paymentHash, PartialPayment(add.amountMsat, add.channelId, receivedAt) :: Nil))
+ assert(paymentReceived == PaymentReceived(add.paymentHash, PaymentEvent.IncomingPayment(add.channelId, receivedFrom, add.amountMsat, receivedAt) :: Nil))
val received = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received] == IncomingPaymentStatus.Received(add.amountMsat, receivedAt))
@@ -151,11 +151,11 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
val add = UpdateAddHtlc(ByteVector32.One, 2, amountMsat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt), receivedFrom))
assert(register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]].message.id == add.id)
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
- assert(paymentReceived == PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, receivedAt) :: Nil))
+ assert(paymentReceived == PaymentReceived(add.paymentHash, PaymentEvent.IncomingPayment(add.channelId, receivedFrom, amountMsat, receivedAt) :: Nil))
val received = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received] == IncomingPaymentStatus.Received(amountMsat, receivedAt))
@@ -176,7 +176,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).isEmpty)
val finalPacket = createBlindedPacket(amountMsat, invoice.paymentHash, defaultExpiry, CltvExpiry(nodeParams.currentBlockHeight), pathId, receivedAt)
- sender.send(handlerWithRouteBlinding, finalPacket)
+ sender.send(handlerWithRouteBlinding, ReceivePacket(finalPacket, receivedFrom))
val receivePayment = offerManager.expectMsgType[OfferManager.ReceivePayment]
assert(receivePayment.paymentHash == invoice.paymentHash)
assert(receivePayment.payload.pathId == pathId.bytes)
@@ -185,7 +185,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]].message.id == finalPacket.add.id)
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
- assert(paymentReceived == PaymentReceived(finalPacket.add.paymentHash, PartialPayment(amountMsat, finalPacket.add.channelId, receivedAt) :: Nil))
+ assert(paymentReceived == PaymentReceived(finalPacket.add.paymentHash, PaymentEvent.IncomingPayment(finalPacket.add.channelId, receivedFrom, amountMsat, receivedAt) :: Nil))
val received = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received] == IncomingPaymentStatus.Received(amountMsat, receivedAt))
@@ -198,7 +198,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, invoice.paymentHash, CltvExpiryDelta(3).toCltvExpiry(nodeParams.currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt), receivedFrom))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(amountMsat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -331,7 +331,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.isExpired())
val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithoutMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
val Some(incoming) = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(incoming.invoice.isExpired() && incoming.status == IncomingPaymentStatus.Expired)
@@ -346,7 +346,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.isExpired())
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
val Some(incoming) = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
@@ -361,7 +361,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(!invoice.features.hasFeature(BasicMultiPartPayment))
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithoutMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -376,7 +376,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val lowCltvExpiry = nodeParams.channelConf.fulfillSafetyBeforeTimeout.toCltvExpiry(nodeParams.currentBlockHeight)
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, lowCltvExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -390,7 +390,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.features.hasFeature(BasicMultiPartPayment))
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash.reverse, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -404,7 +404,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.features.hasFeature(BasicMultiPartPayment))
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 999 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 999 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(999 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -418,7 +418,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.features.hasFeature(BasicMultiPartPayment))
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 2001 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 2001 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(2001 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -433,7 +433,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
// Invalid payment secret.
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -446,7 +446,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt11Invoice]
val packet = createBlindedPacket(1000 msat, invoice.paymentHash, defaultExpiry, CltvExpiry(nodeParams.currentBlockHeight), randomBytes32())
- sender.send(handlerWithRouteBlinding, packet)
+ sender.send(handlerWithRouteBlinding, ReceivePacket(packet, invoice.nodeId))
val receivePayment = offerManager.expectMsgType[OfferManager.ReceivePayment]
assert(receivePayment.paymentHash == invoice.paymentHash)
receivePayment.replyTo ! GetIncomingPaymentActor.RejectPayment("non blinded payment")
@@ -465,7 +465,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt12Invoice]
val add = UpdateAddHtlc(ByteVector32.One, 0, 5000 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, randomBytes32(), None, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, randomBytes32(), None, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(5000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).isEmpty)
@@ -484,7 +484,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).isEmpty)
val packet = createBlindedPacket(5000 msat, invoice.paymentHash, defaultExpiry, CltvExpiry(nodeParams.currentBlockHeight), pathId)
- sender.send(handlerWithRouteBlinding, packet)
+ sender.send(handlerWithRouteBlinding, ReceivePacket(packet, nodeKey.publicKey))
val receivePayment = offerManager.expectMsgType[OfferManager.ReceivePayment]
assert(receivePayment.paymentHash == invoice.paymentHash)
assert(receivePayment.payload.pathId == pathId.bytes)
@@ -506,7 +506,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt12Invoice]
val packet = createBlindedPacket(5000 msat, invoice.paymentHash, defaultExpiry, CltvExpiry(nodeParams.currentBlockHeight), pathId)
- sender.send(handlerWithRouteBlinding, packet)
+ sender.send(handlerWithRouteBlinding, ReceivePacket(packet, nodeKey.publicKey))
val payment = offerManager.expectMsgType[OfferManager.ReceivePayment]
assert(payment.payload.pathId == pathId)
payment.replyTo ! GetIncomingPaymentActor.RejectPayment("internal error")
@@ -527,7 +527,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
// We test the case where the HTLC's cltv_expiry is lower than expected and doesn't meet the min_final_expiry_delta.
val packet = createBlindedPacket(5000 msat, invoice.paymentHash, defaultExpiry - CltvExpiryDelta(1), defaultExpiry, pathId)
- sender.send(handlerWithRouteBlinding, packet)
+ sender.send(handlerWithRouteBlinding, ReceivePacket(packet, nodeKey.publicKey))
val receivePayment = offerManager.expectMsgType[OfferManager.ReceivePayment]
assert(receivePayment.paymentHash == invoice.paymentHash)
assert(receivePayment.payload.pathId == pathId.bytes)
@@ -546,15 +546,17 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
f.sender.send(handler, ReceiveStandardPayment(f.sender.ref, Some(1000 msat), Left("1 slow coffee")))
val pr1 = f.sender.expectMsgType[Bolt11Invoice]
val receivedAt1 = TimestampMilli.now()
+ val receivedFrom1 = randomKey().publicKey
val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, pr1.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, pr1.paymentSecret, pr1.paymentMetadata, upgradeAccountability = false), receivedAt1))
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, pr1.paymentSecret, pr1.paymentMetadata, upgradeAccountability = false), receivedAt1), receivedFrom1))
// Partial payment exceeding the invoice amount, but incomplete because it promises to overpay.
f.sender.send(handler, ReceiveStandardPayment(f.sender.ref, Some(1500 msat), Left("1 slow latte")))
val pr2 = f.sender.expectMsgType[Bolt11Invoice]
val receivedAt2 = receivedAt1 + 1.millis
+ val receivedFrom2 = randomKey().publicKey
val add2 = UpdateAddHtlc(ByteVector32.One, 1, 1600 msat, pr2.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 2000 msat, add2.cltvExpiry, pr2.paymentSecret, pr2.paymentMetadata, upgradeAccountability = false), receivedAt2))
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 2000 msat, add2.cltvExpiry, pr2.paymentSecret, pr2.paymentMetadata, upgradeAccountability = false), receivedAt2), receivedFrom2))
awaitCond {
f.sender.send(handler, GetPendingPayments)
@@ -573,7 +575,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
// Extraneous HTLCs should be failed.
val receivedAt3 = receivedAt1 + 2.millis
- f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr1.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 42, 200 msat, pr1.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None, accountable = false, None), receivedAt3), Some(PaymentTimeout())))
+ val receivedFrom3 = randomKey().publicKey
+ f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr1.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 42, 200 msat, pr1.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None, accountable = false, None), receivedFrom3, receivedAt3), Some(PaymentTimeout())))
f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(42, FailureReason.LocalFailure(PaymentTimeout()), Some(FailureAttributionData(receivedAt3, None)), commit = true)))
// The payment should still be pending in DB.
@@ -590,15 +593,18 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = f.sender.expectMsgType[Bolt11Invoice]
val receivedAt1 = TimestampMilli.now()
+ val receivedFrom1 = randomKey().publicKey
val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1))
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1), receivedFrom1))
// Invalid payment secret -> should be rejected.
val receivedAt2 = receivedAt1 + 1.millis
+ val receivedFrom2 = randomKey().publicKey
val add2 = UpdateAddHtlc(ByteVector32.Zeroes, 42, 200 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1000 msat, add2.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2))
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1000 msat, add2.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2), receivedFrom2))
val receivedAt3 = receivedAt1 + 2.millis
+ val receivedFrom3 = randomKey().publicKey
val add3 = add2.copy(id = 43)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt3))
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt3), receivedFrom3))
f.register.expectMsgAllOf(
Register.Forward(null, add2.channelId, CMD_FAIL_HTLC(add2.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)), Some(FailureAttributionData(receivedAt2, None)), commit = true)),
@@ -607,7 +613,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
)
val paymentReceived = f.eventListener.expectMsgType[PaymentReceived]
- assert(paymentReceived.parts.toSet == Set(PartialPayment(800 msat, ByteVector32.One, receivedAt1), PartialPayment(200 msat, ByteVector32.Zeroes, receivedAt3)))
+ assert(paymentReceived.parts.toSet == Set(PaymentEvent.IncomingPayment(ByteVector32.One, receivedFrom1, 800 msat, receivedAt1), PaymentEvent.IncomingPayment(ByteVector32.Zeroes, receivedFrom3, 200 msat, receivedAt3)))
val received = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].amount == 1000.msat)
@@ -618,7 +624,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
// Extraneous HTLCs should be fulfilled.
val receivedAt4 = receivedAt1 + 3.millis
- f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(invoice.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 44, 200 msat, invoice.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None, accountable = false, None), receivedAt4), None))
+ val receivedFrom4 = randomKey().publicKey
+ f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(invoice.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 44, 200 msat, invoice.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None, accountable = false, None), receivedFrom4, receivedAt4), None))
f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FULFILL_HTLC(44, preimage, Some(FulfillAttributionData(receivedAt4, None, None)), commit = true)))
assert(f.eventListener.expectMsgType[PaymentReceived].amount == 200.msat)
val received2 = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
@@ -638,10 +645,12 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val add1 = UpdateAddHtlc(randomBytes32(), 0, 1100 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt1 = TimestampMilli.now()
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1500 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1))
+ val receivedFrom1 = randomKey().publicKey
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1500 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1), receivedFrom1))
val add2 = UpdateAddHtlc(randomBytes32(), 1, 500 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt2 = TimestampMilli.now() + 5.millis
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1500 msat, add2.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2))
+ val receivedFrom2 = randomKey().publicKey
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1500 msat, add2.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2), receivedFrom2))
f.register.expectMsgAllOf(
Register.Forward(null, add1.channelId, CMD_FULFILL_HTLC(add1.id, preimage, Some(FulfillAttributionData(receivedAt1, None, None)), commit = true)),
@@ -649,7 +658,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
)
val paymentReceived = f.eventListener.expectMsgType[PaymentReceived]
- assert(paymentReceived.parts.toSet == Set(PartialPayment(1100 msat, add1.channelId, receivedAt1), PartialPayment(500 msat, add2.channelId, receivedAt2)))
+ assert(paymentReceived.parts.toSet == Set(PaymentEvent.IncomingPayment(add1.channelId, receivedFrom1, 1100 msat, receivedAt1), PaymentEvent.IncomingPayment(add2.channelId, receivedFrom2, 500 msat, receivedAt2)))
val received = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].amount == 1600.msat)
@@ -667,7 +676,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt1 = TimestampMilli.now()
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1))
+ val receivedFrom1 = randomKey().publicKey
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1), receivedFrom1))
f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(0, FailureReason.LocalFailure(PaymentTimeout()), Some(FailureAttributionData(receivedAt1, None)), commit = true)))
awaitCond({
f.sender.send(handler, GetPendingPayments)
@@ -676,10 +686,12 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val add2 = UpdateAddHtlc(ByteVector32.One, 2, 300 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt2 = TimestampMilli.now() + 10.millis
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1000 msat, add2.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2))
+ val receivedFrom2 = randomKey().publicKey
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1000 msat, add2.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2), receivedFrom2))
val add3 = UpdateAddHtlc(ByteVector32.Zeroes, 5, 700 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt3 = TimestampMilli.now() + 50.millis
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt3))
+ val receivedFrom3 = randomKey().publicKey
+ f.sender.send(handler, ReceivePacket(IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt3), receivedFrom3))
// the fulfill are not necessarily in the same order as the commands
f.register.expectMsgAllOf(
@@ -689,7 +701,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val paymentReceived = f.eventListener.expectMsgType[PaymentReceived]
assert(paymentReceived.paymentHash == invoice.paymentHash)
- assert(paymentReceived.parts.toSet == Set(PartialPayment(300 msat, ByteVector32.One, receivedAt2), PartialPayment(700 msat, ByteVector32.Zeroes, receivedAt3)))
+ assert(paymentReceived.parts.toSet == Set(PaymentEvent.IncomingPayment(ByteVector32.One, receivedFrom2, 300 msat, receivedAt2), PaymentEvent.IncomingPayment(ByteVector32.Zeroes, receivedFrom3, 700 msat, receivedAt3)))
val received = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].amount == 1000.msat)
@@ -712,11 +724,12 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt = TimestampMilli.now()
- sender.send(handlerWithKeySend, IncomingPaymentPacket.FinalPacket(add, payload, receivedAt))
+ val receivedFrom = randomKey().publicKey
+ sender.send(handlerWithKeySend, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, payload, receivedAt), receivedFrom))
register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]]
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
- assert(paymentReceived == PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, receivedAt) :: Nil))
+ assert(paymentReceived == PaymentReceived(add.paymentHash, PaymentEvent.IncomingPayment(add.channelId, receivedFrom, amountMsat, receivedAt) :: Nil))
val received = nodeParams.db.payments.getIncomingPayment(paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received] == IncomingPaymentStatus.Received(amountMsat, receivedAt))
@@ -734,11 +747,12 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt = TimestampMilli.now()
- sender.send(handlerWithKeySend, IncomingPaymentPacket.FinalPacket(add, payload, receivedAt))
+ val receivedFrom = randomKey().publicKey
+ sender.send(handlerWithKeySend, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, payload, receivedAt), receivedFrom))
register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]]
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
- assert(paymentReceived == PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, receivedAt) :: Nil))
+ assert(paymentReceived == PaymentReceived(add.paymentHash, PaymentEvent.IncomingPayment(add.channelId, receivedFrom, amountMsat, receivedAt) :: Nil))
val received = nodeParams.db.payments.getIncomingPayment(paymentHash)
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received] == IncomingPaymentStatus.Received(amountMsat, receivedAt))
@@ -757,7 +771,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt = TimestampMilli.now()
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, payload, receivedAt))
+ val receivedFrom = randomKey().publicKey
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, payload, receivedAt), receivedFrom))
f.register.expectMsg(Register.Forward(null, add.channelId, CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(42000 msat, nodeParams.currentBlockHeight)), Some(FailureAttributionData(receivedAt, None)), commit = true)))
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
@@ -771,7 +786,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, paymentSecret, None, upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithoutMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, paymentSecret, None, upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.id == add.id)
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
@@ -785,7 +800,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, paymentSecret, Some(hex"012345"), upgradeAccountability = false), TimestampMilli.now()))
+ sender.send(handlerWithMpp, ReceivePacket(IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, paymentSecret, Some(hex"012345"), upgradeAccountability = false), TimestampMilli.now()), randomKey().publicKey))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.id == add.id)
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
@@ -801,7 +816,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val invoice = Bolt11Invoice(Block.Testnet3GenesisBlock.hash, None, paymentHash, randomKey(), Left("dummy"), CltvExpiryDelta(12))
val incomingPayment = IncomingStandardPayment(invoice, paymentPreimage, PaymentType.Standard, invoice.createdAt.toTimestampMilli, IncomingPaymentStatus.Pending)
- val fulfill = DoFulfill(incomingPayment, MultiPartPaymentFSM.MultiPartPaymentSucceeded(paymentHash, Queue(HtlcPart(1000 msat, add, TimestampMilli.now()))))
+ val fulfill = DoFulfill(incomingPayment, MultiPartPaymentFSM.MultiPartPaymentSucceeded(paymentHash, Queue(HtlcPart(1000 msat, add, randomKey().publicKey, TimestampMilli.now()))))
sender.send(handlerWithoutMpp, fulfill)
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.id == add.id)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala
index 66beba4..6841e0b 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala
@@ -21,10 +21,9 @@ import akka.testkit.{TestActorRef, TestProbe}
import fr.acinq.bitcoin.scalacompat.ByteVector32
import fr.acinq.eclair.payment.receive.MultiPartPaymentFSM
import fr.acinq.eclair.payment.receive.MultiPartPaymentFSM._
-import fr.acinq.eclair.reputation.Reputation
import fr.acinq.eclair.wire.protocol
import fr.acinq.eclair.wire.protocol.{IncorrectOrUnknownPaymentDetails, UpdateAddHtlc}
-import fr.acinq.eclair.{BlockHeight, CltvExpiry, MilliSatoshi, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, TimestampMilli, randomBytes32}
+import fr.acinq.eclair.{BlockHeight, CltvExpiry, MilliSatoshi, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, TimestampMilli, randomBytes32, randomKey}
import org.scalatest.funsuite.AnyFunSuiteLike
import scodec.bits.ByteVector
@@ -92,7 +91,7 @@ class MultiPartPaymentFSMSpec extends TestKitBaseClass with AnyFunSuiteLike {
f.parent.send(f.handler, extraPart)
val fail = f.parent.expectMsgType[ExtraPaymentReceived[PaymentPart]]
assert(fail.paymentHash == paymentHash)
- assert(fail.failure == Some(protocol.PaymentTimeout()))
+ assert(fail.failure.contains(protocol.PaymentTimeout()))
assert(fail.payment == extraPart)
f.parent.expectNoMessage(50 millis)
@@ -228,13 +227,13 @@ class MultiPartPaymentFSMSpec extends TestKitBaseClass with AnyFunSuiteLike {
object MultiPartPaymentFSMSpec {
- val paymentHash = randomBytes32()
+ val paymentHash: ByteVector32 = randomBytes32()
def htlcIdToChannelId(htlcId: Long) = ByteVector32(ByteVector.fromLong(htlcId).padLeft(32))
def createMultiPartHtlc(totalAmount: MilliSatoshi, htlcAmount: MilliSatoshi, htlcId: Long): HtlcPart = {
val htlc = UpdateAddHtlc(htlcIdToChannelId(htlcId), htlcId, htlcAmount, paymentHash, CltvExpiry(42), TestConstants.emptyOnionPacket, None, accountable = false, None)
- HtlcPart(totalAmount, htlc, TimestampMilli.now())
+ HtlcPart(totalAmount, htlc, randomKey().publicKey, TimestampMilli.now())
}
}
\ No newline at end of file
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
index d7b636d..f9023a2 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
@@ -580,7 +580,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
awaitCond(payFsm.stateName == PAYMENT_ABORTED)
sender.watch(payFsm)
- childPayFsm.send(payFsm, PaymentSent(cfg.id, paymentPreimage, finalAmount, e, Seq(PaymentSent.PartialPayment(successId, successRoute.amount, successRoute.channelFee(false), randomBytes32(), Some(successRoute.fullRoute), 100 unixms, 250 unixms)), None, 75 unixms))
+ childPayFsm.send(payFsm, PaymentSent(cfg.id, paymentPreimage, finalAmount, e, Seq(PaymentSent.PaymentPart(successId, PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, successRoute.amount, 250 unixms), successRoute.channelFee(false), Some(successRoute.fullRoute), 100 unixms)), None, 75 unixms))
sender.expectMsg(PreimageReceived(paymentHash, paymentPreimage, None))
val result = sender.expectMsgType[PaymentSent]
assert(result.id == cfg.id)
@@ -608,7 +608,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
childPayFsm.expectMsgType[SendPaymentToRoute]
val (childId, route) :: (failedId, failedRoute) :: Nil = payFsm.stateData.asInstanceOf[PaymentProgress].pending.toSeq
- childPayFsm.send(payFsm, PaymentSent(cfg.id, paymentPreimage, finalAmount, e, Seq(PaymentSent.PartialPayment(childId, route.amount, route.channelFee(false), randomBytes32(), Some(route.fullRoute), 100 unixms, 250 unixms)), None, 75 unixms))
+ childPayFsm.send(payFsm, PaymentSent(cfg.id, paymentPreimage, finalAmount, e, Seq(PaymentSent.PaymentPart(childId, PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, route.amount, 250 unixms), route.channelFee(false), Some(route.fullRoute), 100 unixms)), None, 75 unixms))
sender.expectMsg(PreimageReceived(paymentHash, paymentPreimage, None))
awaitCond(payFsm.stateName == PAYMENT_SUCCEEDED)
@@ -632,7 +632,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
assert(pending.size == childCount)
val partialPayments = pending.map {
- case (childId, route) => PaymentSent.PartialPayment(childId, route.amount, route.channelFee(false) + route.blindedFee, randomBytes32(), Some(route.fullRoute), 100 unixms, 250 unixms)
+ case (childId, route) => PaymentSent.PaymentPart(childId, PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, route.amount + route.channelFee(false) + route.blindedFee, 250 unixms), route.channelFee(false) + route.blindedFee, Some(route.fullRoute), 100 unixms)
}
partialPayments.foreach(pp => childPayFsm.send(payFsm, PaymentSent(cfg.id, paymentPreimage, finalAmount, e, Seq(pp), None, 100 unixms)))
sender.expectMsg(PreimageReceived(paymentHash, paymentPreimage, None))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala
index 4e43828..b0c7dd4 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala
@@ -28,7 +28,7 @@ import fr.acinq.eclair.channel.Upstream
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.payment.Bolt11Invoice.ExtraHop
import fr.acinq.eclair.payment.PaymentPacketSpec._
-import fr.acinq.eclair.payment.PaymentSent.PartialPayment
+import fr.acinq.eclair.payment.PaymentSent.PaymentPart
import fr.acinq.eclair.payment.send.BlindedPathsResolver.{FullBlindedRoute, ResolvedPath}
import fr.acinq.eclair.payment.send.MultiPartPaymentLifecycle.SendMultiPartPayment
import fr.acinq.eclair.payment.send.PaymentError.UnsupportedFeatures
@@ -231,7 +231,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
sender.send(initiator, GetPayment(PaymentIdentifier.PaymentHash(invoice.paymentHash)))
sender.expectMsg(PaymentIsPending(id, invoice.paymentHash, PendingPaymentToNode(sender.ref, req)))
- val ps = PaymentSent(id, paymentPreimage, finalAmount, priv_c.publicKey, Seq(PartialPayment(UUID.randomUUID(), finalAmount, 0 msat, randomBytes32(), None, 100 unixms, 200 unixms)), None, 80 unixms)
+ val ps = PaymentSent(id, paymentPreimage, finalAmount, priv_c.publicKey, Seq(PaymentPart(UUID.randomUUID(), PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, finalAmount, 200 unixms), 0 msat, None, 100 unixms)), None, 80 unixms)
payFsm.send(initiator, ps)
sender.expectMsg(ps)
eventListener.expectNoMessage(100 millis)
@@ -350,7 +350,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
sender.send(initiator, GetPayment(PaymentIdentifier.PaymentHash(invoice.paymentHash)))
sender.expectMsg(PaymentIsPending(id, invoice.paymentHash, PendingPaymentToNode(sender.ref, req)))
- val ps = PaymentSent(id, paymentPreimage, finalAmount, invoice.nodeId, Seq(PartialPayment(UUID.randomUUID(), finalAmount, 0 msat, randomBytes32(), None, 100 unixms, 200 unixms)), None, 100 unixms)
+ val ps = PaymentSent(id, paymentPreimage, finalAmount, invoice.nodeId, Seq(PaymentPart(UUID.randomUUID(), PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, finalAmount, 200 unixms), 0 msat, None, 100 unixms)), None, 100 unixms)
payFsm.send(initiator, ps)
sender.expectMsg(ps)
eventListener.expectNoMessage(100 millis)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
index c3003ea..fab1526 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
@@ -34,7 +34,7 @@ import fr.acinq.eclair.crypto.{Sphinx, SphinxSpec}
import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus, PaymentType}
import fr.acinq.eclair.io.Peer.PeerRoutingMessage
import fr.acinq.eclair.payment.Invoice.ExtraEdge
-import fr.acinq.eclair.payment.PaymentSent.PartialPayment
+import fr.acinq.eclair.payment.PaymentSent.PaymentPart
import fr.acinq.eclair.payment.relay.Relayer.RelayFees
import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentConfig
import fr.acinq.eclair.payment.send.PaymentLifecycle._
@@ -109,6 +109,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
def addCompleted(result: HtlcResult): RES_ADD_SETTLED[Origin, HtlcResult] = {
RES_ADD_SETTLED(
origin = defaultOrigin,
+ remoteNodeId = randomKey().publicKey,
htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, defaultAmountMsat, defaultPaymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None),
result)
}
@@ -846,8 +847,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
sender.send(paymentFSM, addCompleted(HtlcResult.OnChainFulfill(defaultPaymentPreimage)))
val paymentOK = sender.expectMsgType[PaymentSent]
- val PaymentSent(_, paymentOK.paymentPreimage, finalAmount, _, PartialPayment(_, partAmount, fee, ByteVector32.Zeroes, _, _, _) :: Nil, _, _) = eventListener.expectMsgType[PaymentSent]
- assert(partAmount == request.amount)
+ val PaymentSent(_, paymentOK.paymentPreimage, finalAmount, _, PaymentPart(_, part, fee, _, _) :: Nil, _, _) = eventListener.expectMsgType[PaymentSent]
+ assert(part.amount == request.amount)
assert(finalAmount == defaultAmountMsat)
// NB: A -> B doesn't pay fees because it's our direct neighbor
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala
index ab4c6ec..5c630d6 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala
@@ -23,7 +23,7 @@ import akka.testkit.TestProbe
import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt}
import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxId, TxIn, TxOut}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchTxConfirmedTriggered
-import fr.acinq.eclair.channel.Helpers.{Closing, updateCommitments}
+import fr.acinq.eclair.channel.Helpers.Closing
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.ChannelStateTestsBase
import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus, PaymentType}
@@ -89,9 +89,9 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
// (channel AB2)
val relayedPaymentHash = randomBytes32()
- val relayed = Origin.Cold(Upstream.Cold.Channel(channelId_ab_1, 5, 10 msat))
+ val relayed = Origin.Cold(Upstream.Cold.Channel(channelId_ab_1, a, 5, 10 msat))
val trampolineRelayedPaymentHash = randomBytes32()
- val trampolineRelayed = Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, 0, 1000 msat) :: Upstream.Cold.Channel(channelId_ab_2, 2, 2500 msat) :: Nil))
+ val trampolineRelayed = Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, a, 0, 1000 msat) :: Upstream.Cold.Channel(channelId_ab_2, a, 2, 2500 msat) :: Nil))
val htlc_ab_1 = Seq(
buildHtlcIn(0, channelId_ab_1, trampolineRelayedPaymentHash),
@@ -454,7 +454,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
// Payment 2 should fulfill once we receive the preimage.
val origin_2 = Origin.Cold(Upstream.Cold(upstream_2))
- sender.send(relayer, RES_ADD_SETTLED(origin_2, htlc_2_2, HtlcResult.OnChainFulfill(preimage2)))
+ sender.send(relayer, RES_ADD_SETTLED(origin_2, a, htlc_2_2, HtlcResult.OnChainFulfill(preimage2)))
register.expectMsgAllOf(
Register.Forward(replyTo = null, channelId_ab_1, CMD_FULFILL_HTLC(5, preimage2, None, commit = true)),
Register.Forward(replyTo = null, channelId_ab_2, CMD_FULFILL_HTLC(9, preimage2, None, commit = true))
@@ -502,10 +502,10 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
buildHtlcOut(5, channelId_bc_1, paymentHash2),
)
val origins: Map[Long, Origin] = Map(
- 2L -> Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, 1, 500 msat) :: Upstream.Cold.Channel(channelId_ab_1, 2, 200 msat) :: Nil)),
- 3L -> Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, 1, 500 msat) :: Upstream.Cold.Channel(channelId_ab_1, 2, 200 msat) :: Nil)),
- 4L -> Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, 1, 500 msat) :: Upstream.Cold.Channel(channelId_ab_1, 2, 200 msat) :: Nil)),
- 5L -> Origin.Cold(Upstream.Cold.Channel(channelId_ab_1, 4, 550 msat)),
+ 2L -> Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, a, 1, 500 msat) :: Upstream.Cold.Channel(channelId_ab_1, a, 2, 200 msat) :: Nil)),
+ 3L -> Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, a, 1, 500 msat) :: Upstream.Cold.Channel(channelId_ab_1, a, 2, 200 msat) :: Nil)),
+ 4L -> Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, a, 1, 500 msat) :: Upstream.Cold.Channel(channelId_ab_1, a, 2, 200 msat) :: Nil)),
+ 5L -> Origin.Cold(Upstream.Cold.Channel(channelId_ab_1, a, 4, 550 msat)),
)
val downstreamChannel = {
val normal = ChannelCodecsSpec.makeChannelDataNormal(htlc_bc, origins)
@@ -547,7 +547,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
import f._
val htlc_ab = buildHtlcIn(0, channelId_ab_1, paymentHash1, blinded = true)
- val upstream = Upstream.Cold.Channel(htlc_ab.add)
+ val upstream = Upstream.Cold.Channel(htlc_ab.add, a)
val htlc_bc = buildHtlcOut(6, channelId_bc_1, paymentHash1, blinded = true)
val data_ab = ChannelCodecsSpec.makeChannelDataNormal(Seq(htlc_ab), Map.empty)
val data_bc = ChannelCodecsSpec.makeChannelDataNormal(Seq(htlc_bc), Map(6L -> Origin.Cold(upstream)))
@@ -591,7 +591,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.upstream_1))
val fails = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: Nil
assert(fails.toSet == testCase.upstream_1.originHtlcs.map {
- case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, FailureReason.LocalFailure(TemporaryNodeFailure()), None, commit = true))
+ case Upstream.Cold.Channel(channelId, _, htlcId, _) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, FailureReason.LocalFailure(TemporaryNodeFailure()), None, commit = true))
}.toSet)
sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.upstream_1))
@@ -603,7 +603,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
sender.send(relayer, buildForwardFail(testCase.downstream_2_3, testCase.upstream_2))
register.expectMsg(testCase.upstream_2.originHtlcs.map {
- case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, FailureReason.LocalFailure(TemporaryNodeFailure()), None, commit = true))
+ case Upstream.Cold.Channel(channelId, _, htlcId, _) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, FailureReason.LocalFailure(TemporaryNodeFailure()), None, commit = true))
}.head)
register.expectNoMessage(100 millis)
@@ -622,7 +622,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
sender.send(relayer, buildForwardFulfill(testCase.downstream_1_1, testCase.upstream_1, preimage1))
val fulfills = register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]] :: register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]] :: Nil
assert(fulfills.toSet == testCase.upstream_1.originHtlcs.map {
- case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FULFILL_HTLC(htlcId, preimage1, None, commit = true))
+ case Upstream.Cold.Channel(channelId, _, htlcId, _) => Register.Forward(null, channelId, CMD_FULFILL_HTLC(htlcId, preimage1, None, commit = true))
}.toSet)
sender.send(relayer, buildForwardFulfill(testCase.downstream_1_1, testCase.upstream_1, preimage1))
@@ -631,7 +631,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
// This payment has 3 downstream HTLCs, but we should fulfill upstream as soon as we receive the preimage.
sender.send(relayer, buildForwardFulfill(testCase.downstream_2_1, testCase.upstream_2, preimage2))
register.expectMsg(testCase.upstream_2.originHtlcs.map {
- case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FULFILL_HTLC(htlcId, preimage2, None, commit = true))
+ case Upstream.Cold.Channel(channelId, _, htlcId, _) => Register.Forward(null, channelId, CMD_FULFILL_HTLC(htlcId, preimage2, None, commit = true))
}.head)
sender.send(relayer, buildForwardFulfill(testCase.downstream_2_2, testCase.upstream_2, preimage2))
@@ -651,7 +651,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
sender.send(relayer, buildForwardFail(testCase.downstream_2_1, testCase.upstream_2))
sender.send(relayer, buildForwardFulfill(testCase.downstream_2_2, testCase.upstream_2, preimage2))
register.expectMsg(testCase.upstream_2.originHtlcs.map {
- case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FULFILL_HTLC(htlcId, preimage2, None, commit = true))
+ case Upstream.Cold.Channel(channelId, _, htlcId, _) => Register.Forward(null, channelId, CMD_FULFILL_HTLC(htlcId, preimage2, None, commit = true))
}.head)
sender.send(relayer, buildForwardFail(testCase.downstream_2_3, testCase.upstream_2))
@@ -677,8 +677,8 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
buildHtlcOut(2, channelId_bc_1, paymentHash2).modify(_.add.tlvStream).setTo(TlvStream(UpdateAddHtlcTlv.FundingFeeTlv(LiquidityAds.FundingFee(1500 msat, TxId(randomBytes32()))))), // trampoline relayed
)
- val upstreamChannel = Upstream.Cold.Channel(htlc_ab(1).add)
- val upstreamTrampoline = Upstream.Cold.Trampoline(Upstream.Cold.Channel(htlc_ab(2).add) :: Nil)
+ val upstreamChannel = Upstream.Cold.Channel(htlc_ab(1).add, a)
+ val upstreamTrampoline = Upstream.Cold.Trampoline(Upstream.Cold.Channel(htlc_ab(2).add, a) :: Nil)
val data_ab = ChannelCodecsSpec.makeChannelDataNormal(htlc_ab, Map.empty)
val data_bc = ChannelCodecsSpec.makeChannelDataNormal(htlc_bc, Map(1L -> Origin.Cold(upstreamChannel), 2L -> Origin.Cold(upstreamTrampoline)))
@@ -686,7 +686,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
relayer ! PostRestartHtlcCleaner.Init(Seq(data_ab, data_bc))
// HTLC failures are not relayed upstream, as we will retry until we reach the HTLC timeout.
- sender.send(relayer, buildForwardFail(htlc_bc(0).add, Upstream.Cold.Channel(htlc_ab(0).add)))
+ sender.send(relayer, buildForwardFail(htlc_bc(0).add, Upstream.Cold.Channel(htlc_ab(0).add, a)))
sender.send(relayer, buildForwardFail(htlc_bc(0).add, upstreamChannel))
sender.send(relayer, buildForwardOnChainFail(htlc_bc(0).add, upstreamChannel))
sender.send(relayer, buildForwardFail(htlc_bc(1).add, upstreamTrampoline))
@@ -712,7 +712,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
val relayedPaymentHash = randomBytes32()
val trampolineRelayedPaymentHash = randomBytes32()
- val trampolineRelayed = Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_2, 0, 1 msat) :: Nil))
+ val trampolineRelayed = Origin.Cold(Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_2, a, 0, 1 msat) :: Nil))
val relayedHtlcIn = buildHtlcIn(0L, channelId_ab_2, trampolineRelayedPaymentHash)
val nonRelayedHtlcIn = buildHtlcIn(1L, channelId_ab_2, relayedPaymentHash)
@@ -767,13 +767,13 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
object PostRestartHtlcCleanerSpec {
- val channelId_ab_1 = randomBytes32()
- val channelId_ab_2 = randomBytes32()
- val channelId_bc_1 = randomBytes32()
- val channelId_bc_2 = randomBytes32()
- val channelId_bc_3 = randomBytes32()
- val channelId_bc_4 = randomBytes32()
- val channelId_bc_5 = randomBytes32()
+ val channelId_ab_1: ByteVector32 = randomBytes32()
+ val channelId_ab_2: ByteVector32 = randomBytes32()
+ val channelId_bc_1: ByteVector32 = randomBytes32()
+ val channelId_bc_2: ByteVector32 = randomBytes32()
+ val channelId_bc_3: ByteVector32 = randomBytes32()
+ val channelId_bc_4: ByteVector32 = randomBytes32()
+ val channelId_bc_5: ByteVector32 = randomBytes32()
val (preimage1, preimage2, preimage3) = (randomBytes32(), randomBytes32(), randomBytes32())
val (paymentHash1, paymentHash2, paymentHash3) = (Crypto.sha256(preimage1), Crypto.sha256(preimage2), Crypto.sha256(preimage3))
@@ -798,13 +798,13 @@ object PostRestartHtlcCleanerSpec {
}
def buildForwardFail(add: UpdateAddHtlc, upstream: Upstream.Cold): RES_ADD_SETTLED[Origin.Cold, HtlcResult.Fail] =
- RES_ADD_SETTLED(Origin.Cold(upstream), add, HtlcResult.RemoteFail(UpdateFailHtlc(add.channelId, add.id, ByteVector.empty)))
+ RES_ADD_SETTLED(Origin.Cold(upstream), randomKey().publicKey, add, HtlcResult.RemoteFail(UpdateFailHtlc(add.channelId, add.id, ByteVector.empty)))
def buildForwardOnChainFail(add: UpdateAddHtlc, upstream: Upstream.Cold): RES_ADD_SETTLED[Origin.Cold, HtlcResult.Fail] =
- RES_ADD_SETTLED(Origin.Cold(upstream), add, HtlcResult.OnChainFail(HtlcsTimedoutDownstream(add.channelId, Set(add))))
+ RES_ADD_SETTLED(Origin.Cold(upstream), randomKey().publicKey, add, HtlcResult.OnChainFail(HtlcsTimedoutDownstream(add.channelId, Set(add))))
def buildForwardFulfill(add: UpdateAddHtlc, upstream: Upstream.Cold, preimage: ByteVector32): RES_ADD_SETTLED[Origin.Cold, HtlcResult.Fulfill] =
- RES_ADD_SETTLED(Origin.Cold(upstream), add, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add.channelId, add.id, preimage)))
+ RES_ADD_SETTLED(Origin.Cold(upstream), randomKey().publicKey, add, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add.channelId, add.id, preimage)))
case class LocalPaymentTest(channel: PersistentChannelData, parentId: UUID, childIds: Seq[UUID], fails: Seq[RES_ADD_SETTLED[Origin.Cold, HtlcResult.Fail]], fulfills: Seq[RES_ADD_SETTLED[Origin.Cold, HtlcResult.Fulfill]])
@@ -846,7 +846,7 @@ object PostRestartHtlcCleanerSpec {
buildHtlcIn(0, channelId_ab_1, paymentHash1)
)
- val upstream_1 = Upstream.Cold.Channel(htlc_ab_1.head.add)
+ val upstream_1 = Upstream.Cold.Channel(htlc_ab_1.head.add, a)
val htlc_bc_1 = Seq(
buildHtlcOut(6, channelId_bc_1, paymentHash1)
@@ -897,12 +897,12 @@ object PostRestartHtlcCleanerSpec {
buildHtlcOut(9, channelId_ab_2, randomBytes32()) // ignored
)
- val upstream_1 = Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, 0, 500 msat) :: Upstream.Cold.Channel(channelId_ab_2, 7, 250 msat) :: Nil)
- val upstream_2 = Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, 5, 500 msat) :: Nil)
+ val upstream_1 = Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, a, 0, 500 msat) :: Upstream.Cold.Channel(channelId_ab_2, a, 7, 250 msat) :: Nil)
+ val upstream_2 = Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, a, 5, 500 msat) :: Nil)
// The following two origins reference upstream HTLCs that have already been settled.
// They should be ignored by the post-restart clean-up.
- val upstream_3 = Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, 57, 100 msat) :: Nil)
- val upstream_4 = Upstream.Cold.Channel(channelId_ab_2, 57, 150 msat)
+ val upstream_3 = Upstream.Cold.Trampoline(Upstream.Cold.Channel(channelId_ab_1, a, 57, 100 msat) :: Nil)
+ val upstream_4 = Upstream.Cold.Channel(channelId_ab_2, a, 57, 150 msat)
// Downstream HTLCs.
val htlc_bc_1 = Seq(
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala
index 1603964..8120339 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala
@@ -680,7 +680,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
receiveConfidence(Reputation.Score.max(accountable = false))
val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, outAccountable = false)
fwd.message.replyTo ! RES_SUCCESS(fwd.message, channelId1)
- fwd.message.origin.replyTo ! RES_ADD_SETTLED(fwd.message.origin, downstream_htlc, testCase.result)
+ fwd.message.origin.replyTo ! RES_ADD_SETTLED(fwd.message.origin, remoteNodeId1, downstream_htlc, testCase.result)
expectFwdFail(register, r.add.channelId, testCase.cmd)
}
}
@@ -707,7 +707,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
receiveConfidence(Reputation.Score(1.0, accountable = false))
val fwd = expectFwdAdd(register, channelId1, outgoingAmount, outgoingExpiry, outAccountable = false)
fwd.message.replyTo ! RES_SUCCESS(fwd.message, channelId1)
- fwd.message.origin.replyTo ! RES_ADD_SETTLED(fwd.message.origin, downstream, htlcResult)
+ fwd.message.origin.replyTo ! RES_ADD_SETTLED(fwd.message.origin, remoteNodeId1, downstream, htlcResult)
val cmd = register.expectMessageType[Register.Forward[channel.Command]]
assert(cmd.channelId == r.add.channelId)
if (isIntroduction) {
@@ -753,7 +753,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
val fwd1 = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, outAccountable = false)
fwd1.message.replyTo ! RES_SUCCESS(fwd1.message, channelId1)
- fwd1.message.origin.replyTo ! RES_ADD_SETTLED(fwd1.message.origin, downstream_htlc, testCase.result)
+ fwd1.message.origin.replyTo ! RES_ADD_SETTLED(fwd1.message.origin, remoteNodeId2, downstream_htlc, testCase.result)
val fwd2 = register.expectMessageType[Register.Forward[CMD_FULFILL_HTLC]]
assert(fwd2.channelId == r.add.channelId)
@@ -763,9 +763,11 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
val paymentRelayed = eventListener.expectMessageType[ChannelPaymentRelayed]
assert(paymentRelayed.paymentHash == r.add.paymentHash)
assert(paymentRelayed.amountIn == r.add.amountMsat)
- assert(paymentRelayed.fromChannelId == r.add.channelId)
+ assert(paymentRelayed.paymentIn.channelId == r.add.channelId)
+ assert(paymentRelayed.paymentIn.remoteNodeId == TestConstants.Alice.nodeParams.nodeId)
assert(paymentRelayed.amountOut == r.amountToForward)
- assert(paymentRelayed.toChannelId == channelId1)
+ assert(paymentRelayed.paymentOut.channelId == channelId1)
+ assert(paymentRelayed.paymentOut.remoteNodeId == remoteNodeId2)
assert(paymentRelayed.startedAt == r.receivedAt)
assert(paymentRelayed.settledAt >= now)
}
@@ -787,7 +789,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
// The downstream HTLC is fulfilled.
val downstream = UpdateAddHtlc(randomBytes32(), 7, outgoingAmount, paymentHash, outgoingExpiry, emptyOnionPacket, None, accountable = false, None)
- fwd1.message.origin.replyTo ! RES_ADD_SETTLED(fwd1.message.origin, downstream, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(downstream.channelId, downstream.id, paymentPreimage)))
+ fwd1.message.origin.replyTo ! RES_ADD_SETTLED(fwd1.message.origin, remoteNodeId2, downstream, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(downstream.channelId, downstream.id, paymentPreimage)))
val fulfill = inside(register.expectMessageType[Register.Forward[CMD_FULFILL_HTLC]]) { fwd =>
assert(fwd.channelId == r.add.channelId)
assert(fwd.message.id == r.add.id)
@@ -799,7 +801,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
eventually(assert(nodeParams.db.pendingCommands.listSettlementCommands(r.add.channelId) == Seq(fulfill.copy(commit = false))))
// The downstream HTLC is now failed (e.g. because a revoked commitment confirmed that doesn't include it): this conflicting command is ignored.
- fwd1.message.origin.replyTo ! RES_ADD_SETTLED(fwd1.message.origin, downstream, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(downstream.channelId, downstream)))
+ fwd1.message.origin.replyTo ! RES_ADD_SETTLED(fwd1.message.origin, remoteNodeId2, downstream, HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(downstream.channelId, downstream)))
register.expectNoMessage(100 millis)
assert(nodeParams.db.pendingCommands.listSettlementCommands(r.add.channelId) == Seq(fulfill.copy(commit = false)))
}
@@ -868,7 +870,9 @@ object ChannelRelayerSpec {
val localAlias2: Alias = Alias(222000)
val channelId1: ByteVector32 = randomBytes32()
+ val remoteNodeId1: PublicKey = randomKey().publicKey
val channelId2: ByteVector32 = randomBytes32()
+ val remoteNodeId2: PublicKey = randomKey().publicKey
val channelIds: Map[ShortChannelId, ByteVector32] = Map(
realScid1 -> channelId1,
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala
index 9d37bd3..4368b0f 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala
@@ -1213,7 +1213,7 @@ object NodeRelayerSpec {
(paymentPackets.map(_.outerPayload.expiry).min - nodeParams.relayParams.asyncPaymentsParams.cancelSafetyBeforeTimeout).blockHeight
def createSuccessEvent(): PaymentSent =
- PaymentSent(relayId, paymentPreimage, outgoingAmount, outgoingNodeId, Seq(PaymentSent.PartialPayment(UUID.randomUUID(), outgoingAmount, 10 msat, randomBytes32(), None, 0 unixms, 50 unixms)), None, 0 unixms)
+ PaymentSent(relayId, paymentPreimage, outgoingAmount, outgoingNodeId, Seq(PaymentSent.PaymentPart(UUID.randomUUID(), PaymentEvent.OutgoingPayment(randomBytes32(), randomKey().publicKey, outgoingAmount, 50 unixms), 10 msat, None, 0 unixms)), None, 0 unixms)
def createTrampolinePacket(amount: MilliSatoshi, expiry: CltvExpiry): OnionRoutingPacket = {
val payload = NodePayload(outgoingNodeId, FinalPayload.Standard.createPayload(amount, amount, expiry, paymentSecret, upgradeAccountability = false))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala
index 4b77e96..36d41ee 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala
@@ -860,9 +860,9 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
// The payments are fulfilled.
val (add1, add2) = if (cmd1.paymentHash == paymentHash1) (cmd1, cmd2) else (cmd2, cmd1)
val outgoing = Seq(add1, add2).map(add => UpdateAddHtlc(purchase.channelId, randomHtlcId(), add.amount, add.paymentHash, add.cltvExpiry, add.onion, add.nextPathKey_opt, add.reputationScore.accountable, add.fundingFee_opt))
- add1.replyTo ! RES_ADD_SETTLED(add1.origin, outgoing.head, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(purchase.channelId, outgoing.head.id, preimage1)))
+ add1.replyTo ! RES_ADD_SETTLED(add1.origin, remoteNodeId, outgoing.head, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(purchase.channelId, outgoing.head.id, preimage1)))
verifyFulfilledUpstream(upstream1, preimage1)
- add2.replyTo ! RES_ADD_SETTLED(add2.origin, outgoing.last, HtlcResult.OnChainFulfill(preimage2))
+ add2.replyTo ! RES_ADD_SETTLED(add2.origin, remoteNodeId, outgoing.last, HtlcResult.OnChainFulfill(preimage2))
verifyFulfilledUpstream(upstream2, preimage2)
awaitCond(nodeParams.db.liquidity.listPendingOnTheFlyFunding(remoteNodeId).isEmpty, interval = 100 millis)
register.expectNoMessage(100 millis)
@@ -914,7 +914,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
val htlc = UpdateAddHtlc(channelId, randomHtlcId(), add.amount, paymentHash, add.cltvExpiry, add.onion, add.nextPathKey_opt, add.reputationScore.accountable, add.fundingFee_opt)
val fail = UpdateFailHtlc(channelId, htlc.id, randomBytes(50))
add.replyTo ! RES_SUCCESS(add, purchase.channelId)
- add.replyTo ! RES_ADD_SETTLED(add.origin, htlc, HtlcResult.RemoteFail(fail))
+ add.replyTo ! RES_ADD_SETTLED(add.origin, remoteNodeId, htlc, HtlcResult.RemoteFail(fail))
})
adds1.last.replyTo ! RES_ADD_FAILED(adds1.last, TooManyAcceptedHtlcs(channelId, 5), None)
register.expectNoMessage(100 millis)
@@ -932,7 +932,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
// The payment succeeds.
adds2.foreach(add => {
val htlc = UpdateAddHtlc(channelId, randomHtlcId(), add.amount, paymentHash, add.cltvExpiry, add.onion, add.nextPathKey_opt, add.reputationScore.accountable, add.fundingFee_opt)
- add.replyTo ! RES_ADD_SETTLED(add.origin, htlc, HtlcResult.OnChainFulfill(preimage))
+ add.replyTo ! RES_ADD_SETTLED(add.origin, remoteNodeId, htlc, HtlcResult.OnChainFulfill(preimage))
})
val fwds = Seq(
register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]],
@@ -1002,7 +1002,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
channel.expectNoMessage(100 millis)
// The payment is fulfilled by our peer.
- cmd2.replyTo ! RES_ADD_SETTLED(cmd2.origin, htlc, HtlcResult.OnChainFulfill(preimage))
+ cmd2.replyTo ! RES_ADD_SETTLED(cmd2.origin, remoteNodeId, htlc, HtlcResult.OnChainFulfill(preimage))
verifyFulfilledUpstream(upstream, preimage)
nodeParams.db.liquidity.addOnTheFlyFundingPreimage(preimage)
register.expectNoMessage(100 millis)
@@ -1044,7 +1044,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
channel.expectNoMessage(100 millis)
val add = UpdateAddHtlc(purchase.channelId, randomHtlcId(), cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextPathKey_opt, cmd.reputationScore.accountable, cmd.fundingFee_opt)
- cmd.replyTo ! RES_ADD_SETTLED(cmd.origin, add, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(purchase.channelId, add.id, preimage2)))
+ cmd.replyTo ! RES_ADD_SETTLED(cmd.origin, remoteNodeId, add, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(purchase.channelId, add.id, preimage2)))
verifyFulfilledUpstream(upstream2, preimage2)
register.expectNoMessage(100 millis)
awaitCond(nodeParams.db.liquidity.getFeeCredit(remoteNodeId) == 0.msat, interval = 100 millis)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala
index 02df2b2..6aa411c 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala
@@ -32,6 +32,7 @@ import fr.acinq.eclair.payment.Bolt11Invoice
import fr.acinq.eclair.payment.IncomingPaymentPacket.FinalPacket
import fr.acinq.eclair.payment.OutgoingPaymentPacket.{NodePayload, buildOnion, buildOutgoingPayment}
import fr.acinq.eclair.payment.PaymentPacketSpec._
+import fr.acinq.eclair.payment.receive.MultiPartHandler
import fr.acinq.eclair.payment.relay.Relayer._
import fr.acinq.eclair.payment.send.{ClearRecipient, TrampolinePayment}
import fr.acinq.eclair.reputation.{Reputation, ReputationRecorder}
@@ -76,8 +77,8 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat
withFixture(test.toNoArgTest(FixtureParam(nodeParams, relayer, router, register, childActors, paymentHandler, reputationRecorder)))
}
- val channelId_ab = randomBytes32()
- val channelId_bc = randomBytes32()
+ val channelId_ab: ByteVector32 = randomBytes32()
+ val channelId_bc: ByteVector32 = randomBytes32()
test("relay an htlc-add") { f =>
import f._
@@ -112,7 +113,7 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat
val add_ab = UpdateAddHtlc(channelId_ab, 123456, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, None, accountable = false, None)
relayer ! RelayForward(add_ab, priv_a.publicKey, 0.05)
- val fp = paymentHandler.expectMessageType[FinalPacket]
+ val fp = paymentHandler.expectMessageType[MultiPartHandler.ReceivePacket].packet
assert(fp.add == add_ab)
assert(fp.payload == FinalPayload.Standard.createPayload(finalAmount, finalAmount, finalExpiry, paymentSecret, upgradeAccountability = false))
@@ -133,7 +134,7 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat
val add_ab = UpdateAddHtlc(channelId_ab, 123456, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, None, accountable = false, None)
relayer ! RelayForward(add_ab, priv_a.publicKey, 0.05)
- val fp = paymentHandler.expectMessageType[FinalPacket]
+ val fp = paymentHandler.expectMessageType[MultiPartHandler.ReceivePacket].packet
assert(fp.add == add_ab)
assert(fp.payload.isInstanceOf[FinalPayload.Standard])
assert(fp.payload.amount == finalAmount)
@@ -225,14 +226,14 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat
val trampolineOrigin = Origin.Hot(replyTo.ref.toClassic, Upstream.Hot.Trampoline(List(Upstream.Hot.Channel(add_ab, TimestampMilli.now(), priv_a.publicKey, 0.1))))
val addSettled = Seq(
- RES_ADD_SETTLED(channelOrigin, add_bc, HtlcResult.OnChainFulfill(randomBytes32())),
- RES_ADD_SETTLED(channelOrigin, add_bc, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
- RES_ADD_SETTLED(channelOrigin, add_bc, HtlcResult.OnChainFail(HtlcsTimedoutDownstream(channelId_bc, Set(add_bc)))),
- RES_ADD_SETTLED(channelOrigin, add_bc, HtlcResult.RemoteFail(UpdateFailHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
- RES_ADD_SETTLED(trampolineOrigin, add_bc, HtlcResult.OnChainFulfill(randomBytes32())),
- RES_ADD_SETTLED(trampolineOrigin, add_bc, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
- RES_ADD_SETTLED(trampolineOrigin, add_bc, HtlcResult.OnChainFail(HtlcsTimedoutDownstream(channelId_bc, Set(add_bc)))),
- RES_ADD_SETTLED(trampolineOrigin, add_bc, HtlcResult.RemoteFail(UpdateFailHtlc(add_bc.channelId, add_bc.id, randomBytes32())))
+ RES_ADD_SETTLED(channelOrigin, priv_a.publicKey, add_bc, HtlcResult.OnChainFulfill(randomBytes32())),
+ RES_ADD_SETTLED(channelOrigin, priv_a.publicKey, add_bc, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
+ RES_ADD_SETTLED(channelOrigin, priv_a.publicKey, add_bc, HtlcResult.OnChainFail(HtlcsTimedoutDownstream(channelId_bc, Set(add_bc)))),
+ RES_ADD_SETTLED(channelOrigin, priv_a.publicKey, add_bc, HtlcResult.RemoteFail(UpdateFailHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
+ RES_ADD_SETTLED(trampolineOrigin, priv_a.publicKey, add_bc, HtlcResult.OnChainFulfill(randomBytes32())),
+ RES_ADD_SETTLED(trampolineOrigin, priv_a.publicKey, add_bc, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
+ RES_ADD_SETTLED(trampolineOrigin, priv_a.publicKey, add_bc, HtlcResult.OnChainFail(HtlcsTimedoutDownstream(channelId_bc, Set(add_bc)))),
+ RES_ADD_SETTLED(trampolineOrigin, priv_a.publicKey, add_bc, HtlcResult.RemoteFail(UpdateFailHtlc(add_bc.channelId, add_bc.id, randomBytes32())))
)
for (res <- addSettled) {
@@ -251,10 +252,10 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat
val originCold = Origin.Cold(originHot)
val addFulfilled = Seq(
- RES_ADD_SETTLED(originHot, add_bc, HtlcResult.OnChainFulfill(randomBytes32())),
- RES_ADD_SETTLED(originHot, add_bc, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
- RES_ADD_SETTLED(originCold, add_bc, HtlcResult.OnChainFulfill(randomBytes32())),
- RES_ADD_SETTLED(originCold, add_bc, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
+ RES_ADD_SETTLED(originHot, randomKey().publicKey, add_bc, HtlcResult.OnChainFulfill(randomBytes32())),
+ RES_ADD_SETTLED(originHot, randomKey().publicKey, add_bc, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
+ RES_ADD_SETTLED(originCold, randomKey().publicKey, add_bc, HtlcResult.OnChainFulfill(randomBytes32())),
+ RES_ADD_SETTLED(originCold, randomKey().publicKey, add_bc, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(add_bc.channelId, add_bc.id, randomBytes32()))),
)
for (res <- addFulfilled) {
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala
index fecfd3f..52b14da 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala
@@ -16,7 +16,7 @@
package fr.acinq.eclair.wire.internal.channel
-import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
+import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, ByteVector64, Crypto, DeterministicWallet, OutPoint, SatoshiLong, Transaction, TxId}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
@@ -26,7 +26,6 @@ import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, LocalChannelKeyManager, LocalNodeKeyManager, NodeKeyManager}
import fr.acinq.eclair.json.JsonSerializers
-import fr.acinq.eclair.reputation.Reputation
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.transactions.Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
import fr.acinq.eclair.transactions._
@@ -156,7 +155,7 @@ object ChannelCodecsSpec {
val normal: DATA_NORMAL = {
val origins = Map(
42L -> Origin.Cold(Upstream.Local(UUID.randomUUID)),
- 15000L -> Origin.Cold(Upstream.Cold.Channel(ByteVector32(ByteVector.fill(32)(42)), 43, 11_000_000 msat))
+ 15000L -> Origin.Cold(Upstream.Cold.Channel(ByteVector32(ByteVector.fill(32)(42)), PublicKey(hex"0358e32d245ff5f5a3eb14c78c6f69c67cea7846bdf9aeeb7199e8f6fbb0306484"), 43, 11_000_000 msat))
)
makeChannelDataNormal(htlcs, origins)
}
diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala
index 50e8cc5..71529e7 100644
--- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala
+++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala
@@ -619,11 +619,12 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
test("'send' method should allow blocking until payment completes") {
val invoice = "lnbc12580n1pw2ywztsp563x9gq6keftcu8kh9kh5qdj4p5wpmc3h802ekxwspcgfwpz2g25qpp554ganw404sh4yjkwnysgn3wjcxfcq7gtx53gxczkjr9nlpc3hzvqdq2wpskwctddyxqr4rqrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glc7z9rtvqqwngqqqqqqqlgqqqqqeqqjqrrt8smgjvfj7sg38dwtr9kc9gg3era9k3t2hvq3cup0jvsrtrxuplevqgfhd3rzvhulgcxj97yjuj8gdx8mllwj4wzjd8gdjhpz3lpqqmfe0a6"
+ val nextNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")
val eclair = mock[Eclair]
val mockService = new MockService(eclair)
val uuid = UUID.fromString("487da196-a4dc-4b1e-92b4-3e5e905e9f3f")
- val paymentSent = PaymentSent(uuid, ByteVector32.One, 25 msat, aliceNodeId, Seq(PaymentSent.PartialPayment(uuid, 28 msat, 1 msat, ByteVector32.Zeroes, None, TimestampMilli(1553784337650L), TimestampMilli(1553784337711L))), None, TimestampMilli(1553784337120L))
+ val paymentSent = PaymentSent(uuid, ByteVector32.One, 25 msat, aliceNodeId, Seq(PaymentSent.PaymentPart(uuid, PaymentEvent.OutgoingPayment(ByteVector32.Zeroes, nextNodeId, 28 msat, TimestampMilli(1553784337711L)), 3 msat, None, TimestampMilli(1553784337650L))), None, TimestampMilli(1553784337120L))
eclair.sendBlocking(any, any, any, any, any, any, any)(any[Timeout]).returns(Future.successful(paymentSent))
Post("/payinvoice", FormData("invoice" -> invoice, "blocking" -> "true").toEntity) ~>
addCredentials(BasicHttpCredentials("", mockApi().password)) ~>
@@ -632,7 +633,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
assert(handled)
assert(status == OK)
val response = entityAs[String]
- val expected = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"01d0fabd251fcbbe2b93b4b927b26ad2a1a99077152e45ded1e678afa45dbec5","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","recipientAmount":25,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","parts":[{"id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":28,"feesPaid":1,"toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","startedAt":{"iso":"2019-03-28T14:45:37.650Z","unix":1553784337},"settledAt":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}],"fees":4,"startedAt":{"iso":"2019-03-28T14:45:37.120Z","unix":1553784337},"settledAt":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}"""
+ val expected = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"01d0fabd251fcbbe2b93b4b927b26ad2a1a99077152e45ded1e678afa45dbec5","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","recipientAmount":25,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","parts":[{"id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","channelId":"0000000000000000000000000000000000000000000000000000000000000000","nextNodeId":"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87","amountWithFees":28,"fees":3,"startedAt":{"iso":"2019-03-28T14:45:37.650Z","unix":1553784337},"settledAt":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}],"fees":3,"startedAt":{"iso":"2019-03-28T14:45:37.120Z","unix":1553784337},"settledAt":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}"""
assert(response == expected)
}
@@ -1115,6 +1116,8 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
val mockService = new MockService(mock[Eclair])
val fixedUUID = UUID.fromString("487da196-a4dc-4b1e-92b4-3e5e905e9f3f")
val fundingTxId = TxId.fromValidHex("9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692")
+ val previousNodeId = PublicKey(hex"02e899d99662f2e64ea0eeaecb53c4628fa40a22d7185076e42e8a3d67fcb7b8e6")
+ val nextNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")
val wsClient = WSProbe()
WS("/ws", wsClient.flow) ~>
@@ -1127,26 +1130,26 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
system.eventStream.publish(pf)
wsClient.expectMessage(expectedSerializedPf)
- val ps = PaymentSent(fixedUUID, ByteVector32.One, 25 msat, aliceNodeId, Seq(PaymentSent.PartialPayment(fixedUUID, 28 msat, 1 msat, ByteVector32.Zeroes, None, startedAt = TimestampMilli(1553784337539L), settledAt = TimestampMilli(1553784337711L))), None, startedAt = TimestampMilli(1553784337073L))
- val expectedSerializedPs = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"01d0fabd251fcbbe2b93b4b927b26ad2a1a99077152e45ded1e678afa45dbec5","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","recipientAmount":25,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","parts":[{"id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":28,"feesPaid":1,"toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","startedAt":{"iso":"2019-03-28T14:45:37.539Z","unix":1553784337},"settledAt":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}],"fees":4,"startedAt":{"iso":"2019-03-28T14:45:37.073Z","unix":1553784337},"settledAt":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}"""
+ val ps = PaymentSent(fixedUUID, ByteVector32.One, 25 msat, aliceNodeId, Seq(PaymentSent.PaymentPart(fixedUUID, PaymentEvent.OutgoingPayment(ByteVector32.Zeroes, nextNodeId, 28 msat, settledAt = TimestampMilli(1553784337711L)), 3 msat, None, startedAt = TimestampMilli(1553784337539L))), None, startedAt = TimestampMilli(1553784337073L))
+ val expectedSerializedPs = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"01d0fabd251fcbbe2b93b4b927b26ad2a1a99077152e45ded1e678afa45dbec5","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","recipientAmount":25,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","parts":[{"id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","channelId":"0000000000000000000000000000000000000000000000000000000000000000","nextNodeId":"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87","amountWithFees":28,"fees":3,"startedAt":{"iso":"2019-03-28T14:45:37.539Z","unix":1553784337},"settledAt":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}],"fees":3,"startedAt":{"iso":"2019-03-28T14:45:37.073Z","unix":1553784337},"settledAt":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}"""
assert(serialization.write(ps) == expectedSerializedPs)
system.eventStream.publish(ps)
wsClient.expectMessage(expectedSerializedPs)
- val prel = ChannelPaymentRelayed(21 msat, 20 msat, ByteVector32.Zeroes, ByteVector32.Zeroes, ByteVector32.One, TimestampMilli(1553784961048L), TimestampMilli(1553784963659L))
- val expectedSerializedPrel = """{"type":"payment-relayed","amountIn":21,"amountOut":20,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","toChannelId":"0100000000000000000000000000000000000000000000000000000000000000","startedAt":{"iso":"2019-03-28T14:56:01.048Z","unix":1553784961},"settledAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}"""
+ val prel = ChannelPaymentRelayed(ByteVector32.Zeroes, PaymentEvent.IncomingPayment(ByteVector32.Zeroes, previousNodeId, 21 msat, TimestampMilli(1553784961048L)), PaymentEvent.OutgoingPayment(ByteVector32.One, nextNodeId, 20 msat, TimestampMilli(1553784963659L)))
+ val expectedSerializedPrel = """{"type":"payment-relayed","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentIn":{"channelId":"0000000000000000000000000000000000000000000000000000000000000000","remoteNodeId":"02e899d99662f2e64ea0eeaecb53c4628fa40a22d7185076e42e8a3d67fcb7b8e6","amount":21,"receivedAt":{"iso":"2019-03-28T14:56:01.048Z","unix":1553784961}},"paymentOut":{"channelId":"0100000000000000000000000000000000000000000000000000000000000000","remoteNodeId":"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87","amount":20,"settledAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}}"""
assert(serialization.write(prel) == expectedSerializedPrel)
system.eventStream.publish(prel)
wsClient.expectMessage(expectedSerializedPrel)
- val ptrel = TrampolinePaymentRelayed(ByteVector32.Zeroes, Seq(PaymentRelayed.IncomingPart(21 msat, ByteVector32.Zeroes, TimestampMilli(1553784963659L))), Seq(PaymentRelayed.OutgoingPart(8 msat, ByteVector32.Zeroes, TimestampMilli(1553784963659L)), PaymentRelayed.OutgoingPart(10 msat, ByteVector32.One, TimestampMilli(1553784963659L))), bobNodeId, 17 msat)
- val expectedSerializedPtrel = """{"type":"trampoline-payment-relayed","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","incoming":[{"amount":21,"channelId":"0000000000000000000000000000000000000000000000000000000000000000","receivedAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}],"outgoing":[{"amount":8,"channelId":"0000000000000000000000000000000000000000000000000000000000000000","settledAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}},{"amount":10,"channelId":"0100000000000000000000000000000000000000000000000000000000000000","settledAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}],"nextTrampolineNodeId":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585","nextTrampolineAmount":17}"""
+ val ptrel = TrampolinePaymentRelayed(ByteVector32.Zeroes, Seq(PaymentEvent.IncomingPayment(ByteVector32.Zeroes, previousNodeId, 21 msat, TimestampMilli(1553784963659L))), Seq(PaymentEvent.OutgoingPayment(ByteVector32.Zeroes, nextNodeId, 8 msat, TimestampMilli(1553784963659L)), PaymentEvent.OutgoingPayment(ByteVector32.One, nextNodeId, 10 msat, TimestampMilli(1553784963659L))), bobNodeId, 17 msat)
+ val expectedSerializedPtrel = """{"type":"trampoline-payment-relayed","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","incoming":[{"channelId":"0000000000000000000000000000000000000000000000000000000000000000","remoteNodeId":"02e899d99662f2e64ea0eeaecb53c4628fa40a22d7185076e42e8a3d67fcb7b8e6","amount":21,"receivedAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}],"outgoing":[{"channelId":"0000000000000000000000000000000000000000000000000000000000000000","remoteNodeId":"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87","amount":8,"settledAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}},{"channelId":"0100000000000000000000000000000000000000000000000000000000000000","remoteNodeId":"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87","amount":10,"settledAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}],"nextTrampolineNodeId":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585","nextTrampolineAmount":17}"""
assert(serialization.write(ptrel) == expectedSerializedPtrel)
system.eventStream.publish(ptrel)
wsClient.expectMessage(expectedSerializedPtrel)
- val precv = PaymentReceived(ByteVector32.Zeroes, Seq(PaymentReceived.PartialPayment(21 msat, ByteVector32.Zeroes, TimestampMilli(1553784963659L))))
- val expectedSerializedPrecv = """{"type":"payment-received","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","parts":[{"amount":21,"fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","receivedAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}]}"""
+ val precv = PaymentReceived(ByteVector32.Zeroes, Seq(PaymentEvent.IncomingPayment(ByteVector32.Zeroes, previousNodeId, 21 msat, TimestampMilli(1553784963659L))))
+ val expectedSerializedPrecv = """{"type":"payment-received","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","parts":[{"channelId":"0000000000000000000000000000000000000000000000000000000000000000","remoteNodeId":"02e899d99662f2e64ea0eeaecb53c4628fa40a22d7185076e42e8a3d67fcb7b8e6","amount":21,"receivedAt":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}]}"""
assert(serialization.write(precv) == expectedSerializedPrecv)
system.eventStream.publish(precv)
wsClient.expectMessage(expectedSerializedPrecv)
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.