@@ -24,6 +24,7 @@ import fr.acinq.eclair.Features._
2424import fr .acinq .eclair .channel .ChannelSpendSignature .IndividualSignature
2525import fr .acinq .eclair .channel ._
2626import fr .acinq .eclair .channel .fsm .Channel
27+ import fr .acinq .eclair .crypto .Sphinx .HoldTime
2728import fr .acinq .eclair .crypto .{ShaChain , Sphinx }
2829import fr .acinq .eclair .payment .IncomingPaymentPacket ._
2930import fr .acinq .eclair .payment .OutgoingPaymentPacket ._
@@ -670,6 +671,37 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
670671 assert(decryptedFailure == failure)
671672 }
672673
674+ test(" build htlc failure onion with attribution data" ) {
675+ // a -> b -> c -> d -> e
676+ val recipient = ClearRecipient (e, Features .empty, finalAmount, finalExpiry, paymentSecret)
677+ val Right (payment) = buildOutgoingPayment(TestConstants .emptyOrigin, paymentHash, Route (finalAmount, hops, None ), recipient, 1.0 )
678+ val add_b = UpdateAddHtlc (randomBytes32(), 0 , amount_ab, paymentHash, expiry_ab, payment.cmd.onion, None , 1.0 , None )
679+ val Right (ChannelRelayPacket (_, _, packet_c)) = decrypt(add_b, priv_b.privateKey, Features .empty)
680+ val add_c = UpdateAddHtlc (randomBytes32(), 1 , amount_bc, paymentHash, expiry_bc, packet_c, None , 1.0 , None )
681+ val Right (ChannelRelayPacket (_, _, packet_d)) = decrypt(add_c, priv_c.privateKey, Features .empty)
682+ val add_d = UpdateAddHtlc (randomBytes32(), 2 , amount_cd, paymentHash, expiry_cd, packet_d, None , 1.0 , None )
683+ val Right (ChannelRelayPacket (_, _, packet_e)) = decrypt(add_d, priv_d.privateKey, Features .empty)
684+ val add_e = UpdateAddHtlc (randomBytes32(), 3 , amount_de, paymentHash, expiry_de, packet_e, None , 1.0 , None )
685+ val Right (FinalPacket (_, payload_e)) = decrypt(add_e, priv_e.privateKey, Features .empty)
686+ assert(payload_e.isInstanceOf [FinalPayload .Standard ])
687+
688+ // e returns a failure
689+ val failure = IncorrectOrUnknownPaymentDetails (finalAmount, BlockHeight (currentBlockCount))
690+ val Right (fail_e : UpdateFailHtlc ) = buildHtlcFailure(priv_e.privateKey, useAttributableFailures = true , CMD_FAIL_HTLC (add_e.id, FailureReason .LocalFailure (failure), TimestampMilli (60 )), add_e, now = TimestampMilli (62 ))
691+ assert(fail_e.id == add_e.id)
692+ val Right (fail_d : UpdateFailHtlc ) = buildHtlcFailure(priv_d.privateKey, useAttributableFailures = true , CMD_FAIL_HTLC (add_d.id, FailureReason .EncryptedDownstreamFailure (fail_e.reason, fail_e.attribution_opt), TimestampMilli (25 )), add_d, now = TimestampMilli (63 ))
693+ assert(fail_d.id == add_d.id)
694+ val Right (fail_c : UpdateFailHtlc ) = buildHtlcFailure(priv_c.privateKey, useAttributableFailures = true , CMD_FAIL_HTLC (add_c.id, FailureReason .EncryptedDownstreamFailure (fail_d.reason, fail_d.attribution_opt), TimestampMilli (10 )), add_c, now = TimestampMilli (70 ))
695+ assert(fail_c.id == add_c.id)
696+ val Right (fail_b : UpdateFailHtlc ) = buildHtlcFailure(priv_b.privateKey, useAttributableFailures = true , CMD_FAIL_HTLC (add_b.id, FailureReason .EncryptedDownstreamFailure (fail_c.reason, fail_c.attribution_opt), TimestampMilli (0 )), add_b, now = TimestampMilli (76 ))
697+ assert(fail_b.id == add_b.id)
698+ val htlcFailure = Sphinx .FailurePacket .decrypt(fail_b.reason, fail_b.attribution_opt, payment.sharedSecrets)
699+ assert(htlcFailure.holdTimes == Seq (HoldTime (76 milliseconds, b), HoldTime (60 milliseconds, c), HoldTime (38 milliseconds, d), HoldTime (2 milliseconds, e)))
700+ val Right (Sphinx .DecryptedFailurePacket (failingNode, decryptedFailure)) = htlcFailure.failure
701+ assert(failingNode == e)
702+ assert(decryptedFailure == failure)
703+ }
704+
673705 test(" build htlc failure onion (blinded payment)" ) {
674706 // a -> b -> c -> d -> e, blinded after c
675707 val (_, route, recipient) = longBlindedHops(hex " 0451 " )
0 commit comments