Skip to content

Commit 4a04e4d

Browse files
committed
nits
1 parent 103fc65 commit 4a04e4d

File tree

10 files changed

+46
-26
lines changed

10 files changed

+46
-26
lines changed

docs/release-notes/eclair-vnext.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ Eclair will not allow remote peers to open new obsolete channels that do not sup
3535

3636
### Peer storage
3737

38-
When `option_provide_storage` is enabled, eclair will store a small for our peers.
39-
This is mostly intended for LSPs that serve mobile wallets to allow the users to restore their channel when they switch phones.
38+
With this release, eclair supports the `option_provide_storage` feature introduced in <https://github.com/lightning/bolts/pull/1110>.
39+
When `option_provide_storage` is enabled, eclair will store a small encrypted backup for peers that request it.
40+
This backup is limited to 65kB and node operators should customize the `eclair.peer-storage` configuration section to match their desired SLAs.
41+
This is mostly intended for LSPs that serve mobile wallets to allow users to restore their channels when they switch phones.
4042

4143
### API changes
4244

eclair-core/src/main/resources/reference.conf

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ eclair {
7373
option_dual_fund = optional
7474
option_quiesce = optional
7575
option_onion_messages = optional
76-
// Enable this if you serve mobile wallets.
76+
// This feature should only be enabled when acting as an LSP for mobile wallets.
77+
// When activating this feature, the peer-storage section should be customized to match desired SLAs.
7778
option_provide_storage = disabled
7879
option_channel_type = optional
7980
option_scid_alias = optional
@@ -601,8 +602,12 @@ eclair {
601602

602603
peer-storage {
603604
// Peer storage is persisted only after this delay to reduce the number of writes when updating it multiple times in a row.
605+
// A small delay may result in a lot of IO write operations, which can have a negative performance impact on the node.
606+
// But using a large delay increases the risk of not storing the latest peer data if you restart your node while writes are pending.
604607
write-delay = 1 minute
605-
// Peer storage is kept this long after the last channel has been closed.
608+
// Peer storage is kept this long after the last channel with that peer has been closed.
609+
// A long delay here guarantees that peers who are offline while their channels are closed will be able to get their funds
610+
// back if they restore from seed on a different device after the channels have been closed.
606611
removal-delay = 30 days
607612
}
608613
}

eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
9393
willFundRates_opt: Option[LiquidityAds.WillFundRates],
9494
peerWakeUpConfig: PeerReadyNotifier.WakeUpConfig,
9595
onTheFlyFundingConfig: OnTheFlyFunding.Config,
96-
peerStorageWriteDelay: FiniteDuration,
97-
peerStorageRemovalDelay: FiniteDuration) {
96+
peerStorageConfig: PeerStorageConfig) {
9897
val privateKey: Crypto.PrivateKey = nodeKeyManager.nodeKey.privateKey
9998

10099
val nodeId: PublicKey = nodeKeyManager.nodeId
@@ -158,6 +157,12 @@ case class PaymentFinalExpiryConf(min: CltvExpiryDelta, max: CltvExpiryDelta) {
158157
}
159158
}
160159

