Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ eclair {
node-color = "49daaa"

trampoline-payments-enable = false // TODO: @t-bast: once spec-ed this should use a global feature flag
features = "0a8a" // initial_routing_sync + option_data_loss_protect + option_channel_range_queries + option_channel_range_queries_ex + variable_length_onion
features = "0aaa" // initial_routing_sync + option_data_loss_protect + option_upfront_shutdown_script + option_channel_range_queries + option_channel_range_queries_ex + variable_length_onion
override-features = [ // optional per-node features
# {
# nodeid = "02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Expand Down
6 changes: 6 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 @@ -54,6 +54,11 @@ object Features {
val mandatory = 2
}

case object UpfrontShutdownScript extends Feature {
val rfcName = "option_upfront_shutdown_script"
val mandatory = 4
}

case object ChannelRangeQueries extends Feature {
val rfcName = "gossip_queries"
val mandatory = 6
Expand Down Expand Up @@ -126,6 +131,7 @@ object Features {
def areSupported(features: BitVector): Boolean = {
val supportedMandatoryFeatures = Set(
OptionDataLossProtect,
UpfrontShutdownScript,
ChannelRangeQueries,
VariableLengthOnion,
ChannelRangeQueriesExtended,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ object LightningMessageCodecs {
("yourLastPerCommitmentSecret" | optional(bitsRemaining, privateKey)) ::
("myCurrentPerCommitmentPoint" | optional(bitsRemaining, publicKey))).as[ChannelReestablish]

// Legacy nodes are supposed to encode an upfront_shutdown_script only if both sides advertised support for option_upfront_shutdown_script.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that a node may include an optional shutdown script even if its peer does not support it:

  • if both nodes advertised the option_upfront_shutdown_script feature:
  • MUST include either a valid shutdown_scriptpubkey as required by shutdown >scriptpubkey, or a zero-length shutdown_scriptpubkey.
  • otherwise:
  • MAY include ashutdown_scriptpubkey.

// To allow extending all messages with TLV streams, the upfront_shutdown_script was made mandatory in https://github.com/lightningnetwork/lightning-rfc/pull/714.
// This codec decodes both legacy and new versions, while always encoding with an upfront_shutdown_script (of length 0 if none actually provided).
private val upfrontShutdownScript = Codec[Option[ByteVector]](
(script: Option[ByteVector]) => variableSizeBytes(uint16, bytes).encode(script.getOrElse(ByteVector.empty)),
(b: BitVector) => optional(bitsRemaining, variableSizeBytes(uint16, bytes)).decode(b).map(r => r.map {
case Some(s) if s.nonEmpty => Some(s)
case _ => None
})
)

val openChannelCodec: Codec[OpenChannel] = (
("chainHash" | bytes32) ::
("temporaryChannelId" | bytes32) ::
Expand All @@ -77,7 +88,9 @@ object LightningMessageCodecs {
("delayedPaymentBasepoint" | publicKey) ::
("htlcBasepoint" | publicKey) ::
("firstPerCommitmentPoint" | publicKey) ::
("channelFlags" | byte)).as[OpenChannel]
("channelFlags" | byte) ::
("upfront_shutdown_script" | upfrontShutdownScript) ::
("tlvStream_opt" | optional(bitsRemaining, OpenTlv.openTlvCodec))).as[OpenChannel]

val acceptChannelCodec: Codec[AcceptChannel] = (
("temporaryChannelId" | bytes32) ::
Expand All @@ -93,7 +106,8 @@ object LightningMessageCodecs {
("paymentBasepoint" | publicKey) ::
("delayedPaymentBasepoint" | publicKey) ::
("htlcBasepoint" | publicKey) ::
("firstPerCommitmentPoint" | publicKey)).as[AcceptChannel]
("firstPerCommitmentPoint" | publicKey) ::
("upfront_shutdown_script" | upfrontShutdownScript)).as[AcceptChannel]

val fundingCreatedCodec: Codec[FundingCreated] = (
("temporaryChannelId" | bytes32) ::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ case class OpenChannel(chainHash: ByteVector32,
delayedPaymentBasepoint: PublicKey,
htlcBasepoint: PublicKey,
firstPerCommitmentPoint: PublicKey,
channelFlags: Byte) extends ChannelMessage with HasTemporaryChannelId with HasChainHash
channelFlags: Byte,
upfrontShutdownScript: Option[ByteVector] = None,
tlvStream_opt: Option[TlvStream[OpenTlv]] = None) extends ChannelMessage with HasTemporaryChannelId with HasChainHash

case class AcceptChannel(temporaryChannelId: ByteVector32,
dustLimitSatoshis: Satoshi,
Expand All @@ -99,7 +101,8 @@ case class AcceptChannel(temporaryChannelId: ByteVector32,
paymentBasepoint: PublicKey,
delayedPaymentBasepoint: PublicKey,
htlcBasepoint: PublicKey,
firstPerCommitmentPoint: PublicKey) extends ChannelMessage with HasTemporaryChannelId
firstPerCommitmentPoint: PublicKey,
upfrontShutdownScript: Option[ByteVector] = None) extends ChannelMessage with HasTemporaryChannelId

case class FundingCreated(temporaryChannelId: ByteVector32,
fundingTxid: ByteVector32,
Expand Down
36 changes: 36 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2019 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

import fr.acinq.eclair.UInt64
import fr.acinq.eclair.wire.CommonCodecs._
import fr.acinq.eclair.wire.TlvCodecs.tlvStream
import scodec.Codec
import scodec.bits.ByteVector
import scodec.codecs._

sealed trait OpenTlv extends Tlv

object OpenTlv {

case class Placeholder(dummy: ByteVector) extends OpenTlv

val openTlvCodec: Codec[TlvStream[OpenTlv]] = tlvStream(discriminated.by(varint)
.typecase(UInt64(0x47000001), variableSizeBytesLong(varintoverflow, bytes).as[Placeholder])
)

}
8 changes: 8 additions & 0 deletions eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class FeaturesSpec extends FunSuite {
assert(hasFeature(hex"02", OptionDataLossProtect, Some(FeatureSupport.Optional)))
}

test("'option_upfront_shutdown_script' feature") {
assert(hasFeature(hex"10", UpfrontShutdownScript, Some(FeatureSupport.Mandatory)))
assert(hasFeature(hex"20", UpfrontShutdownScript, Some(FeatureSupport.Optional)))
}

test("'initial_routing_sync', 'data_loss_protect' and 'variable_length_onion' features") {
val features = hex"010a"
assert(areSupported(features))
Expand Down Expand Up @@ -89,6 +94,8 @@ class FeaturesSpec extends FunSuite {
assert(areSupported(ByteVector.fromLong(1L << InitialRoutingSync.optional)))
assert(areSupported(ByteVector.fromLong(1L << OptionDataLossProtect.mandatory)))
assert(areSupported(ByteVector.fromLong(1L << OptionDataLossProtect.optional)))
assert(areSupported(ByteVector.fromLong(1L << UpfrontShutdownScript.mandatory)))
assert(areSupported(ByteVector.fromLong(1L << UpfrontShutdownScript.optional)))
assert(areSupported(ByteVector.fromLong(1L << ChannelRangeQueries.mandatory)))
assert(areSupported(ByteVector.fromLong(1L << ChannelRangeQueries.optional)))
assert(areSupported(ByteVector.fromLong(1L << VariableLengthOnion.mandatory)))
Expand All @@ -102,6 +109,7 @@ class FeaturesSpec extends FunSuite {

val testCases = Map(
bin" 00000000000000001011" -> true,
bin" 00000000101010101010" -> true,
bin" 00010000100001000000" -> true,
bin" 00100000100000100000" -> true,
bin" 00010100000000001000" -> true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,68 @@ class LightningMessageCodecsSpec extends FunSuite {
assert(bin === bin2)
}

test("encode/decode open_channel") {
val defaultOpen = OpenChannel(ByteVector32.Zeroes, ByteVector32.Zeroes, 1 sat, 1 msat, 1 sat, UInt64(1), 1 sat, 1 msat, 1, CltvExpiryDelta(1), 1, publicKey(1), point(2), point(3), point(4), point(5), point(6), 0.toByte)
// Default encoding that completely omits the upfront_shutdown_script (nodes were supposed to encode it only if both
// sides advertised support for option_upfront_shutdown_script).
// To allow extending all messages with TLV streams, the upfront_shutdown_script was made mandatory in https://github.com/lightningnetwork/lightning-rfc/pull/714
val defaultEncoded = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000100000000000000010000000000000001000000000000000100000000000000010000000100010001031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00"
case class TestCase(encoded: ByteVector, decoded: OpenChannel, reEncoded: Option[ByteVector] = None)
val testCases = Seq(
TestCase(defaultEncoded, defaultOpen, Some(defaultEncoded ++ hex"0000")), // legacy encoding without upfront_shutdown_script
TestCase(defaultEncoded ++ hex"0000", defaultOpen), // empty upfront_shutdown_script
TestCase(defaultEncoded ++ hex"0004 01abcdef", defaultOpen.copy(upfrontShutdownScript = Some(hex"01abcdef"))), // non-empty upfront_shutdown_script
TestCase(defaultEncoded ++ hex"0000 0102002a 030102", defaultOpen.copy(tlvStream_opt = Some(TlvStream(Nil, Seq(GenericTlv(UInt64(1), hex"002a"), GenericTlv(UInt64(3), hex"02")))))), // empty upfront_shutdown_script + unknown odd tlv records
TestCase(defaultEncoded ++ hex"0002 1234 0303010203", defaultOpen.copy(upfrontShutdownScript = Some(hex"1234"), tlvStream_opt = Some(TlvStream(Nil, Seq(GenericTlv(UInt64(3), hex"010203")))))) // non-empty upfront_shutdown_script + unknown odd tlv records
)

for (testCase <- testCases) {
val decoded = openChannelCodec.decode(testCase.encoded.bits).require.value
assert(decoded === testCase.decoded)
val reEncoded = openChannelCodec.encode(decoded).require.bytes
assert(reEncoded === testCase.reEncoded.getOrElse(testCase.encoded))
}
}

test("decode invalid open_channel") {
val defaultEncoded = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000100000000000000010000000000000001000000000000000100000000000000010000000100010001031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00"
val testCases = Seq(
defaultEncoded ++ hex"00", // truncated length
defaultEncoded ++ hex"01", // truncated length
defaultEncoded ++ hex"0004 123456", // truncated script
defaultEncoded ++ hex"0000 02012a", // invalid tlv stream (unknown even record)
defaultEncoded ++ hex"0000 01012a 030201", // invalid tlv stream (truncated)
defaultEncoded ++ hex"01012a" // missing upfront_shutdown_script before tlv stream
)

for (testCase <- testCases) {
assert(openChannelCodec.decode(testCase.bits).isFailure, testCase.toHex)
}
}

test("encode/decode accept_channel") {
val defaultAccept = AcceptChannel(ByteVector32.Zeroes, 1 sat, UInt64(1), 1 sat, 1 msat, 1, CltvExpiryDelta(1), 1, publicKey(1), point(2), point(3), point(4), point(5), point(6))
// Default encoding that completely omits the upfront_shutdown_script (nodes were supposed to encode it only if both
// sides advertised support for option_upfront_shutdown_script).
// To allow extending all messages with TLV streams, the upfront_shutdown_script was made mandatory in https://github.com/lightningnetwork/lightning-rfc/pull/714
val defaultEncoded = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000000100000000000000010000000100010001031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a"
case class TestCase(encoded: ByteVector, decoded: AcceptChannel, reEncoded: Option[ByteVector] = None)
val testCases = Seq(
TestCase(defaultEncoded, defaultAccept, Some(defaultEncoded ++ hex"0000")), // legacy encoding without upfront_shutdown_script
TestCase(defaultEncoded ++ hex"0000", defaultAccept), // empty upfront_shutdown_script
TestCase(defaultEncoded ++ hex"0004 01abcdef", defaultAccept.copy(upfrontShutdownScript = Some(hex"01abcdef"))), // non-empty upfront_shutdown_script
TestCase(defaultEncoded ++ hex"0000 010202a 030102", defaultAccept, Some(defaultEncoded ++ hex"0000")), // empty upfront_shutdown_script + unknown odd tlv records
TestCase(defaultEncoded ++ hex"0002 1234 0303010203", defaultAccept.copy(upfrontShutdownScript = Some(hex"1234")), Some(defaultEncoded ++ hex"0002 1234")) // non-empty upfront_shutdown_script + unknown odd tlv records
)

for (testCase <- testCases) {
val decoded = acceptChannelCodec.decode(testCase.encoded.bits).require.value
assert(decoded === testCase.decoded)
val reEncoded = acceptChannelCodec.encode(decoded).require.bytes
assert(reEncoded === testCase.reEncoded.getOrElse(testCase.encoded))
}
}

test("encode/decode all channel messages") {
val open = OpenChannel(randomBytes32, randomBytes32, 3 sat, 4 msat, 5 sat, UInt64(6), 7 sat, 8 msat, 9, CltvExpiryDelta(10), 11, publicKey(1), point(2), point(3), point(4), point(5), point(6), 0.toByte)
val accept = AcceptChannel(randomBytes32, 3 sat, UInt64(4), 5 sat, 6 msat, 7, CltvExpiryDelta(8), 9, publicKey(1), point(2), point(3), point(4), point(5), point(6))
Expand Down