From 8d58abc5c56e02cfabaf4f62ac7858e0231eb676 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 22 Jul 2019 14:50:40 +0200 Subject: [PATCH] Introduce scodecs for new `LocalParamsWithDKP` --- .../acinq/eclair/channel/ChannelTypes.scala | 17 ++++ .../fr/acinq/eclair/wire/ChannelCodecs.scala | 88 ++++++++++++++++++- .../acinq/eclair/wire/ChannelCodecsSpec.scala | 42 ++++++--- 3 files changed, 134 insertions(+), 13 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index c45590dcfd..f3af72eab5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -209,6 +209,22 @@ final case class LocalParams(nodeId: PublicKey, globalFeatures: ByteVector, localFeatures: ByteVector) +final case class LocalParamsWithDKP(nodeId: PublicKey, + channelKeyPath: Either[DeterministicWallet.KeyPath, KeyPathFundee], + dustLimitSatoshis: Long, + maxHtlcValueInFlightMsat: UInt64, + channelReserveSatoshis: Long, + htlcMinimumMsat: Long, + toSelfDelay: Int, + maxAcceptedHtlcs: Int, + defaultFinalScriptPubKey: ByteVector, + globalFeatures: ByteVector, + localFeatures: ByteVector) { + def isFunder = channelKeyPath.isLeft +} + +case class KeyPathFundee(fundingKeyPath: KeyPath, pointsKeyPath: KeyPath) + final case class RemoteParams(nodeId: PublicKey, dustLimitSatoshis: Long, maxHtlcValueInFlightMsat: UInt64, @@ -235,5 +251,6 @@ case class ChannelVersion(bits: BitVector) { object ChannelVersion { val LENGTH_BITS = 4 * 8 val STANDARD = ChannelVersion(BitVector.fill(LENGTH_BITS)(false)) + val DETERMINISTIC_KEYPATH = ChannelVersion(BitVector.one ++ BitVector.fill(LENGTH_BITS -1)(false)) } // @formatter:on diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 97937c05de..977f7572d3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -19,8 +19,11 @@ package fr.acinq.eclair.wire import java.util.UUID import akka.actor.ActorRef +import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut} +import fr.acinq.eclair.UInt64 +import fr.acinq.eclair.channel.ChannelVersion.{DETERMINISTIC_KEYPATH, STANDARD} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.payment.{Local, Origin, Relayed} @@ -29,9 +32,10 @@ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.CommonCodecs._ import fr.acinq.eclair.wire.LightningMessageCodecs._ import grizzled.slf4j.Logging -import scodec.bits.BitVector +import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ -import scodec.{Attempt, Codec} +import scodec.{Attempt, Codec, Decoder, Encoder} +import shapeless.{HList, HNil} import scala.compat.Platform import scala.concurrent.duration._ @@ -42,6 +46,12 @@ import scala.concurrent.duration._ object ChannelCodecs extends Logging { val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => new KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] + val keyPathFundeeCodec: Codec[KeyPathFundee] = ( + ("fundingKeyPath" | keyPathCodec) :: + ("pointsKeyPath" | keyPathCodec) + ).as[KeyPathFundee] + + val channelKeyPathCodec = either(bool, keyPathCodec, keyPathFundeeCodec) val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( ("secretkeybytes" | bytes32) :: @@ -72,6 +82,80 @@ object ChannelCodecs extends Logging { ("globalFeatures" | varsizebinarydata) :: ("localFeatures" | varsizebinarydata)).as[LocalParams] + + import shapeless._ + val localParamsCodecPreDKP: Codec[LocalParamsWithDKP] = ( + ("nodeId" | publicKey) :: + ("channelPath" | keyPathCodec) :: + ("dustLimitSatoshis" | uint64overflow) :: + ("maxHtlcValueInFlightMsat" | uint64) :: + ("channelReserveSatoshis" | uint64overflow) :: + ("htlcMinimumMsat" | uint64overflow) :: + ("toSelfDelay" | uint16) :: + ("maxAcceptedHtlcs" | uint16) :: + ("isFunder" | bool) :: + ("defaultFinalScriptPubKey" | varsizebinarydata) :: + ("globalFeatures" | varsizebinarydata) :: + ("localFeatures" | varsizebinarydata)).xmap({ + case nodeId :: + keyPath :: + dustLimitSatoshis :: + maxHtlcValueInFlightMsat :: + channelReserveSatoshis :: + htlcMinimumMsat :: + toSelfDelay :: + maxAcceptedHtlcs :: + isFunder :: + defaultFinalScriptPubKey :: + globalFeatures :: localFeatures :: HNil => + + LocalParamsWithDKP( + nodeId = nodeId, + // old versions of "LocalParams" use a single keypath + channelKeyPath = if(isFunder) Left(keyPath) else Right(KeyPathFundee(keyPath, keyPath)), + dustLimitSatoshis = dustLimitSatoshis, + maxHtlcValueInFlightMsat = maxHtlcValueInFlightMsat, + channelReserveSatoshis = channelReserveSatoshis, + htlcMinimumMsat = htlcMinimumMsat, + toSelfDelay = toSelfDelay, + maxAcceptedHtlcs = maxAcceptedHtlcs, + defaultFinalScriptPubKey = defaultFinalScriptPubKey, + globalFeatures = globalFeatures, + localFeatures = localFeatures) + },{ localParams => + + localParams.nodeId :: + localParams.channelKeyPath.fold(kp => kp, fKp => fKp.fundingKeyPath) :: // when encoding an old version of local params 'KeyPathFundee' holds 2 identical keypaths + localParams.dustLimitSatoshis :: + localParams.maxHtlcValueInFlightMsat :: + localParams.channelReserveSatoshis :: + localParams.htlcMinimumMsat :: + localParams.toSelfDelay :: + localParams.maxAcceptedHtlcs :: + localParams.isFunder :: + localParams.defaultFinalScriptPubKey :: + localParams.globalFeatures :: localParams.localFeatures :: HNil + }) + + val localParamsCodecCurrent: Codec[LocalParamsWithDKP] = ( + ("nodeId" | publicKey) :: + ("channelPath" | channelKeyPathCodec) :: + ("dustLimitSatoshis" | uint64overflow) :: + ("maxHtlcValueInFlightMsat" | uint64) :: + ("channelReserveSatoshis" | uint64overflow) :: + ("htlcMinimumMsat" | uint64overflow) :: + ("toSelfDelay" | uint16) :: + ("maxAcceptedHtlcs" | uint16) :: + ("defaultFinalScriptPubKey" | varsizebinarydata) :: + ("globalFeatures" | varsizebinarydata) :: + ("localFeatures" | varsizebinarydata)).as[LocalParamsWithDKP] + + // unused for now + def localParamsCodecWithVersion(version: ChannelVersion): Codec[LocalParamsWithDKP] = version match { + case STANDARD => localParamsCodecPreDKP + case DETERMINISTIC_KEYPATH => localParamsCodecCurrent + } + val remoteParamsCodec: Codec[RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimitSatoshis" | uint64overflow) :: diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index c5ba94558b..970c636399 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -21,9 +21,11 @@ import java.util.UUID import akka.actor.ActorSystem import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath +import fr.acinq.bitcoin.{Crypto, DeterministicWallet, OutPoint} import fr.acinq.bitcoin.{Block, ByteVector32, Crypto, DeterministicWallet, MilliSatoshi, OutPoint, Satoshi, Transaction} import fr.acinq.eclair._ import fr.acinq.eclair.api.JsonSupport +import fr.acinq.eclair.channel.ChannelVersion.STANDARD import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.{LocalKeyManager, ShaChain, Sphinx} @@ -80,9 +82,9 @@ class ChannelCodecsSpec extends FunSuite { } test("encode/decode localparams") { - val o = LocalParams( + val localParamFundee = LocalParamsWithDKP( nodeId = randomKey.publicKey, - channelKeyPath = DeterministicWallet.KeyPath(Seq(42L)), + channelKeyPath = Right(KeyPathFundee(KeyPath(Seq(4, 3, 2, 1L)), KeyPath(Seq(1, 2, 3, 4L)))), dustLimitSatoshis = Random.nextInt(Int.MaxValue), maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), channelReserveSatoshis = Random.nextInt(Int.MaxValue), @@ -90,12 +92,29 @@ class ChannelCodecsSpec extends FunSuite { toSelfDelay = Random.nextInt(Short.MaxValue), maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), - isFunder = Random.nextBoolean(), globalFeatures = randomBytes(256), localFeatures = randomBytes(256)) - val encoded = localParamsCodec.encode(o).require - val decoded = localParamsCodec.decode(encoded).require - assert(o === decoded.value) + + val encoded = localParamsCodecWithVersion(ChannelVersion.DETERMINISTIC_KEYPATH).encode(localParamFundee).require + val decoded = localParamsCodecWithVersion(ChannelVersion.DETERMINISTIC_KEYPATH).decode(encoded).require + assert(localParamFundee === decoded.value) + + val localParamFunder = LocalParamsWithDKP( + nodeId = randomKey.publicKey, + channelKeyPath = Left(KeyPath(Seq(4, 3, 2, 1L))), + dustLimitSatoshis = Random.nextInt(Int.MaxValue), + maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), + channelReserveSatoshis = Random.nextInt(Int.MaxValue), + htlcMinimumMsat = Random.nextInt(Int.MaxValue), + toSelfDelay = Random.nextInt(Short.MaxValue), + maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), + defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), + globalFeatures = randomBytes(256), + localFeatures = randomBytes(256)) + + val encoded1 = localParamsCodecWithVersion(ChannelVersion.DETERMINISTIC_KEYPATH).encode(localParamFunder).require + val decoded1 = localParamsCodecWithVersion(ChannelVersion.DETERMINISTIC_KEYPATH).decode(encoded1).require + assert(localParamFunder === decoded1.value) } test("encode/decode remoteparams") { @@ -200,6 +219,7 @@ class ChannelCodecsSpec extends FunSuite { test("basic serialization test (NORMAL)") { val data = normal val bin = ChannelCodecs.DATA_NORMAL_Codec.encode(data).require + assert(data.commitments.channelVersion == STANDARD) val check = ChannelCodecs.DATA_NORMAL_Codec.decodeValue(bin).require assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec) assert(data === check) @@ -311,11 +331,11 @@ class ChannelCodecsSpec extends FunSuite { val newbin = stateDataCodec.encode(oldnormal).require.bytes // and we decode with the new codec val newnormal = stateDataCodec.decode(newbin.bits).require.value - // finally we check that the actual data is the same as before (we just remove the new json field) - val oldjson = Serialization.write(oldnormal)(JsonSupport.formats).replace(""","unknownFields":""""", "").replace(""""channelVersion":"00000000000000000000000000000000",""", "") - val newjson = Serialization.write(newnormal)(JsonSupport.formats).replace(""","unknownFields":""""", "").replace(""""channelVersion":"00000000000000000000000000000000",""", "") - assert(oldjson === refjson) - assert(newjson === refjson) + // finally we check that the actual data is the same as before + val oldjson = Serialization.write(oldnormal)(JsonSupport.formats) + val newjson = Serialization.write(newnormal)(JsonSupport.formats) + + assert(oldjson === newjson) } }