Skip to content

Commit

Permalink
Peer storage
Browse files Browse the repository at this point in the history
Replaces the custom channel data backup with the new peer storage (lightning/bolts#1110)
  • Loading branch information
thomash-acinq committed Nov 13, 2024
1 parent 451f9df commit ad27653
Show file tree
Hide file tree
Showing 64 changed files with 252 additions and 462 deletions.
12 changes: 10 additions & 2 deletions modules/core/src/commonMain/kotlin/fr/acinq/lightning/Features.kt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ sealed class Feature {
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

@Serializable
object ProvideStorage : Feature() {
override val rfcName get() = "option_provide_storage"
override val mandatory get() = 42
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

@Serializable
object ChannelType : Feature() {
override val rfcName get() = "option_channel_type"
Expand Down Expand Up @@ -224,15 +231,15 @@ sealed class Feature {
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

/** This feature bit should be activated when a node wants to send channel backups to their peers. */
/** This feature is deprecated but must be kept to allow deserialization of old backups. */
@Serializable
object ChannelBackupClient : Feature() {
override val rfcName get() = "channel_backup_client"
override val mandatory get() = 144
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init)
}

/** This feature bit should be activated when a node stores channel backups for their peers. */
/** This feature is deprecated but must be kept to allow deserialization of old backups. */
@Serializable
object ChannelBackupProvider : Feature() {
override val rfcName get() = "channel_backup_provider"
Expand Down Expand Up @@ -337,6 +344,7 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
Feature.ShutdownAnySegwit,
Feature.DualFunding,
Feature.Quiescence,
Feature.ProvideStorage,
Feature.ChannelType,
Feature.PaymentMetadata,
Feature.TrampolinePayment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ data class NodeParams(
val paymentRecipientExpiryParams: RecipientCltvExpiryParams,
val zeroConfPeers: Set<PublicKey>,
val liquidityPolicy: MutableStateFlow<LiquidityPolicy>,
val usePeerStorage: Boolean,
) {
val nodePrivateKey get() = keyManager.nodeKeys.nodeKey.privateKey
val nodeId get() = keyManager.nodeKeys.nodeKey.publicKey
Expand Down Expand Up @@ -207,7 +208,6 @@ data class NodeParams(
Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional,
Feature.ZeroReserveChannels to FeatureSupport.Optional,
Feature.WakeUpNotificationClient to FeatureSupport.Optional,
Feature.ChannelBackupClient to FeatureSupport.Optional,
Feature.ExperimentalSplice to FeatureSupport.Optional,
Feature.OnTheFlyFunding to FeatureSupport.Optional,
Feature.FundingFeeCredit to FeatureSupport.Optional,
Expand Down Expand Up @@ -254,6 +254,7 @@ data class NodeParams(
maxAllowedFeeCredit = 0.msat
)
),
usePeerStorage = true,
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ sealed class ChannelCommand {
}

data class MessageReceived(val message: LightningMessage) : ChannelCommand()
data object PeerBackupReceived : ChannelCommand()
data class WatchReceived(val watch: WatchEvent) : ChannelCommand()

sealed interface ForbiddenDuringSplice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,6 @@ data class Commitments(
val payments: Map<Long, UUID>, // for outgoing htlcs, maps to paymentId
val remoteNextCommitInfo: Either<WaitingForRevocation, PublicKey>, // this one is tricky, it must be kept in sync with Commitment.nextRemoteCommit
val remotePerCommitmentSecrets: ShaChain,
val remoteChannelData: EncryptedChannelData = EncryptedChannelData.empty
) {
init {
require(active.isNotEmpty()) { "there must be at least one active commitment" }
Expand Down Expand Up @@ -794,7 +793,6 @@ data class Commitments(
localChanges = changes.localChanges.copy(acked = emptyList()),
remoteChanges = changes.remoteChanges.copy(proposed = emptyList(), acked = changes.remoteChanges.acked + changes.remoteChanges.proposed)
),
remoteChannelData = commits.last().channelData // the last message is the most recent
)
return Either.Right(Pair(commitments1, revocation))
}
Expand Down Expand Up @@ -849,7 +847,6 @@ data class Commitments(
remoteNextCommitInfo = Either.Right(revocation.nextPerCommitmentPoint),
remotePerCommitmentSecrets = remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret.value, 0xFFFFFFFFFFFFL - remoteCommitIndex),
payments = payments1,
remoteChannelData = revocation.channelData
)
return Either.Right(Pair(commitments1, actions.toList()))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,28 +62,12 @@ sealed class ChannelState {
suspend fun ChannelContext.process(cmd: ChannelCommand): Pair<ChannelState, List<ChannelAction>> {
return try {
processInternal(cmd)
.let { (newState, actions) -> Pair(newState, newState.run { maybeAddBackupToMessages(actions) }) }
.let { (newState, actions) -> Pair(newState, actions + onTransition(newState)) }
} catch (t: Throwable) {
handleLocalError(cmd, t)
}
}

/** Update outgoing messages to include an encrypted backup when necessary. */
private fun ChannelContext.maybeAddBackupToMessages(actions: List<ChannelAction>): List<ChannelAction> = when {
this@ChannelState is PersistedChannelState && staticParams.nodeParams.features.hasFeature(Feature.ChannelBackupClient) -> actions.map {
when {
it is ChannelAction.Message.Send && it.message is TxSignatures -> it.copy(message = it.message.withChannelData(EncryptedChannelData.from(privateKey, this@ChannelState), logger))
it is ChannelAction.Message.Send && it.message is CommitSig -> it.copy(message = it.message.withChannelData(EncryptedChannelData.from(privateKey, this@ChannelState), logger))
it is ChannelAction.Message.Send && it.message is RevokeAndAck -> it.copy(message = it.message.withChannelData(EncryptedChannelData.from(privateKey, this@ChannelState), logger))
it is ChannelAction.Message.Send && it.message is Shutdown -> it.copy(message = it.message.withChannelData(EncryptedChannelData.from(privateKey, this@ChannelState), logger))
it is ChannelAction.Message.Send && it.message is ClosingSigned -> it.copy(message = it.message.withChannelData(EncryptedChannelData.from(privateKey, this@ChannelState), logger))
else -> it
}
}
else -> actions
}

/** Add actions for some transitions */
private fun ChannelContext.onTransition(newState: ChannelState): List<ChannelAction> {
val oldState = when (this@ChannelState) {
Expand Down Expand Up @@ -301,7 +285,7 @@ sealed class ChannelState {
sealed class PersistedChannelState : ChannelState() {
abstract val channelId: ByteVector32

internal fun ChannelContext.createChannelReestablish(): HasEncryptedChannelData = when (val state = this@PersistedChannelState) {
internal fun ChannelContext.createChannelReestablish(): ChannelReestablish = when (val state = this@PersistedChannelState) {
is WaitForFundingSigned -> {
val myFirstPerCommitmentPoint = keyManager.channelKeys(state.channelParams.localParams.fundingKeyPath).commitmentPoint(0)
ChannelReestablish(
Expand All @@ -311,7 +295,7 @@ sealed class PersistedChannelState : ChannelState() {
yourLastCommitmentSecret = PrivateKey(ByteVector32.Zeroes),
myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint,
TlvStream(ChannelReestablishTlv.NextFunding(state.signingSession.fundingTx.txId))
).withChannelData(state.remoteChannelData, logger)
)
}
is ChannelStateWithCommitments -> {
val yourLastPerCommitmentSecret = state.commitments.remotePerCommitmentSecrets.lastIndex?.let { state.commitments.remotePerCommitmentSecrets.getHash(it) } ?: ByteVector32.Zeroes
Expand All @@ -329,7 +313,7 @@ sealed class PersistedChannelState : ChannelState() {
yourLastCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret),
myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint,
tlvStream = tlvs
).withChannelData(state.commitments.remoteChannelData, logger)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,6 @@ data class Closing(
null -> Pair(closing1, listOf())
else -> {
logger.info { "channel is now closed" }
if (closingType !is MutualClose) {
logger.debug { "last known remoteChannelData=${commitments.remoteChannelData}" }
}
Pair(Closed(closing1), listOf(setClosingStatus(closingType)))
}
}
Expand Down Expand Up @@ -375,6 +372,7 @@ data class Closing(
is ChannelCommand.Funding -> unhandled(cmd)
is ChannelCommand.Connected -> unhandled(cmd)
is ChannelCommand.Disconnected -> unhandled(cmd)
is ChannelCommand.PeerBackupReceived -> unhandled(cmd)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ data class LegacyWaitForFundingConfirmed(
is ChannelCommand.Closing -> unhandled(cmd)
is ChannelCommand.Connected -> unhandled(cmd)
is ChannelCommand.Disconnected -> Pair(Offline(this@LegacyWaitForFundingConfirmed), listOf())
is ChannelCommand.PeerBackupReceived -> unhandled(cmd)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ data class LegacyWaitForFundingLocked(
is ChannelCommand.Closing -> unhandled(cmd)
is ChannelCommand.Connected -> unhandled(cmd)
is ChannelCommand.Disconnected -> Pair(Offline(this@LegacyWaitForFundingLocked), listOf())
is ChannelCommand.PeerBackupReceived -> unhandled(cmd)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ data class Negotiating(
closingTxProposed.last() + listOf(ClosingTxProposed(closingTx, closingSigned))
)
val nextState = this@Negotiating.copy(
commitments = commitments.copy(remoteChannelData = cmd.message.channelData),
closingTxProposed = closingProposed1,
bestUnpublishedClosingTx = signedClosingTx
)
Expand Down Expand Up @@ -127,7 +126,6 @@ data class Negotiating(
closingTxProposed.last() + listOf(ClosingTxProposed(closingTx, closingSigned))
)
val nextState = this@Negotiating.copy(
commitments = commitments.copy(remoteChannelData = cmd.message.channelData),
closingTxProposed = closingProposed1,
bestUnpublishedClosingTx = signedClosingTx
)
Expand Down Expand Up @@ -179,6 +177,7 @@ data class Negotiating(
is ChannelCommand.Closing -> unhandled(cmd)
is ChannelCommand.Connected -> unhandled(cmd)
is ChannelCommand.Disconnected -> Pair(Offline(this@Negotiating), listOf())
is ChannelCommand.PeerBackupReceived -> unhandled(cmd)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ data class Normal(
logger.info { "waiting for tx_sigs" }
Pair(this@Normal.copy(spliceStatus = spliceStatus.copy(session = signingSession1)), listOf())
}
is InteractiveTxSigningSessionAction.SendTxSigs -> sendSpliceTxSigs(spliceStatus.origins, action, spliceStatus.liquidityPurchase, cmd.message.channelData)
is InteractiveTxSigningSessionAction.SendTxSigs -> sendSpliceTxSigs(spliceStatus.origins, action, spliceStatus.liquidityPurchase)
}
}
ignoreRetransmittedCommitSig(cmd.message) -> {
Expand Down Expand Up @@ -298,7 +298,7 @@ data class Normal(
}
is Either.Right -> {
// no, let's sign right away
val newState = this@Normal.copy(remoteShutdown = cmd.message, commitments = commitments.copy(remoteChannelData = cmd.message.channelData))
val newState = this@Normal.copy(remoteShutdown = cmd.message)
Pair(newState, listOf(ChannelAction.Message.SendToSelf(ChannelCommand.Commitment.Sign)))
}
}
Expand All @@ -308,18 +308,17 @@ data class Normal(
val actions = mutableListOf<ChannelAction>()
val localShutdown = this@Normal.localShutdown ?: Shutdown(channelId, commitments.params.localParams.defaultFinalScriptPubKey)
if (this@Normal.localShutdown == null) actions.add(ChannelAction.Message.Send(localShutdown))
val commitments1 = commitments.copy(remoteChannelData = cmd.message.channelData)
when {
commitments1.hasNoPendingHtlcsOrFeeUpdate() && paysClosingFees -> {
commitments.hasNoPendingHtlcsOrFeeUpdate() && paysClosingFees -> {
val (closingTx, closingSigned) = Helpers.Closing.makeFirstClosingTx(
channelKeys(),
commitments1.latest,
commitments.latest,
localShutdown.scriptPubKey.toByteArray(),
cmd.message.scriptPubKey.toByteArray(),
closingFeerates ?: ClosingFeerates(currentOnChainFeerates().mutualCloseFeerate),
)
val nextState = Negotiating(
commitments1,
commitments,
localShutdown,
cmd.message,
listOf(listOf(ClosingTxProposed(closingTx, closingSigned))),
Expand All @@ -329,14 +328,14 @@ data class Normal(
actions.addAll(listOf(ChannelAction.Storage.StoreState(nextState), ChannelAction.Message.Send(closingSigned)))
Pair(nextState, actions)
}
commitments1.hasNoPendingHtlcsOrFeeUpdate() -> {
val nextState = Negotiating(commitments1, localShutdown, cmd.message, listOf(listOf()), null, closingFeerates)
commitments.hasNoPendingHtlcsOrFeeUpdate() -> {
val nextState = Negotiating(commitments, localShutdown, cmd.message, listOf(listOf()), null, closingFeerates)
actions.add(ChannelAction.Storage.StoreState(nextState))
Pair(nextState, actions)
}
else -> {
// there are some pending changes, we need to wait for them to be settled (fail/fulfill htlcs and sign fee updates)
val nextState = ShuttingDown(commitments1, localShutdown, cmd.message, closingFeerates)
val nextState = ShuttingDown(commitments, localShutdown, cmd.message, closingFeerates)
actions.add(ChannelAction.Storage.StoreState(nextState))
Pair(nextState, actions)
}
Expand Down Expand Up @@ -670,7 +669,7 @@ data class Normal(
}
is Either.Right -> {
val action: InteractiveTxSigningSessionAction.SendTxSigs = res.value
sendSpliceTxSigs(spliceStatus.origins, action, spliceStatus.liquidityPurchase, cmd.message.channelData)
sendSpliceTxSigs(spliceStatus.origins, action, spliceStatus.liquidityPurchase)
}
}
}
Expand Down Expand Up @@ -844,20 +843,20 @@ data class Normal(
is ChannelCommand.Connected -> unhandled(cmd)
is ChannelCommand.Closing -> unhandled(cmd)
is ChannelCommand.Init -> unhandled(cmd)
is ChannelCommand.PeerBackupReceived -> unhandled(cmd)
}
}

private fun ChannelContext.sendSpliceTxSigs(
origins: List<Origin>,
action: InteractiveTxSigningSessionAction.SendTxSigs,
liquidityPurchase: LiquidityAds.Purchase?,
remoteChannelData: EncryptedChannelData
): Pair<Normal, List<ChannelAction>> {
logger.info { "sending tx_sigs" }
// We watch for confirmation in all cases, to allow pruning outdated commitments when transactions confirm.
val fundingMinDepth = Helpers.minDepthForFunding(staticParams.nodeParams, action.fundingTx.fundingParams.fundingAmount)
val watchConfirmed = WatchConfirmed(channelId, action.commitment.fundingTxId, action.commitment.commitInput.txOut.publicKeyScript, fundingMinDepth.toLong(), BITCOIN_FUNDING_DEPTHOK)
val commitments = commitments.add(action.commitment).copy(remoteChannelData = remoteChannelData)
val commitments = commitments.add(action.commitment)
val nextState = this@Normal.copy(commitments = commitments, spliceStatus = SpliceStatus.None)
val actions = buildList {
add(ChannelAction.Storage.StoreState(nextState))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package fr.acinq.lightning.channel.states

import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.Feature
import fr.acinq.lightning.ShortChannelId
import fr.acinq.lightning.blockchain.*
import fr.acinq.lightning.channel.*
Expand Down Expand Up @@ -37,7 +36,7 @@ data class Offline(val state: PersistedChannelState) : ChannelState() {
}
is ChannelStateWithCommitments -> {
logger.info { "syncing ${state::class}" }
val sendChannelReestablish = !staticParams.nodeParams.features.hasFeature(Feature.ChannelBackupClient)
val sendChannelReestablish = !staticParams.nodeParams.usePeerStorage
val actions = buildList {
if (!sendChannelReestablish) {
// We wait for them to go first, which lets us restore from the latest backup if we've lost data.
Expand Down Expand Up @@ -126,6 +125,7 @@ data class Offline(val state: PersistedChannelState) : ChannelState() {
is ChannelCommand.Init -> unhandled(cmd)
is ChannelCommand.Funding -> unhandled(cmd)
is ChannelCommand.Closing -> unhandled(cmd)
is ChannelCommand.PeerBackupReceived -> unhandled(cmd)
}
}
}
Loading

0 comments on commit ad27653

Please sign in to comment.