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

Sync whitelist #954

Merged
merged 2 commits into from
Aug 29, 2019
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
1 change: 1 addition & 0 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ eclair {
# local-features = ""
# }
]
sync-whitelist = [] // a list of public keys; if non-empty, we will only do the initial sync with those peers
channel-flags = 1 // announce channels

dust-limit-satoshis = 546
Expand Down
4 changes: 4 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ case class NodeParams(keyManager: KeyManager,
globalFeatures: ByteVector,
localFeatures: ByteVector,
overrideFeatures: Map[PublicKey, (ByteVector, ByteVector)],
syncWhitelist: Set[PublicKey],
dustLimit: Satoshi,
onChainFeeConf: OnChainFeeConf,
maxHtlcValueInFlightMsat: UInt64,
Expand Down Expand Up @@ -162,6 +163,8 @@ object NodeParams {
p -> (gf, lf)
}.toMap

val syncWhitelist: Set[PublicKey] = config.getStringList("sync-whitelist").map(s => PublicKey(ByteVector.fromValidHex(s))).toSet

val socksProxy_opt = if (config.getBoolean("socks5.enabled")) {
Some(Socks5ProxyParams(
address = new InetSocketAddress(config.getString("socks5.host"), config.getInt("socks5.port")),
Expand Down Expand Up @@ -204,6 +207,7 @@ object NodeParams {
globalFeatures = ByteVector.fromValidHex(config.getString("global-features")),
localFeatures = ByteVector.fromValidHex(config.getString("local-features")),
overrideFeatures = overrideFeatures,
syncWhitelist = syncWhitelist,
dustLimit = dustLimitSatoshis,
onChainFeeConf = OnChainFeeConf(
feeTargets = feeTargets,
Expand Down
8 changes: 6 additions & 2 deletions eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,12 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: A
Some(QueryChannelRangeTlv.QueryFlags(QueryChannelRangeTlv.QueryFlags.WANT_ALL))
case _ => None
}
log.info(s"sending sync channel range query with flags_opt=$flags_opt")
router ! SendChannelQuery(remoteNodeId, d.transport, flags_opt = flags_opt)
if (nodeParams.syncWhitelist.isEmpty || nodeParams.syncWhitelist.contains(remoteNodeId)) {
log.info(s"sending sync channel range query with flags_opt=$flags_opt")
router ! SendChannelQuery(remoteNodeId, d.transport, flags_opt = flags_opt)
} else {
log.info("not syncing with this peer")
}
}

// let's bring existing/requested channels online
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ object TestConstants {
globalFeatures = ByteVector.empty,
localFeatures = ByteVector(0),
overrideFeatures = Map.empty,
syncWhitelist = Set.empty,
dustLimit = 1100 sat,
onChainFeeConf = OnChainFeeConf(
feeTargets = FeeTargets(6, 2, 2, 6),
Expand Down Expand Up @@ -142,6 +143,7 @@ object TestConstants {
globalFeatures = ByteVector.empty,
localFeatures = ByteVector.empty, // no announcement
overrideFeatures = Map.empty,
syncWhitelist = Set.empty,
dustLimit = 1000 sat,
onChainFeeConf = OnChainFeeConf(
feeTargets = FeeTargets(6, 2, 2, 6),
Expand Down
56 changes: 40 additions & 16 deletions eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
import akka.actor.{ActorRef, PoisonPill}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{Satoshi}
import fr.acinq.bitcoin.Satoshi
import fr.acinq.eclair.TestConstants._
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.{EclairWallet, TestWallet}
Expand All @@ -31,10 +31,10 @@ import fr.acinq.eclair.channel.{ChannelCreated, HasCommitments}
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.io.Peer._
import fr.acinq.eclair.router.RoutingSyncSpec.makeFakeRoutingInfo
import fr.acinq.eclair.router.{Rebroadcast, RoutingSyncSpec}
import fr.acinq.eclair.wire.{ChannelCodecsSpec, Color, EncodedShortChannelIds, EncodingType, Error, IPv4, NodeAddress, NodeAnnouncement, Ping, Pong, QueryShortChannelIds, Tlv, TlvStream}
import fr.acinq.eclair.router.{Rebroadcast, RoutingSyncSpec, SendChannelQuery}
import fr.acinq.eclair.wire.{ChannelCodecsSpec, Color, EncodedShortChannelIds, EncodingType, Error, IPv4, NodeAddress, NodeAnnouncement, Ping, Pong, QueryShortChannelIds, TlvStream}
import org.scalatest.{Outcome, Tag}
import scodec.bits.ByteVector
import scodec.bits.{ByteVector, _}

import scala.concurrent.duration._

Expand All @@ -52,15 +52,6 @@ class PeerSpec extends TestkitBaseClass with StateTestsHelperMethods {
case class FixtureParam(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: TestFSMRef[Peer.State, Peer.Data, Peer])

override protected def withFixture(test: OneArgTest): Outcome = {
val aParams = Alice.nodeParams
val aliceParams = test.tags.contains("with_node_announcements") match {
case true =>
val bobAnnouncement = NodeAnnouncement(randomBytes64, ByteVector.empty, 1, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", fakeIPAddress :: Nil)
aParams.db.network.addNode(bobAnnouncement)
aParams
case false => aParams
}

val authenticator = TestProbe()
val watcher = TestProbe()
val router = TestProbe()
Expand All @@ -69,20 +60,35 @@ class PeerSpec extends TestkitBaseClass with StateTestsHelperMethods {
val transport = TestProbe()
val wallet: EclairWallet = new TestWallet()
val remoteNodeId = Bob.nodeParams.nodeId

import com.softwaremill.quicklens._
val aliceParams = TestConstants.Alice.nodeParams
.modify(_.syncWhitelist).setToIf(test.tags.contains("sync-whitelist-bob"))(Set(remoteNodeId))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would find this slightly better:

Suggested change
.modify(_.syncWhitelist).setToIf(test.tags.contains("sync-whitelist-bob"))(Set(remoteNodeId))
.modify(_.syncWhitelist).setToIf(test.tags.contains("sync-whitelist-bob"))(Set(Bob.nodeParams.nodeId))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is we reuse remoteNodeId a few lines below and it has to be the same value.

.modify(_.syncWhitelist).setToIf(test.tags.contains("sync-whitelist-random"))(Set(randomKey.publicKey))

if (test.tags.contains("with_node_announcements")) {
val bobAnnouncement = NodeAnnouncement(randomBytes64, ByteVector.empty, 1, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", fakeIPAddress :: Nil)
aliceParams.db.network.addNode(bobAnnouncement)
}

val peer: TestFSMRef[Peer.State, Peer.Data, Peer] = TestFSMRef(new Peer(aliceParams, remoteNodeId, authenticator.ref, watcher.ref, router.ref, relayer.ref, wallet))
withFixture(test.toNoArgTest(FixtureParam(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer)))
}

def connect(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: ActorRef, channels: Set[HasCommitments] = Set.empty): Unit = {
def connect(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: ActorRef, channels: Set[HasCommitments] = Set.empty, remoteInit: wire.Init = wire.Init(Bob.nodeParams.globalFeatures, Bob.nodeParams.localFeatures), expectSync: Boolean = false): Unit = {
// let's simulate a connection
val probe = TestProbe()
probe.send(peer, Peer.Init(None, channels))
authenticator.send(peer, Authenticator.Authenticated(connection.ref, transport.ref, remoteNodeId, fakeIPAddress.socketAddress, outgoing = true, None))
transport.expectMsgType[TransportHandler.Listener]
transport.expectMsgType[wire.Init]
transport.send(peer, wire.Init(Bob.nodeParams.globalFeatures, Bob.nodeParams.localFeatures))
transport.send(peer, remoteInit)
transport.expectMsgType[TransportHandler.ReadAck]
router.expectNoMsg(1 second) // bob's features require no sync
if (expectSync) {
router.expectMsgType[SendChannelQuery]
} else {
router.expectNoMsg(1 second)
}
probe.send(peer, Peer.GetPeerInfo)
assert(probe.expectMsgType[Peer.PeerInfo].state == "CONNECTED")
}
Expand Down Expand Up @@ -255,6 +261,24 @@ class PeerSpec extends TestkitBaseClass with StateTestsHelperMethods {
assert(channelCreated.fundingTxFeeratePerKw.get == peer.feeEstimator.getFeeratePerKw(peer.feeTargets.fundingBlockTarget))
}

test("sync if no whitelist is defined") { f =>
import f._
val remoteInit = wire.Init(Bob.nodeParams.globalFeatures, bin"10000000".toByteVector) // bob support channel range queries
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, Set.empty, remoteInit, expectSync = true)
}

test("sync if whitelist contains peer", Tag("sync-whitelist-bob")) { f =>
import f._
val remoteInit = wire.Init(Bob.nodeParams.globalFeatures, bin"10000000".toByteVector) // bob support channel range queries
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, Set.empty, remoteInit, expectSync = true)
}

test("don't sync if whitelist doesn't contain peer", Tag("sync-whitelist-random")) { f =>
import f._
val remoteInit = wire.Init(Bob.nodeParams.globalFeatures, bin"10000000".toByteVector) // bob support channel range queries
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, Set.empty, remoteInit, expectSync = false)
}

test("reply to ping") { f =>
import f._
val probe = TestProbe()
Expand Down