1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-24 14:50:46 +01:00

Fix override-features implementation (#1549)

We were calling `nodeParams.features` from inside the channel, which is
problematic because we may have overridden those features for specific
peers. This is now fixed.
This commit is contained in:
Pierre-Marie Padiou 2020-10-08 17:59:01 +02:00 committed by GitHub
parent 428349a341
commit 20e0b4bc4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 36 additions and 30 deletions

View file

@ -51,7 +51,7 @@ case class NodeParams(keyManager: KeyManager,
color: Color,
publicAddresses: List[NodeAddress],
features: Features,
overrideFeatures: Map[PublicKey, Features],
private val overrideFeatures: Map[PublicKey, Features],
syncWhitelist: Set[PublicKey],
pluginParams: Seq[PluginParams],
dustLimit: Satoshi,
@ -92,6 +92,8 @@ case class NodeParams(keyManager: KeyManager,
val keyPair = KeyPair(nodeId.value, privateKey.value)
def currentBlockHeight: Long = blockCount.get
def featuresFor(nodeId: PublicKey) = overrideFeatures.getOrElse(nodeId, features)
}
object NodeParams {

View file

@ -289,7 +289,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
when(WAIT_FOR_OPEN_CHANNEL)(handleExceptions {
case Event(open: OpenChannel, d@DATA_WAIT_FOR_OPEN_CHANNEL(INPUT_INIT_FUNDEE(_, localParams, _, remoteInit, channelVersion))) =>
log.info("received OpenChannel={}", open)
Try(Helpers.validateParamsFundee(nodeParams, open)) match {
Try(Helpers.validateParamsFundee(nodeParams, localParams.features, open)) match {
case Failure(t) => handleLocalError(t, d, Some(open))
case Success(_) =>
context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isFunder = false, open.temporaryChannelId, open.feeratePerKw, None))

View file

@ -95,7 +95,7 @@ object Helpers {
/**
* Called by the fundee
*/
def validateParamsFundee(nodeParams: NodeParams, open: OpenChannel): Unit = {
def validateParamsFundee(nodeParams: NodeParams, features: Features, open: OpenChannel): Unit = {
// BOLT #2: if the chain_hash value, within the open_channel, message is set to a hash of a chain that is unknown to the receiver:
// MUST reject the channel.
if (nodeParams.chainHash != open.chainHash) throw InvalidChainHash(open.temporaryChannelId, local = nodeParams.chainHash, remote = open.chainHash)
@ -103,7 +103,7 @@ object Helpers {
if (open.fundingSatoshis < nodeParams.minFundingSatoshis || open.fundingSatoshis > nodeParams.maxFundingSatoshis) throw InvalidFundingAmount(open.temporaryChannelId, open.fundingSatoshis, nodeParams.minFundingSatoshis, nodeParams.maxFundingSatoshis)
// BOLT #2: Channel funding limits
if (open.fundingSatoshis >= Channel.MAX_FUNDING && !nodeParams.features.hasFeature(Features.Wumbo)) throw InvalidFundingAmount(open.temporaryChannelId, open.fundingSatoshis, nodeParams.minFundingSatoshis, Channel.MAX_FUNDING)
if (open.fundingSatoshis >= Channel.MAX_FUNDING && !features.hasFeature(Features.Wumbo)) throw InvalidFundingAmount(open.temporaryChannelId, open.fundingSatoshis, nodeParams.minFundingSatoshis, Channel.MAX_FUNDING)
// BOLT #2: The receiving node MUST fail the channel if: push_msat is greater than funding_satoshis * 1000.
if (open.pushMsat > open.fundingSatoshis) throw InvalidPushAmount(open.temporaryChannelId, open.pushMsat, open.fundingSatoshis.toMilliSatoshi)

View file

@ -112,18 +112,18 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, watcher: ActorRe
stay
case Event(c: Peer.OpenChannel, d: ConnectedData) =>
if (c.fundingSatoshis >= Channel.MAX_FUNDING && !nodeParams.features.hasFeature(Wumbo)) {
if (c.fundingSatoshis >= Channel.MAX_FUNDING && !d.localFeatures.hasFeature(Wumbo)) {
sender ! Status.Failure(new RuntimeException(s"fundingSatoshis=${c.fundingSatoshis} is too big, you must enable large channels support in 'eclair.features' to use funding above ${Channel.MAX_FUNDING} (see eclair.conf)"))
stay
} else if (c.fundingSatoshis >= Channel.MAX_FUNDING && !d.remoteInit.features.hasFeature(Wumbo)) {
} else if (c.fundingSatoshis >= Channel.MAX_FUNDING && !d.remoteFeatures.hasFeature(Wumbo)) {
sender ! Status.Failure(new RuntimeException(s"fundingSatoshis=${c.fundingSatoshis} is too big, the remote peer doesn't support wumbo"))
stay
} else if (c.fundingSatoshis > nodeParams.maxFundingSatoshis) {
sender ! Status.Failure(new RuntimeException(s"fundingSatoshis=${c.fundingSatoshis} is too big for the current settings, increase 'eclair.max-funding-satoshis' (see eclair.conf)"))
stay
} else {
val channelVersion = ChannelVersion.pickChannelVersion(d.localInit.features, d.remoteInit.features)
val (channel, localParams) = createNewChannel(nodeParams, funder = true, c.fundingSatoshis, origin_opt = Some(sender), channelVersion)
val channelVersion = ChannelVersion.pickChannelVersion(d.localFeatures, d.remoteFeatures)
val (channel, localParams) = createNewChannel(nodeParams, d.localFeatures, funder = true, c.fundingSatoshis, origin_opt = Some(sender), channelVersion)
c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher))
val temporaryChannelId = randomBytes32
val channelFeeratePerKw = nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget)
@ -136,8 +136,8 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, watcher: ActorRe
case Event(msg: wire.OpenChannel, d: ConnectedData) =>
d.channels.get(TemporaryChannelId(msg.temporaryChannelId)) match {
case None =>
val channelVersion = ChannelVersion.pickChannelVersion(d.localInit.features, d.remoteInit.features)
val (channel, localParams) = createNewChannel(nodeParams, funder = false, fundingAmount = msg.fundingSatoshis, origin_opt = None, channelVersion)
val channelVersion = ChannelVersion.pickChannelVersion(d.localFeatures, d.remoteFeatures)
val (channel, localParams) = createNewChannel(nodeParams, d.localFeatures, funder = false, fundingAmount = msg.fundingSatoshis, origin_opt = None, channelVersion)
val temporaryChannelId = msg.temporaryChannelId
log.info(s"accepting a new channel with temporaryChannelId=$temporaryChannelId localParams=$localParams")
channel ! INPUT_INIT_FUNDEE(temporaryChannelId, localParams, d.peerConnection, d.remoteInit, channelVersion)
@ -282,7 +282,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, watcher: ActorRe
s(e)
}
def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingAmount: Satoshi, origin_opt: Option[ActorRef], channelVersion: ChannelVersion): (ActorRef, LocalParams) = {
def createNewChannel(nodeParams: NodeParams, features: Features, funder: Boolean, fundingAmount: Satoshi, origin_opt: Option[ActorRef], channelVersion: ChannelVersion): (ActorRef, LocalParams) = {
val (finalScript, walletStaticPaymentBasepoint) = channelVersion match {
case v if v.paysDirectlyToWallet =>
val walletKey = Helpers.getWalletPaymentBasepoint(wallet)
@ -290,7 +290,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, watcher: ActorRe
case _ =>
(Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash), None)
}
val localParams = makeChannelParams(nodeParams, finalScript, walletStaticPaymentBasepoint, funder, fundingAmount)
val localParams = makeChannelParams(nodeParams, features, finalScript, walletStaticPaymentBasepoint, funder, fundingAmount)
val channel = spawnChannel(nodeParams, origin_opt)
(channel, localParams)
}
@ -367,7 +367,9 @@ object Peer {
case object Nothing extends Data { override def channels = Map.empty }
case class DisconnectedData(channels: Map[FinalChannelId, ActorRef]) extends Data
case class ConnectedData(address: InetSocketAddress, peerConnection: ActorRef, localInit: wire.Init, remoteInit: wire.Init, channels: Map[ChannelId, ActorRef]) extends Data {
val connectionInfo: ConnectionInfo = ConnectionInfo(peerConnection, remoteInit)
val connectionInfo: ConnectionInfo = ConnectionInfo(peerConnection, localInit, remoteInit)
def localFeatures: Features = localInit.features
def remoteFeatures: Features = remoteInit.features
}
sealed trait State
@ -399,13 +401,13 @@ object Peer {
// @formatter:on
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubkey: ByteVector, walletStaticPaymentBasepoint: Option[PublicKey], isFunder: Boolean, fundingAmount: Satoshi): LocalParams = {
def makeChannelParams(nodeParams: NodeParams, features: Features, defaultFinalScriptPubkey: ByteVector, walletStaticPaymentBasepoint: Option[PublicKey], isFunder: Boolean, fundingAmount: Satoshi): LocalParams = {
// we make sure that funder and fundee key path end differently
val fundingKeyPath = nodeParams.keyManager.newFundingKeyPath(isFunder)
makeChannelParams(nodeParams, defaultFinalScriptPubkey, walletStaticPaymentBasepoint, isFunder, fundingAmount, fundingKeyPath)
makeChannelParams(nodeParams, features, defaultFinalScriptPubkey, walletStaticPaymentBasepoint, isFunder, fundingAmount, fundingKeyPath)
}
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubkey: ByteVector, walletStaticPaymentBasepoint: Option[PublicKey], isFunder: Boolean, fundingAmount: Satoshi, fundingKeyPath: DeterministicWallet.KeyPath): LocalParams = {
def makeChannelParams(nodeParams: NodeParams, features: Features, defaultFinalScriptPubkey: ByteVector, walletStaticPaymentBasepoint: Option[PublicKey], isFunder: Boolean, fundingAmount: Satoshi, fundingKeyPath: DeterministicWallet.KeyPath): LocalParams = {
LocalParams(
nodeParams.nodeId,
fundingKeyPath,
@ -418,6 +420,6 @@ object Peer {
isFunder = isFunder,
defaultFinalScriptPubKey = defaultFinalScriptPubkey,
walletStaticPaymentBasepoint = walletStaticPaymentBasepoint,
features = nodeParams.features)
features = features)
}
}

View file

@ -23,7 +23,7 @@ import fr.acinq.eclair.wire
sealed trait PeerEvent
case class ConnectionInfo(peerConnection: ActorRef, remoteInit: wire.Init)
case class ConnectionInfo(peerConnection: ActorRef, localInit: wire.Init, remoteInit: wire.Init)
case class PeerConnected(peer: ActorRef, nodeId: PublicKey, connectionInfo: ConnectionInfo) extends PeerEvent

View file

@ -72,10 +72,7 @@ class Switchboard(nodeParams: NodeParams, watcher: ActorRef, relayer: ActorRef,
case authenticated: PeerConnection.Authenticated =>
// if this is an incoming connection, we might not yet have created the peer
val peer = createOrGetPeer(authenticated.remoteNodeId, offlineChannels = Set.empty)
val features = nodeParams.overrideFeatures.get(authenticated.remoteNodeId) match {
case Some(f) => f
case None => nodeParams.features.maskFeaturesForEclairMobile()
}
val features = nodeParams.featuresFor(authenticated.remoteNodeId).maskFeaturesForEclairMobile()
val doSync = nodeParams.syncWhitelist.isEmpty || nodeParams.syncWhitelist.contains(authenticated.remoteNodeId)
authenticated.peerConnection ! PeerConnection.InitializeConnection(peer, nodeParams.chainHash, features, doSync)

View file

@ -130,7 +130,7 @@ class StartupSpec extends AnyFunSuite {
)
val nodeParams = makeNodeParamsWithDefaults(perNodeConf.withFallback(defaultConf))
val perNodeFeatures = nodeParams.overrideFeatures(PublicKey(ByteVector.fromValidHex("02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))
val perNodeFeatures = nodeParams.featuresFor(PublicKey(ByteVector.fromValidHex("02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))
assert(perNodeFeatures.hasFeature(BasicMultiPartPayment, Some(Mandatory)))
}

View file

@ -224,6 +224,7 @@ object TestConstants {
def channelParams = Peer.makeChannelParams(
nodeParams,
nodeParams.features,
Script.write(Script.pay2wpkh(PrivateKey(randomBytes32).publicKey)),
None,
isFunder = true,
@ -317,6 +318,7 @@ object TestConstants {
def channelParams = Peer.makeChannelParams(
nodeParams,
nodeParams.features,
Script.write(Script.pay2wpkh(PrivateKey(randomBytes32).publicKey)),
None,
isFunder = false,

View file

@ -49,21 +49,22 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
}
import com.softwaremill.quicklens._
val aliceNodeParams = TestConstants.Alice.nodeParams
val aliceNodeParams = Alice.nodeParams
.modify(_.chainHash).setToIf(test.tags.contains("mainnet"))(Block.LivenetGenesisBlock.hash)
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("high-max-funding-size"))(Btc(100))
val aliceParams = Alice.channelParams
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
val bobNodeParams = TestConstants.Bob.nodeParams
val bobNodeParams = Bob.nodeParams
.modify(_.chainHash).setToIf(test.tags.contains("mainnet"))(Block.LivenetGenesisBlock.hash)
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("high-max-funding-size"))(Btc(100))
val bobParams = Bob.channelParams
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
val setup = init(aliceNodeParams, bobNodeParams, wallet = noopWallet)
import setup._
val channelVersion = ChannelVersion.STANDARD
val (aliceParams, bobParams) = (Alice.channelParams, Bob.channelParams)
val aliceInit = Init(aliceParams.features)
val bobInit = Init(bobParams.features)
within(30 seconds) {

View file

@ -42,15 +42,17 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
override def withFixture(test: OneArgTest): Outcome = {
import com.softwaremill.quicklens._
val aliceParams = Alice.channelParams
val bobNodeParams = Bob.nodeParams
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("max-funding-satoshis"))(Btc(1))
val bobParams = Bob.channelParams
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
val setup = init(nodeParamsB = bobNodeParams)
import setup._
val channelVersion = ChannelVersion.STANDARD
val (aliceParams, bobParams) = (Alice.channelParams, Bob.channelParams)
val aliceInit = Init(aliceParams.features)
val bobInit = Init(bobParams.features)
within(30 seconds) {