160+
/**
161+
* @param writeDelay delay before writing the peer's data to disk, which avoids doing multiple writes during bursts of storage updates.
162+
* @param removalDelay we keep our peer's data in our DB even after closing all of our channels with them, up to this duration.
163+
*/
164+
case class PeerStorageConfig(writeDelay: FiniteDuration, removalDelay: FiniteDuration)
165+
161166
object NodeParams extends Logging {
162167

163168
/**
@@ -682,8 +687,10 @@ object NodeParams extends Logging {
682687
onTheFlyFundingConfig = OnTheFlyFunding.Config(
683688
proposalTimeout = FiniteDuration(config.getDuration("on-the-fly-funding.proposal-timeout").getSeconds, TimeUnit.SECONDS),
684689
),
685-
peerStorageWriteDelay = FiniteDuration(config.getDuration("peer-storage.write-delay").getSeconds, TimeUnit.SECONDS),
686-
peerStorageRemovalDelay = FiniteDuration(config.getDuration("peer-storage.removal-delay").getSeconds, TimeUnit.SECONDS),
690+
peerStorageConfig = PeerStorageConfig(
691+
writeDelay = FiniteDuration(config.getDuration("peer-storage.write-delay").getSeconds, TimeUnit.SECONDS),
692+
removalDelay = FiniteDuration(config.getDuration("peer-storage.removal-delay").getSeconds, TimeUnit.SECONDS),
693+
)
687694
)
688695
}
689696
}

eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ class Setup(val datadir: File,
357357
system.deadLetters
358358
}
359359
_ = if (nodeParams.features.hasFeature(Features.ProvideStorage)) {
360-
system.spawn(Behaviors.supervise(PeerStorageCleaner(nodeParams.db.peers, nodeParams.peerStorageRemovalDelay)).onFailure(typed.SupervisorStrategy.restart), name = "peer-storage-cleaner")
360+
system.spawn(Behaviors.supervise(PeerStorageCleaner(nodeParams.db.peers, nodeParams.peerStorageConfig.removalDelay)).onFailure(typed.SupervisorStrategy.restart), name = "peer-storage-cleaner")
361361
}
362362
dbEventHandler = system.actorOf(SimpleSupervisor.props(DbEventHandler.props(nodeParams), "db-event-handler", SupervisorStrategy.Resume))
363363
register = system.actorOf(SimpleSupervisor.props(Register.props(), "register", SupervisorStrategy.Resume))

eclair-core/src/main/scala/fr/acinq/eclair/db/PeersDb.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ trait PeersDb {
3636

3737
def getRelayFees(nodeId: PublicKey): Option[RelayFees]
3838

39-
// Used only when option_provide_storage is enabled.
39+
/** Update our peer's blob data when [[fr.acinq.eclair.Features.ProvideStorage]] is enabled. */
4040
def updateStorage(nodeId: PublicKey, data: ByteVector): Unit
4141

42-
// Used only when option_provide_storage is enabled.
42+
/** Get the last blob of data we stored for that peer, if [[fr.acinq.eclair.Features.ProvideStorage]] is enabled. */
4343
def getStorage(nodeId: PublicKey): Option[ByteVector]
4444

45-
// Reclaim storage from peers that have had no active channel with us for a while.
45+
/** Remove storage from peers that have had no active channel with us for a while. */
4646
def removePeerStorage(peerRemovedBefore: TimestampSecond): Unit
47+
4748
}

eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,13 @@ class Peer(val nodeParams: NodeParams,
528528
stay()
529529

530530
case Event(store: PeerStorageStore, d: ConnectedData) if nodeParams.features.hasFeature(Features.ProvideStorage) && d.channels.nonEmpty =>
531-
startSingleTimer("peer-storage-write", WritePeerStorage, nodeParams.peerStorageWriteDelay)
531+
// If we don't have any pending write operations, we write the updated peer storage to disk after a delay.
532+
// This ensures that when we receive a burst of peer storage updates, we will rate-limit our IO disk operations.
533+
// If we already have a pending write operation, we must not reset the timer, otherwise we may indefinitely delay
534+
// writing to the DB and may never store our peer's backup.
535+
if (d.peerStorage.written) {
536+
startSingleTimer("peer-storage-write", WritePeerStorage, nodeParams.peerStorageConfig.writeDelay)
537+
}
532538
stay() using d.copy(peerStorage = PeerStorage(Some(store.blob), written = false))
533539

534540
case Event(WritePeerStorage, d: ConnectedData) =>

eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,9 +602,9 @@ case class GossipTimestampFilter(chainHash: BlockHash, firstTimestamp: Timestamp
602602

603603
case class OnionMessage(blindingKey: PublicKey, onionRoutingPacket: OnionRoutingPacket, tlvStream: TlvStream[OnionMessageTlv] = TlvStream.empty) extends LightningMessage
604604

605-
case class PeerStorageStore(blob: ByteVector, tlvStream: TlvStream[PeerStorageTlv] = TlvStream.empty) extends LightningMessage
605+
case class PeerStorageStore(blob: ByteVector, tlvStream: TlvStream[PeerStorageTlv] = TlvStream.empty) extends SetupMessage
606606

607-
case class PeerStorageRetrieval(blob: ByteVector, tlvStream: TlvStream[PeerStorageTlv] = TlvStream.empty) extends LightningMessage
607+
case class PeerStorageRetrieval(blob: ByteVector, tlvStream: TlvStream[PeerStorageTlv] = TlvStream.empty) extends SetupMessage
608608

609609
// NB: blank lines to minimize merge conflicts
610610

eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PeerStorageTlv.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 ACINQ SAS
2+
* Copyright 2024 ACINQ SAS
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,7 @@ object TestConstants {
241241
willFundRates_opt = Some(defaultLiquidityRates),
242242
peerWakeUpConfig = PeerReadyNotifier.WakeUpConfig(enabled = false, timeout = 30 seconds),
243243
onTheFlyFundingConfig = OnTheFlyFunding.Config(proposalTimeout = 90 seconds),
244-
peerStorageWriteDelay = 5 seconds,
245-
peerStorageRemovalDelay = 10 seconds,
244+
peerStorageConfig = PeerStorageConfig(writeDelay = 5 seconds, removalDelay = 10 seconds)
246245
)
247246

248247
def channelParams: LocalParams = OpenChannelInterceptor.makeChannelParams(
@@ -419,8 +418,7 @@ object TestConstants {
419418
willFundRates_opt = Some(defaultLiquidityRates),
420419
peerWakeUpConfig = PeerReadyNotifier.WakeUpConfig(enabled = false, timeout = 30 seconds),
421420
onTheFlyFundingConfig = OnTheFlyFunding.Config(proposalTimeout = 90 seconds),
422-
peerStorageWriteDelay = 5 seconds,
423-
peerStorageRemovalDelay = 10 seconds,
421+
peerStorageConfig = PeerStorageConfig(writeDelay = 5 seconds, removalDelay = 10 seconds)
424422
)
425423

426424
def channelParams: LocalParams = OpenChannelInterceptor.makeChannelParams(

eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ class PeerSpec extends FixtureSpec {
108108

109109
def cleanupFixture(fixture: FixtureParam): Unit = fixture.cleanup()
110110

111-
def connect(remoteNodeId: PublicKey, peer: TestFSMRef[Peer.State, Peer.Data, Peer], peerConnection: TestProbe, switchboard: TestProbe, channels: Set[PersistentChannelData] = Set.empty, remoteInit: protocol.Init = protocol.Init(Bob.nodeParams.features.initFeatures()), sendInit: Boolean = true, peerStorage: Option[ByteVector] = None)(implicit system: ActorSystem): Unit = {
111+
def connect(remoteNodeId: PublicKey, peer: TestFSMRef[Peer.State, Peer.Data, Peer], peerConnection: TestProbe, switchboard: TestProbe, channels: Set[PersistentChannelData] = Set.empty, remoteInit: protocol.Init = protocol.Init(Bob.nodeParams.features.initFeatures()), initializePeer: Boolean = true, peerStorage: Option[ByteVector] = None)(implicit system: ActorSystem): Unit = {
112112
// let's simulate a connection
113-
if (sendInit) {
113+
if (initializePeer) {
114114
switchboard.send(peer, Peer.Init(channels, Map.empty))
115115
}
116116
val localInit = protocol.Init(peer.underlyingActor.nodeParams.features.initFeatures())
@@ -764,14 +764,15 @@ class PeerSpec extends FixtureSpec {
764764

765765
nodeParams.db.peers.updateStorage(remoteNodeId, hex"abcdef")
766766
connect(remoteNodeId, peer, peerConnection1, switchboard, channels = Set(ChannelCodecsSpec.normal), peerStorage = Some(hex"abcdef"))
767+
peerConnection1.send(peer, PeerStorageStore(hex"deadbeef"))
767768
peerConnection1.send(peer, PeerStorageStore(hex"0123456789"))
768769
peer ! Peer.Disconnect(f.remoteNodeId)
769-
connect(remoteNodeId, peer, peerConnection2, switchboard, channels = Set(ChannelCodecsSpec.normal), sendInit = false, peerStorage = Some(hex"0123456789"))
770+
connect(remoteNodeId, peer, peerConnection2, switchboard, channels = Set(ChannelCodecsSpec.normal), initializePeer = false, peerStorage = Some(hex"0123456789"))
770771
peerConnection2.send(peer, PeerStorageStore(hex"1111"))
771-
connect(remoteNodeId, peer, peerConnection3, switchboard, channels = Set(ChannelCodecsSpec.normal), sendInit = false, peerStorage = Some(hex"1111"))
772-
assert(nodeParams.db.peers.getStorage(remoteNodeId).contains(hex"abcdef")) // Because of the delayed writes, the original value hasn't been updated yet.
772+
connect(remoteNodeId, peer, peerConnection3, switchboard, channels = Set(ChannelCodecsSpec.normal), initializePeer = false, peerStorage = Some(hex"1111"))
773+
// Because of the delayed writes, we may not have stored the latest value immediately, but we will eventually store it.
773774
eventually {
774-
assert(nodeParams.db.peers.getStorage(remoteNodeId).contains(hex"1111")) // Now it is updated.
775+
assert(nodeParams.db.peers.getStorage(remoteNodeId).contains(hex"1111"))
775776
}
776777
}
777778

0 commit comments

Comments
 (0)