Skip to content

Commit

Permalink
Add tests for invoice feature validation (#2421)
Browse files Browse the repository at this point in the history
Add non-reg tests to ensure that we only validate invoice features,
and correctly refuse to pay invoices containing unsupported mandatory
features.
  • Loading branch information
t-bast authored Sep 16, 2022
1 parent 3191878 commit ba2b928
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,19 @@ class Bolt11InvoiceSpec extends AnyFunSuite {
assert(Bolt11Invoice.fromString(invoice.toString).get == invoice)
}

test("Invoices can't have high features") {
test("filter non-invoice features when parsing invoices") {
// The following invoice has feature bit 20 activated (option_anchor_outputs) without feature bit 12 (option_static_remotekey).
// This doesn't satisfy the feature dependency graph, but since those aren't invoice features, we should ignore it.
val features = Features(
Map(VariableLengthOnion -> FeatureSupport.Mandatory, PaymentSecret -> FeatureSupport.Mandatory, AnchorOutputs -> Mandatory),
Set(UnknownFeature(121), UnknownFeature(156))
)
val invoice = createInvoiceUnsafe(Block.LivenetGenesisBlock.hash, None, randomBytes32(), priv, Left("non-invoice features"), CltvExpiryDelta(6), features = features.unscoped()).toString
val Success(pr) = Bolt11Invoice.fromString(invoice)
assert(pr.features == features.remove(AnchorOutputs))
}

test("invoices can't have high features") {
assertThrows[Exception](createInvoiceUnsafe(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, Left("Some invoice"), CltvExpiryDelta(18), features = Features[Feature](Map[Feature, FeatureSupport](VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), Set(UnknownFeature(424242)))))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv.{AmountToForward, KeySend, OutgoingCltv}
import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload}
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiryDelta, Features, InvoiceFeature, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, TimestampSecond, UnknownFeature, randomBytes32, randomKey}
import fr.acinq.eclair.{CltvExpiryDelta, Feature, Features, InvoiceFeature, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, TimestampSecond, UnknownFeature, randomBytes32, randomKey}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
import scodec.bits.{ByteVector, HexStringSyntax}
Expand Down Expand Up @@ -121,23 +121,29 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(tlvs.unknown.isEmpty)
}

test("reject payment with unknown mandatory feature") { f =>
test("reject payment with unsupported mandatory feature") { f =>
import f._
val taggedFields = List(
Bolt11Invoice.PaymentHash(paymentHash),
Bolt11Invoice.Description("Some invoice"),
Bolt11Invoice.PaymentSecret(randomBytes32()),
Bolt11Invoice.Expiry(3600),
Bolt11Invoice.InvoiceFeatures(Features(Map(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), unknown = Set(UnknownFeature(42))))
val testCases: Seq[Features[Feature]] = Seq(
Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory, PaymentMetadata -> Mandatory),
Features(Map(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), unknown = Set(UnknownFeature(42))),
)
val invoice = Bolt11Invoice("lnbc", Some(finalAmount), TimestampSecond.now(), randomKey().publicKey, taggedFields, ByteVector.empty)
val req = SendPaymentToNode(finalAmount + 100.msat, invoice, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams)
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
val fail = sender.expectMsgType[PaymentFailed]
assert(fail.id == id)
assert(fail.failures.head.isInstanceOf[LocalFailure])
assert(fail.failures.head.asInstanceOf[LocalFailure].t == UnsupportedFeatures(invoice.features))
testCases.foreach { invoiceFeatures =>
val taggedFields = List(
Bolt11Invoice.PaymentHash(paymentHash),
Bolt11Invoice.Description("Some invoice"),
Bolt11Invoice.PaymentSecret(randomBytes32()),
Bolt11Invoice.Expiry(3600),
Bolt11Invoice.InvoiceFeatures(invoiceFeatures)
)
val invoice = Bolt11Invoice("lnbc", Some(finalAmount), TimestampSecond.now(), randomKey().publicKey, taggedFields, ByteVector.empty)
val req = SendPaymentToNode(finalAmount + 100.msat, invoice, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams)
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
val fail = sender.expectMsgType[PaymentFailed]
assert(fail.id == id)
assert(fail.failures.head.isInstanceOf[LocalFailure])
assert(fail.failures.head.asInstanceOf[LocalFailure].t == UnsupportedFeatures(invoice.features))
}
}

test("forward payment with pre-defined route") { f =>
Expand Down

0 comments on commit ba2b928

Please sign in to comment.