diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala index dc3c3d45be..67566c8c7a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala @@ -24,12 +24,10 @@ import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.eclair.channel.{ChannelErrorOccurred, LocalError, NetworkFeePaid, RemoteError} import fr.acinq.eclair.db._ import fr.acinq.eclair.payment._ -import fr.acinq.eclair.wire.ChannelCodecs import fr.acinq.eclair.{LongToBtcAmount, MilliSatoshi} import grizzled.slf4j.Logging import scala.collection.immutable.Queue -import scala.compat.Platform class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { @@ -44,7 +42,8 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { using(sqlite.createStatement(), inTransaction = true) { statement => def migration12(statement: Statement): Int = { - statement.executeUpdate(s"ALTER TABLE sent ADD id BLOB DEFAULT '${ChannelCodecs.UNKNOWN_UUID.toString}' NOT NULL") + val ZERO_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000") + statement.executeUpdate(s"ALTER TABLE sent ADD id BLOB DEFAULT '${ZERO_UUID.toString}' NOT NULL") } def migration23(statement: Statement): Int = { 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 5515a89367..e699197d96 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 @@ -16,11 +16,9 @@ package fr.acinq.eclair.wire -import java.util.UUID - import akka.actor.ActorRef import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} -import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut} +import fr.acinq.bitcoin.{ByteVector32, OutPoint, Transaction, TxOut} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.payment.relay.Origin @@ -29,19 +27,19 @@ 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.codecs._ import scodec.{Attempt, Codec} -import shapeless.{HNil, ::} - -import scala.compat.Platform -import scala.concurrent.duration._ /** * Created by PM on 02/06/2017. */ object ChannelCodecs extends Logging { + /** + * All LN protocol message must be stored as length-delimited, because they may have arbitrary trailing data + */ + def lengthDelimited[T](codec: Codec[T]): Codec[T] = variableSizeBytesLong(varintoverflow, codec) + val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => new KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( @@ -51,14 +49,12 @@ object ChannelCodecs extends Logging { ("path" | keyPathCodec) :: ("parent" | int64)).as[ExtendedPrivateKey] - val channelVersionCodec: Codec[ChannelVersion] = discriminatorWithDefault[ChannelVersion]( - discriminator = discriminated[ChannelVersion].by(byte) - .typecase(0x01, bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion]) - // NB: 0x02 and 0x03 are *reserved* for backward compatibility reasons - , - fallback = provide(ChannelVersion.ZEROES) // README: DO NOT CHANGE THIS !! old channels don't have a channel version - // field and don't support additional features which is why all bits are set to 0. - ) + val channelVersionCodec: Codec[ChannelVersion] = bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion] + + /** + * byte-aligned boolean codec + */ + val bool8: Codec[Boolean] = bool(8) def localParamsCodec(channelVersion: ChannelVersion): Codec[LocalParams] = ( ("nodeId" | publicKey) :: @@ -69,8 +65,8 @@ object ChannelCodecs extends Logging { ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: - ("isFunder" | bool) :: - ("defaultFinalScriptPubKey" | varsizebinarydata) :: + ("isFunder" | bool8) :: + ("defaultFinalScriptPubKey" | lengthDelimited(bytes)) :: ("localPaymentBasepoint" | optional(provide(channelVersion.hasStaticRemotekey), publicKey)) :: ("features" | combinedFeaturesCodec)).as[LocalParams] @@ -89,14 +85,9 @@ object ChannelCodecs extends Logging { ("htlcBasepoint" | publicKey) :: ("features" | combinedFeaturesCodec)).as[RemoteParams] - val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool) - .typecase(true, updateAddHtlcCodec.as[IncomingHtlc]) - .typecase(false, updateAddHtlcCodec.as[OutgoingHtlc]) - - def setCodec[T](codec: Codec[T]): Codec[Set[T]] = Codec[Set[T]]( - (elems: Set[T]) => listOfN(uint16, codec).encode(elems.toList), - (wire: BitVector) => listOfN(uint16, codec).decode(wire).map(_.map(_.toSet)) - ) + val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool8) + .typecase(true, lengthDelimited(updateAddHtlcCodec).as[IncomingHtlc]) + .typecase(false, lengthDelimited(updateAddHtlcCodec).as[OutgoingHtlc]) val commitmentSpecCodec: Codec[CommitmentSpec] = ( ("htlcs" | setCodec(htlcCodec)) :: @@ -104,16 +95,16 @@ object ChannelCodecs extends Logging { ("toLocal" | millisatoshi) :: ("toRemote" | millisatoshi)).as[CommitmentSpec] - val outPointCodec: Codec[OutPoint] = variableSizeBytes(uint16, bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d))) + val outPointCodec: Codec[OutPoint] = lengthDelimited(bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d))) - val txOutCodec: Codec[TxOut] = variableSizeBytes(uint16, bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d))) + val txOutCodec: Codec[TxOut] = lengthDelimited(bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d))) - val txCodec: Codec[Transaction] = variableSizeBytes(uint16, bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) + val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) val inputInfoCodec: Codec[InputInfo] = ( ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: - ("redeemScript" | varsizebinarydata)).as[InputInfo] + ("redeemScript" | lengthDelimited(bytes))).as[InputInfo] val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16) .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) @@ -127,19 +118,10 @@ object ChannelCodecs extends Logging { .typecase(0x09, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx]) .typecase(0x10, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClosingTx]) - // this is a backward compatible codec (we used to store the sig as DER encoded), now we store it as 64-bytes - val sig64OrDERCodec: Codec[ByteVector64] = Codec[ByteVector64]( - (value: ByteVector64) => bytes(64).encode(value), - (wire: BitVector) => bytes.decode(wire).map(_.map { - case bin64 if bin64.size == 64 => ByteVector64(bin64) - case der => Crypto.der2compact(der) - }) - ) - val htlcTxAndSigsCodec: Codec[HtlcTxAndSigs] = ( ("txinfo" | txWithInputInfoCodec) :: - ("localSig" | variableSizeBytes(uint16, sig64OrDERCodec)) :: // we store as variable length for historical purposes (we used to store as DER encoded) - ("remoteSig" | variableSizeBytes(uint16, sig64OrDERCodec))).as[HtlcTxAndSigs] + ("localSig" | lengthDelimited(bytes64)) :: // we store as variable length for historical purposes (we used to store as DER encoded) + ("remoteSig" | lengthDelimited(bytes64))).as[HtlcTxAndSigs] val publishableTxsCodec: Codec[PublishableTxs] = ( ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) :: @@ -156,7 +138,7 @@ object ChannelCodecs extends Logging { ("txid" | bytes32) :: ("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit] - val updateMessageCodec: Codec[UpdateMessage] = lightningMessageCodec.narrow(f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g) + val updateMessageCodec: Codec[UpdateMessage] = lengthDelimited(lightningMessageCodec.narrow[UpdateMessage](f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g)) val localChangesCodec: Codec[LocalChanges] = ( ("proposed" | listOfN(uint16, updateMessageCodec)) :: @@ -170,9 +152,9 @@ object ChannelCodecs extends Logging { val waitingForRevocationCodec: Codec[WaitingForRevocation] = ( ("nextRemoteCommit" | remoteCommitCodec) :: - ("sent" | commitSigCodec) :: + ("sent" | lengthDelimited(commitSigCodec)) :: ("sentAfterLocalCommitIndex" | uint64overflow) :: - ("reSignAsap" | bool)).as[WaitingForRevocation] + ("reSignAsap" | bool8)).as[WaitingForRevocation] val localCodec: Codec[Origin.Local] = ( ("id" | uuid) :: @@ -185,33 +167,19 @@ object ChannelCodecs extends Logging { ("amountIn" | millisatoshi) :: ("amountOut" | millisatoshi)).as[Origin.Relayed] - // this is for backward compatibility to handle legacy payments that didn't have identifiers - val UNKNOWN_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000") - val trampolineRelayedCodec: Codec[Origin.TrampolineRelayed] = ( listOfN(uint16, bytes32 ~ int64) :: ("sender" | provide(Option.empty[ActorRef])) ).as[Origin.TrampolineRelayed] val originCodec: Codec[Origin] = discriminated[Origin].by(uint16) - .typecase(0x03, localCodec) // backward compatible - .typecase(0x01, provide(Origin.Local(UNKNOWN_UUID, None))) .typecase(0x02, relayedCodec) + .typecase(0x03, localCodec) .typecase(0x04, trampolineRelayedCodec) - val originsListCodec: Codec[List[(Long, Origin)]] = listOfN(uint16, int64 ~ originCodec) + val originsMapCodec: Codec[Map[Long, Origin]] = mapCodec(int64, originCodec) - val originsMapCodec: Codec[Map[Long, Origin]] = Codec[Map[Long, Origin]]( - (map: Map[Long, Origin]) => originsListCodec.encode(map.toList), - (wire: BitVector) => originsListCodec.decode(wire).map(_.map(_.toMap)) - ) - - val spentListCodec: Codec[List[(OutPoint, ByteVector32)]] = listOfN(uint16, outPointCodec ~ bytes32) - - val spentMapCodec: Codec[Map[OutPoint, ByteVector32]] = Codec[Map[OutPoint, ByteVector32]]( - (map: Map[OutPoint, ByteVector32]) => spentListCodec.encode(map.toList), - (wire: BitVector) => spentListCodec.decode(wire).map(_.map(_.toMap)) - ) + val spentMapCodec: Codec[Map[OutPoint, ByteVector32]] = mapCodec(outPointCodec, bytes32) val commitmentsCodec: Codec[Commitments] = ( ("channelVersion" | channelVersionCodec) >>:~ { channelVersion => @@ -225,19 +193,19 @@ object ChannelCodecs extends Logging { ("localNextHtlcId" | uint64overflow) :: ("remoteNextHtlcId" | uint64overflow) :: ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) :: + ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: + ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("channelId" | bytes32) }).as[Commitments] val closingTxProposedCodec: Codec[ClosingTxProposed] = ( ("unsignedTx" | txCodec) :: - ("localClosingSigned" | closingSignedCodec)).as[ClosingTxProposed] + ("localClosingSigned" | lengthDelimited(closingSignedCodec))).as[ClosingTxProposed] val localCommitPublishedCodec: Codec[LocalCommitPublished] = ( ("commitTx" | txCodec) :: - ("claimMainDelayedOutputTx" | optional(bool, txCodec)) :: + ("claimMainDelayedOutputTx" | optional(bool8, txCodec)) :: ("htlcSuccessTxs" | listOfN(uint16, txCodec)) :: ("htlcTimeoutTxs" | listOfN(uint16, txCodec)) :: ("claimHtlcDelayedTx" | listOfN(uint16, txCodec)) :: @@ -245,115 +213,68 @@ object ChannelCodecs extends Logging { val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( ("commitTx" | txCodec) :: - ("claimMainOutputTx" | optional(bool, txCodec)) :: + ("claimMainOutputTx" | optional(bool8, txCodec)) :: ("claimHtlcSuccessTxs" | listOfN(uint16, txCodec)) :: ("claimHtlcTimeoutTxs" | listOfN(uint16, txCodec)) :: ("spent" | spentMapCodec)).as[RemoteCommitPublished] val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( ("commitTx" | txCodec) :: - ("claimMainOutputTx" | optional(bool, txCodec)) :: - ("mainPenaltyTx" | optional(bool, txCodec)) :: + ("claimMainOutputTx" | optional(bool8, txCodec)) :: + ("mainPenaltyTx" | optional(bool8, txCodec)) :: ("htlcPenaltyTxs" | listOfN(uint16, txCodec)) :: ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, txCodec)) :: ("spent" | spentMapCodec)).as[RevokedCommitPublished] - // this is a decode-only codec compatible with versions 997acee and below, with placeholders for new fields - val DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( - ("commitments" | commitmentsCodec) :: - ("fundingTx" | provide[Option[Transaction]](None)) :: - ("waitingSince" | provide(System.currentTimeMillis.milliseconds.toSeconds)) :: - ("deferred" | optional(bool, fundingLockedCodec)) :: - ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly - val DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentsCodec) :: - ("fundingTx" | optional(bool, txCodec)) :: + ("fundingTx" | optional(bool8, txCodec)) :: ("waitingSince" | int64) :: - ("deferred" | optional(bool, fundingLockedCodec)) :: - ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] + ("deferred" | optional(bool8, lengthDelimited(fundingLockedCodec))) :: + ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: - ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED] - - // All channel_announcement's written prior to supporting unknown trailing fields had the same fixed size, because - // those are the announcements that *we* created and we always used an empty features field, which was the only - // variable-length field. - val noUnknownFieldsChannelAnnouncementSizeCodec: Codec[Int] = provide(430) - - // We used to ignore unknown trailing fields, and assume that channel_update size was known. This is not true anymore, - // so we need to tell the codec where to stop, otherwise all the remaining part of the data will be decoded as unknown - // fields. Fortunately, we can easily tell what size the channel_update will be. - val noUnknownFieldsChannelUpdateSizeCodec: Codec[Int] = peek( // we need to take a peek at a specific byte to know what size the message will be, and then rollback to read the full message - ignore(8 * (64 + 32 + 8 + 4)) ~> // we skip the first fields: signature + chain_hash + short_channel_id + timestamp - byte // this is the messageFlags byte - ) - .map(messageFlags => if ((messageFlags & 1) != 0) 136 else 128) // depending on the value of option_channel_htlc_max, size will be 128B or 136B - .decodeOnly // this is for compat, we only need to decode - - // this is a decode-only codec compatible with versions 9afb26e and below - val DATA_NORMAL_COMPAT_03_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: - ("buried" | bool) :: - ("channelAnnouncement" | optional(bool, variableSizeBytes(noUnknownFieldsChannelAnnouncementSizeCodec, channelAnnouncementCodec))) :: - ("channelUpdate" | variableSizeBytes(noUnknownFieldsChannelUpdateSizeCodec, channelUpdateCodec)) :: - ("localShutdown" | optional(bool, shutdownCodec)) :: - ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL].decodeOnly + ("lastSent" | lengthDelimited(fundingLockedCodec))).as[DATA_WAIT_FOR_FUNDING_LOCKED] val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: - ("buried" | bool) :: - ("channelAnnouncement" | optional(bool, variableSizeBytes(uint16, channelAnnouncementCodec))) :: - ("channelUpdate" | variableSizeBytes(uint16, channelUpdateCodec)) :: - ("localShutdown" | optional(bool, shutdownCodec)) :: - ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL] + ("buried" | bool8) :: + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec)))).as[DATA_NORMAL] val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( ("commitments" | commitmentsCodec) :: - ("localShutdown" | shutdownCodec) :: - ("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN] + ("localShutdown" | lengthDelimited(shutdownCodec)) :: + ("remoteShutdown" | lengthDelimited(shutdownCodec))).as[DATA_SHUTDOWN] val DATA_NEGOTIATING_Codec: Codec[DATA_NEGOTIATING] = ( ("commitments" | commitmentsCodec) :: - ("localShutdown" | shutdownCodec) :: - ("remoteShutdown" | shutdownCodec) :: - ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) :: - ("bestUnpublishedClosingTx_opt" | optional(bool, txCodec))).as[DATA_NEGOTIATING] - - // this is a decode-only codec compatible with versions 818199e and below, with placeholders for new fields - val DATA_CLOSING_COMPAT_06_Codec: Codec[DATA_CLOSING] = ( - ("commitments" | commitmentsCodec) :: - ("fundingTx" | provide[Option[Transaction]](None)) :: - ("waitingSince" | provide(System.currentTimeMillis.milliseconds.toSeconds)) :: - ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: - ("mutualClosePublished" | listOfN(uint16, txCodec)) :: - ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: - ("remoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: - ("nextRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: - ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING].decodeOnly + ("localShutdown" | lengthDelimited(shutdownCodec)) :: + ("remoteShutdown" | lengthDelimited(shutdownCodec)) :: + ("closingTxProposed" | listOfN(uint16, listOfN(uint16, lengthDelimited(closingTxProposedCodec)))) :: + ("bestUnpublishedClosingTx_opt" | optional(bool8, txCodec))).as[DATA_NEGOTIATING] val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = ( ("commitments" | commitmentsCodec) :: - ("fundingTx" | optional(bool, txCodec)) :: + ("fundingTx" | optional(bool8, txCodec)) :: ("waitingSince" | int64) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualClosePublished" | listOfN(uint16, txCodec)) :: - ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: - ("remoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: - ("nextRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: - ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: + ("localCommitPublished" | optional(bool8, localCommitPublishedCodec)) :: + ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: + ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: + ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( ("commitments" | commitmentsCodec) :: ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] - /** * Order matters!! * @@ -365,16 +286,25 @@ object ChannelCodecs extends Logging { * * More info here: https://github.com/scodec/scodec/issues/122 */ - val stateDataCodec: Codec[HasCommitments] = ("version" | constant(0x00)) ~> discriminated[HasCommitments].by(uint16) - .typecase(0x10, DATA_NORMAL_Codec) - .typecase(0x09, DATA_CLOSING_Codec) - .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) - .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec) - .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec) - .typecase(0x03, DATA_NORMAL_COMPAT_03_Codec) - .typecase(0x04, DATA_SHUTDOWN_Codec) - .typecase(0x05, DATA_NEGOTIATING_Codec) - .typecase(0x06, DATA_CLOSING_COMPAT_06_Codec) - .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec) + val stateDataCodec: Codec[HasCommitments] = discriminated[HasCommitments].by(byte) + .typecase(1, discriminated[HasCommitments].by(uint16) + .typecase(0x20, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) + .typecase(0x21, DATA_WAIT_FOR_FUNDING_LOCKED_Codec) + .typecase(0x22, DATA_NORMAL_Codec) + .typecase(0x23, DATA_SHUTDOWN_Codec) + .typecase(0x24, DATA_NEGOTIATING_Codec) + .typecase(0x25, DATA_CLOSING_Codec) + .typecase(0x26, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec)) + .typecase(0, discriminated[HasCommitments].by(uint16) + .typecase(0x10, LegacyChannelCodecs.DATA_NORMAL_Codec) + .typecase(0x09, LegacyChannelCodecs.DATA_CLOSING_Codec) + .typecase(0x08, LegacyChannelCodecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) + .typecase(0x01, LegacyChannelCodecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec) + .typecase(0x02, LegacyChannelCodecs.DATA_WAIT_FOR_FUNDING_LOCKED_Codec) + .typecase(0x03, LegacyChannelCodecs.DATA_NORMAL_COMPAT_03_Codec) + .typecase(0x04, LegacyChannelCodecs.DATA_SHUTDOWN_Codec) + .typecase(0x05, LegacyChannelCodecs.DATA_NEGOTIATING_Codec) + .typecase(0x06, LegacyChannelCodecs.DATA_CLOSING_COMPAT_06_Codec) + .typecase(0x07, LegacyChannelCodecs.DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala index 24456b9bba..a60c1636c0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala @@ -99,6 +99,10 @@ object CommonCodecs { val varsizebinarydata: Codec[ByteVector] = variableSizeBytes(uint16, bytes) + def mapCodec[K, V](keyCodec: Codec[K], valueCodec: Codec[V]): Codec[Map[K, V]] = listOfN(uint16, keyCodec ~ valueCodec).xmap(_.toMap, _.toList) + + def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) + val listofsignatures: Codec[List[ByteVector64]] = listOfN(uint16, bytes64) val ipv4address: Codec[Inet4Address] = bytes(4).xmap(b => InetAddress.getByAddress(b.toArray).asInstanceOf[Inet4Address], a => ByteVector(a.getAddress)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LegacyChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LegacyChannelCodecs.scala new file mode 100644 index 0000000000..cd1a2b2f65 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LegacyChannelCodecs.scala @@ -0,0 +1,356 @@ +/* + * 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 java.util.UUID + +import akka.actor.ActorRef +import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} +import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut} +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.crypto.ShaChain +import fr.acinq.eclair.payment.relay.Origin +import fr.acinq.eclair.transactions.Transactions._ +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.codecs._ +import scodec.{Attempt, Codec} + +import scala.concurrent.duration._ + +/** + * Those codecs are here solely for backward compatibility reasons. + * + * Created by PM on 02/06/2017. + */ +private[wire] object LegacyChannelCodecs extends Logging { + + val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => new KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath].decodeOnly + + val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( + ("secretkeybytes" | bytes32) :: + ("chaincode" | bytes32) :: + ("depth" | uint16) :: + ("path" | keyPathCodec) :: + ("parent" | int64)).as[ExtendedPrivateKey].decodeOnly + + val channelVersionCodec: Codec[ChannelVersion] = discriminatorWithDefault[ChannelVersion]( + discriminator = discriminated[ChannelVersion].by(byte) + .typecase(0x01, bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion]) + // NB: 0x02 and 0x03 are *reserved* for backward compatibility reasons + , + fallback = provide(ChannelVersion.ZEROES) // README: DO NOT CHANGE THIS !! old channels don't have a channel version + // field and don't support additional features which is why all bits are set to 0. + ) + + def localParamsCodec(channelVersion: ChannelVersion): Codec[LocalParams] = ( + ("nodeId" | publicKey) :: + ("channelPath" | keyPathCodec) :: + ("dustLimit" | satoshi) :: + ("maxHtlcValueInFlightMsat" | uint64) :: + ("channelReserve" | satoshi) :: + ("htlcMinimum" | millisatoshi) :: + ("toSelfDelay" | cltvExpiryDelta) :: + ("maxAcceptedHtlcs" | uint16) :: + ("isFunder" | bool) :: + ("defaultFinalScriptPubKey" | varsizebinarydata) :: + ("localPaymentBasepoint" | optional(provide(channelVersion.hasStaticRemotekey), publicKey)) :: + ("features" | combinedFeaturesCodec)).as[LocalParams].decodeOnly + + val remoteParamsCodec: Codec[RemoteParams] = ( + ("nodeId" | publicKey) :: + ("dustLimit" | satoshi) :: + ("maxHtlcValueInFlightMsat" | uint64) :: + ("channelReserve" | satoshi) :: + ("htlcMinimum" | millisatoshi) :: + ("toSelfDelay" | cltvExpiryDelta) :: + ("maxAcceptedHtlcs" | uint16) :: + ("fundingPubKey" | publicKey) :: + ("revocationBasepoint" | publicKey) :: + ("paymentBasepoint" | publicKey) :: + ("delayedPaymentBasepoint" | publicKey) :: + ("htlcBasepoint" | publicKey) :: + ("features" | combinedFeaturesCodec)).as[RemoteParams].decodeOnly + + val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool) + .typecase(true, updateAddHtlcCodec.as[IncomingHtlc]) + .typecase(false, updateAddHtlcCodec.as[OutgoingHtlc]) + + def setCodec[T](codec: Codec[T]): Codec[Set[T]] = Codec[Set[T]]( + (elems: Set[T]) => listOfN(uint16, codec).encode(elems.toList), + (wire: BitVector) => listOfN(uint16, codec).decode(wire).map(_.map(_.toSet)) + ) + + val commitmentSpecCodec: Codec[CommitmentSpec] = ( + ("htlcs" | setCodec(htlcCodec)) :: + ("feeratePerKw" | uint32) :: + ("toLocal" | millisatoshi) :: + ("toRemote" | millisatoshi)).as[CommitmentSpec].decodeOnly + + val outPointCodec: Codec[OutPoint] = variableSizeBytes(uint16, bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d))) + + val txOutCodec: Codec[TxOut] = variableSizeBytes(uint16, bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d))) + + val txCodec: Codec[Transaction] = variableSizeBytes(uint16, bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) + + val inputInfoCodec: Codec[InputInfo] = ( + ("outPoint" | outPointCodec) :: + ("txOut" | txOutCodec) :: + ("redeemScript" | varsizebinarydata)).as[InputInfo].decodeOnly + + val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16) + .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) + .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32)).as[HtlcSuccessTx]) + .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcTimeoutTx]) + .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcSuccessTx]) + .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcTimeoutTx]) + .typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]) + .typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimDelayedOutputTx]) + .typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]) + .typecase(0x09, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx]) + .typecase(0x10, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClosingTx]) + + // this is a backward compatible codec (we used to store the sig as DER encoded), now we store it as 64-bytes + val sig64OrDERCodec: Codec[ByteVector64] = Codec[ByteVector64]( + (value: ByteVector64) => bytes(64).encode(value), + (wire: BitVector) => bytes.decode(wire).map(_.map { + case bin64 if bin64.size == 64 => ByteVector64(bin64) + case der => Crypto.der2compact(der) + }) + ) + + val htlcTxAndSigsCodec: Codec[HtlcTxAndSigs] = ( + ("txinfo" | txWithInputInfoCodec) :: + ("localSig" | variableSizeBytes(uint16, sig64OrDERCodec)) :: // we store as variable length for historical purposes (we used to store as DER encoded) + ("remoteSig" | variableSizeBytes(uint16, sig64OrDERCodec))).as[HtlcTxAndSigs].decodeOnly + + val publishableTxsCodec: Codec[PublishableTxs] = ( + ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) :: + ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs].decodeOnly + + val localCommitCodec: Codec[LocalCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("publishableTxs" | publishableTxsCodec)).as[LocalCommit].decodeOnly + + val remoteCommitCodec: Codec[RemoteCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("txid" | bytes32) :: + ("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit].decodeOnly + + val updateMessageCodec: Codec[UpdateMessage] = lightningMessageCodec.narrow(f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g) + + val localChangesCodec: Codec[LocalChanges] = ( + ("proposed" | listOfN(uint16, updateMessageCodec)) :: + ("signed" | listOfN(uint16, updateMessageCodec)) :: + ("acked" | listOfN(uint16, updateMessageCodec))).as[LocalChanges].decodeOnly + + val remoteChangesCodec: Codec[RemoteChanges] = ( + ("proposed" | listOfN(uint16, updateMessageCodec)) :: + ("acked" | listOfN(uint16, updateMessageCodec)) :: + ("signed" | listOfN(uint16, updateMessageCodec))).as[RemoteChanges].decodeOnly + + val waitingForRevocationCodec: Codec[WaitingForRevocation] = ( + ("nextRemoteCommit" | remoteCommitCodec) :: + ("sent" | commitSigCodec) :: + ("sentAfterLocalCommitIndex" | uint64overflow) :: + ("reSignAsap" | bool)).as[WaitingForRevocation].decodeOnly + + val localCodec: Codec[Origin.Local] = ( + ("id" | uuid) :: + ("sender" | provide(Option.empty[ActorRef])) + ).as[Origin.Local] + + val relayedCodec: Codec[Origin.Relayed] = ( + ("originChannelId" | bytes32) :: + ("originHtlcId" | int64) :: + ("amountIn" | millisatoshi) :: + ("amountOut" | millisatoshi)).as[Origin.Relayed] + + // this is for backward compatibility to handle legacy payments that didn't have identifiers + val UNKNOWN_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000") + + val trampolineRelayedCodec: Codec[Origin.TrampolineRelayed] = ( + listOfN(uint16, bytes32 ~ int64) :: + ("sender" | provide(Option.empty[ActorRef])) + ).as[Origin.TrampolineRelayed] + + val originCodec: Codec[Origin] = discriminated[Origin].by(uint16) + .typecase(0x03, localCodec) // backward compatible + .typecase(0x01, provide(Origin.Local(UNKNOWN_UUID, None))) + .typecase(0x02, relayedCodec) + .typecase(0x04, trampolineRelayedCodec) + + val originsListCodec: Codec[List[(Long, Origin)]] = listOfN(uint16, int64 ~ originCodec) + + val originsMapCodec: Codec[Map[Long, Origin]] = Codec[Map[Long, Origin]]( + (map: Map[Long, Origin]) => originsListCodec.encode(map.toList), + (wire: BitVector) => originsListCodec.decode(wire).map(_.map(_.toMap)) + ) + + val spentListCodec: Codec[List[(OutPoint, ByteVector32)]] = listOfN(uint16, outPointCodec ~ bytes32) + + val spentMapCodec: Codec[Map[OutPoint, ByteVector32]] = Codec[Map[OutPoint, ByteVector32]]( + (map: Map[OutPoint, ByteVector32]) => spentListCodec.encode(map.toList), + (wire: BitVector) => spentListCodec.decode(wire).map(_.map(_.toMap)) + ) + + val commitmentsCodec: Codec[Commitments] = ( + ("channelVersion" | channelVersionCodec) >>:~ { channelVersion => + ("localParams" | localParamsCodec(channelVersion)) :: + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64overflow) :: + ("remoteNextHtlcId" | uint64overflow) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: + ("channelId" | bytes32) + }).as[Commitments].decodeOnly + + val closingTxProposedCodec: Codec[ClosingTxProposed] = ( + ("unsignedTx" | txCodec) :: + ("localClosingSigned" | closingSignedCodec)).as[ClosingTxProposed].decodeOnly + + val localCommitPublishedCodec: Codec[LocalCommitPublished] = ( + ("commitTx" | txCodec) :: + ("claimMainDelayedOutputTx" | optional(bool, txCodec)) :: + ("htlcSuccessTxs" | listOfN(uint16, txCodec)) :: + ("htlcTimeoutTxs" | listOfN(uint16, txCodec)) :: + ("claimHtlcDelayedTx" | listOfN(uint16, txCodec)) :: + ("spent" | spentMapCodec)).as[LocalCommitPublished].decodeOnly + + val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( + ("commitTx" | txCodec) :: + ("claimMainOutputTx" | optional(bool, txCodec)) :: + ("claimHtlcSuccessTxs" | listOfN(uint16, txCodec)) :: + ("claimHtlcTimeoutTxs" | listOfN(uint16, txCodec)) :: + ("spent" | spentMapCodec)).as[RemoteCommitPublished].decodeOnly + + val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( + ("commitTx" | txCodec) :: + ("claimMainOutputTx" | optional(bool, txCodec)) :: + ("mainPenaltyTx" | optional(bool, txCodec)) :: + ("htlcPenaltyTxs" | listOfN(uint16, txCodec)) :: + ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, txCodec)) :: + ("spent" | spentMapCodec)).as[RevokedCommitPublished].decodeOnly + + // this is a decode-only codec compatible with versions 997acee and below, with placeholders for new fields + val DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | commitmentsCodec) :: + ("fundingTx" | provide[Option[Transaction]](None)) :: + ("waitingSince" | provide(System.currentTimeMillis.milliseconds.toSeconds)) :: + ("deferred" | optional(bool, fundingLockedCodec)) :: + ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly + + val DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | commitmentsCodec) :: + ("fundingTx" | optional(bool, txCodec)) :: + ("waitingSince" | int64) :: + ("deferred" | optional(bool, fundingLockedCodec)) :: + ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly + + val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + ("commitments" | commitmentsCodec) :: + ("shortChannelId" | shortchannelid) :: + ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED].decodeOnly + + // All channel_announcement's written prior to supporting unknown trailing fields had the same fixed size, because + // those are the announcements that *we* created and we always used an empty features field, which was the only + // variable-length field. + val noUnknownFieldsChannelAnnouncementSizeCodec: Codec[Int] = provide(430) + + // We used to ignore unknown trailing fields, and assume that channel_update size was known. This is not true anymore, + // so we need to tell the codec where to stop, otherwise all the remaining part of the data will be decoded as unknown + // fields. Fortunately, we can easily tell what size the channel_update will be. + val noUnknownFieldsChannelUpdateSizeCodec: Codec[Int] = peek( // we need to take a peek at a specific byte to know what size the message will be, and then rollback to read the full message + ignore(8 * (64 + 32 + 8 + 4)) ~> // we skip the first fields: signature + chain_hash + short_channel_id + timestamp + byte // this is the messageFlags byte + ) + .map(messageFlags => if ((messageFlags & 1) != 0) 136 else 128) // depending on the value of option_channel_htlc_max, size will be 128B or 136B + .decodeOnly // this is for compat, we only need to decode + + // this is a decode-only codec compatible with versions 9afb26e and below + val DATA_NORMAL_COMPAT_03_Codec: Codec[DATA_NORMAL] = ( + ("commitments" | commitmentsCodec) :: + ("shortChannelId" | shortchannelid) :: + ("buried" | bool) :: + ("channelAnnouncement" | optional(bool, variableSizeBytes(noUnknownFieldsChannelAnnouncementSizeCodec, channelAnnouncementCodec))) :: + ("channelUpdate" | variableSizeBytes(noUnknownFieldsChannelUpdateSizeCodec, channelUpdateCodec)) :: + ("localShutdown" | optional(bool, shutdownCodec)) :: + ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL].decodeOnly + + val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( + ("commitments" | commitmentsCodec) :: + ("shortChannelId" | shortchannelid) :: + ("buried" | bool) :: + ("channelAnnouncement" | optional(bool, variableSizeBytes(uint16, channelAnnouncementCodec))) :: + ("channelUpdate" | variableSizeBytes(uint16, channelUpdateCodec)) :: + ("localShutdown" | optional(bool, shutdownCodec)) :: + ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL].decodeOnly + + val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( + ("commitments" | commitmentsCodec) :: + ("localShutdown" | shutdownCodec) :: + ("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN].decodeOnly + + val DATA_NEGOTIATING_Codec: Codec[DATA_NEGOTIATING] = ( + ("commitments" | commitmentsCodec) :: + ("localShutdown" | shutdownCodec) :: + ("remoteShutdown" | shutdownCodec) :: + ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) :: + ("bestUnpublishedClosingTx_opt" | optional(bool, txCodec))).as[DATA_NEGOTIATING].decodeOnly + + // this is a decode-only codec compatible with versions 818199e and below, with placeholders for new fields + val DATA_CLOSING_COMPAT_06_Codec: Codec[DATA_CLOSING] = ( + ("commitments" | commitmentsCodec) :: + ("fundingTx" | provide[Option[Transaction]](None)) :: + ("waitingSince" | provide(System.currentTimeMillis.milliseconds.toSeconds)) :: + ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: + ("mutualClosePublished" | listOfN(uint16, txCodec)) :: + ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: + ("remoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: + ("nextRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: + ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING].decodeOnly + + val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = ( + ("commitments" | commitmentsCodec) :: + ("fundingTx" | optional(bool, txCodec)) :: + ("waitingSince" | int64) :: + ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: + ("mutualClosePublished" | listOfN(uint16, txCodec)) :: + ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: + ("remoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: + ("nextRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: + ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING].decodeOnly + + val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + ("commitments" | commitmentsCodec) :: + ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT].decodeOnly + +} diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/db/sqlite/SqliteWalletDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/db/sqlite/SqliteWalletDbSpec.scala index abc8e50eb6..bbfa995996 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/db/sqlite/SqliteWalletDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/db/sqlite/SqliteWalletDbSpec.scala @@ -17,16 +17,14 @@ package fr.acinq.eclair.blockchain.electrum.db.sqlite import fr.acinq.bitcoin.{Block, BlockHeader, OutPoint, Satoshi, Transaction, TxIn, TxOut} -import fr.acinq.eclair.{TestConstants, randomBytes, randomBytes32} import fr.acinq.eclair.blockchain.electrum.ElectrumClient import fr.acinq.eclair.blockchain.electrum.ElectrumClient.GetMerkleResponse import fr.acinq.eclair.blockchain.electrum.ElectrumWallet.PersistentData -import fr.acinq.eclair.blockchain.electrum.db.sqlite.SqliteWalletDb.version -import fr.acinq.eclair.wire.ChannelCodecs.txCodec +import fr.acinq.eclair.{TestConstants, randomBytes, randomBytes32} +import fr.acinq.eclair.wire.CommonCodecs.setCodec import org.scalatest.funsuite.AnyFunSuite import scodec.Codec import scodec.bits.BitVector -import scodec.codecs.{constant, listOfN, provide, uint16} import scala.util.Random @@ -105,9 +103,9 @@ class SqliteWalletDbSpec extends AnyFunSuite { } test("read old persistent data") { - import scodec.codecs._ import SqliteWalletDb._ import fr.acinq.eclair.wire.ChannelCodecs._ + import scodec.codecs._ val oldPersistentDataCodec: Codec[PersistentData] = ( ("version" | constant(BitVector.fromInt(version))) :: @@ -119,7 +117,7 @@ class SqliteWalletDbSpec extends AnyFunSuite { ("history" | historyCodec) :: ("proofs" | proofsCodec) :: ("pendingTransactions" | listOfN(uint16, txCodec)) :: - ("locks" | setCodec(txCodec))).as[PersistentData] + ("locks" | setCodec(txCodec))).as[PersistentData] for (i <- 0 until 50) { val data = randomPersistentData diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala index 1ef1a1abb9..953e51ae84 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala @@ -25,16 +25,16 @@ import fr.acinq.eclair.channel.{ChannelErrorOccurred, LocalError, NetworkFeePaid import fr.acinq.eclair.db.sqlite.SqliteAuditDb import fr.acinq.eclair.db.sqlite.SqliteUtils.{getVersion, using} import fr.acinq.eclair.payment._ -import fr.acinq.eclair.wire.ChannelCodecs import org.scalatest.Tag import org.scalatest.funsuite.AnyFunSuite -import scala.compat.Platform import scala.concurrent.duration._ import scala.util.Random class SqliteAuditDbSpec extends AnyFunSuite { + val ZERO_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000") + test("init sqlite 2 times in a row") { val sqlite = TestConstants.sqliteInMemory() val db1 = new SqliteAuditDb(sqlite) @@ -45,7 +45,7 @@ class SqliteAuditDbSpec extends AnyFunSuite { val sqlite = TestConstants.sqliteInMemory() val db = new SqliteAuditDb(sqlite) - val e1 = PaymentSent(ChannelCodecs.UNKNOWN_UUID, randomBytes32, randomBytes32, 40000 msat, randomKey.publicKey, PaymentSent.PartialPayment(ChannelCodecs.UNKNOWN_UUID, 42000 msat, 1000 msat, randomBytes32, None) :: Nil) + val e1 = PaymentSent(ZERO_UUID, randomBytes32, randomBytes32, 40000 msat, randomKey.publicKey, PaymentSent.PartialPayment(ZERO_UUID, 42000 msat, 1000 msat, randomBytes32, None) :: Nil) val pp2a = PaymentReceived.PartialPayment(42000 msat, randomBytes32) val pp2b = PaymentReceived.PartialPayment(42100 msat, randomBytes32) val e2 = PaymentReceived(randomBytes32, pp2a :: pp2b :: Nil) @@ -203,7 +203,7 @@ class SqliteAuditDbSpec extends AnyFunSuite { } // existing rows in the 'sent' table will use id=00000000-0000-0000-0000-000000000000 as default - assert(migratedDb.listSent(0, (System.currentTimeMillis.milliseconds + 1.minute).toMillis) === Seq(ps.copy(id = ChannelCodecs.UNKNOWN_UUID, parts = Seq(ps.parts.head.copy(id = ChannelCodecs.UNKNOWN_UUID))))) + assert(migratedDb.listSent(0, (System.currentTimeMillis.milliseconds + 1.minute).toMillis) === Seq(ps.copy(id = ZERO_UUID, parts = Seq(ps.parts.head.copy(id = ZERO_UUID))))) val postMigrationDb = new SqliteAuditDb(connection) @@ -216,7 +216,7 @@ class SqliteAuditDbSpec extends AnyFunSuite { postMigrationDb.add(e2) // the old record will have the UNKNOWN_UUID but the new ones will have their actual id - val expected = Seq(ps.copy(id = ChannelCodecs.UNKNOWN_UUID, parts = Seq(ps.parts.head.copy(id = ChannelCodecs.UNKNOWN_UUID))), ps1) + val expected = Seq(ps.copy(id = ZERO_UUID, parts = Seq(ps.parts.head.copy(id = ZERO_UUID))), ps1) assert(postMigrationDb.listSent(0, (System.currentTimeMillis.milliseconds + 1.minute).toMillis) === expected) } 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 8944f4d746..0dfa475123 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 @@ -33,15 +33,15 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo, TransactionWithInputInfo} import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.ChannelCodecs._ -import fr.acinq.eclair.{TestConstants, UInt64, randomBytes, randomBytes32, randomKey, _} +import fr.acinq.eclair.wire.CommonCodecs.setCodec +import fr.acinq.eclair.{TestConstants, UInt64, randomBytes32, randomKey, _} import org.json4s.JsonAST._ import org.json4s.jackson.Serialization import org.json4s.{CustomKeySerializer, CustomSerializer} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ -import scodec.{Attempt, DecodeResult} +import scodec.{Attempt, Codec, DecodeResult} -import scala.compat.Platform import scala.concurrent.duration._ import scala.io.Source import scala.util.Random @@ -68,7 +68,9 @@ class ChannelCodecsSpec extends AnyFunSuite { assert(keyPath === decoded.value) } - test("encode/decode channel version in a backward compatible way") { + test("encode/decode channel version in a backward compatible way (legacy)") { + val codec = LegacyChannelCodecs.channelVersionCodec + // before we had commitment version, public keys were stored first (they started with 0x02 and 0x03) val legacy02 = hex"02a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b" val legacy03 = hex"03d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3" @@ -76,17 +78,36 @@ class ChannelCodecsSpec extends AnyFunSuite { val current03 = hex"010000000103d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3" val current04 = hex"010000000303d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3" - assert(channelVersionCodec.decode(legacy02.bits) === Attempt.successful(DecodeResult(ChannelVersion.ZEROES, legacy02.bits))) - assert(channelVersionCodec.decode(legacy03.bits) === Attempt.successful(DecodeResult(ChannelVersion.ZEROES, legacy03.bits))) - assert(channelVersionCodec.decode(current02.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current02.drop(5).bits))) - assert(channelVersionCodec.decode(current03.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current03.drop(5).bits))) - assert(channelVersionCodec.decode(current04.bits) === Attempt.successful(DecodeResult(ChannelVersion.STATIC_REMOTEKEY, current04.drop(5).bits))) + assert(codec.decode(legacy02.bits) === Attempt.successful(DecodeResult(ChannelVersion.ZEROES, legacy02.bits))) + assert(codec.decode(legacy03.bits) === Attempt.successful(DecodeResult(ChannelVersion.ZEROES, legacy03.bits))) + assert(codec.decode(current02.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current02.drop(5).bits))) + assert(codec.decode(current03.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current03.drop(5).bits))) + assert(codec.decode(current04.bits) === Attempt.successful(DecodeResult(ChannelVersion.STATIC_REMOTEKEY, current04.drop(5).bits))) - assert(channelVersionCodec.encode(ChannelVersion.STANDARD) === Attempt.successful(hex"0100000001".bits)) - assert(channelVersionCodec.encode(ChannelVersion.STATIC_REMOTEKEY) === Attempt.successful(hex"0100000003".bits)) + assert(codec.encode(ChannelVersion.STANDARD) === Attempt.successful(hex"0100000001".bits)) + assert(codec.encode(ChannelVersion.STATIC_REMOTEKEY) === Attempt.successful(hex"0100000003".bits)) + } + + test("encode/decode channel version") { + val current02 = hex"0000000102a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b" + val current03 = hex"0000000103d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3" + val current04 = hex"0000000303d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3" + + assert(channelVersionCodec.decode(current02.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current02.drop(4).bits))) + assert(channelVersionCodec.decode(current03.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current03.drop(4).bits))) + assert(channelVersionCodec.decode(current04.bits) === Attempt.successful(DecodeResult(ChannelVersion.STATIC_REMOTEKEY, current04.drop(4).bits))) + + assert(channelVersionCodec.encode(ChannelVersion.STANDARD) === Attempt.successful(hex"00000001".bits)) + assert(channelVersionCodec.encode(ChannelVersion.STATIC_REMOTEKEY) === Attempt.successful(hex"00000003".bits)) } test("encode/decode localparams") { + def roundtrip(localParams: LocalParams, codec: Codec[LocalParams]) = { + val encoded = codec.encode(localParams).require + val decoded = codec.decode(encoded).require + assert(localParams === decoded.value) + } + val o = LocalParams( nodeId = randomKey.publicKey, fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)), @@ -96,37 +117,21 @@ class ChannelCodecsSpec extends AnyFunSuite { htlcMinimum = MilliSatoshi(Random.nextInt(Int.MaxValue)), toSelfDelay = CltvExpiryDelta(Random.nextInt(Short.MaxValue)), maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), - defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), + defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32).publicKey)), staticPaymentBasepoint = None, isFunder = Random.nextBoolean(), - features = TestConstants.Alice.nodeParams.features) - val encoded = localParamsCodec(ChannelVersion.ZEROES).encode(o).require - val decoded = localParamsCodec(ChannelVersion.ZEROES).decode(encoded).require.value - assert(decoded.staticPaymentBasepoint.isEmpty) - assert(o === decoded) + features = Features(randomBytes(256))) + val o1 = o.copy(staticPaymentBasepoint = Some(PrivateKey(randomBytes32).publicKey)) + roundtrip(o, localParamsCodec(ChannelVersion.ZEROES)) + roundtrip(o1, localParamsCodec(ChannelVersion.STATIC_REMOTEKEY)) + } + + test("backward compatibility local params with global features") { // Backwards-compatibility: decode localparams with global features. val withGlobalFeatures = hex"033b1d42aa7c6a1a3502cbcfe4d2787e9f96237465cd1ba675f50cadf0be17092500010000002a0000000026cb536b00000000568a2768000000004f182e8d0000000040dd1d3d10e3040d00422f82d368b09056d1dcb2d67c4e8cae516abbbc8932f2b7d8f93b3be8e8cc6b64bb164563d567189bad0e07e24e821795aaef2dcbb9e5c1ad579961680202b38de5dd5426c524c7523b1fcdcf8c600d47f4b96a6dd48516b8e0006e81c83464b2800db0f3f63ceeb23a81511d159bae9ad07d10c0d144ba2da6f0cff30e7154eb48c908e9000101000001044500" - val withGlobalFeaturesDecoded = localParamsCodec(ChannelVersion.STANDARD).decode(withGlobalFeatures.bits).require.value + val withGlobalFeaturesDecoded = LegacyChannelCodecs.localParamsCodec(ChannelVersion.STANDARD).decode(withGlobalFeatures.bits).require.value assert(withGlobalFeaturesDecoded.features.toByteVector === hex"0a8a") - - val o1 = LocalParams( - nodeId = randomKey.publicKey, - fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)), - dustLimit = Satoshi(Random.nextInt(Int.MaxValue)), - maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), - channelReserve = Satoshi(Random.nextInt(Int.MaxValue)), - htlcMinimum = MilliSatoshi(Random.nextInt(Int.MaxValue)), - toSelfDelay = CltvExpiryDelta(Random.nextInt(Short.MaxValue)), - maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), - defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32).publicKey)), - staticPaymentBasepoint = Some(PrivateKey(randomBytes32).publicKey), - isFunder = Random.nextBoolean(), - features = Features(randomBytes(256))) - val encoded1 = localParamsCodec(ChannelVersion.STATIC_REMOTEKEY).encode(o1).require - val decoded1 = localParamsCodec(ChannelVersion.STATIC_REMOTEKEY).decode(encoded1).require.value - assert(decoded1.staticPaymentBasepoint.isDefined) - assert(o1 === decoded1) } test("encode/decode remoteparams") { @@ -169,6 +174,9 @@ class ChannelCodecsSpec extends AnyFunSuite { } test("backward compatibility of htlc codec") { + + val codec = LegacyChannelCodecs.htlcCodec + // these encoded HTLC were produced by a previous version of the codec (at commit 8932785e001ddfe32839b3f83468ea19cf00b289) val encodedHtlc1 = hex"89d5618930919bd77c07ea3931e7791010a9637c3d06e091b2dad38f21bbcffa00000000384ffa48000000003dbf35101f8724fbd36096ea5f462be20d20bc2f93ebc2c8a3f00b7a78c5158b5a0e9f6b1666923e800f1d073e2adcfba5904f1b8234af1c43a6e84a862a044d15f33addf8d41b3cfb7f96d815d2248322aeadd0ce7bacbcc44e611f66c35c439423c099e678c077203d5fc415ec37b798c6c74c9eed0806e6cb20f2b613855772c086cee60642b3b9c3919627c877a62a57dcf6ce003bedc53a8b7a0d7aa91b2304ef8b512fe1a9a043e410d7cd3009edffcb5d4c05cfb545aced4afea8bbe26c5ff492602edf9d4eb731541e60e48fd1ae5e33b04a614346fb16e09ccd9bcb8907fe9fc287757ea9280a03462299e950a274c1dc53fbae8c421e67d7de35709eda0f11bcd417c0f215667e8b8ccae1035d0281214af25bf690102b180e5d4b57323d02ab5cee5d3669b4300539d02eff553143f085cd70e5428b7af3262418aa7664d56c3fd29c00a2f88a6a5ee9685f45b6182c45d492b2170b092e4b5891247bcffe82623b86637bec291cca1dc729f5747d842ecdf2fc24eaf95c522cbebe9a841e7cff837e715b689f0b366b92a2850875636962ba42863ab6df12ee938ada6e6ae795f8b4fbe81adea478caa9899fed0d6ccdf7a2173b69b2d3ff1b93c82c08c4da63b426d2f94912109997e8ee5830c5ffe3b60c97438ae1521a2956e73a9a60f16dc13a5e6565904e04bf66ceda3db693fc7a0c6ad4f7dc8cb7f1ef54527c11589b7c35ce5b20e7f23a0ab107a406fa747435ff08096a7533a8ab7f5d3630d5c20c9161101f922c76079497e00e3ca62bce033b2bb065ea1733c50b5a06492d2b46715812003f29a8754b5dc1649082893e7be76550c58d98e81556e4ddf20a244f363bc23e756c95224335d0eeccd3da06a9161c4c72ae3d93afe902a806eadd2167d15c04cf3028fc61d0843bd270fd702a2c5af889ab5bc79a294847914f8dd409a9b990a96397d9046c385ca9810fb7c7b2c61491c67264257a601be7fe8c47a859b56af41caf06be7ea1cdb540719fc3bc2603675b79fd36a6f2911043b78da9f186d2a01f1209d0d91508e8ebecce09fd72823d0c166542f6d059fa8725d9d719a2532289c88f7a291a6bbe01f5b1f83cc2232d716f7dfc6a103fb8637d759aab939aaa278cffe04a64f4142564113080276bee7d3ec62e3f887838e3821f0dd713337972df994160edc29ccb9b9630c41a9ec7c994cbef2501a610e1c3684e697df230fd6f6f10526c9446e8307a1fb7e4988cdf7fc8aa32c8a09206113d8247aaae42e3942c0ffd291d67837d2c88231c85882667582eca1d2566134c4ee1301de8e1637f09467b473ba3e353992488048bd34b26dcc6f6f474751b7ac5bbad468c64eda2aeabfe6a92150a4faab142229d7934c4a24427441850d0deae5db802b02940435f39ceaa85e2d3d2269510881ab26926c3167487aa138d38b9cf650f59f0aa0b84297479271c2009cde61e5c58c26bf8a15aba86869af83941ec14972d93b6ae4a6ecf6584238150a61487d6bd394db40a10d710fd2d065850e52ea6536a74d88947448221c1ce493fecbf2070998e04d5263935488c2935f2d3afed4d0fc7472c03e652f928e6a18f78029043f219f652d992e104529149a978e5c660c0081fe6a179dbe62dcb597f3b4e497c6049b0255f8f306e4b18c97c339c98270abf86a4eb1af93b14d880eeda203bb3ba5b6e3113d0e003f8e55f3d446bd4dcda686b357ca0adf1fe25390767a40ff086a9258d04c19b0474488aaafac321f087d2bd0dc0e056ad9f5b5afa5f3d82bc3f18b33de9044529637fed05879f6bd440f331c06008dd38c2fb822c22fc4201e97f9ef9fc351807c045dece147d19fd01a68604c3cb6b5e0db1b4d1ebe387670021067d94206fbdc9ed33ac1f49d87f961cb5d44f48805e55f8637ca3de4ec9dd969944ed61de45970b7ef96d9f313a41de1cae380e0fe4b56729f275e2a0a87403c90e80" val encodedHtlc2 = hex"09d5618930919bd77c07ea3931e7791010a9637c3d06e091b2dad38f21bbcffa00000000384ffa48000000003dbf35101f8724fbd36096ea5f462be20d20bc2f93ebc2c8a3f00b7a78c5158b5a0e9f6b1666923e800f1d073e2adcfba5904f1b8234af1c43a6e84a862a044d15f33addf8d41b3cfb7f96d815d2248322aeadd0ce7bacbcc44e611f66c35c439423c099e678c077203d5fc415ec37b798c6c74c9eed0806e6cb20f2b613855772c086cee60642b3b9c3919627c877a62a57dcf6ce003bedc53a8b7a0d7aa91b2304ef8b512fe1a9a043e410d7cd3009edffcb5d4c05cfb545aced4afea8bbe26c5ff492602edf9d4eb731541e60e48fd1ae5e33b04a614346fb16e09ccd9bcb8907fe9fc287757ea9280a03462299e950a274c1dc53fbae8c421e67d7de35709eda0f11bcd417c0f215667e8b8ccae1035d0281214af25bf690102b180e5d4b57323d02ab5cee5d3669b4300539d02eff553143f085cd70e5428b7af3262418aa7664d56c3fd29c00a2f88a6a5ee9685f45b6182c45d492b2170b092e4b5891247bcffe82623b86637bec291cca1dc729f5747d842ecdf2fc24eaf95c522cbebe9a841e7cff837e715b689f0b366b92a2850875636962ba42863ab6df12ee938ada6e6ae795f8b4fbe81adea478caa9899fed0d6ccdf7a2173b69b2d3ff1b93c82c08c4da63b426d2f94912109997e8ee5830c5ffe3b60c97438ae1521a2956e73a9a60f16dc13a5e6565904e04bf66ceda3db693fc7a0c6ad4f7dc8cb7f1ef54527c11589b7c35ce5b20e7f23a0ab107a406fa747435ff08096a7533a8ab7f5d3630d5c20c9161101f922c76079497e00e3ca62bce033b2bb065ea1733c50b5a06492d2b46715812003f29a8754b5dc1649082893e7be76550c58d98e81556e4ddf20a244f363bc23e756c95224335d0eeccd3da06a9161c4c72ae3d93afe902a806eadd2167d15c04cf3028fc61d0843bd270fd702a2c5af889ab5bc79a294847914f8dd409a9b990a96397d9046c385ca9810fb7c7b2c61491c67264257a601be7fe8c47a859b56af41caf06be7ea1cdb540719fc3bc2603675b79fd36a6f2911043b78da9f186d2a01f1209d0d91508e8ebecce09fd72823d0c166542f6d059fa8725d9d719a2532289c88f7a291a6bbe01f5b1f83cc2232d716f7dfc6a103fb8637d759aab939aaa278cffe04a64f4142564113080276bee7d3ec62e3f887838e3821f0dd713337972df994160edc29ccb9b9630c41a9ec7c994cbef2501a610e1c3684e697df230fd6f6f10526c9446e8307a1fb7e4988cdf7fc8aa32c8a09206113d8247aaae42e3942c0ffd291d67837d2c88231c85882667582eca1d2566134c4ee1301de8e1637f09467b473ba3e353992488048bd34b26dcc6f6f474751b7ac5bbad468c64eda2aeabfe6a92150a4faab142229d7934c4a24427441850d0deae5db802b02940435f39ceaa85e2d3d2269510881ab26926c3167487aa138d38b9cf650f59f0aa0b84297479271c2009cde61e5c58c26bf8a15aba86869af83941ec14972d93b6ae4a6ecf6584238150a61487d6bd394db40a10d710fd2d065850e52ea6536a74d88947448221c1ce493fecbf2070998e04d5263935488c2935f2d3afed4d0fc7472c03e652f928e6a18f78029043f219f652d992e104529149a978e5c660c0081fe6a179dbe62dcb597f3b4e497c6049b0255f8f306e4b18c97c339c98270abf86a4eb1af93b14d880eeda203bb3ba5b6e3113d0e003f8e55f3d446bd4dcda686b357ca0adf1fe25390767a40ff086a9258d04c19b0474488aaafac321f087d2bd0dc0e056ad9f5b5afa5f3d82bc3f18b33de9044529637fed05879f6bd440f331c06008dd38c2fb822c22fc4201e97f9ef9fc351807c045dece147d19fd01a68604c3cb6b5e0db1b4d1ebe387670021067d94206fbdc9ed33ac1f49d87f961cb5d44f48805e55f8637ca3de4ec9dd969944ed61de45970b7ef96d9f313a41de1cae380e0fe4b56729f275e2a0a87403c90e80" @@ -187,16 +195,16 @@ class ChannelCodecsSpec extends AnyFunSuite { ) val remaining = bin"0000000" // 7 bits remainder because the direction is encoded with 1 bit and we are dealing with bytes - val DecodeResult(h1, r1) = htlcCodec.decode(encodedHtlc1.toBitVector).require - val DecodeResult(h2, r2) = htlcCodec.decode(encodedHtlc2.toBitVector).require + val DecodeResult(h1, r1) = codec.decode(encodedHtlc1.toBitVector).require + val DecodeResult(h2, r2) = codec.decode(encodedHtlc2.toBitVector).require assert(h1 == IncomingHtlc(ref)) assert(h2 == OutgoingHtlc(ref)) assert(r1 == remaining) assert(r2 == remaining) - assert(htlcCodec.encode(h1).require.bytes === encodedHtlc1) - assert(htlcCodec.encode(h2).require.bytes === encodedHtlc2) + assert(codec.encode(h1).require.bytes === encodedHtlc1) + assert(codec.encode(h2).require.bytes === encodedHtlc2) } test("encode/decode commitment spec") { @@ -232,7 +240,7 @@ class ChannelCodecsSpec extends AnyFunSuite { test("encode/decode origin") { val id = UUID.randomUUID() assert(originCodec.decodeValue(originCodec.encode(Local(id, Some(ActorSystem("test").deadLetters))).require).require === Local(id, None)) - assert(originCodec.decodeValue(hex"0001 0123456789abcdef0123456789abcdef".bits).require === Local(UNKNOWN_UUID, None)) + val ZERO_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000") val relayed = Relayed(randomBytes32, 4324, 12000000 msat, 11000000 msat) assert(originCodec.decodeValue(originCodec.encode(relayed).require).require === relayed) val trampolineRelayed = TrampolineRelayed((randomBytes32, 1L) :: (randomBytes32, 1L) :: (randomBytes32, 2L) :: Nil, None) @@ -311,8 +319,8 @@ class ChannelCodecsSpec extends AnyFunSuite { assert(System.currentTimeMillis.milliseconds.toSeconds - data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].waitingSince < 3600) // we just set this timestamp to current time // and re-encode it with the new codec val bin_new = ByteVector(stateDataCodec.encode(data_new).require.toByteVector.toArray) - // data should now be encoded under the new format, with version=0 and type=8 - assert(bin_new.startsWith(hex"000008")) + // data should now be encoded under the new format + assert(bin_new.startsWith(hex"010020")) // now let's decode it again val data_new2 = stateDataCodec.decode(bin_new.toBitVector).require.value // data should match perfectly @@ -326,8 +334,8 @@ class ChannelCodecsSpec extends AnyFunSuite { val u2 = hex"A94A853FCDE515F89259E03D10368B1A600B3BF78F6BD5C968469C0816F45EFF7878714DF26B580D5A304334E46816D5AC37B098EBC46C1CE47E37504D052DD643497FD7F826957108F4A30FD9CEC3AEBA79972084E90EAD01EA33090000000013AB9500006E00005D1149290001009000000000000003E8000003E800000001".bits // check that we decode correct length, and that we just take a peek without actually consuming data - assert(noUnknownFieldsChannelUpdateSizeCodec.decode(u1) == Attempt.successful(DecodeResult(136, u1))) - assert(noUnknownFieldsChannelUpdateSizeCodec.decode(u2) == Attempt.successful(DecodeResult(128, u2))) + assert(LegacyChannelCodecs.noUnknownFieldsChannelUpdateSizeCodec.decode(u1) == Attempt.successful(DecodeResult(136, u1))) + assert(LegacyChannelCodecs.noUnknownFieldsChannelUpdateSizeCodec.decode(u2) == Attempt.successful(DecodeResult(128, u2))) } test("backward compatibility DATA_NORMAL_COMPAT_03_Codec (roundtrip)") { @@ -354,8 +362,8 @@ class ChannelCodecsSpec extends AnyFunSuite { val oldnormal = stateDataCodec.decode(oldbin.bits).require.value // and we encode with new codec val newbin = stateDataCodec.encode(oldnormal).require.bytes - // make sure that encoding used the new 0x10 codec - assert(newbin.startsWith(hex"000010")) + // make sure that encoding used the new codec + assert(newbin.startsWith(hex"010022")) // make sure that roundtrip yields the same data val newnormal = stateDataCodec.decode(newbin.bits).require.value assert(newnormal === oldnormal)