@@ -18,14 +18,14 @@ package fr.acinq.eclair.wire.protocol
18
18
19
19
import com .google .common .base .Charsets
20
20
import 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 }
22
22
import fr .acinq .eclair .blockchain .fee .FeeratePerKw
23
23
import fr .acinq .eclair .transactions .Transactions
24
24
import 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 }
27
27
import scodec .Codec
28
- import scodec .bits .ByteVector
28
+ import scodec .bits .{ BitVector , ByteVector }
29
29
import scodec .codecs ._
30
30
31
31
/**
@@ -39,118 +39,163 @@ import scodec.codecs._
39
39
*/
40
40
object LiquidityAds {
41
41
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 {
51
51
/**
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
54
58
*/
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
+ }
60
70
}
61
71
}
62
72
63
73
// @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
+ }
67
78
// @formatter:on
68
79
69
80
// @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
+ }
73
89
// @formatter:on
74
90
75
91
// @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
+ }
79
99
// @formatter:on
80
100
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 )
82
104
83
105
case class WillFund (leaseWitness : FundingLeaseWitness , signature : ByteVector64 )
84
106
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
+ }
87
114
}
88
115
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
93
121
}
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))
97
128
}
129
+ val toSign = Crypto .sha256(ByteVector (tag.getBytes(Charsets .US_ASCII )) ++ Codecs .fundingLeaseWitness.encode(witness).require.bytes)
98
130
WillFund (witness, Crypto .sign(toSign, nodeKey))
99
131
}
100
132
101
133
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 ] = (
109
135
(" minLeaseAmount" | satoshi32) ::
110
136
(" 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 ]
122
141
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))
126
144
127
- val basicFundingLeaseWitness : Codec [BasicFundingLeaseWitness ] = (" fundingScript" | varsizebinarydata ).as[BasicFundingLeaseWitness ]
145
+ private val basicFundingLeaseWitness : Codec [FundingLeaseWitness . Basic ] = (" fundingScript" | bytes ).as[FundingLeaseWitness . Basic ]
128
146
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))
135
149
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 ]))
139
154
140
155
val requestFunds : Codec [RequestFunds ] = (
141
156
(" requestedAmount" | satoshi) ::
142
- (" fundingLease" | fundingLease)
157
+ (" fundingLease" | fundingLease) ::
158
+ (" paymentDetails" | paymentDetails)
143
159
).as[RequestFunds ]
144
160
145
161
val willFund : Codec [WillFund ] = (
146
162
(" leaseWitness" | fundingLeaseWitness) ::
147
163
(" signature" | bytes64)
148
164
).as[WillFund ]
149
165
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
+ }
153
187
)
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 ]
154
199
}
155
200
156
201
}
0 commit comments