mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 06:35:11 +01:00
Use length-delimited byte-aligned codecs (#1442)
Legacy codecs are isolated in a separate file, with a visibility restricted to "package" in order to reduce the risk of using those codecs. Also codecs are restricted to `decodeOnly` for the same reason.
This commit is contained in:
parent
d5ec6a56e8
commit
6c81f95644
7 changed files with 502 additions and 207 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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))
|
||||
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue