commonMain.fr.acinq.lightning.serialization.v4.Serialization.kt Maven / Gradle / Ivy
package fr.acinq.lightning.serialization.v4
import fr.acinq.bitcoin.*
import fr.acinq.bitcoin.crypto.musig2.IndividualNonce
import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.io.Output
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.FeatureSupport
import fr.acinq.lightning.Features
import fr.acinq.lightning.channel.*
import fr.acinq.lightning.channel.states.*
import fr.acinq.lightning.transactions.*
import fr.acinq.lightning.transactions.Transactions.TransactionWithInputInfo.*
import fr.acinq.lightning.wire.LightningCodecs
import fr.acinq.lightning.wire.LightningMessage
import fr.acinq.lightning.wire.LiquidityAds
/**
* Serialization for [ChannelStateWithCommitments].
*
* This is a large file, but it's just repetition of the same pattern
*
* We define extension methods on the class [Output] to serialize various types
* from primitives to structures:
* ```
* private fun Output.write(o: MyType): Unit
* ```
*
* Then we just iterate over all fields of the top level class we want to serialize, and call the
* write method on all of them.
*
* We defer serialization of Bitcoin and Lightning protocol messages to the respective [BtcSerializer]
* and [LightningCodecs].
*/
object Serialization {
const val versionMagic = 4
fun serialize(o: PersistedChannelState): ByteArray {
val out = ByteArrayOutput()
out.write(versionMagic)
out.writePersistedChannelState(o)
return out.toByteArray()
}
private fun Output.writePersistedChannelState(o: PersistedChannelState) = when (o) {
is LegacyWaitForFundingConfirmed -> {
write(0x08); writeLegacyWaitForFundingConfirmed(o)
}
is LegacyWaitForFundingLocked -> {
write(0x09); writeLegacyWaitForFundingLocked(o)
}
is WaitForFundingConfirmed -> {
write(0x00); writeWaitForFundingConfirmed(o)
}
is WaitForChannelReady -> {
write(0x01); writeWaitForChannelReady(o)
}
is Normal -> {
write(0x0b); writeNormal(o)
}
is ShuttingDown -> {
write(0x03); writeShuttingDown(o)
}
is Negotiating -> {
write(0x04); writeNegotiating(o)
}
is Closing -> {
write(0x05); writeClosing(o)
}
is WaitForRemotePublishFutureCommitment -> {
write(0x06); writeWaitForRemotePublishFutureCommitment(o)
}
is Closed -> {
write(0x07); writeClosed(o)
}
is WaitForFundingSigned -> {
write(0x0c); writeWaitForFundingSigned(o)
}
}
private fun Output.writeLegacyWaitForFundingConfirmed(o: LegacyWaitForFundingConfirmed) = o.run {
writeCommitments(commitments)
writeNullable(fundingTx) { writeBtcObject(it) }
writeNumber(waitingSinceBlock)
writeNullable(deferred) { writeLightningMessage(it) }
writeEither(lastSent,
writeLeft = { writeLightningMessage(it) },
writeRight = { writeLightningMessage(it) }
)
}
private fun Output.writeLegacyWaitForFundingLocked(o: LegacyWaitForFundingLocked) = o.run {
writeCommitments(commitments)
writeNumber(shortChannelId.toLong())
writeLightningMessage(lastSent)
}
private fun Output.writeWaitForFundingSigned(o: WaitForFundingSigned) = o.run {
writeChannelParams(channelParams)
writeInteractiveTxSigningSession(signingSession)
writeNumber(localPushAmount.toLong())
writeNumber(remotePushAmount.toLong())
writePublicKey(remoteSecondPerCommitmentPoint)
writeNullable(liquidityPurchase) { writeLiquidityPurchase(it) }
writeNullable(channelOrigin) { writeChannelOrigin(it) }
}
private fun Output.writeWaitForFundingConfirmed(o: WaitForFundingConfirmed) = o.run {
writeCommitments(commitments)
writeNumber(localPushAmount.toLong())
writeNumber(remotePushAmount.toLong())
writeNumber(waitingSinceBlock)
writeNullable(deferred) { writeLightningMessage(it) }
when (rbfStatus) {
is RbfStatus.WaitingForSigs -> {
write(0x01)
writeInteractiveTxSigningSession(rbfStatus.session)
}
else -> {
write(0x00)
}
}
}
private fun Output.writeWaitForChannelReady(o: WaitForChannelReady) = o.run {
writeCommitments(commitments)
writeNumber(shortChannelId.toLong())
writeLightningMessage(lastSent)
}
private fun Output.writeNormal(o: Normal) = o.run {
writeCommitments(commitments)
writeNumber(shortChannelId.toLong())
writeLightningMessage(channelUpdate)
writeNullable(remoteChannelUpdate) { writeLightningMessage(it) }
writeNullable(localShutdown) { writeLightningMessage(it) }
writeNullable(remoteShutdown) { writeLightningMessage(it) }
writeNullable(closingFeerates) { writeClosingFeerates(it) }
when (spliceStatus) {
is SpliceStatus.WaitingForSigs -> {
write(0x01)
writeInteractiveTxSigningSession(spliceStatus.session)
writeNullable(spliceStatus.liquidityPurchase) { writeLiquidityPurchase(it) }
writeCollection(spliceStatus.origins) { writeChannelOrigin(it) }
}
else -> {
write(0x00)
}
}
}
private fun Output.writeShuttingDown(o: ShuttingDown) = o.run {
writeCommitments(commitments)
writeLightningMessage(localShutdown)
writeLightningMessage(remoteShutdown)
writeNullable(closingFeerates) { writeClosingFeerates(it) }
}
private fun Output.writeNegotiating(o: Negotiating) = o.run {
writeCommitments(commitments)
writeLightningMessage(localShutdown)
writeLightningMessage(remoteShutdown)
writeCollection(closingTxProposed) {
writeCollection(it) { closingTxProposed ->
closingTxProposed.run {
writeTransactionWithInputInfo(unsignedTx)
writeLightningMessage(localClosingSigned)
}
}
}
writeNullable(bestUnpublishedClosingTx) { writeTransactionWithInputInfo(it) }
writeNullable(closingFeerates) { writeClosingFeerates(it) }
}
private fun Output.writeClosing(o: Closing) = o.run {
writeCommitments(commitments)
writeNumber(waitingSinceBlock)
writeCollection(mutualCloseProposed) { writeTransactionWithInputInfo(it) }
writeCollection(mutualClosePublished) { writeTransactionWithInputInfo(it) }
writeNullable(localCommitPublished) { writeLocalCommitPublished(it) }
writeNullable(remoteCommitPublished) { writeRemoteCommitPublished(it) }
writeNullable(nextRemoteCommitPublished) { writeRemoteCommitPublished(it) }
writeNullable(futureRemoteCommitPublished) { writeRemoteCommitPublished(it) }
writeCollection(revokedCommitPublished) { writeRevokedCommitPublished(it) }
}
private fun Output.writeLocalCommitPublished(o: LocalCommitPublished) = o.run {
writeBtcObject(commitTx)
writeNullable(claimMainDelayedOutputTx) { writeTransactionWithInputInfo(it) }
writeCollection(htlcTxs.entries) {
writeBtcObject(it.key)
writeNullable(it.value) { htlcTx -> writeTransactionWithInputInfo(htlcTx) }
}
writeCollection(claimHtlcDelayedTxs) { writeTransactionWithInputInfo(it) }
writeCollection(claimAnchorTxs) { writeTransactionWithInputInfo(it) }
writeIrrevocablySpent(irrevocablySpent)
}
private fun Output.writeRemoteCommitPublished(o: RemoteCommitPublished) = o.run {
writeBtcObject(commitTx)
writeNullable(claimMainOutputTx) { writeTransactionWithInputInfo(it) }
writeCollection(claimHtlcTxs.entries) {
writeBtcObject(it.key)
writeNullable(it.value) { claimHtlcTx -> writeTransactionWithInputInfo(claimHtlcTx) }
}
writeCollection(claimAnchorTxs) { writeTransactionWithInputInfo(it) }
writeIrrevocablySpent(irrevocablySpent)
}
private fun Output.writeRevokedCommitPublished(o: RevokedCommitPublished) = o.run {
writeBtcObject(commitTx)
writeByteVector32(remotePerCommitmentSecret.value)
writeNullable(claimMainOutputTx) { writeTransactionWithInputInfo(it) }
writeNullable(mainPenaltyTx) { writeTransactionWithInputInfo(it) }
writeCollection(htlcPenaltyTxs) { writeTransactionWithInputInfo(it) }
writeCollection(claimHtlcDelayedPenaltyTxs) { writeTransactionWithInputInfo(it) }
writeIrrevocablySpent(irrevocablySpent)
}
private fun Output.writeIrrevocablySpent(o: Map) = writeCollection(o.entries) {
writeBtcObject(it.key)
writeBtcObject(it.value)
}
private fun Output.writeWaitForRemotePublishFutureCommitment(o: WaitForRemotePublishFutureCommitment) = o.run {
writeCommitments(commitments)
writeLightningMessage(remoteChannelReestablish)
}
private fun Output.writeClosed(o: Closed) = o.run {
writeClosing(state)
}
private fun Output.writeSharedFundingInput(i: SharedFundingInput) = when (i) {
is SharedFundingInput.Multisig2of2 -> {
write(0x01)
writeInputInfo(i.info)
writeNumber(i.fundingTxIndex)
writePublicKey(i.remoteFundingPubkey)
}
}
private fun Output.writeInteractiveTxParams(o: InteractiveTxParams) = o.run {
writeByteVector32(channelId)
writeBoolean(isInitiator)
writeNumber(localContribution.toLong())
writeNumber(remoteContribution.toLong())
writeNullable(sharedInput) { writeSharedFundingInput(it) }
writePublicKey(remoteFundingPubkey)
writeCollection(localOutputs) { writeBtcObject(it) }
writeNumber(lockTime)
writeNumber(dustLimit.toLong())
writeNumber(targetFeerate.toLong())
}
private fun Output.writeSharedInteractiveTxInput(i: InteractiveTxInput.Shared) = i.run {
write(0x03)
writeNumber(serialId)
writeBtcObject(outPoint)
writeDelimited(publicKeyScript.toByteArray())
writeNumber(sequence.toLong())
writeNumber(localAmount.toLong())
writeNumber(remoteAmount.toLong())
writeNumber(htlcAmount.toLong())
}
private fun Output.writeLocalInteractiveTxInput(i: InteractiveTxInput.Local) = when (i) {
is InteractiveTxInput.LocalOnly -> i.run {
write(0x01)
writeNumber(serialId)
writeBtcObject(previousTx)
writeNumber(previousTxOutput)
writeNumber(sequence.toLong())
}
is InteractiveTxInput.LocalLegacySwapIn -> i.run {
write(0x02)
writeNumber(serialId)
writeBtcObject(previousTx)
writeNumber(previousTxOutput)
writeNumber(sequence.toLong())
writePublicKey(userKey)
writePublicKey(serverKey)
writeNumber(refundDelay)
}
is InteractiveTxInput.LocalSwapIn -> i.run {
write(0x03)
writeNumber(serialId)
writeBtcObject(previousTx)
writeNumber(previousTxOutput)
writeNumber(sequence.toLong())
writeNumber(addressIndex)
writePublicKey(userKey)
writePublicKey(serverKey)
writePublicKey(userRefundKey)
writeNumber(refundDelay)
}
}
private fun Output.writeRemoteInteractiveTxInput(i: InteractiveTxInput.Remote) = when (i) {
is InteractiveTxInput.RemoteOnly -> i.run {
write(0x01)
writeNumber(serialId)
writeBtcObject(outPoint)
writeBtcObject(txOut)
writeNumber(sequence.toLong())
}
is InteractiveTxInput.RemoteLegacySwapIn -> i.run {
write(0x02)
writeNumber(serialId)
writeBtcObject(outPoint)
writeBtcObject(txOut)
writeNumber(sequence.toLong())
writePublicKey(userKey)
writePublicKey(serverKey)
writeNumber(refundDelay)
}
is InteractiveTxInput.RemoteSwapIn -> i.run {
write(0x03)
writeNumber(serialId)
writeBtcObject(outPoint)
writeBtcObject(txOut)
writeNumber(sequence.toLong())
writePublicKey(userKey)
writePublicKey(serverKey)
writePublicKey(userRefundKey)
writeNumber(refundDelay)
}
}
private fun Output.writeSharedInteractiveTxOutput(o: InteractiveTxOutput.Shared) = o.run {
write(0x02)
writeNumber(serialId)
writeDelimited(pubkeyScript.toByteArray())
writeNumber(localAmount.toLong())
writeNumber(remoteAmount.toLong())
writeNumber(htlcAmount.toLong())
}
private fun Output.writeLocalInteractiveTxOutput(o: InteractiveTxOutput.Local) = when (o) {
is InteractiveTxOutput.Local.Change -> o.run {
write(0x01)
writeNumber(serialId)
writeNumber(amount.toLong())
writeDelimited(pubkeyScript.toByteArray())
}
is InteractiveTxOutput.Local.NonChange -> o.run {
write(0x02)
writeNumber(serialId)
writeNumber(amount.toLong())
writeDelimited(pubkeyScript.toByteArray())
}
}
private fun Output.writeRemoteInteractiveTxOutput(o: InteractiveTxOutput.Remote) = o.run {
write(0x01)
writeNumber(serialId)
writeNumber(amount.toLong())
writeDelimited(pubkeyScript.toByteArray())
}
private fun Output.writeSharedTransaction(tx: SharedTransaction) = tx.run {
writeNullable(sharedInput) { writeSharedInteractiveTxInput(it) }
writeSharedInteractiveTxOutput(sharedOutput)
writeCollection(localInputs) { writeLocalInteractiveTxInput(it) }
writeCollection(remoteInputs) { writeRemoteInteractiveTxInput(it) }
writeCollection(localOutputs) { writeLocalInteractiveTxOutput(it) }
writeCollection(remoteOutputs) { writeRemoteInteractiveTxOutput(it) }
writeNumber(lockTime)
}
private fun Output.writeScriptWitness(w: ScriptWitness) = writeCollection(w.stack) { writeDelimited(it.toByteArray()) }
private fun Output.writeSignedSharedTransaction(o: SignedSharedTransaction) = when (o) {
is PartiallySignedSharedTransaction -> o.run {
write(0x01)
writeSharedTransaction(tx)
writeLightningMessage(localSigs)
}
is FullySignedSharedTransaction -> o.run {
write(0x02)
writeSharedTransaction(tx)
writeLightningMessage(localSigs)
writeLightningMessage(remoteSigs)
writeNullable(sharedSigs) { writeScriptWitness(it) }
}
}
private fun Output.writeUnsignedLocalCommitWithHtlcs(localCommit: InteractiveTxSigningSession.Companion.UnsignedLocalCommit) {
writeNumber(localCommit.index)
writeCommitmentSpecWithHtlcs(localCommit.spec)
writeTransactionWithInputInfo(localCommit.commitTx)
writeCollection(localCommit.htlcTxs) { writeTransactionWithInputInfo(it) }
}
private fun Output.writeLocalCommitWithHtlcs(localCommit: LocalCommit) {
writeNumber(localCommit.index)
writeCommitmentSpecWithHtlcs(localCommit.spec)
localCommit.publishableTxs.run {
writeTransactionWithInputInfo(commitTx)
writeCollection(htlcTxsAndSigs) { htlc ->
writeTransactionWithInputInfo(htlc.txinfo)
writeByteVector64(htlc.localSig)
writeByteVector64(htlc.remoteSig)
}
}
}
private fun Output.writeLiquidityFees(fees: LiquidityAds.Fees) {
writeNumber(fees.miningFee.toLong())
writeNumber(fees.serviceFee.toLong())
}
private fun Output.writeLiquidityPurchase(purchase: LiquidityAds.Purchase) {
when (purchase) {
is LiquidityAds.Purchase.Standard -> {
write(0x00) // discriminator
writeNumber(purchase.amount.toLong())
writeLiquidityFees(purchase.fees)
writeLiquidityAdsPaymentDetails(purchase.paymentDetails)
}
is LiquidityAds.Purchase.WithFeeCredit -> {
write(0x01) // discriminator
writeNumber(purchase.amount.toLong())
writeLiquidityFees(purchase.fees)
writeNumber(purchase.feeCreditUsed.toLong())
writeLiquidityAdsPaymentDetails(purchase.paymentDetails)
}
}
}
private fun Output.writeLiquidityAdsPaymentDetails(paymentDetails: LiquidityAds.PaymentDetails) {
when (paymentDetails) {
is LiquidityAds.PaymentDetails.FromChannelBalance -> write(0x00)
is LiquidityAds.PaymentDetails.FromFutureHtlc -> {
write(0x80)
writeCollection(paymentDetails.paymentHashes) { writeByteVector32(it) }
}
is LiquidityAds.PaymentDetails.FromFutureHtlcWithPreimage -> {
write(0x81)
writeCollection(paymentDetails.preimages) { writeByteVector32(it) }
}
is LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc -> {
write(0x82)
writeCollection(paymentDetails.paymentHashes) { writeByteVector32(it) }
}
}
}
private fun Output.writeInteractiveTxSigningSession(s: InteractiveTxSigningSession) = s.run {
writeInteractiveTxParams(fundingParams)
writeNumber(s.fundingTxIndex)
writeSignedSharedTransaction(fundingTx)
// Note that we don't bother removing the duplication across HTLCs in the local commit: this is a short-lived
// state during which the channel cannot be used for payments.
writeEither(localCommit, { localCommit -> writeUnsignedLocalCommitWithHtlcs(localCommit) }, { localCommit -> writeLocalCommitWithHtlcs(localCommit) })
remoteCommit.run {
writeNumber(index)
writeCommitmentSpecWithHtlcs(spec)
writeTxId(txid)
writePublicKey(remotePerCommitmentPoint)
}
}
private fun Output.writeChannelOrigin(o: Origin) = when (o) {
is Origin.OffChainPayment -> {
write(0x03)
writeByteVector32(o.paymentPreimage)
writeNumber(o.amountBeforeFees.toLong())
writeNumber(o.fees.miningFee.toLong())
writeNumber(o.fees.serviceFee.toLong())
}
is Origin.OnChainWallet -> {
write(0x04)
writeCollection(o.inputs) { writeBtcObject(it) }
writeNumber(o.amountBeforeFees.toLong())
writeNumber(o.fees.miningFee.toLong())
writeNumber(o.fees.serviceFee.toLong())
}
}
private fun Output.writeChannelParams(o: ChannelParams) = o.run {
writeByteVector32(channelId)
writeDelimited(channelConfig.toByteArray())
writeDelimited(Features(channelFeatures.features.associateWith { FeatureSupport.Mandatory }).toByteArray())
localParams.run {
writePublicKey(nodeId)
writeCollection(fundingKeyPath.path) { writeNumber(it) }
writeNumber(dustLimit.toLong())
writeNumber(maxHtlcValueInFlightMsat)
writeNumber(htlcMinimum.toLong())
writeNumber(toSelfDelay.toLong())
writeNumber(maxAcceptedHtlcs)
// We encode those two booleans in the same byte.
val isOpenerFlag = if (isChannelOpener) 1 else 0
val payCommitTxFeesFlag = if (paysCommitTxFees) 2 else 0
writeNumber(isOpenerFlag + payCommitTxFeesFlag)
writeDelimited(defaultFinalScriptPubKey.toByteArray())
writeDelimited(features.toByteArray())
}
remoteParams.run {
writePublicKey(nodeId)
writeNumber(dustLimit.toLong())
writeNumber(maxHtlcValueInFlightMsat)
writeNumber(htlcMinimum.toLong())
writeNumber(toSelfDelay.toLong())
writeNumber(maxAcceptedHtlcs)
writePublicKey(revocationBasepoint)
writePublicKey(paymentBasepoint)
writePublicKey(delayedPaymentBasepoint)
writePublicKey(htlcBasepoint)
writeDelimited(features.toByteArray())
}
// We encode channel flags in the same byte.
val announceChannelFlag = if (channelFlags.announceChannel) 1 else 0
val nonInitiatorPaysCommitFeesFlag = if (channelFlags.nonInitiatorPaysCommitFees) 2 else 0
writeNumber(announceChannelFlag + nonInitiatorPaysCommitFeesFlag)
}
private fun Output.writeCommitmentChanges(o: CommitmentChanges) = o.run {
localChanges.run {
writeCollection(proposed) { writeLightningMessage(it) }
writeCollection(signed) { writeLightningMessage(it) }
writeCollection(acked) { writeLightningMessage(it) }
}
remoteChanges.run {
writeCollection(proposed) { writeLightningMessage(it) }
writeCollection(acked) { writeLightningMessage(it) }
writeCollection(signed) { writeLightningMessage(it) }
}
writeNumber(localNextHtlcId)
writeNumber(remoteNextHtlcId)
}
private fun Output.writeCommitment(o: Commitment) = o.run {
writeNumber(fundingTxIndex)
writePublicKey(remoteFundingPubkey)
when (localFundingStatus) {
is LocalFundingStatus.UnconfirmedFundingTx -> {
write(0x00)
writeSignedSharedTransaction(localFundingStatus.sharedTx)
writeInteractiveTxParams(localFundingStatus.fundingParams)
writeNumber(localFundingStatus.createdAt)
}
is LocalFundingStatus.ConfirmedFundingTx -> {
write(0x02)
writeBtcObject(localFundingStatus.signedTx)
writeNumber(localFundingStatus.fee.toLong())
writeLightningMessage(localFundingStatus.localSigs)
}
}
when (remoteFundingStatus) {
is RemoteFundingStatus.NotLocked -> write(0x00)
is RemoteFundingStatus.Locked -> write(0x01)
}
localCommit.run {
writeNumber(index)
writeCommitmentSpecWithoutHtlcs(spec)
publishableTxs.run {
writeTransactionWithInputInfo(commitTx)
writeCollection(htlcTxsAndSigs) { htlc ->
writeTransactionWithInputInfo(htlc.txinfo)
writeByteVector64(htlc.localSig)
writeByteVector64(htlc.remoteSig)
}
}
}
remoteCommit.run {
writeNumber(index)
writeCommitmentSpecWithoutHtlcs(spec)
writeTxId(txid)
writePublicKey(remotePerCommitmentPoint)
}
writeNullable(nextRemoteCommit) {
writeLightningMessage(it.sig)
writeNumber(it.commit.index)
writeCommitmentSpecWithoutHtlcs(it.commit.spec)
writeTxId(it.commit.txid)
writePublicKey(it.commit.remotePerCommitmentPoint)
}
}
private fun Output.writeCommitments(o: Commitments) = o.run {
writeChannelParams(params)
writeCommitmentChanges(changes)
// When multiple commitments are active, htlcs are shared between all of these commitments, so we serialize them separately.
// The direction we use is from our local point of view: we use sets, which deduplicates htlcs that are in both local and remote commitments.
val htlcs = buildSet {
// All active commitments have the same htlc set, so we only consider the first one
addAll(active.first().localCommit.spec.htlcs)
addAll(active.first().remoteCommit.spec.htlcs.map { htlc -> htlc.opposite() })
active.first().nextRemoteCommit?.let { addAll(it.commit.spec.htlcs.map { htlc -> htlc.opposite() }) }
// Each inactive commitment may have a distinct htlc set
inactive.forEach { c ->
addAll(c.localCommit.spec.htlcs)
addAll(c.remoteCommit.spec.htlcs.map { htlc -> htlc.opposite() })
c.nextRemoteCommit?.let { addAll(it.commit.spec.htlcs.map { htlc -> htlc.opposite() }) }
}
}
writeCollection(htlcs) { writeDirectedHtlc(it) }
writeCollection(active) { writeCommitment(it) }
writeCollection(inactive) { writeCommitment(it) }
writeCollection(payments.entries) { entry ->
writeNumber(entry.key)
writeString(entry.value.toString())
}
writeEither(remoteNextCommitInfo,
writeLeft = { writeNumber(it.sentAfterLocalCommitIndex) },
writeRight = { writePublicKey(it) }
)
remotePerCommitmentSecrets.run {
writeCollection(knownHashes.entries) { entry ->
writeCollection(entry.key) { writeBoolean(it) }
writeByteVector32(entry.value)
}
writeNullable(lastIndex) { writeNumber(it) }
}
writeDelimited(remoteChannelData.data.toByteArray())
}
private fun Output.writeDirectedHtlc(htlc: DirectedHtlc) = htlc.run {
when (htlc) {
is IncomingHtlc -> write(0)
is OutgoingHtlc -> write(1)
}
writeLightningMessage(add)
}
private fun Output.writeCommitmentSpecWithHtlcs(spec: CommitmentSpec) = spec.run {
writeCollection(htlcs) { writeDirectedHtlc(it) }
writeNumber(feerate.toLong())
writeNumber(toLocal.toLong())
writeNumber(toRemote.toLong())
}
private fun Output.writeCommitmentSpecWithoutHtlcs(spec: CommitmentSpec) = spec.run {
writeCollection(htlcs) {
when (it) {
is IncomingHtlc -> write(0)
is OutgoingHtlc -> write(1)
}
// To avoid duplication, HTLCs are serialized separately.
writeNumber(it.add.id)
}
writeNumber(feerate.toLong())
writeNumber(toLocal.toLong())
writeNumber(toRemote.toLong())
}
private fun Output.writeInputInfo(o: Transactions.InputInfo): Unit = o.run {
writeBtcObject(outPoint)
writeBtcObject(txOut)
writeDelimited(redeemScript.toByteArray())
}
private fun Output.writeTransactionWithInputInfo(o: Transactions.TransactionWithInputInfo) {
when (o) {
is CommitTx -> {
write(0x00); writeInputInfo(o.input); writeBtcObject(o.tx)
}
is HtlcTx.HtlcSuccessTx -> {
write(0x01); writeInputInfo(o.input); writeBtcObject(o.tx); writeByteVector32(o.paymentHash); writeNumber(o.htlcId)
}
is HtlcTx.HtlcTimeoutTx -> {
write(0x02); writeInputInfo(o.input); writeBtcObject(o.tx); writeNumber(o.htlcId)
}
is ClaimHtlcTx.ClaimHtlcSuccessTx -> {
write(0x03); writeInputInfo(o.input); writeBtcObject(o.tx); writeNumber(o.htlcId)
}
is ClaimHtlcTx.ClaimHtlcTimeoutTx -> {
write(0x04); writeInputInfo(o.input); writeBtcObject(o.tx); writeNumber(o.htlcId)
}
is ClaimAnchorOutputTx.ClaimLocalAnchorOutputTx -> {
write(0x05); writeInputInfo(o.input); writeBtcObject(o.tx)
}
is ClaimAnchorOutputTx.ClaimRemoteAnchorOutputTx -> {
write(0x06); writeInputInfo(o.input); writeBtcObject(o.tx)
}
is ClaimLocalDelayedOutputTx -> {
write(0x07); writeInputInfo(o.input); writeBtcObject(o.tx)
}
is ClaimRemoteCommitMainOutputTx.ClaimRemoteDelayedOutputTx -> {
write(0x09); writeInputInfo(o.input); writeBtcObject(o.tx)
}
is MainPenaltyTx -> {
write(0x0a); writeInputInfo(o.input); writeBtcObject(o.tx)
}
is HtlcPenaltyTx -> {
write(0x0b); writeInputInfo(o.input); writeBtcObject(o.tx)
}
is ClaimHtlcDelayedOutputPenaltyTx -> {
write(0x0c); writeInputInfo(o.input); writeBtcObject(o.tx)
}
is ClosingTx -> {
write(0x0d); writeInputInfo(o.input); writeBtcObject(o.tx); writeNullable(o.toLocalIndex) { writeNumber(it) }
}
is SpliceTx -> {
write(0x0e); writeInputInfo(o.input); writeBtcObject(o.tx)
}
}
}
private fun Output.writeClosingFeerates(o: ClosingFeerates): Unit = o.run {
writeNumber(preferred.toLong())
writeNumber(min.toLong())
writeNumber(max.toLong())
}
private fun Output.writeNumber(o: Number): Unit = LightningCodecs.writeBigSize(o.toLong(), this)
private fun Output.writeBoolean(o: Boolean): Unit = if (o) write(1) else write(0)
private fun Output.writeString(o: String): Unit = writeDelimited(o.encodeToByteArray())
private fun Output.writeByteVector32(o: ByteVector32) = write(o.toByteArray())
private fun Output.writeByteVector64(o: ByteVector64) = write(o.toByteArray())
private fun Output.writePublicKey(o: PublicKey) = write(o.value.toByteArray())
private fun Output.writeTxId(o: TxId) = write(o.value.toByteArray())
private fun Output.writePublicNonce(o: IndividualNonce) = write(o.toByteArray())
private fun Output.writeDelimited(o: ByteArray) {
writeNumber(o.size)
write(o)
}
private fun > Output.writeBtcObject(o: T): Unit = writeDelimited(o.serializer().write(o))
private fun Output.writeLightningMessage(o: LightningMessage) = writeDelimited(LightningMessage.encode(o))
private fun Output.writeCollection(o: Collection, writeElem: (T) -> Unit) {
writeNumber(o.size)
o.forEach { writeElem(it) }
}
private fun Output.writeEither(o: Either, writeLeft: (L) -> Unit, writeRight: (R) -> Unit) = when (o) {
is Either.Left -> {
write(0); writeLeft(o.value)
}
is Either.Right -> {
write(1); writeRight(o.value)
}
}
private fun Output.writeNullable(o: T?, writeNotNull: (T) -> Unit) = when (o) {
is T -> {
write(1); writeNotNull(o)
}
else -> write(0)
}
}