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

Peer storage #723

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
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 = [email protected](
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 = [email protected](
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([email protected](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 = [email protected](remoteShutdown = cmd.message, commitments = commitments.copy(remoteChannelData = cmd.message.channelData))
val newState = [email protected](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 = [email protected] ?: Shutdown(channelId, commitments.params.localParams.defaultFinalScriptPubKey)
if ([email protected] == 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 = [email protected](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