Skip to content

Commit 76c0a42

Browse files
committed
Add basic liquidity purchase information to funding txs
We record whether a liquidity purchase happened as part of every funding transaction with the purchased amount and fees. We don't record how the payment was made, because: - it creates more backwards-compatibility risk if the liquidity ads spec changes - it's most likely unnecessary once the purchase happened, and can only be stored in the liquidity DB for auditing
1 parent 1b749e1 commit 76c0a42

File tree

11 files changed

+100
-30
lines changed

11 files changed

+100
-30
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,8 @@ sealed trait LocalFundingStatus {
439439
def signedTx_opt: Option[Transaction]
440440
/** We store local signatures for the purpose of retransmitting if the funding/splicing flow is interrupted. */
441441
def localSigs_opt: Option[TxSignatures]
442+
/** Basic information about the liquidity purchase negotiated in this transaction, if any. */
443+
def liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]
442444
}
443445
object LocalFundingStatus {
444446
sealed trait NotLocked extends LocalFundingStatus
@@ -453,15 +455,16 @@ object LocalFundingStatus {
453455
*/
454456
case class SingleFundedUnconfirmedFundingTx(signedTx_opt: Option[Transaction]) extends UnconfirmedFundingTx with NotLocked {
455457
override val localSigs_opt: Option[TxSignatures] = None
458+
override val liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo] = None
456459
}
457-
case class DualFundedUnconfirmedFundingTx(sharedTx: SignedSharedTransaction, createdAt: BlockHeight, fundingParams: InteractiveTxParams) extends UnconfirmedFundingTx with NotLocked {
460+
case class DualFundedUnconfirmedFundingTx(sharedTx: SignedSharedTransaction, createdAt: BlockHeight, fundingParams: InteractiveTxParams, liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends UnconfirmedFundingTx with NotLocked {
458461
override val signedTx_opt: Option[Transaction] = sharedTx.signedTx_opt
459462
override val localSigs_opt: Option[TxSignatures] = Some(sharedTx.localSigs)
460463
}
461-
case class ZeroconfPublishedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures]) extends UnconfirmedFundingTx with Locked {
464+
case class ZeroconfPublishedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends UnconfirmedFundingTx with Locked {
462465
override val signedTx_opt: Option[Transaction] = Some(tx)
463466
}
464-
case class ConfirmedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures]) extends LocalFundingStatus with Locked {
467+
case class ConfirmedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus with Locked {
465468
override val signedTx_opt: Option[Transaction] = Some(tx)
466469
}
467470
}

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,10 @@ case class Commitments(params: ChannelParams,
11531153
def localFundingSigs(fundingTxId: TxId): Option[TxSignatures] = {
11541154
all.find(_.fundingTxId == fundingTxId).flatMap(_.localFundingStatus.localSigs_opt)
11551155
}
1156+
1157+
def liquidityPurchase(fundingTxId: TxId): Option[LiquidityAds.PurchaseBasicInfo] = {
1158+
all.find(_.fundingTxId == fundingTxId).flatMap(_.localFundingStatus.liquidityPurchase_opt)
1159+
}
11561160

11571161
/**
11581162
* Update the local/remote funding status

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
11051105

11061106
case Event(msg: TxSignatures, d: DATA_NORMAL) =>
11071107
d.commitments.latest.localFundingStatus match {
1108-
case dfu@LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _) if fundingTx.txId == msg.txId =>
1108+
case dfu@LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _, _) if fundingTx.txId == msg.txId =>
11091109
// we already sent our tx_signatures
11101110
InteractiveTxSigningSession.addRemoteSigs(keyManager, d.commitments.params, dfu.fundingParams, fundingTx, msg) match {
11111111
case Left(cause) =>
@@ -1149,7 +1149,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
11491149
}
11501150

11511151
case Event(w: WatchPublishedTriggered, d: DATA_NORMAL) =>
1152-
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid))
1152+
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid))
11531153
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus) match {
11541154
case Right((commitments1, _)) =>
11551155
watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthBlocks), delay_opt = None)
@@ -1910,7 +1910,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
19101910
case d: DATA_NORMAL => d.spliceStatus match {
19111911
case SpliceStatus.SpliceWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
19121912
case _ => d.commitments.latest.localFundingStatus match {
1913-
case LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _) => Set(ChannelReestablishTlv.NextFundingTlv(fundingTx.txId))
1913+
case LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _, _) => Set(ChannelReestablishTlv.NextFundingTlv(fundingTx.txId))
19141914
case _ => Set.empty
19151915
}
19161916
}
@@ -2300,11 +2300,11 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
23002300
// slightly before us. In that case, the WatchConfirmed may trigger first, and it would be inefficient to let the
23012301
// WatchPublished override our funding status: it will make us set a new WatchConfirmed that will instantly
23022302
// trigger and rewrite the funding status again.
2303-
val alreadyConfirmed = d.commitments.active.map(_.localFundingStatus).collect { case LocalFundingStatus.ConfirmedFundingTx(tx, _) => tx }.exists(_.txid == w.tx.txid)
2303+
val alreadyConfirmed = d.commitments.active.map(_.localFundingStatus).collect { case LocalFundingStatus.ConfirmedFundingTx(tx, _, _) => tx }.exists(_.txid == w.tx.txid)
23042304
if (alreadyConfirmed) {
23052305
stay()
23062306
} else {
2307-
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid))
2307+
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid))
23082308
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus) match {
23092309
case Right((commitments1, _)) =>
23102310
log.info(s"zero-conf funding txid=${w.tx.txid} has been published")

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
712712

713713
case Event(w: WatchPublishedTriggered, d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED) =>
714714
log.info("funding txid={} was successfully published for zero-conf channelId={}", w.tx.txid, d.channelId)
715-
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid))
715+
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid))
716716
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus) match {
717717
case Right((commitments1, _)) =>
718718
// we still watch the funding tx for confirmation even if we can use the zero-conf channel right away

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
391391
stay() using d.copy(deferred = Some(remoteChannelReady)) // no need to store, they will re-send if we get disconnected
392392

393393
case Event(w: WatchPublishedTriggered, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) =>
394-
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, None)
394+
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, None, None)
395395
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus) match {
396396
case Right((commitments1, _)) =>
397397
log.info("funding txid={} was successfully published for zero-conf channelId={}", w.tx.txid, d.channelId)

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ trait CommonFundingHandlers extends CommonHandlers {
8282
}
8383
case _ => () // in the dual-funding case, we have already verified the funding tx
8484
}
85-
val fundingStatus = ConfirmedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid))
85+
val fundingStatus = ConfirmedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid))
8686
context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, w.tx))
8787
// When a splice transaction confirms, it double-spends all the commitment transactions that only applied to the
8888
// previous funding transaction. Our peer cannot publish the corresponding revoked commitments anymore, so we can

eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,15 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon
865865
)
866866
context.system.eventStream ! EventStream.Publish(ChannelLiquidityPurchased(replyTo.toClassic, channelParams.channelId, remoteNodeId, purchase))
867867
}
868-
replyTo ! Succeeded(InteractiveTxSigningSession.WaitingForSigs(fundingParams, purpose.fundingTxIndex, signedTx, Left(localCommit), remoteCommit), commitSig, liquidityPurchase_opt)
868+
val signingSession = InteractiveTxSigningSession.WaitingForSigs(
869+
fundingParams,
870+
purpose.fundingTxIndex,
871+
signedTx,
872+
Left(localCommit),
873+
remoteCommit,
874+
liquidityPurchase_opt.map(_.basicInfo(isBuyer = fundingParams.isInitiator))
875+
)
876+
replyTo ! Succeeded(signingSession, commitSig, liquidityPurchase_opt)
869877
Behaviors.stopped
870878
case WalletFailure(t) =>
871879
log.error("could not sign funding transaction: ", t)
@@ -1050,7 +1058,8 @@ object InteractiveTxSigningSession {
10501058
fundingTxIndex: Long,
10511059
fundingTx: PartiallySignedSharedTransaction,
10521060
localCommit: Either[UnsignedLocalCommit, LocalCommit],
1053-
remoteCommit: RemoteCommit) extends InteractiveTxSigningSession {
1061+
remoteCommit: RemoteCommit,
1062+
liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends InteractiveTxSigningSession {
10541063
val commitInput: InputInfo = localCommit.fold(_.commitTx.input, _.commitTxAndRemoteSig.commitTx.input)
10551064
val localCommitIndex: Long = localCommit.fold(_.index, _.index)
10561065

@@ -1061,7 +1070,7 @@ object InteractiveTxSigningSession {
10611070
val localPerCommitmentPoint = nodeParams.channelKeyManager.commitmentPoint(channelKeyPath, localCommitIndex)
10621071
LocalCommit.fromCommitSig(nodeParams.channelKeyManager, channelParams, fundingTx.txId, fundingTxIndex, fundingParams.remoteFundingPubKey, commitInput, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec, localPerCommitmentPoint).map { signedLocalCommit =>
10631072
if (shouldSignFirst(fundingParams.isInitiator, channelParams, fundingTx.tx)) {
1064-
val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx, nodeParams.currentBlockHeight, fundingParams)
1073+
val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx, nodeParams.currentBlockHeight, fundingParams, liquidityPurchase_opt)
10651074
val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None)
10661075
SendingSigs(fundingStatus, commitment, fundingTx.localSigs)
10671076
} else {
@@ -1086,7 +1095,7 @@ object InteractiveTxSigningSession {
10861095
Left(f)
10871096
case Right(fullySignedTx) =>
10881097
log.info("interactive-tx fully signed with {} local inputs, {} remote inputs, {} local outputs and {} remote outputs", fullySignedTx.tx.localInputs.length, fullySignedTx.tx.remoteInputs.length, fullySignedTx.tx.localOutputs.length, fullySignedTx.tx.remoteOutputs.length)
1089-
val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fullySignedTx, nodeParams.currentBlockHeight, fundingParams)
1098+
val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fullySignedTx, nodeParams.currentBlockHeight, fundingParams, liquidityPurchase_opt)
10901099
val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None)
10911100
Right(SendingSigs(fundingStatus, commitment, fullySignedTx.localSigs))
10921101
}

0 commit comments

Comments
 (0)