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 Oct 30, 2024
1 parent 0537e61 commit 8b6d95b
Show file tree
Hide file tree
Showing 58 changed files with 227 additions and 531 deletions.
12 changes: 10 additions & 2 deletions 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
1 change: 0 additions & 1 deletion src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,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
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
22 changes: 3 additions & 19 deletions src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt
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
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
22 changes: 10 additions & 12 deletions src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt
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 @@ -851,13 +850,12 @@ data class Normal(
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
16 changes: 4 additions & 12 deletions src/commonMain/kotlin/fr/acinq/lightning/channel/states/Offline.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,14 @@ data class Offline(val state: PersistedChannelState) : ChannelState() {
val channelReestablish = state.run { createChannelReestablish() }
val actions = listOf(ChannelAction.Message.Send(channelReestablish))
val nextState = state.copy(channelParams = state.channelParams.updateFeatures(cmd.localInit, cmd.remoteInit))
Pair(Syncing(nextState, channelReestablishSent = true), actions)
Pair(Syncing(nextState), actions)
}
is ChannelStateWithCommitments -> {
logger.info { "syncing ${state::class}" }
val sendChannelReestablish = !staticParams.nodeParams.features.hasFeature(Feature.ChannelBackupClient)
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.
logger.info { "waiting for their channel_reestablish message" }
} else {
val channelReestablish = state.run { createChannelReestablish() }
add(ChannelAction.Message.Send(channelReestablish))
}
}
val channelReestablish = state.run { createChannelReestablish() }
val actions = listOf(ChannelAction.Message.Send(channelReestablish))
val nextState = state.updateCommitments(state.commitments.copy(params = state.commitments.params.updateFeatures(cmd.localInit, cmd.remoteInit)))
Pair(Syncing(nextState, channelReestablishSent = sendChannelReestablish), actions)
Pair(Syncing(nextState), actions)
}
}
}
Expand Down
Loading

0 comments on commit 8b6d95b

Please sign in to comment.