Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make payment_secret mandatory #1810

Merged
merged 1 commit into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ eclair {
option_data_loss_protect = optional
gossip_queries = optional
gossip_queries_ex = optional
var_onion_optin = optional
var_onion_optin = mandatory
option_static_remotekey = optional
payment_secret = optional
payment_secret = mandatory
basic_mpp = optional
option_support_large_channel = optional
option_anchor_outputs = disabled
Expand Down
3 changes: 2 additions & 1 deletion eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ object NodeParams extends Logging {
def validateFeatures(features: Features): Unit = {
val featuresErr = Features.validateFeatureGraph(features)
require(featuresErr.isEmpty, featuresErr.map(_.message))
require(features.hasFeature(Features.VariableLengthOnion), s"${Features.VariableLengthOnion.rfcName} must be enabled")
require(features.hasFeature(Features.VariableLengthOnion, Some(FeatureSupport.Mandatory)), s"${Features.VariableLengthOnion.rfcName} must be enabled and mandatory")
require(features.hasFeature(Features.PaymentSecret, Some(FeatureSupport.Mandatory)), s"${Features.PaymentSecret.rfcName} must be enabled and mandatory")
require(!features.hasFeature(Features.InitialRoutingSync), s"${Features.InitialRoutingSync.rfcName} is not supported anymore, use ${Features.ChannelRangeQueries.rfcName} instead")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ object PaymentRequest {
expirySeconds: Option[Long] = None,
extraHops: List[List[ExtraHop]] = Nil,
timestamp: Long = System.currentTimeMillis() / 1000L,
features: Option[PaymentRequestFeatures] = Some(PaymentRequestFeatures(Features.VariableLengthOnion.optional, Features.PaymentSecret.optional))): PaymentRequest = {
features: Option[PaymentRequestFeatures] = Some(PaymentRequestFeatures(Features.VariableLengthOnion.mandatory, Features.PaymentSecret.mandatory))): PaymentRequest = {

val prefix = prefixes(chainHash)
val tags = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,8 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
val paymentPreimage = paymentPreimage_opt.getOrElse(randomBytes32)
val paymentHash = Crypto.sha256(paymentPreimage)
val expirySeconds = expirySeconds_opt.getOrElse(nodeParams.paymentRequestExpiry.toSeconds)
// We currently only optionally support payment secrets (to allow legacy clients to pay invoices).
// Once we're confident most of the network has upgraded, we should switch to mandatory payment secrets.
val features = {
val f1 = Seq(Features.PaymentSecret.optional, Features.VariableLengthOnion.optional)
val f1 = Seq(Features.PaymentSecret.mandatory, Features.VariableLengthOnion.mandatory)
val allowMultiPart = nodeParams.features.hasFeature(Features.BasicMultiPartPayment)
val f2 = if (allowMultiPart) Seq(Features.BasicMultiPartPayment.optional) else Nil
val f3 = if (nodeParams.enableTrampolinePayment) Seq(Features.TrampolinePayment.optional) else Nil
Expand Down Expand Up @@ -99,9 +97,9 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
val paymentHash = Crypto.sha256(paymentPreimage)
val desc = "Donation"
val features = if (nodeParams.features.hasFeature(Features.BasicMultiPartPayment)) {
PaymentRequestFeatures(Features.BasicMultiPartPayment.optional, Features.PaymentSecret.optional, Features.VariableLengthOnion.optional)
PaymentRequestFeatures(Features.BasicMultiPartPayment.optional, Features.PaymentSecret.mandatory, Features.VariableLengthOnion.mandatory)
} else {
PaymentRequestFeatures(Features.PaymentSecret.optional, Features.VariableLengthOnion.optional)
PaymentRequestFeatures(Features.PaymentSecret.mandatory, Features.VariableLengthOnion.mandatory)
}

// Insert a fake invoice and then restart the incoming payment handler
Expand Down
31 changes: 24 additions & 7 deletions eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ class StartupSpec extends AnyFunSuite {
s"features.${OptionDataLossProtect.rfcName}" -> "optional",
s"features.${ChannelRangeQueries.rfcName}" -> "optional",
s"features.${ChannelRangeQueriesExtended.rfcName}" -> "optional",
s"features.${VariableLengthOnion.rfcName}" -> "optional",
s"features.${PaymentSecret.rfcName}" -> "optional",
s"features.${VariableLengthOnion.rfcName}" -> "mandatory",
s"features.${PaymentSecret.rfcName}" -> "mandatory",
s"features.${BasicMultiPartPayment.rfcName}" -> "optional"
).asJava)

Expand All @@ -106,22 +106,39 @@ class StartupSpec extends AnyFunSuite {
s"features.${ChannelRangeQueriesExtended.rfcName}" -> "optional"
).asJava)

// var_onion_optin cannot be optional
val optionalVarOnionOptinConf = ConfigFactory.parseMap(Map(
s"features.${OptionDataLossProtect.rfcName}" -> "optional",
s"features.${VariableLengthOnion.rfcName}" -> "optional"
).asJava)

// payment_secret cannot be optional
val optionalPaymentSecretConf = ConfigFactory.parseMap(Map(
s"features.${OptionDataLossProtect.rfcName}" -> "optional",
s"features.${VariableLengthOnion.rfcName}" -> "mandatory",
s"features.${PaymentSecret.rfcName}" -> "optional",
).asJava)

// initial_routing_sync cannot be enabled
val initialRoutingSyncConf = ConfigFactory.parseMap(Map(
s"features.${OptionDataLossProtect.rfcName}" -> "optional",
s"features.${InitialRoutingSync.rfcName}" -> "optional",
s"features.${ChannelRangeQueries.rfcName}" -> "optional",
s"features.${ChannelRangeQueriesExtended.rfcName}" -> "optional",
s"features.${VariableLengthOnion.rfcName}" -> "optional"
s"features.${VariableLengthOnion.rfcName}" -> "mandatory",
s"features.${PaymentSecret.rfcName}" -> "mandatory",
).asJava)

// basic_mpp without payment_secret
// extended channel queries without channel queries
val illegalFeaturesConf = ConfigFactory.parseMap(Map(
s"features.${BasicMultiPartPayment.rfcName}" -> "optional"
s"features.${OptionDataLossProtect.rfcName}" -> "optional",
s"features.${ChannelRangeQueriesExtended.rfcName}" -> "optional"
).asJava)

assert(Try(makeNodeParamsWithDefaults(finalizeConf(legalFeaturesConf))).isSuccess)
assert(Try(makeNodeParamsWithDefaults(finalizeConf(noVariableLengthOnionConf))).isFailure)
assert(Try(makeNodeParamsWithDefaults(finalizeConf(optionalVarOnionOptinConf))).isFailure)
assert(Try(makeNodeParamsWithDefaults(finalizeConf(optionalPaymentSecretConf))).isFailure)
assert(Try(makeNodeParamsWithDefaults(finalizeConf(initialRoutingSyncConf))).isFailure)
assert(Try(makeNodeParamsWithDefaults(finalizeConf(illegalFeaturesConf))).isFailure)
}
Expand All @@ -133,7 +150,7 @@ class StartupSpec extends AnyFunSuite {
| {
| nodeid = "02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
| features {
| var_onion_optin = optional
| var_onion_optin = mandatory
| payment_secret = mandatory
| basic_mpp = mandatory
| }
Expand All @@ -144,7 +161,7 @@ class StartupSpec extends AnyFunSuite {

val nodeParams = makeNodeParamsWithDefaults(perNodeConf.withFallback(defaultConf))
val perNodeFeatures = nodeParams.featuresFor(PublicKey(ByteVector.fromValidHex("02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))
assert(perNodeFeatures === Features(VariableLengthOnion -> Optional, PaymentSecret -> Mandatory, BasicMultiPartPayment -> Mandatory))
assert(perNodeFeatures === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory, BasicMultiPartPayment -> Mandatory))
}

test("override feerate mismatch tolerance") {
Expand Down
10 changes: 5 additions & 5 deletions eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package fr.acinq.eclair

import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{Block, ByteVector32, Satoshi, SatoshiLong, Script}
import fr.acinq.eclair.FeatureSupport.Optional
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features._
import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratesPerKw, OnChainFeeConf, _}
import fr.acinq.eclair.channel.LocalParams
Expand Down Expand Up @@ -88,8 +88,8 @@ object TestConstants {
OptionDataLossProtect -> Optional,
ChannelRangeQueries -> Optional,
ChannelRangeQueriesExtended -> Optional,
VariableLengthOnion -> Optional,
PaymentSecret -> Optional,
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
BasicMultiPartPayment -> Optional
),
Set(UnknownFeature(TestFeature.optional))
Expand Down Expand Up @@ -194,8 +194,8 @@ object TestConstants {
OptionDataLossProtect -> Optional,
ChannelRangeQueries -> Optional,
ChannelRangeQueriesExtended -> Optional,
VariableLengthOnion -> Optional,
PaymentSecret -> Optional,
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
BasicMultiPartPayment -> Optional
),
pluginParams = Nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ abstract class IntegrationSpec extends TestKitBaseClass with BitcoindService wit
s"eclair.features.${OptionDataLossProtect.rfcName}" -> "optional",
s"eclair.features.${ChannelRangeQueries.rfcName}" -> "optional",
s"eclair.features.${ChannelRangeQueriesExtended.rfcName}" -> "optional",
s"eclair.features.${VariableLengthOnion.rfcName}" -> "optional",
s"eclair.features.${PaymentSecret.rfcName}" -> "optional",
s"eclair.features.${VariableLengthOnion.rfcName}" -> "mandatory",
s"eclair.features.${PaymentSecret.rfcName}" -> "mandatory",
s"eclair.features.${BasicMultiPartPayment.rfcName}" -> "optional"
).asJava)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import akka.actor.PoisonPill
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Block
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.eclair.FeatureSupport.Optional
import fr.acinq.eclair.Features.{BasicMultiPartPayment, ChannelRangeQueries, VariableLengthOnion}
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features.{BasicMultiPartPayment, ChannelRangeQueries, PaymentSecret, VariableLengthOnion}
import fr.acinq.eclair.TestConstants._
import fr.acinq.eclair._
import fr.acinq.eclair.crypto.TransportHandler
Expand Down Expand Up @@ -190,7 +190,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi

test("sync when requested") { f =>
import f._
val remoteInit = protocol.Init(Features(ChannelRangeQueries -> Optional))
val remoteInit = protocol.Init(Features(ChannelRangeQueries -> Optional, VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory))
connect(nodeParams, remoteNodeId, switchboard, router, connection, transport, peerConnection, peer, remoteInit, doSync = true)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import akka.actor.ActorRef
import akka.actor.Status.Failure
import akka.testkit.{TestActorRef, TestProbe}
import fr.acinq.bitcoin.{ByteVector32, Crypto}
import fr.acinq.eclair.FeatureSupport.Optional
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features._
import fr.acinq.eclair.TestConstants.Alice
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, Register}
Expand All @@ -45,18 +45,19 @@ import scala.concurrent.duration._
class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {

val featuresWithoutMpp = Features(
VariableLengthOnion -> Optional,
PaymentSecret -> Optional,
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
)

val featuresWithMpp = Features(
VariableLengthOnion -> Optional,
PaymentSecret -> Optional,
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
BasicMultiPartPayment -> Optional
)

val featuresWithKeySend = Features(
VariableLengthOnion -> Optional,
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
KeySend -> Optional
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package fr.acinq.eclair.payment
import akka.actor.{ActorContext, ActorRef}
import akka.testkit.{TestActorRef, TestProbe}
import fr.acinq.bitcoin.Block
import fr.acinq.eclair.FeatureSupport.Optional
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features._
import fr.acinq.eclair.UInt64.Conversions._
import fr.acinq.eclair.channel.Channel
Expand Down Expand Up @@ -53,13 +53,13 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
case class FixtureParam(nodeParams: NodeParams, initiator: TestActorRef[PaymentInitiator], payFsm: TestProbe, multiPartPayFsm: TestProbe, sender: TestProbe, eventListener: TestProbe)

val featuresWithoutMpp = Features(
VariableLengthOnion -> Optional,
PaymentSecret -> Optional
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory
)

val featuresWithMpp = Features(
VariableLengthOnion -> Optional,
PaymentSecret -> Optional,
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
BasicMultiPartPayment -> Optional,
)

Expand Down Expand Up @@ -143,7 +143,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("forward single-part payment when multi-part deactivated", Tag("mpp_disabled")) { f =>
import f._
val finalExpiryDelta = CltvExpiryDelta(24)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some MPP invoice", finalExpiryDelta, features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some MPP invoice", finalExpiryDelta, features = Some(PaymentRequestFeatures(VariableLengthOnion.mandatory, PaymentSecret.mandatory, BasicMultiPartPayment.optional)))
val req = SendPaymentRequest(finalAmount, paymentHash, c, 1, /* ignored since the invoice provides it */ CltvExpiryDelta(12), Some(pr))
assert(req.finalExpiry(nodeParams.currentBlockHeight) === (finalExpiryDelta + 1).toCltvExpiry(nodeParams.currentBlockHeight))
sender.send(initiator, req)
Expand All @@ -154,7 +154,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike

test("forward multi-part payment") { f =>
import f._
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some invoice", CltvExpiryDelta(18), features = Some(PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)))
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(finalAmount), paymentHash, randomKey, "Some invoice", CltvExpiryDelta(18), features = Some(PaymentRequestFeatures(VariableLengthOnion.mandatory, PaymentSecret.mandatory, BasicMultiPartPayment.optional)))
val req = SendPaymentRequest(finalAmount + 100.msat, paymentHash, c, 1, CltvExpiryDelta(42), Some(pr))
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
Expand Down Expand Up @@ -245,7 +245,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(trampolinePayload.outgoingCltv.toLong === currentBlockCount + 9 + 1)
assert(trampolinePayload.outgoingNodeId === c)
assert(trampolinePayload.paymentSecret === pr.paymentSecret)
assert(trampolinePayload.invoiceFeatures === Some(hex"8200")) // var_onion_optin, payment_secret
assert(trampolinePayload.invoiceFeatures === Some(hex"4100")) // var_onion_optin, payment_secret
}

test("reject trampoline to legacy payment for 0-value invoice") { f =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
// a -> b -> c d -> e

val routingHints = List(List(PaymentRequest.ExtraHop(randomKey.publicKey, ShortChannelId(42), 10 msat, 100, CltvExpiryDelta(144))))
val invoiceFeatures = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.optional, BasicMultiPartPayment.optional)
val invoiceFeatures = PaymentRequestFeatures(VariableLengthOnion.mandatory, PaymentSecret.mandatory, BasicMultiPartPayment.optional)
val invoice = PaymentRequest(Block.RegtestGenesisBlock.hash, Some(finalAmount), paymentHash, priv_a.privateKey, "#reckless", CltvExpiryDelta(18), None, None, routingHints, features = Some(invoiceFeatures))
val (amount_ac, expiry_ac, trampolineOnion) = buildTrampolineToLegacyPacket(invoice, trampolineHops, FinalLegacyPayload(finalAmount, finalExpiry))
assert(amount_ac === amount_bc)
Expand Down Expand Up @@ -257,7 +257,7 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
assert(inner_d.outgoingNodeId === e)
assert(inner_d.totalAmount === finalAmount)
assert(inner_d.paymentSecret === invoice.paymentSecret)
assert(inner_d.invoiceFeatures === Some(hex"028200")) // var_onion_optin, payment_secret, basic_mpp
assert(inner_d.invoiceFeatures === Some(hex"024100")) // var_onion_optin, payment_secret, basic_mpp
assert(inner_d.invoiceRoutingInfo === Some(routingHints))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,8 @@ class PaymentRequestSpec extends AnyFunSuite {
test("payment secret") {
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, "Some invoice", CltvExpiryDelta(18))
assert(pr.paymentSecret.isDefined)
assert(pr.features === PaymentRequestFeatures(PaymentSecret.optional, VariableLengthOnion.optional))
assert(!pr.features.requirePaymentSecret)
assert(pr.features === PaymentRequestFeatures(PaymentSecret.mandatory, VariableLengthOnion.mandatory))
assert(pr.features.requirePaymentSecret)

val pr1 = PaymentRequest.read(PaymentRequest.write(pr))
assert(pr1.paymentSecret === pr.paymentSecret)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl

// Receive an upstream multi-part payment.
val hints = List(List(ExtraHop(outgoingNodeId, ShortChannelId(42), feeBase = 10 msat, feeProportionalMillionths = 1, cltvExpiryDelta = CltvExpiryDelta(12))))
val features = PaymentRequestFeatures(VariableLengthOnion.optional, PaymentSecret.mandatory, BasicMultiPartPayment.optional)
val features = PaymentRequestFeatures(VariableLengthOnion.mandatory, PaymentSecret.mandatory, BasicMultiPartPayment.optional)
val pr = PaymentRequest(Block.LivenetGenesisBlock.hash, Some(outgoingAmount * 3), paymentHash, randomKey, "Some invoice", CltvExpiryDelta(18), extraHops = hints, features = Some(features))
val incomingPayments = incomingMultiPart.map(incoming => incoming.copy(innerPayload = Onion.createNodeRelayToNonTrampolinePayload(
incoming.innerPayload.amountToForward, outgoingAmount * 3, outgoingExpiry, outgoingNodeId, pr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class AnnouncementsSpec extends AnyFunSuite {

test("create valid signed node announcement") {
val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, Alice.nodeParams.publicAddresses, Alice.nodeParams.features)
assert(ann.features.hasFeature(Features.VariableLengthOnion, Some(FeatureSupport.Optional)))
assert(ann.features.hasFeature(Features.VariableLengthOnion, Some(FeatureSupport.Mandatory)))
assert(checkSig(ann))
assert(checkSig(ann.copy(timestamp = 153)) === false)
}
Expand Down