From efc6b2835ad82d28122bbe23bbfc0e603ed55e3b Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Tue, 21 Jan 2020 15:15:19 +0100 Subject: [PATCH 1/3] UpfrontShutdownScript feature bit Add to features list. Activate optional bit by default. --- eclair-core/src/main/resources/reference.conf | 2 +- eclair-core/src/main/scala/fr/acinq/eclair/Features.scala | 6 ++++++ .../src/test/scala/fr/acinq/eclair/FeaturesSpec.scala | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 56de75d310..9a46d9c78f 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -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", diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala index 7f9ea52bd1..6848aee650 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -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 @@ -126,6 +131,7 @@ object Features { def areSupported(features: BitVector): Boolean = { val supportedMandatoryFeatures = Set( OptionDataLossProtect, + UpfrontShutdownScript, ChannelRangeQueries, VariableLengthOnion, ChannelRangeQueriesExtended, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala index efa1cd205c..ca1e5b110f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala @@ -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)) @@ -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))) @@ -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, From 611da20f344e5301aad7f4bd410bca6c5953694c Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Tue, 21 Jan 2020 18:11:52 +0100 Subject: [PATCH 2/3] UpfrontShutdownScript codec Encode/decode upfront_shutdown_script in open_channel and accept_channel messages. Decode both old and new format, always encode with the new one. --- .../eclair/wire/LightningMessageCodecs.scala | 17 +++++- .../eclair/wire/LightningMessageTypes.scala | 6 +- .../wire/LightningMessageCodecsSpec.scala | 59 +++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala index 140e633a44..7ac44bdf8e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala @@ -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. + // 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) :: @@ -77,7 +88,8 @@ object LightningMessageCodecs { ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: ("firstPerCommitmentPoint" | publicKey) :: - ("channelFlags" | byte)).as[OpenChannel] + ("channelFlags" | byte) :: + ("upfront_shutdown_script" | upfrontShutdownScript)).as[OpenChannel] val acceptChannelCodec: Codec[AcceptChannel] = ( ("temporaryChannelId" | bytes32) :: @@ -93,7 +105,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) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala index be88cfda64..7a75249cb1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala @@ -84,7 +84,8 @@ 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) extends ChannelMessage with HasTemporaryChannelId with HasChainHash case class AcceptChannel(temporaryChannelId: ByteVector32, dustLimitSatoshis: Satoshi, @@ -99,7 +100,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, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala index 7b037fdb99..f20683c2f1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala @@ -87,6 +87,65 @@ 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 010202a 030102", defaultOpen, Some(defaultEncoded ++ hex"0000")), // empty upfront_shutdown_script + unknown odd tlv records + TestCase(defaultEncoded ++ hex"0002 1234 0303010203", defaultOpen.copy(upfrontShutdownScript = Some(hex"1234")), Some(defaultEncoded ++ hex"0002 1234")) // 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 + ) + + 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)) From cd73fb5b20733f7974bf80a2bc380f669acae712 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Thu, 23 Jan 2020 13:55:40 +0100 Subject: [PATCH 3/3] fixup! UpfrontShutdownScript codec --- .../eclair/wire/LightningMessageCodecs.scala | 3 +- .../eclair/wire/LightningMessageTypes.scala | 3 +- .../scala/fr/acinq/eclair/wire/OpenTlv.scala | 36 +++++++++++++++++++ .../wire/LightningMessageCodecsSpec.scala | 9 +++-- 4 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala index 7ac44bdf8e..04814864fb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala @@ -89,7 +89,8 @@ object LightningMessageCodecs { ("htlcBasepoint" | publicKey) :: ("firstPerCommitmentPoint" | publicKey) :: ("channelFlags" | byte) :: - ("upfront_shutdown_script" | upfrontShutdownScript)).as[OpenChannel] + ("upfront_shutdown_script" | upfrontShutdownScript) :: + ("tlvStream_opt" | optional(bitsRemaining, OpenTlv.openTlvCodec))).as[OpenChannel] val acceptChannelCodec: Codec[AcceptChannel] = ( ("temporaryChannelId" | bytes32) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala index 7a75249cb1..091ac46e93 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala @@ -85,7 +85,8 @@ case class OpenChannel(chainHash: ByteVector32, htlcBasepoint: PublicKey, firstPerCommitmentPoint: PublicKey, channelFlags: Byte, - upfrontShutdownScript: Option[ByteVector] = None) extends ChannelMessage with HasTemporaryChannelId with HasChainHash + upfrontShutdownScript: Option[ByteVector] = None, + tlvStream_opt: Option[TlvStream[OpenTlv]] = None) extends ChannelMessage with HasTemporaryChannelId with HasChainHash case class AcceptChannel(temporaryChannelId: ByteVector32, dustLimitSatoshis: Satoshi, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala new file mode 100644 index 0000000000..6f5bdc41f6 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala @@ -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]) + ) + +} \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala index f20683c2f1..558305c71b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala @@ -98,8 +98,8 @@ class LightningMessageCodecsSpec extends FunSuite { 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 010202a 030102", defaultOpen, Some(defaultEncoded ++ hex"0000")), // empty upfront_shutdown_script + unknown odd tlv records - TestCase(defaultEncoded ++ hex"0002 1234 0303010203", defaultOpen.copy(upfrontShutdownScript = Some(hex"1234")), Some(defaultEncoded ++ hex"0002 1234")) // non-empty upfront_shutdown_script + unknown odd tlv records + 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) { @@ -115,7 +115,10 @@ class LightningMessageCodecsSpec extends FunSuite { val testCases = Seq( defaultEncoded ++ hex"00", // truncated length defaultEncoded ++ hex"01", // truncated length - defaultEncoded ++ hex"0004 123456" // truncated script + 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) {