Skip to content

Commit

Permalink
Add dual funding codecs and feature bit (#2231)
Browse files Browse the repository at this point in the history
Add dual funding feature bit, but keep it disabled for now.
Add dual funding protocol messages and codecs.
We don't actually handle these messages yet.
When we receive them, they will simply be ignored and log a warning.
  • Loading branch information
t-bast authored Apr 14, 2022
1 parent 9a31dfa commit 443266d
Show file tree
Hide file tree
Showing 11 changed files with 499 additions and 38 deletions.
1 change: 1 addition & 0 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ eclair {
option_anchor_outputs = disabled
option_anchors_zero_fee_htlc_tx = optional
option_shutdown_anysegwit = optional
option_dual_fund = disabled
option_onion_messages = optional
option_channel_type = optional
option_payment_metadata = optional
Expand Down
7 changes: 7 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ object Features {
val mandatory = 26
}

case object DualFunding extends Feature with InitFeature with NodeFeature {
val rfcName = "option_dual_fund"
val mandatory = 28
}

case object OnionMessages extends Feature with InitFeature with NodeFeature {
val rfcName = "option_onion_messages"
val mandatory = 38
Expand Down Expand Up @@ -258,6 +263,7 @@ object Features {
AnchorOutputs,
AnchorOutputsZeroFeeHtlcTx,
ShutdownAnySegwit,
DualFunding,
OnionMessages,
ChannelType,
PaymentMetadata,
Expand All @@ -272,6 +278,7 @@ object Features {
BasicMultiPartPayment -> (PaymentSecret :: Nil),
AnchorOutputs -> (StaticRemoteKey :: Nil),
AnchorOutputsZeroFeeHtlcTx -> (StaticRemoteKey :: Nil),
DualFunding -> (AnchorOutputsZeroFeeHtlcTx :: Nil),
TrampolinePaymentPrototype -> (PaymentSecret :: Nil),
KeySend -> (VariableLengthOnion :: Nil)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,21 @@ sealed trait OpenChannelTlv extends Tlv

sealed trait AcceptChannelTlv extends Tlv

sealed trait OpenDualFundedChannelTlv extends Tlv

sealed trait AcceptDualFundedChannelTlv extends Tlv

object ChannelTlv {

/** Commitment to where the funds will go in case of a mutual close, which remote node will enforce in case we're compromised. */
case class UpfrontShutdownScriptTlv(script: ByteVector) extends OpenChannelTlv with AcceptChannelTlv {
case class UpfrontShutdownScriptTlv(script: ByteVector) extends OpenChannelTlv with AcceptChannelTlv with OpenDualFundedChannelTlv with AcceptDualFundedChannelTlv {
val isEmpty: Boolean = script.isEmpty
}

val upfrontShutdownScriptCodec: Codec[UpfrontShutdownScriptTlv] = variableSizeBytesLong(varintoverflow, bytes).as[UpfrontShutdownScriptTlv]

/** A channel type is a set of even feature bits that represent persistent features which affect channel operations. */
case class ChannelTypeTlv(channelType: ChannelType) extends OpenChannelTlv with AcceptChannelTlv
case class ChannelTypeTlv(channelType: ChannelType) extends OpenChannelTlv with AcceptChannelTlv with OpenDualFundedChannelTlv with AcceptDualFundedChannelTlv

val channelTypeCodec: Codec[ChannelTypeTlv] = variableSizeBytesLong(varintoverflow, bytes).xmap(
b => ChannelTypeTlv(ChannelTypes.fromFeatures(Features(b).initFeatures())),
Expand Down Expand Up @@ -69,6 +73,28 @@ object AcceptChannelTlv {
)
}

object OpenDualFundedChannelTlv {

import ChannelTlv._

val openTlvCodec: Codec[TlvStream[OpenDualFundedChannelTlv]] = tlvStream(discriminated[OpenDualFundedChannelTlv].by(varint)
.typecase(UInt64(0), upfrontShutdownScriptCodec)
.typecase(UInt64(1), channelTypeCodec)
)

}

object AcceptDualFundedChannelTlv {

import ChannelTlv._

val acceptTlvCodec: Codec[TlvStream[AcceptDualFundedChannelTlv]] = tlvStream(discriminated[AcceptDualFundedChannelTlv].by(varint)
.typecase(UInt64(0), upfrontShutdownScriptCodec)
.typecase(UInt64(1), channelTypeCodec)
)

}

sealed trait FundingCreatedTlv extends Tlv

object FundingCreatedTlv {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package fr.acinq.eclair.wire.protocol

import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, Transaction}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel.ChannelFlags
import fr.acinq.eclair.crypto.Mac32
Expand Down Expand Up @@ -100,6 +100,9 @@ object CommonCodecs {
// It is useful in combination with variableSizeBytesLong to encode/decode TLV lengths because those will always be < 2^63.
val varintoverflow: Codec[Long] = varint.narrow(l => if (l <= UInt64(Long.MaxValue)) Attempt.successful(l.toBigInt.toLong) else Attempt.failure(Err(s"overflow for value $l")), l => UInt64(l))

// This codec can be safely used for values < 2^32 and will fail otherwise.
val smallvarint: Codec[Int] = varint.narrow(l => if (l <= UInt64(Int.MaxValue)) Attempt.successful(l.toBigInt.toInt) else Attempt.failure(Err(s"overflow for value $l")), l => UInt64(l))

val bytes32: Codec[ByteVector32] = limitedSizeBytes(32, bytesStrict(32).xmap(d => ByteVector32(d), d => d.bytes))

val bytes64: Codec[ByteVector64] = limitedSizeBytes(64, bytesStrict(64).xmap(d => ByteVector64(d), d => d.bytes))
Expand All @@ -112,6 +115,11 @@ object CommonCodecs {

val channelflags: Codec[ChannelFlags] = (ignore(7) dropLeft bool).as[ChannelFlags]

val extendedChannelFlags: Codec[ChannelFlags] = variableSizeBytesLong(varintoverflow, bytes).xmap(
bin => ChannelFlags(bin.lastOption.exists(_ % 2 == 1)),
flags => if (flags.announceChannel) ByteVector(1) else ByteVector(0)
)

val ipv4address: Codec[Inet4Address] = bytes(4).xmap(b => InetAddress.getByAddress(b.toArray).asInstanceOf[Inet4Address], a => ByteVector(a.getAddress))

val ipv6address: Codec[Inet6Address] = bytes(16).exmap(b => Attempt.fromTry(Try(Inet6Address.getByAddress(null, b.toArray, null))), a => Attempt.fromTry(Try(ByteVector(a.getAddress))))
Expand Down Expand Up @@ -144,6 +152,8 @@ object CommonCodecs {

val rgb: Codec[Color] = bytes(3).xmap(buf => Color(buf(0), buf(1), buf(2)), t => ByteVector(t.r, t.g, t.b))

val txCodec: Codec[Transaction] = bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))

def zeropaddedstring(size: Int): Codec[String] = fixedSizeBytes(size, utf8).xmap(s => s.takeWhile(_ != '\u0000'), s => s)

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2022 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.acinq.eclair.wire.protocol

import fr.acinq.bitcoin.scalacompat.Satoshi
import fr.acinq.eclair.UInt64
import fr.acinq.eclair.wire.protocol.CommonCodecs.{varint, varintoverflow}
import fr.acinq.eclair.wire.protocol.TlvCodecs.{tlvStream, tsatoshi}
import scodec.Codec
import scodec.codecs.{discriminated, variableSizeBytesLong}

/**
* Created by t-bast on 08/04/2022.
*/

sealed trait TxAddInputTlv extends Tlv

object TxAddInputTlv {
val txAddInputTlvCodec: Codec[TlvStream[TxAddInputTlv]] = tlvStream(discriminated[TxAddInputTlv].by(varint))
}

sealed trait TxAddOutputTlv extends Tlv

object TxAddOutputTlv {
val txAddOutputTlvCodec: Codec[TlvStream[TxAddOutputTlv]] = tlvStream(discriminated[TxAddOutputTlv].by(varint))
}

sealed trait TxRemoveInputTlv extends Tlv

object TxRemoveInputTlv {
val txRemoveInputTlvCodec: Codec[TlvStream[TxRemoveInputTlv]] = tlvStream(discriminated[TxRemoveInputTlv].by(varint))
}

sealed trait TxRemoveOutputTlv extends Tlv

object TxRemoveOutputTlv {
val txRemoveOutputTlvCodec: Codec[TlvStream[TxRemoveOutputTlv]] = tlvStream(discriminated[TxRemoveOutputTlv].by(varint))
}

sealed trait TxCompleteTlv extends Tlv

object TxCompleteTlv {
val txCompleteTlvCodec: Codec[TlvStream[TxCompleteTlv]] = tlvStream(discriminated[TxCompleteTlv].by(varint))
}

sealed trait TxSignaturesTlv extends Tlv

object TxSignaturesTlv {
val txSignaturesTlvCodec: Codec[TlvStream[TxSignaturesTlv]] = tlvStream(discriminated[TxSignaturesTlv].by(varint))
}

sealed trait TxInitRbfTlv extends Tlv

sealed trait TxAckRbfTlv extends Tlv

object TxRbfTlv {
/** Amount that the peer will contribute to the transaction's shared output. */
case class SharedOutputContributionTlv(amount: Satoshi) extends TxInitRbfTlv with TxAckRbfTlv
}

object TxInitRbfTlv {

import TxRbfTlv._

val txInitRbfTlvCodec: Codec[TlvStream[TxInitRbfTlv]] = tlvStream(discriminated[TxInitRbfTlv].by(varint)
.typecase(UInt64(0), variableSizeBytesLong(varintoverflow, tsatoshi).as[SharedOutputContributionTlv])
)

}

object TxAckRbfTlv {

import TxRbfTlv._

val txAckRbfTlvCodec: Codec[TlvStream[TxAckRbfTlv]] = tlvStream(discriminated[TxAckRbfTlv].by(varint)
.typecase(UInt64(0), variableSizeBytesLong(varintoverflow, tsatoshi).as[SharedOutputContributionTlv])
)

}

sealed trait TxAbortTlv extends Tlv

object TxAbortTlv {
val txAbortTlvCodec: Codec[TlvStream[TxAbortTlv]] = tlvStream(discriminated[TxAbortTlv].by(varint))
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

package fr.acinq.eclair.wire.protocol

import fr.acinq.bitcoin.scalacompat.ScriptWitness
import fr.acinq.eclair.wire.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.wire.protocol.CommonCodecs._
import fr.acinq.eclair.{Feature, Features, InitFeature, KamonExt, NodeFeature}
import fr.acinq.eclair.{Feature, Features, InitFeature, KamonExt}
import scodec.bits.{BitVector, ByteVector}
import scodec.codecs._
import scodec.{Attempt, Codec}
Expand Down Expand Up @@ -81,7 +82,7 @@ object LightningMessageCodecs {
("fundingSatoshis" | satoshi) ::
("pushMsat" | millisatoshi) ::
("dustLimitSatoshis" | satoshi) ::
("maxHtlcValueInFlightMsat" | uint64) ::
("maxHtlcValueInFlightMsat" | uint64) :: // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
("channelReserveSatoshis" | satoshi) ::
("htlcMinimumMsat" | millisatoshi) ::
("feeratePerKw" | feeratePerKw) ::
Expand All @@ -96,10 +97,31 @@ object LightningMessageCodecs {
("channelFlags" | channelflags) ::
("tlvStream" | OpenChannelTlv.openTlvCodec)).as[OpenChannel]

val openDualFundedChannelCodec: Codec[OpenDualFundedChannel] = (
("chainHash" | bytes32) ::
("temporaryChannelId" | bytes32) ::
("fundingFeerate" | feeratePerKw) ::
("commitmentFeerate" | feeratePerKw) ::
("fundingAmount" | satoshi) ::
("dustLimit" | satoshi) ::
("maxHtlcValueInFlightMsat" | uint64) :: // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
("htlcMinimumMsat" | millisatoshi) ::
("toSelfDelay" | cltvExpiryDelta) ::
("maxAcceptedHtlcs" | uint16) ::
("lockTime" | uint32) ::
("fundingPubkey" | publicKey) ::
("revocationBasepoint" | publicKey) ::
("paymentBasepoint" | publicKey) ::
("delayedPaymentBasepoint" | publicKey) ::
("htlcBasepoint" | publicKey) ::
("firstPerCommitmentPoint" | publicKey) ::
("channelFlags" | extendedChannelFlags) ::
("tlvStream" | OpenDualFundedChannelTlv.openTlvCodec)).as[OpenDualFundedChannel]

val acceptChannelCodec: Codec[AcceptChannel] = (
("temporaryChannelId" | bytes32) ::
("dustLimitSatoshis" | satoshi) ::
("maxHtlcValueInFlightMsat" | uint64) ::
("maxHtlcValueInFlightMsat" | uint64) :: // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
("channelReserveSatoshis" | satoshi) ::
("htlcMinimumMsat" | millisatoshi) ::
("minimumDepth" | uint32) ::
Expand All @@ -113,6 +135,24 @@ object LightningMessageCodecs {
("firstPerCommitmentPoint" | publicKey) ::
("tlvStream" | AcceptChannelTlv.acceptTlvCodec)).as[AcceptChannel]

val acceptDualFundedChannelCodec: Codec[AcceptDualFundedChannel] = (
("temporaryChannelId" | bytes32) ::
("fundingAmount" | satoshi) ::
("dustLimit" | satoshi) ::
("maxHtlcValueInFlightMsat" | uint64) :: // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
("htlcMinimumMsat" | millisatoshi) ::
("minimumDepth" | uint32) ::
("toSelfDelay" | cltvExpiryDelta) ::
("maxAcceptedHtlcs" | uint16) ::
("fundingPubkey" | publicKey) ::
("revocationBasepoint" | publicKey) ::
("paymentBasepoint" | publicKey) ::
("delayedPaymentBasepoint" | publicKey) ::
("htlcBasepoint" | publicKey) ::
("firstPerCommitmentPoint" | publicKey) ::
("channelFlags" | extendedChannelFlags) ::
("tlvStream" | AcceptDualFundedChannelTlv.acceptTlvCodec)).as[AcceptDualFundedChannel]

val fundingCreatedCodec: Codec[FundingCreated] = (
("temporaryChannelId" | bytes32) ::
("fundingTxid" | bytes32) ::
Expand All @@ -130,6 +170,66 @@ object LightningMessageCodecs {
("nextPerCommitmentPoint" | publicKey) ::
("tlvStream" | FundingLockedTlv.fundingLockedTlvCodec)).as[FundingLocked]

private val scriptSigOptCodec: Codec[Option[ByteVector]] = lengthDelimited(bytes).xmap[Option[ByteVector]](
b => if (b.isEmpty) None else Some(b),
b => b.getOrElse(ByteVector.empty)
)

val txAddInputCodec: Codec[TxAddInput] = (
("channelId" | bytes32) ::
("serialId" | uint64) ::
("previousTx" | lengthDelimited(txCodec)) ::
("previousTxOutput" | uint32) ::
("sequence" | uint32) ::
("scriptSig" | scriptSigOptCodec) ::
("tlvStream" | TxAddInputTlv.txAddInputTlvCodec)).as[TxAddInput]

val txAddOutputCodec: Codec[TxAddOutput] = (
("channelId" | bytes32) ::
("serialId" | uint64) ::
("amount" | satoshi) ::
("scriptPubKey" | lengthDelimited(bytes)) ::
("tlvStream" | TxAddOutputTlv.txAddOutputTlvCodec)).as[TxAddOutput]

val txRemoveInputCodec: Codec[TxRemoveInput] = (
("channelId" | bytes32) ::
("serialId" | uint64) ::
("tlvStream" | TxRemoveInputTlv.txRemoveInputTlvCodec)).as[TxRemoveInput]

val txRemoveOutputCodec: Codec[TxRemoveOutput] = (
("channelId" | bytes32) ::
("serialId" | uint64) ::
("tlvStream" | TxRemoveOutputTlv.txRemoveOutputTlvCodec)).as[TxRemoveOutput]

val txCompleteCodec: Codec[TxComplete] = (
("channelId" | bytes32) ::
("tlvStream" | TxCompleteTlv.txCompleteTlvCodec)).as[TxComplete]

private val witnessElementCodec: Codec[ByteVector] = lengthDelimited(bytes)
private val witnessStackCodec: Codec[ScriptWitness] = listOfN(smallvarint, witnessElementCodec).xmap(s => ScriptWitness(s.toSeq), w => w.stack.toList)
private val witnessesCodec: Codec[Seq[ScriptWitness]] = listOfN(smallvarint, witnessStackCodec).xmap(l => l.toSeq, l => l.toList)

val txSignaturesCodec: Codec[TxSignatures] = (
("channelId" | bytes32) ::
("txId" | sha256) ::
("witnesses" | witnessesCodec) ::
("tlvStream" | TxSignaturesTlv.txSignaturesTlvCodec)).as[TxSignatures]

val txInitRbfCodec: Codec[TxInitRbf] = (
("channelId" | bytes32) ::
("lockTime" | uint32) ::
("feerate" | feeratePerKw) ::
("tlvStream" | TxInitRbfTlv.txInitRbfTlvCodec)).as[TxInitRbf]

val txAckRbfCodec: Codec[TxAckRbf] = (
("channelId" | bytes32) ::
("tlvStream" | TxAckRbfTlv.txAckRbfTlvCodec)).as[TxAckRbf]

val txAbortCodec: Codec[TxAbort] = (
("channelId" | bytes32) ::
("data" | lengthDelimited(bytes)) ::
("tlvStream" | TxAbortTlv.txAbortTlvCodec)).as[TxAbort]

val shutdownCodec: Codec[Shutdown] = (
("channelId" | bytes32) ::
("scriptPubKey" | varsizebinarydata) ::
Expand Down Expand Up @@ -351,6 +451,17 @@ object LightningMessageCodecs {
.typecase(36, fundingLockedCodec)
.typecase(38, shutdownCodec)
.typecase(39, closingSignedCodec)
.typecase(64, openDualFundedChannelCodec)
.typecase(65, acceptDualFundedChannelCodec)
.typecase(66, txAddInputCodec)
.typecase(67, txAddOutputCodec)
.typecase(68, txRemoveInputCodec)
.typecase(69, txRemoveOutputCodec)
.typecase(70, txCompleteCodec)
.typecase(71, txSignaturesCodec)
.typecase(72, txInitRbfCodec)
.typecase(73, txAckRbfCodec)
.typecase(74, txAbortCodec)
.typecase(128, updateAddHtlcCodec)
.typecase(130, updateFulfillHtlcCodec)
.typecase(131, updateFailHtlcCodec)
Expand Down
Loading

0 comments on commit 443266d

Please sign in to comment.