@@ -18,14 +18,14 @@ package fr.acinq.eclair.wire.protocol
1818
1919import com .google .common .base .Charsets
2020import fr .acinq .bitcoin .scalacompat .Crypto .PrivateKey
21- import fr .acinq .bitcoin .scalacompat .{ByteVector64 , Crypto , Satoshi }
21+ import fr .acinq .bitcoin .scalacompat .{ByteVector32 , ByteVector64 , Crypto , Satoshi }
2222import fr .acinq .eclair .blockchain .fee .FeeratePerKw
2323import fr .acinq .eclair .transactions .Transactions
2424import fr .acinq .eclair .wire .protocol .CommonCodecs ._
25- import fr .acinq .eclair .wire .protocol .TlvCodecs .{tlvField , tlvStream }
26- import fr .acinq .eclair .{MilliSatoshi , ToMilliSatoshiConversion , UInt64 }
25+ import fr .acinq .eclair .wire .protocol .TlvCodecs .{genericTlv , tlvField , tsatoshi32 }
26+ import fr .acinq .eclair .{ToMilliSatoshiConversion , UInt64 }
2727import scodec .Codec
28- import scodec .bits .ByteVector
28+ import scodec .bits .{ BitVector , ByteVector }
2929import scodec .codecs ._
3030
3131/**
@@ -39,118 +39,163 @@ import scodec.codecs._
3939 */
4040object LiquidityAds {
4141
42- /**
43- * Liquidity fees are paid using the following :
44- *
45- * - the buyer pays [[ leaseFeeBase ]] regardless of the amount contributed by the seller
46- * - the buyer pays [[ leaseFeeProportional ]] (expressed in basis points) based on the amount contributed by the seller
47- * - the seller will have to add inputs/outputs to the transaction and pay on-chain fees for them, but the buyer
48- * refunds [[ fundingWeight ]] vbytes of those on-chain fees
49- */
50- case class FundingLeaseFee ( fundingWeight : Int , leaseFeeProportional : Int , leaseFeeBase : Satoshi ) {
42+ case class LeaseFees ( miningFee : Satoshi , serviceFee : Satoshi ) {
43+ val total : Satoshi = miningFee + serviceFee
44+ }
45+
46+ sealed trait FundingLease extends Tlv {
47+ def fees ( feerate : FeeratePerKw , requestedAmount : Satoshi , contributedAmount : Satoshi ) : LeaseFees
48+ }
49+
50+ object FundingLease {
5151 /**
52- * Fees paid by the liquidity buyer: the resulting amount must be added to the seller's output in the corresponding
53- * commitment transaction.
52+ * Liquidity fees are paid using the following:
53+ *
54+ * - the buyer pays [[leaseFeeBase ]] regardless of the amount contributed by the seller
55+ * - the buyer pays [[leaseFeeProportional ]] (expressed in basis points) based on the amount contributed by the seller
56+ * - the seller will have to add inputs/outputs to the transaction and pay on-chain fees for them, but the buyer
57+ * refunds [[fundingWeight ]] vbytes of those on-chain fees
5458 */
55- def fees (feerate : FeeratePerKw , requestedAmount : Satoshi , contributedAmount : Satoshi ): LeaseFees = {
56- val onChainFees = Transactions .weight2fee(feerate, fundingWeight)
57- // If the seller adds more liquidity than requested, the buyer doesn't pay for that extra liquidity.
58- val proportionalFee = requestedAmount.min(contributedAmount).toMilliSatoshi * leaseFeeProportional / 10_000
59- LeaseFees (onChainFees, leaseFeeBase + proportionalFee.truncateToSatoshi)
59+ case class Basic (minAmount : Satoshi , maxAmount : Satoshi , fundingWeight : Int , leaseFeeProportional : Int , leaseFeeBase : Satoshi ) extends FundingLease {
60+ /**
61+ * Fees paid by the liquidity buyer: the resulting amount must be added to the seller's output in the corresponding
62+ * commitment transaction.
63+ */
64+ override def fees (feerate : FeeratePerKw , requestedAmount : Satoshi , contributedAmount : Satoshi ): LeaseFees = {
65+ val onChainFees = Transactions .weight2fee(feerate, fundingWeight)
66+ // If the seller adds more liquidity than requested, the buyer doesn't pay for that extra liquidity.
67+ val proportionalFee = requestedAmount.min(contributedAmount).toMilliSatoshi * leaseFeeProportional / 10_000
68+ LeaseFees (onChainFees, leaseFeeBase + proportionalFee.truncateToSatoshi)
69+ }
6070 }
6171 }
6272
6373 // @formatter:off
64- sealed trait FundingLease { def fundingFee : FundingLeaseFee }
65- case class BasicFundingLease (minAmount : Satoshi , maxAmount : Satoshi , fundingFee : FundingLeaseFee ) extends FundingLease
66- case class DurationBasedFundingLease (leaseDuration : Int , minAmount : Satoshi , maxAmount : Satoshi , fundingFee : FundingLeaseFee , maxRelayFeeProportional : Int , maxRelayFeeBase : MilliSatoshi ) extends FundingLease
74+ sealed trait FundingLeaseWitness extends Tlv
75+ object FundingLeaseWitness {
76+ case class Basic (fundingScript : ByteVector ) extends FundingLeaseWitness
77+ }
6778 // @formatter:on
6879
6980 // @formatter:off
70- sealed trait LeaseRatesTlv extends Tlv
71- case class BasicFundingLeaseRates (rates : List [BasicFundingLease ]) extends LeaseRatesTlv
72- case class DurationBasedFundingLeaseRates (rates : List [DurationBasedFundingLease ]) extends LeaseRatesTlv
81+ sealed trait PaymentType
82+ object PaymentType {
83+ case object FromChannelBalance extends PaymentType
84+ case class Unknown (bitIndex : Int ) extends PaymentType
85+ // TODO: move to on-the-fly funding commit
86+ case object FromFutureHtlc extends PaymentType
87+ case object FromFutureHtlcWithPreimage extends PaymentType
88+ }
7389 // @formatter:on
7490
7591 // @formatter:off
76- sealed trait FundingLeaseWitness
77- case class BasicFundingLeaseWitness (fundingScript : ByteVector ) extends FundingLeaseWitness
78- case class DurationBasedFundingLeaseWitness (leaseExpiry : Long , fundingScript : ByteVector , maxRelayFeeProportional : Int , maxRelayFeeBase : MilliSatoshi ) extends FundingLeaseWitness
92+ sealed trait PaymentDetails extends Tlv { def paymentType : PaymentType }
93+ object PaymentDetails {
94+ case object FromChannelBalance extends PaymentDetails { override val paymentType : PaymentType = PaymentType .FromChannelBalance }
95+ // TODO: move to on-the-fly funding commit
96+ case class FromFutureHtlc (paymentHashes : List [ByteVector32 ]) extends PaymentDetails { override val paymentType : PaymentType = PaymentType .FromFutureHtlc }
97+ case class FromFutureHtlcWithPreimage (preimages : List [ByteVector32 ]) extends PaymentDetails { override val paymentType : PaymentType = PaymentType .FromFutureHtlcWithPreimage }
98+ }
7999 // @formatter:on
80100
81- case class RequestFunds (requestedAmount : Satoshi , fundingLease : FundingLease )
101+ case class WillFundRates (fundingRates : List [FundingLease ], paymentTypes : Set [PaymentType ])
102+
103+ case class RequestFunds (requestedAmount : Satoshi , fundingLease : FundingLease , paymentDetails : PaymentDetails )
82104
83105 case class WillFund (leaseWitness : FundingLeaseWitness , signature : ByteVector64 )
84106
85- case class LeaseFees (miningFee : Satoshi , serviceFee : Satoshi ) {
86- val total : Satoshi = miningFee + serviceFee
107+ def requestFunding (amount : Satoshi , paymentDetails : PaymentDetails , rates : WillFundRates ): Option [RequestFunds ] = {
108+ rates.fundingRates.collectFirst {
109+ case l : FundingLease .Basic if l.minAmount <= amount && amount <= l.maxAmount => l
110+ } match {
111+ case Some (l) if rates.paymentTypes.contains(paymentDetails.paymentType) => Some (RequestFunds (amount, l, paymentDetails))
112+ case _ => None
113+ }
87114 }
88115
89- def signLease (request : RequestFunds , nodeKey : PrivateKey , fundingScript : ByteVector , currentBlockHeight : Long ): WillFund = {
90- val witness = request.fundingLease match {
91- case _ : BasicFundingLease => BasicFundingLeaseWitness (fundingScript)
92- case l : DurationBasedFundingLease => DurationBasedFundingLeaseWitness (currentBlockHeight + l.leaseDuration, fundingScript, l.maxRelayFeeProportional, l.maxRelayFeeBase)
116+ def validateRequest (request : RequestFunds , fundingRates : WillFundRates ): Boolean = {
117+ val paymentTypeOk = fundingRates.paymentTypes.contains(request.paymentDetails.paymentType)
118+ val leaseOk = fundingRates.fundingRates.contains(request.fundingLease)
119+ val amountOk = request.fundingLease match {
120+ case lease : FundingLease .Basic => lease.minAmount <= request.requestedAmount && request.requestedAmount <= lease.maxAmount
93121 }
94- val toSign = witness match {
95- case w : BasicFundingLeaseWitness => Crypto .sha256(ByteVector (" basic_funding_lease" .getBytes(Charsets .US_ASCII )) ++ Codecs .basicFundingLeaseWitness.encode(w).require.bytes)
96- case w : DurationBasedFundingLeaseWitness => Crypto .sha256(ByteVector (" duration_based_funding_lease" .getBytes(Charsets .US_ASCII )) ++ Codecs .durationBasedFundingLeaseWitness.encode(w).require.bytes)
122+ paymentTypeOk && leaseOk && amountOk
123+ }
124+
125+ def signLease (request : RequestFunds , nodeKey : PrivateKey , fundingScript : ByteVector ): WillFund = {
126+ val (tag, witness) = request.fundingLease match {
127+ case _ : FundingLease .Basic => (" basic_funding_lease" , FundingLeaseWitness .Basic (fundingScript))
97128 }
129+ val toSign = Crypto .sha256(ByteVector (tag.getBytes(Charsets .US_ASCII )) ++ Codecs .fundingLeaseWitness.encode(witness).require.bytes)
98130 WillFund (witness, Crypto .sign(toSign, nodeKey))
99131 }
100132
101133 object Codecs {
102- private val fundingLeaseFee : Codec [FundingLeaseFee ] = (
103- (" fundingWeight" | uint16) ::
104- (" leaseFeeBasis" | uint16) ::
105- (" leaseFeeBase" | satoshi32)
106- ).as[FundingLeaseFee ]
107-
108- private val basicFundingLease : Codec [BasicFundingLease ] = (
134+ private val basicFundingLease : Codec [FundingLease .Basic ] = (
109135 (" minLeaseAmount" | satoshi32) ::
110136 (" maxLeaseAmount" | satoshi32) ::
111- (" leaseFee" | fundingLeaseFee)
112- ).as[BasicFundingLease ]
113-
114- private val durationBasedFundingLease : Codec [DurationBasedFundingLease ] = (
115- (" leaseDuration" | uint16) ::
116- (" minLeaseAmount" | satoshi32) ::
117- (" maxLeaseAmount" | satoshi32) ::
118- (" leaseFee" | fundingLeaseFee) ::
119- (" maxChannelFeeBasis" | uint16) ::
120- (" maxChannelFeeBase" | millisatoshi32)
121- ).as[DurationBasedFundingLease ]
137+ (" fundingWeight" | uint16) ::
138+ (" leaseFeeBasis" | uint16) ::
139+ (" leaseFeeBase" | tsatoshi32)
140+ ).as[FundingLease .Basic ]
122141
123- private val fundingLease : Codec [FundingLease ] = discriminated[FundingLease ].by(byte)
124- .typecase(1 , basicFundingLease)
125- .typecase(3 , durationBasedFundingLease)
142+ private val fundingLease : Codec [FundingLease ] = discriminated[FundingLease ].by(varint)
143+ .typecase(UInt64 (0 ), variableSizeBytesLong(varintoverflow, basicFundingLease.complete))
126144
127- val basicFundingLeaseWitness : Codec [BasicFundingLeaseWitness ] = (" fundingScript" | varsizebinarydata ).as[BasicFundingLeaseWitness ]
145+ private val basicFundingLeaseWitness : Codec [FundingLeaseWitness . Basic ] = (" fundingScript" | bytes ).as[FundingLeaseWitness . Basic ]
128146
129- val durationBasedFundingLeaseWitness : Codec [DurationBasedFundingLeaseWitness ] = (
130- (" leaseExpiry" | uint32) ::
131- (" fundingScript" | varsizebinarydata) ::
132- (" maxChannelFeeBasis" | uint16) ::
133- (" maxChannelFeeBase" | millisatoshi32)
134- ).as[DurationBasedFundingLeaseWitness ]
147+ val fundingLeaseWitness : Codec [FundingLeaseWitness ] = discriminated[FundingLeaseWitness ].by(varint)
148+ .typecase(UInt64 (0 ), tlvField(basicFundingLeaseWitness))
135149
136- private val fundingLeaseWitness : Codec [FundingLeaseWitness ] = discriminated[FundingLeaseWitness ].by(byte)
137- .typecase(1 , basicFundingLeaseWitness)
138- .typecase(3 , durationBasedFundingLeaseWitness)
150+ private val paymentDetails : Codec [PaymentDetails ] = discriminated[PaymentDetails ].by(varint)
151+ .typecase(UInt64 (0 ), tlvField(provide(PaymentDetails .FromChannelBalance )))
152+ .typecase(UInt64 (128 ), tlvField((" paymentHashes" | list(bytes32)).as[PaymentDetails .FromFutureHtlc ]))
153+ .typecase(UInt64 (129 ), tlvField((" paymentPreimages" | list(bytes32)).as[PaymentDetails .FromFutureHtlcWithPreimage ]))
139154
140155 val requestFunds : Codec [RequestFunds ] = (
141156 (" requestedAmount" | satoshi) ::
142- (" fundingLease" | fundingLease)
157+ (" fundingLease" | fundingLease) ::
158+ (" paymentDetails" | paymentDetails)
143159 ).as[RequestFunds ]
144160
145161 val willFund : Codec [WillFund ] = (
146162 (" leaseWitness" | fundingLeaseWitness) ::
147163 (" signature" | bytes64)
148164 ).as[WillFund ]
149165
150- val leaseRates : Codec [TlvStream [LeaseRatesTlv ]] = tlvStream(discriminated[LeaseRatesTlv ].by(varint)
151- .typecase(UInt64 (1 ), tlvField(list(basicFundingLease).as[BasicFundingLeaseRates ]))
152- .typecase(UInt64 (3 ), tlvField(list(durationBasedFundingLease).as[DurationBasedFundingLeaseRates ]))
166+ private val paymentTypes : Codec [Set [PaymentType ]] = bytes.xmap(
167+ f = { bytes =>
168+ bytes.bits.toIndexedSeq.reverse.zipWithIndex.collect {
169+ case (true , 0 ) => PaymentType .FromChannelBalance
170+ case (true , 128 ) => PaymentType .FromFutureHtlc
171+ case (true , 129 ) => PaymentType .FromFutureHtlcWithPreimage
172+ case (true , idx) => PaymentType .Unknown (idx)
173+ }.toSet
174+ },
175+ g = { paymentTypes =>
176+ val indexes = paymentTypes.collect {
177+ case PaymentType .FromChannelBalance => 0
178+ case PaymentType .FromFutureHtlc => 128
179+ case PaymentType .FromFutureHtlcWithPreimage => 129
180+ case PaymentType .Unknown (idx) => idx
181+ }
182+ // When converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting bits.
183+ var buf = BitVector .fill(indexes.max + 1 )(high = false ).bytes.bits
184+ indexes.foreach { i => buf = buf.set(i) }
185+ buf.reverse.bytes
186+ }
153187 )
188+
189+ // We filter and ignore unknown lease types.
190+ private val supportedFundingLeases : Codec [List [FundingLease ]] = listOfN(uint16, discriminatorFallback(genericTlv, fundingLease)).xmap(
191+ _.collect { case Right (lease) => lease },
192+ _.map(lease => Right (lease)),
193+ )
194+
195+ val willFundRates : Codec [WillFundRates ] = (
196+ (" fundingRates" | supportedFundingLeases) ::
197+ (" paymentTypes" | variableSizeBytes(uint16, paymentTypes))
198+ ).as[WillFundRates ]
154199 }
155200
156201}
0 commit comments