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

Remove deprecated serialization format and fields #685

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
86 changes: 23 additions & 63 deletions src/commonMain/kotlin/fr/acinq/lightning/Features.kt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ sealed class Feature {
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

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

@Serializable
object ChannelType : Feature() {
override val rfcName get() = "option_channel_type"
Expand All @@ -142,17 +149,6 @@ sealed class Feature {

// The following features have not been standardised, hence the high feature bits to avoid conflicts.

// We historically used the following feature bit in our invoices.
// However, the spec assigned the same feature bit to `option_scid_alias` (https://github.com/lightning/bolts/pull/910).
// We're moving this feature bit to 148, but we have to keep supporting it until enough wallet users have migrated, then we can remove it.
// We cannot rename that object otherwise we will not be able to read old serialized data.
@Serializable
object TrampolinePayment : Feature() {
override val rfcName get() = "trampoline_payment_backwards_compat"
override val mandatory get() = 50
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice)
}

/** This feature bit should be activated when a node accepts having their channel reserve set to 0. */
@Serializable
object ZeroReserveChannels : Feature() {
Expand All @@ -161,14 +157,6 @@ sealed class Feature {
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

/** DEPRECATED: this feature bit should not be used, it is only kept for serialization backwards-compatibility. */
@Serializable
object ZeroConfChannels : Feature() {
override val rfcName get() = "zero_conf_channels"
override val mandatory get() = 130
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

/** This feature bit should be activated when a mobile node supports waking up via push notifications. */
@Serializable
object WakeUpNotificationClient : Feature() {
Expand All @@ -185,38 +173,6 @@ sealed class Feature {
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

/** This feature bit should be activated when a node accepts on-the-fly channel creation. */
@Serializable
object PayToOpenClient : Feature() {
override val rfcName get() = "pay_to_open_client"
override val mandatory get() = 136
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init)
}

/** This feature bit should be activated when a node supports opening channels on-the-fly when liquidity is missing to receive a payment. */
@Serializable
object PayToOpenProvider : Feature() {
override val rfcName get() = "pay_to_open_provider"
override val mandatory get() = 138
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

/** DEPRECATED: this feature bit should not be used, it is only kept for serialization backwards-compatibility. */
@Serializable
object TrustedSwapInClient : Feature() {
override val rfcName get() = "trusted_swap_in_client"
override val mandatory get() = 140
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init)
}

/** DEPRECATED: this feature bit should not be used, it is only kept for serialization backwards-compatibility. */
@Serializable
object TrustedSwapInProvider : Feature() {
override val rfcName get() = "trusted_swap_in_provider"
override val mandatory get() = 142
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. */
@Serializable
object ChannelBackupClient : Feature() {
Expand Down Expand Up @@ -250,9 +206,16 @@ sealed class Feature {
}

@Serializable
object Quiescence : Feature() {
override val rfcName get() = "option_quiescence"
override val mandatory get() = 34
object OnTheFlyFunding : Feature() {
override val rfcName get() = "on_the_fly_funding"
override val mandatory get() = 560
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
}

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

Expand Down Expand Up @@ -322,22 +285,18 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
Feature.RouteBlinding,
Feature.ShutdownAnySegwit,
Feature.DualFunding,
Feature.Quiescence,
Feature.ChannelType,
Feature.PaymentMetadata,
Feature.TrampolinePayment,
Feature.ExperimentalTrampolinePayment,
Feature.ZeroReserveChannels,
Feature.ZeroConfChannels,
Feature.WakeUpNotificationClient,
Feature.WakeUpNotificationProvider,
Feature.PayToOpenClient,
Feature.PayToOpenProvider,
Feature.TrustedSwapInClient,
Feature.TrustedSwapInProvider,
Feature.ChannelBackupClient,
Feature.ChannelBackupProvider,
Feature.ExperimentalSplice,
Feature.Quiescence
Feature.OnTheFlyFunding,
Feature.FundingFeeCredit
)

operator fun invoke(bytes: ByteVector): Features = invoke(bytes.toByteArray())
Expand Down Expand Up @@ -368,8 +327,9 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
Feature.PaymentSecret to listOf(Feature.VariableLengthOnion),
Feature.BasicMultiPartPayment to listOf(Feature.PaymentSecret),
Feature.AnchorOutputs to listOf(Feature.StaticRemoteKey),
Feature.TrampolinePayment to listOf(Feature.PaymentSecret),
Feature.ExperimentalTrampolinePayment to listOf(Feature.PaymentSecret)
Feature.ExperimentalTrampolinePayment to listOf(Feature.PaymentSecret),
Feature.OnTheFlyFunding to listOf(Feature.ExperimentalSplice),
Feature.FundingFeeCredit to listOf(Feature.OnTheFlyFunding)
)

class FeatureException(message: String) : IllegalArgumentException(message)
Expand Down
25 changes: 16 additions & 9 deletions src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package fr.acinq.lightning

import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.PublicKey
import fr.acinq.bitcoin.OutPoint
import fr.acinq.bitcoin.Satoshi
import fr.acinq.lightning.blockchain.electrum.WalletState
import fr.acinq.lightning.channel.ChannelManagementFees
import fr.acinq.lightning.channel.InteractiveTxParams
import fr.acinq.lightning.channel.SharedFundingInput
import fr.acinq.lightning.channel.states.ChannelStateWithCommitments
Expand All @@ -11,16 +14,18 @@ import fr.acinq.lightning.channel.states.WaitForFundingCreated
import fr.acinq.lightning.db.IncomingPayment
import fr.acinq.lightning.utils.sum
import fr.acinq.lightning.wire.Init
import fr.acinq.lightning.wire.PleaseOpenChannel
import kotlinx.coroutines.CompletableDeferred

sealed interface NodeEvents

data class PeerConnected(val remoteNodeId: PublicKey, val theirInit: Init) : NodeEvents

sealed interface SwapInEvents : NodeEvents {
data class Requested(val req: PleaseOpenChannel) : SwapInEvents
data class Accepted(val requestId: ByteVector32, val serviceFee: MilliSatoshi, val miningFee: Satoshi) : SwapInEvents
data class Requested(val walletInputs: List<WalletState.Utxo>) : SwapInEvents {
val totalAmount: Satoshi = walletInputs.map { it.amount }.sum()
}
data class Accepted(val inputs: Set<OutPoint>, val amountBeforeFees: Satoshi, val fees: ChannelManagementFees) : SwapInEvents {
val receivedAmount: Satoshi = amountBeforeFees - fees.total
}
}

sealed interface ChannelEvents : NodeEvents {
Expand All @@ -30,6 +35,7 @@ sealed interface ChannelEvents : NodeEvents {
}

sealed interface LiquidityEvents : NodeEvents {
/** Amount of liquidity purchased, before fees are paid. */
val amount: MilliSatoshi
val fee: MilliSatoshi
val source: Source
Expand All @@ -42,11 +48,13 @@ sealed interface LiquidityEvents : NodeEvents {
data class OverAbsoluteFee(val maxAbsoluteFee: Satoshi) : TooExpensive()
data class OverRelativeFee(val maxRelativeFeeBasisPoints: Int) : TooExpensive()
}
data object ChannelInitializing : Reason()
data object ChannelFundingInProgress : Reason()
data object NoMatchingFundingRate : Reason()
data class MissingOffChainAmountTooLow(val missingOffChainAmount: MilliSatoshi, val currentFeeCredit: MilliSatoshi) : Reason()
data class TooManyParts(val parts: Int) : Reason()
}
}

data class ApprovalRequested(override val amount: MilliSatoshi, override val fee: MilliSatoshi, override val source: Source, val replyTo: CompletableDeferred<Boolean>) : LiquidityEvents
data class Accepted(override val amount: MilliSatoshi, override val fee: MilliSatoshi, override val source: Source) : LiquidityEvents
}

/** This is useful on iOS to ask the OS for time to finish some sensitive tasks. */
Expand All @@ -59,15 +67,14 @@ sealed interface SensitiveTaskEvents : NodeEvents {
}
data class TaskStarted(val id: TaskIdentifier) : SensitiveTaskEvents
data class TaskEnded(val id: TaskIdentifier) : SensitiveTaskEvents

}

/** This will be emitted in a corner case where the user restores a wallet on an older version of the app, which is unable to read the channel data. */
data object UpgradeRequired : NodeEvents

sealed interface PaymentEvents : NodeEvents {
data class PaymentReceived(val paymentHash: ByteVector32, val receivedWith: List<IncomingPayment.ReceivedWith>) : PaymentEvents {
val amount: MilliSatoshi = receivedWith.map { it.amount }.sum()
val amount: MilliSatoshi = receivedWith.map { it.amountReceived }.sum()
val fees: MilliSatoshi = receivedWith.map { it.fees }.sum()
}
}
18 changes: 12 additions & 6 deletions src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import fr.acinq.lightning.payment.LiquidityPolicy
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.sat
import fr.acinq.lightning.utils.toMilliSatoshi
import fr.acinq.lightning.wire.LiquidityAds
import fr.acinq.lightning.wire.OfferTypes
import io.ktor.utils.io.charsets.*
import io.ktor.utils.io.core.*
Expand Down Expand Up @@ -169,9 +170,6 @@ data class NodeParams(
require(features.hasFeature(Feature.ChannelType, FeatureSupport.Mandatory)) { "${Feature.ChannelType.rfcName} should be mandatory" }
require(features.hasFeature(Feature.DualFunding, FeatureSupport.Mandatory)) { "${Feature.DualFunding.rfcName} should be mandatory" }
require(features.hasFeature(Feature.RouteBlinding)) { "${Feature.RouteBlinding.rfcName} should be supported" }
require(!features.hasFeature(Feature.ZeroConfChannels)) { "${Feature.ZeroConfChannels.rfcName} has been deprecated: use the zeroConfPeers whitelist instead" }
require(!features.hasFeature(Feature.TrustedSwapInClient)) { "${Feature.TrustedSwapInClient.rfcName} has been deprecated" }
require(!features.hasFeature(Feature.TrustedSwapInProvider)) { "${Feature.TrustedSwapInProvider.rfcName} has been deprecated" }
Features.validateFeatureGraph(features)
}

Expand All @@ -193,15 +191,15 @@ data class NodeParams(
Feature.RouteBlinding to FeatureSupport.Optional,
Feature.DualFunding to FeatureSupport.Mandatory,
Feature.ShutdownAnySegwit to FeatureSupport.Mandatory,
Feature.Quiescence to FeatureSupport.Mandatory,
Feature.ChannelType to FeatureSupport.Mandatory,
Feature.PaymentMetadata to FeatureSupport.Optional,
Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional,
Feature.ZeroReserveChannels to FeatureSupport.Optional,
Feature.WakeUpNotificationClient to FeatureSupport.Optional,
Feature.PayToOpenClient to FeatureSupport.Optional,
Feature.ChannelBackupClient to FeatureSupport.Optional,
Feature.ExperimentalSplice to FeatureSupport.Optional,
Feature.Quiescence to FeatureSupport.Mandatory
Feature.OnTheFlyFunding to FeatureSupport.Optional,
),
dustLimit = 546.sat,
maxRemoteDustLimit = 600.sat,
Expand Down Expand Up @@ -229,7 +227,15 @@ data class NodeParams(
maxPaymentAttempts = 5,
zeroConfPeers = emptySet(),
paymentRecipientExpiryParams = RecipientCltvExpiryParams(CltvExpiryDelta(75), CltvExpiryDelta(200)),
liquidityPolicy = MutableStateFlow<LiquidityPolicy>(LiquidityPolicy.Auto(maxAbsoluteFee = 2_000.sat, maxRelativeFeeBasisPoints = 3_000 /* 3000 = 30 % */, skipAbsoluteFeeCheck = false)),
liquidityPolicy = MutableStateFlow<LiquidityPolicy>(
LiquidityPolicy.Auto(
inboundLiquidityTarget = null,
maxAbsoluteFee = 2_000.sat,
maxRelativeFeeBasisPoints = 3_000 /* 3000 = 30 % */,
skipAbsoluteFeeCheck = false,
maxAllowedFeeCredit = 0.msat
)
),
minFinalCltvExpiryDelta = Bolt11Invoice.DEFAULT_MIN_FINAL_EXPIRY_DELTA,
maxFinalCltvExpiryDelta = CltvExpiryDelta(360),
bolt12invoiceExpiry = 60.seconds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,18 @@ package fr.acinq.lightning.blockchain.electrum

import fr.acinq.bitcoin.OutPoint
import fr.acinq.bitcoin.Transaction
import fr.acinq.bitcoin.TxId
import fr.acinq.lightning.Lightning
import fr.acinq.lightning.SwapInParams
import fr.acinq.lightning.channel.FundingContributions.Companion.stripInputWitnesses
import fr.acinq.lightning.channel.LocalFundingStatus
import fr.acinq.lightning.channel.RbfStatus
import fr.acinq.lightning.channel.SignedSharedTransaction
import fr.acinq.lightning.channel.SpliceStatus
import fr.acinq.lightning.channel.states.*
import fr.acinq.lightning.io.RequestChannelOpen
import fr.acinq.lightning.io.AddWalletInputsToChannel
import fr.acinq.lightning.logging.MDCLogger
import fr.acinq.lightning.utils.sat

internal sealed class SwapInCommand {
data class TrySwapIn(val currentBlockHeight: Int, val wallet: WalletState, val swapInParams: SwapInParams, val trustedTxs: Set<TxId>) : SwapInCommand()
data class TrySwapIn(val currentBlockHeight: Int, val wallet: WalletState, val swapInParams: SwapInParams) : SwapInCommand()
data class UnlockWalletInputs(val inputs: Set<OutPoint>) : SwapInCommand()
}

Expand All @@ -33,20 +30,15 @@ internal sealed class SwapInCommand {
class SwapInManager(private var reservedUtxos: Set<OutPoint>, private val logger: MDCLogger) {
constructor(bootChannels: List<PersistedChannelState>, logger: MDCLogger) : this(reservedWalletInputs(bootChannels), logger)

internal fun process(cmd: SwapInCommand): RequestChannelOpen? = when (cmd) {
internal fun process(cmd: SwapInCommand): AddWalletInputsToChannel? = when (cmd) {
is SwapInCommand.TrySwapIn -> {
val availableWallet = cmd.wallet.withoutReservedUtxos(reservedUtxos).withConfirmations(cmd.currentBlockHeight, cmd.swapInParams)
logger.info { "swap-in wallet balance: deeplyConfirmed=${availableWallet.deeplyConfirmed.balance}, weaklyConfirmed=${availableWallet.weaklyConfirmed.balance}, unconfirmed=${availableWallet.unconfirmed.balance}" }
val utxos = buildSet {
// some utxos may be used for swap-in even if they are not confirmed, for example when migrating from the legacy phoenix android app
addAll(availableWallet.unconfirmed.filter { cmd.trustedTxs.contains(it.outPoint.txid) })
addAll(availableWallet.weaklyConfirmed.filter { cmd.trustedTxs.contains(it.outPoint.txid) })
addAll(availableWallet.deeplyConfirmed.filter { Transaction.write(it.previousTx.stripInputWitnesses()).size < 65_000 })
}.toList()
val utxos = availableWallet.deeplyConfirmed.filter { Transaction.write(it.previousTx.stripInputWitnesses()).size < 65_000 }
if (utxos.balance > 0.sat) {
logger.info { "swap-in wallet: requesting channel using ${utxos.size} utxos with balance=${utxos.balance}" }
reservedUtxos = reservedUtxos.union(utxos.map { it.outPoint })
RequestChannelOpen(Lightning.randomBytes32(), utxos)
AddWalletInputsToChannel(utxos)
} else {
null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package fr.acinq.lightning.channel

import fr.acinq.bitcoin.*
import fr.acinq.lightning.ChannelEvents
import fr.acinq.lightning.CltvExpiry
import fr.acinq.lightning.MilliSatoshi
import fr.acinq.lightning.NodeEvents
import fr.acinq.lightning.blockchain.Watch
import fr.acinq.lightning.channel.states.PersistedChannelState
import fr.acinq.lightning.db.ChannelClosingType
import fr.acinq.lightning.transactions.Transactions
import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.utils.toMilliSatoshi
import fr.acinq.lightning.wire.*

/** Channel Actions (outputs produced by the state machine). */
Expand Down Expand Up @@ -78,16 +79,16 @@ sealed class ChannelAction {
abstract val origin: Origin?
abstract val txId: TxId
abstract val localInputs: Set<OutPoint>
data class ViaNewChannel(val amount: MilliSatoshi, val serviceFee: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin?) : StoreIncomingPayment()
data class ViaSpliceIn(val amount: MilliSatoshi, val serviceFee: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin.PayToOpenOrigin?) : StoreIncomingPayment()
data class ViaNewChannel(val amountReceived: MilliSatoshi, val serviceFee: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin?) : StoreIncomingPayment()
data class ViaSpliceIn(val amountReceived: MilliSatoshi, val serviceFee: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin?) : StoreIncomingPayment()
}
/** Payment sent through on-chain operations (channel close or splice-out) */
sealed class StoreOutgoingPayment : Storage() {
abstract val miningFees: Satoshi
abstract val txId: TxId
data class ViaSpliceOut(val amount: Satoshi, override val miningFees: Satoshi, val address: String, override val txId: TxId) : StoreOutgoingPayment()
data class ViaSpliceCpfp(override val miningFees: Satoshi, override val txId: TxId) : StoreOutgoingPayment()
data class ViaInboundLiquidityRequest(override val txId: TxId, override val miningFees: Satoshi, val lease: LiquidityAds.Lease) : StoreOutgoingPayment()
data class ViaInboundLiquidityRequest(override val txId: TxId, override val miningFees: Satoshi, val purchase: LiquidityAds.Purchase) : StoreOutgoingPayment()
data class ViaClose(val amount: Satoshi, override val miningFees: Satoshi, val address: String, override val txId: TxId, val isSentToDefaultAddress: Boolean, val closingType: ChannelClosingType) : StoreOutgoingPayment()
}
data class SetLocked(val txId: TxId) : Storage()
Expand Down Expand Up @@ -128,8 +129,8 @@ sealed class ChannelAction {
}
}

data class EmitEvent(val event: ChannelEvents) : ChannelAction()
data class EmitEvent(val event: NodeEvents) : ChannelAction()

object Disconnect : ChannelAction()
data object Disconnect : ChannelAction()
// @formatter:on
}
Loading
Loading