1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 10:39:19 +01:00

Handle unknown fields in network announcements (#1047)

All the data contained in `node_announcement`, `channel_announcement`
and `channel_update` is to be included in the signature, including
unknown trailing fields. We were ignoring them, causing signature
verification to fail when there was unknown fields.

In the case of `channel_update` there is a backward compatibility issue
to handle, because when persisting channel data in state `NORMAL`, we
used to store the `channel_update` followed by other data, and without
prefixing it with size information.

To work around that we use the same trick as before, based on an
additional discriminator codec.
This commit is contained in:
Pierre-Marie Padiou 2019-07-08 17:14:01 +02:00 committed by GitHub
parent 22548733e6
commit e5c5a4cfbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 292 additions and 222 deletions

View File

@ -121,6 +121,7 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
Transactions.sign(tx, currentKey)
}
/**
* Ths method is used to spend revoked transactions, with the corresponding revocation key
*
@ -137,13 +138,8 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
}
override def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector64, ByteVector64) = {
val witness = if (Announcements.isNode1(nodeId, remoteNodeId)) {
Announcements.channelAnnouncementWitnessEncode(chainHash, shortChannelId, nodeId, remoteNodeId, fundingPublicKey(channelKeyPath).publicKey, remoteFundingKey, features)
} else {
Announcements.channelAnnouncementWitnessEncode(chainHash, shortChannelId, remoteNodeId, nodeId, remoteFundingKey, fundingPublicKey(channelKeyPath).publicKey, features)
}
val nodeSig = Crypto.sign(witness, nodeKey.privateKey)
val bitcoinSig = Crypto.sign(witness, fundingPrivateKey(channelKeyPath).privateKey)
(nodeSig, bitcoinSig)
val localNodeSecret = nodeKey.privateKey
val localFundingPrivKey = fundingPrivateKey(channelKeyPath).privateKey
Announcements.signChannelAnnouncement(chainHash, shortChannelId, localNodeSecret, remoteNodeId, localFundingPrivKey, remoteFundingKey, features)
}
}

View File

@ -32,20 +32,20 @@ import scala.concurrent.duration._
*/
object Announcements {
def channelAnnouncementWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, bitcoinKey1: PublicKey, bitcoinKey2: PublicKey, features: ByteVector): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.channelAnnouncementWitnessCodec.encode(features :: chainHash :: shortChannelId :: nodeId1 :: nodeId2 :: bitcoinKey1 :: bitcoinKey2 :: HNil))))
def channelAnnouncementWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, bitcoinKey1: PublicKey, bitcoinKey2: PublicKey, features: ByteVector, unknownFields: ByteVector): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.channelAnnouncementWitnessCodec.encode(features :: chainHash :: shortChannelId :: nodeId1 :: nodeId2 :: bitcoinKey1 :: bitcoinKey2 :: unknownFields :: HNil))))
def nodeAnnouncementWitnessEncode(timestamp: Long, nodeId: PublicKey, rgbColor: Color, alias: String, features: ByteVector, addresses: List[NodeAddress]): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.nodeAnnouncementWitnessCodec.encode(features :: timestamp :: nodeId :: rgbColor :: alias :: addresses :: HNil))))
def nodeAnnouncementWitnessEncode(timestamp: Long, nodeId: PublicKey, rgbColor: Color, alias: String, features: ByteVector, addresses: List[NodeAddress], unknownFields: ByteVector): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.nodeAnnouncementWitnessCodec.encode(features :: timestamp :: nodeId :: rgbColor :: alias :: addresses :: unknownFields :: HNil))))
def channelUpdateWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, timestamp: Long, messageFlags: Byte, channelFlags: Byte, cltvExpiryDelta: Int, htlcMinimumMsat: Long, feeBaseMsat: Long, feeProportionalMillionths: Long, htlcMaximumMsat: Option[Long]): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: messageFlags :: channelFlags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: htlcMaximumMsat :: HNil))))
def channelUpdateWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, timestamp: Long, messageFlags: Byte, channelFlags: Byte, cltvExpiryDelta: Int, htlcMinimumMsat: Long, feeBaseMsat: Long, feeProportionalMillionths: Long, htlcMaximumMsat: Option[Long], unknownFields: ByteVector): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: messageFlags :: channelFlags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: htlcMaximumMsat :: unknownFields :: HNil))))
def signChannelAnnouncement(chainHash: ByteVector32, shortChannelId: ShortChannelId, localNodeSecret: PrivateKey, remoteNodeId: PublicKey, localFundingPrivKey: PrivateKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector64, ByteVector64) = {
val witness = if (isNode1(localNodeSecret.publicKey, remoteNodeId)) {
channelAnnouncementWitnessEncode(chainHash, shortChannelId, localNodeSecret.publicKey, remoteNodeId, localFundingPrivKey.publicKey, remoteFundingKey, features)
channelAnnouncementWitnessEncode(chainHash, shortChannelId, localNodeSecret.publicKey, remoteNodeId, localFundingPrivKey.publicKey, remoteFundingKey, features, unknownFields = ByteVector.empty)
} else {
channelAnnouncementWitnessEncode(chainHash, shortChannelId, remoteNodeId, localNodeSecret.publicKey, remoteFundingKey, localFundingPrivKey.publicKey, features)
channelAnnouncementWitnessEncode(chainHash, shortChannelId, remoteNodeId, localNodeSecret.publicKey, remoteFundingKey, localFundingPrivKey.publicKey, features, unknownFields = ByteVector.empty)
}
val nodeSig = Crypto.sign(witness, localNodeSecret)
val bitcoinSig = Crypto.sign(witness, localFundingPrivKey)
@ -76,7 +76,7 @@ object Announcements {
def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, nodeAddresses: List[NodeAddress], timestamp: Long = Platform.currentTime.milliseconds.toSeconds): NodeAnnouncement = {
require(alias.size <= 32)
val witness = nodeAnnouncementWitnessEncode(timestamp, nodeSecret.publicKey, color, alias, ByteVector.empty, nodeAddresses)
val witness = nodeAnnouncementWitnessEncode(timestamp, nodeSecret.publicKey, color, alias, ByteVector.empty, nodeAddresses, unknownFields = ByteVector.empty)
val sig = Crypto.sign(witness, nodeSecret)
NodeAnnouncement(
signature = sig,
@ -135,7 +135,7 @@ object Announcements {
val channelFlags = makeChannelFlags(isNode1 = isNode1(nodeSecret.publicKey, remoteNodeId), enable = enable)
val htlcMaximumMsatOpt = Some(htlcMaximumMsat)
val witness = channelUpdateWitnessEncode(chainHash, shortChannelId, timestamp, messageFlags, channelFlags, cltvExpiryDelta, htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths, htlcMaximumMsatOpt)
val witness = channelUpdateWitnessEncode(chainHash, shortChannelId, timestamp, messageFlags, channelFlags, cltvExpiryDelta, htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths, htlcMaximumMsatOpt, unknownFields = ByteVector.empty)
val sig = Crypto.sign(witness, nodeSecret)
ChannelUpdate(
signature = sig,
@ -153,7 +153,7 @@ object Announcements {
}
def checkSigs(ann: ChannelAnnouncement): Boolean = {
val witness = channelAnnouncementWitnessEncode(ann.chainHash, ann.shortChannelId, ann.nodeId1, ann.nodeId2, ann.bitcoinKey1, ann.bitcoinKey2, ann.features)
val witness = channelAnnouncementWitnessEncode(ann.chainHash, ann.shortChannelId, ann.nodeId1, ann.nodeId2, ann.bitcoinKey1, ann.bitcoinKey2, ann.features, ann.unknownFields)
verifySignature(witness, ann.nodeSignature1, ann.nodeId1) &&
verifySignature(witness, ann.nodeSignature2, ann.nodeId2) &&
verifySignature(witness, ann.bitcoinSignature1, ann.bitcoinKey1) &&
@ -161,12 +161,12 @@ object Announcements {
}
def checkSig(ann: NodeAnnouncement): Boolean = {
val witness = nodeAnnouncementWitnessEncode(ann.timestamp, ann.nodeId, ann.rgbColor, ann.alias, ann.features, ann.addresses)
val witness = nodeAnnouncementWitnessEncode(ann.timestamp, ann.nodeId, ann.rgbColor, ann.alias, ann.features, ann.addresses, ann.unknownFields)
verifySignature(witness, ann.signature, ann.nodeId)
}
def checkSig(upd: ChannelUpdate, nodeId: PublicKey): Boolean = {
val witness = channelUpdateWitnessEncode(upd.chainHash, upd.shortChannelId, upd.timestamp, upd.messageFlags, upd.channelFlags, upd.cltvExpiryDelta, upd.htlcMinimumMsat, upd.feeBaseMsat, upd.feeProportionalMillionths, upd.htlcMaximumMsat)
val witness = channelUpdateWitnessEncode(upd.chainHash, upd.shortChannelId, upd.timestamp, upd.messageFlags, upd.channelFlags, upd.cltvExpiryDelta, upd.htlcMinimumMsat, upd.feeBaseMsat, upd.feeProportionalMillionths, upd.htlcMaximumMsat, upd.unknownFields)
verifySignature(witness, upd.signature, nodeId)
}
}

View File

@ -127,8 +127,8 @@ object ChannelCodecs extends Logging {
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)
case bin64 if bin64.size == 64 => ByteVector64(bin64)
case der => Crypto.der2compact(der)
})
)
@ -266,12 +266,37 @@ object ChannelCodecs extends Logging {
("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
val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = (
("commitments" | commitmentsCodec) ::
("shortChannelId" | shortchannelid) ::
("buried" | bool) ::
("channelAnnouncement" | optional(bool, channelAnnouncementCodec)) ::
("channelUpdate" | channelUpdateCodec) ::
("channelAnnouncement" | optional(bool, variableSizeBytes(uint16, channelAnnouncementCodec))) ::
("channelUpdate" | variableSizeBytes(uint16, channelUpdateCodec)) ::
("localShutdown" | optional(bool, shutdownCodec)) ::
("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL]
@ -329,11 +354,12 @@ 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_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)

View File

@ -16,11 +16,19 @@
package fr.acinq.eclair.wire
import java.net.{Inet4Address, Inet6Address, InetAddress}
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{ByteVector32, ByteVector64}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.wire
import fr.acinq.eclair.wire.CommonCodecs._
import scodec.bits.ByteVector
import scodec.codecs._
import scodec.{Attempt, Codec, Err}
import scala.util.{Failure, Success, Try}
import scodec.Codec
/**
@ -154,13 +162,15 @@ object LightningMessageCodecs {
("nodeSignature" | bytes64) ::
("bitcoinSignature" | bytes64)).as[AnnouncementSignatures]
val channelAnnouncementWitnessCodec = ("features" | varsizebinarydata) ::
("chainHash" | bytes32) ::
("shortChannelId" | shortchannelid) ::
("nodeId1" | publicKey) ::
("nodeId2" | publicKey) ::
("bitcoinKey1" | publicKey) ::
("bitcoinKey2" | publicKey)
val channelAnnouncementWitnessCodec =
("features" | varsizebinarydata) ::
("chainHash" | bytes32) ::
("shortChannelId" | shortchannelid) ::
("nodeId1" | publicKey) ::
("nodeId2" | publicKey) ::
("bitcoinKey1" | publicKey) ::
("bitcoinKey2" | publicKey) ::
("unknownFields" | bytes)
val channelAnnouncementCodec: Codec[ChannelAnnouncement] = (
("nodeSignature1" | bytes64) ::
@ -169,17 +179,19 @@ object LightningMessageCodecs {
("bitcoinSignature2" | bytes64) ::
channelAnnouncementWitnessCodec).as[ChannelAnnouncement]
val nodeAnnouncementWitnessCodec = ("features" | varsizebinarydata) ::
("timestamp" | uint32) ::
("nodeId" | publicKey) ::
("rgbColor" | rgb) ::
("alias" | zeropaddedstring(32)) ::
("addresses" | listofnodeaddresses)
val nodeAnnouncementWitnessCodec =
("features" | varsizebinarydata) ::
("timestamp" | uint32) ::
("nodeId" | publicKey) ::
("rgbColor" | rgb) ::
("alias" | zeropaddedstring(32)) ::
("addresses" | listofnodeaddresses) ::
("unknownFields" | bytes)
val nodeAnnouncementCodec: Codec[NodeAnnouncement] = (
("signature" | bytes64) ::
nodeAnnouncementWitnessCodec).as[NodeAnnouncement]
val channelUpdateWitnessCodec =
("chainHash" | bytes32) ::
("shortChannelId" | shortchannelid) ::
@ -190,7 +202,8 @@ object LightningMessageCodecs {
("htlcMinimumMsat" | uint64overflow) ::
("feeBaseMsat" | uint32) ::
("feeProportionalMillionths" | uint32) ::
("htlcMaximumMsat" | conditional((messageFlags & 1) != 0, uint64overflow))
("htlcMaximumMsat" | conditional((messageFlags & 1) != 0, uint64overflow)) ::
("unknownFields" | bytes)
})
val channelUpdateCodec: Codec[ChannelUpdate] = (

View File

@ -164,7 +164,8 @@ case class ChannelAnnouncement(nodeSignature1: ByteVector64,
nodeId1: PublicKey,
nodeId2: PublicKey,
bitcoinKey1: PublicKey,
bitcoinKey2: PublicKey) extends RoutingMessage with HasChainHash
bitcoinKey2: PublicKey,
unknownFields: ByteVector = ByteVector.empty) extends RoutingMessage with HasChainHash
case class Color(r: Byte, g: Byte, b: Byte) {
override def toString: String = f"#$r%02x$g%02x$b%02x" // to hexa s"# ${r}%02x ${r & 0xFF}${g & 0xFF}${b & 0xFF}"
@ -206,7 +207,8 @@ case class NodeAnnouncement(signature: ByteVector64,
nodeId: PublicKey,
rgbColor: Color,
alias: String,
addresses: List[NodeAddress]) extends RoutingMessage with HasTimestamp
addresses: List[NodeAddress],
unknownFields: ByteVector = ByteVector.empty) extends RoutingMessage with HasTimestamp
case class ChannelUpdate(signature: ByteVector64,
chainHash: ByteVector32,
@ -218,7 +220,8 @@ case class ChannelUpdate(signature: ByteVector64,
htlcMinimumMsat: Long,
feeBaseMsat: Long,
feeProportionalMillionths: Long,
htlcMaximumMsat: Option[Long]) extends RoutingMessage with HasTimestamp with HasChainHash {
htlcMaximumMsat: Option[Long],
unknownFields: ByteVector = ByteVector.empty) extends RoutingMessage with HasTimestamp with HasChainHash {
require(((messageFlags & 1) != 0) == htlcMaximumMsat.isDefined, "htlcMaximumMsat is not consistent with messageFlags")
}

View File

@ -24,6 +24,7 @@ import akka.actor.{ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import fr.acinq.eclair.channel.ChannelPersisted
import fr.acinq.eclair.db.sqlite.SqliteChannelsDb
import fr.acinq.eclair.wire.ChannelCodecsSpec
import fr.acinq.eclair.{TestConstants, TestUtils, randomBytes32}
import org.scalatest.FunSuiteLike
@ -35,7 +36,7 @@ class BackupHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
val dest = new File(TestUtils.BUILD_DIRECTORY, s"backup-${UUID.randomUUID()}")
wip.deleteOnExit()
dest.deleteOnExit()
val channel = ChannelStateSpec.normal
val channel = ChannelCodecsSpec.normal
db.channels.addOrUpdateChannel(channel)
assert(db.channels.listLocalChannels() == Seq(channel))

View File

@ -1,147 +0,0 @@
/*
* 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.db
import java.util.UUID
import fr.acinq.bitcoin.Crypto.{PrivateKey}
import fr.acinq.bitcoin.{Block, ByteVector32, Crypto, DeterministicWallet, MilliSatoshi, Satoshi, Transaction}
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.{LocalKeyManager, ShaChain, Sphinx}
import fr.acinq.eclair.payment.{Local, Relayed}
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc}
import fr.acinq.eclair.{ShortChannelId, UInt64, randomKey}
import org.scalatest.FunSuite
import scodec.bits._
import scala.io.Source
/**
* Created by fabrice on 07/02/17.
*/
class ChannelStateSpec extends FunSuite {
import ChannelStateSpec._
test("basic serialization test (NORMAL)") {
val data = normal
val bin = ChannelCodecs.DATA_NORMAL_Codec.encode(data).require
val check = ChannelCodecs.DATA_NORMAL_Codec.decodeValue(bin).require
assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec)
assert(data === check)
}
test("nonreg") {
val bin = ByteVector.fromValidHex(Source.fromInputStream(getClass.getResourceAsStream("/normal_data_htlcs.bin")).mkString)
val c = ChannelCodecs.stateDataCodec.decode(bin.toBitVector).require.value
val ref = Seq(
(hex"30440220104aed8d52fe50e2313a9a456607838a0cdac75fdc37afe581c415e7a20da944022034d1ac69c64f34571251be8cc6b50116f26453813267fb9afa3e318a79f4f32401", hex"304502210097fcda40b22916b5d61badedf6126658c2b5927d5002cc2c3e5f88a78ba5f45b02204a74bcf8827d894cab153fc051f39d8e2aeb660162a6a05797f7140587a6133301"),
(hex"30450221009e0e57886b81fd4159f672728891d799203b23c351e93134aba445b288443d3502207b77faa4227126b7d087144c75f4e5c8af9db705b37806dbd2d3f4339666d32201", hex"3045022100aa403fa23e82379a16ba16b446dbdee5a4f879ba690ad3f4f10dc445df2832ba022000a51fdbdb69dcbd5518274303fcece60d719cb6d593e882fdb0190253bbaaab01"),
(hex"3045022100fb44e66fc294d9ece2c33465398221edcfd857208d32a36dedab9906d00b356d022008c5fcfa7b41f8616d57ed009a8614aca636edae1479b6114e03407ba6fceea701", hex"3045022100a9ad65dada5e5500897173bca610135a13008895ce445fbc90d440d5406bd6150220644d75e5ca774ef6b559ffaf1083a9a5da250c3c87666d96daf35b480ef0c65701"),
(hex"3044022009e4f39656dc8712863bffe2acdfa4d2245f65f38a230dd034b226dc2e5fd7ce022049c0108e44c399d1a1b67ab6f60566f491d8682406ac4a03a914f9a21196d6ba01", hex"3044022063a6839c031fd5534d7807a8fff8ca0f9a72d8aa9d78ee104d6ece2b417ac5ce0220193d9b521a44011d31d2bb85be5043119c42a7aee3d9ef68b7388c3c9c3a780501"),
(hex"304402207eaf435948b9e04cb6551f97ee5d85ac879e20d3fae3f5c9a0880ef452d32ac902206e9c5c9098c3e3bef010d3142578823c7fb43b43fe0a0036d481f18a0168b20f01", hex"304402205dda44c9d8aaf37a6f5f6c99713d2a001682f2593a960ccaf5c23059cd20016b02200991b09bccdfc87918852650a4bfa7b4ac9028101362631b5ec376427084138e01"),
(hex"304402200232dbb9d46dabc6569f3f65f4f2a4b7e5acf7be85687b9897141e9784cb9d370220087b2c1dda444d7351976135b56f2f2ca22d8c03d5aa40acbce8c4241daf541501", hex"3045022100eddaa4f767bc70fd672bee983b1644dbff9479def0efc7cca79f0daa1bad370d02204c810238968ae9e86b99d348464e9ac7a06e40225022ae4203ae36fad928c22401"),
(hex"3045022100daa604934db542aa5a9bcbd48eb666fac8acdee92ccd8d42228f52377c51184a022069f855477b27cec39b15fb9e666c09b6c4860c8b80cd1315d2498d97d9cf024601", hex"3044022020e6d43dee03f54574d8245edf2e312d0a492dd2350b7f8df68390b8876de5640220555d46cd545ff0ecc280e6bc82e976ff494bab5f2b128807626753ffb9e5796e01"),
(hex"3044022046c3cf88f9e8639c954c584725482dd6e6403deda3824c37ae95db9bf99d341602206432f76c5ca3d61951155c1b223fd35dd4227f83e1ff9a93437b63515567d23f01", hex"3045022100812a360a6ddc44179f80e5b4252bca74bb5dbe1da25230c9e8afcd388a2fd64702202e45a658123f0263ca1157ef9a9995ede1625d1ecba532957185f1d8044aa1d301"),
(hex"30440220482df018e51b4f684271682bc3c81c481d288d61000a77df2126afe042c3471d02204772720ff1ea323a271259022a0458ae4d228e5f613ade63fca38eb5d443756a01", hex"3044022076a338d225b8954412198ce5936aaa6433da1f51dd9bcbe69d95a1e0960c169802207db267517fc73e358e09f4c89313ae17ed4d5f6d8432faec9ec1e784a2a7da7c01"),
(hex"3045022100916255b5758d66cd46f653f8a5a71b1c857bfae1a7cf85195b5d78476c4138c502200101e3ec9874aa2644691662bf8945a182af1237bb61c459e9dbff495b9097d001", hex"304402201d099a464a7696b22a8e58b65c52e9a519a06a5c49e944155d4e5fbd14d3f5b902203c091c0ec5b840a80be739d29b5fc2c75cb94928e5ea83133f84d226f28cd4b701"),
(hex"3045022100d8eaa436faec6b38f155065893f1212ce43615fbec49b4af72f16119774b5472022033aa303992f4a8cfe1c77e6a0d2baa73baad0305a88da16d26122e536867431101", hex"304402203af7b7ea16cc018fdb414f52cd38ed548dc257cbb06c812c9dc1d60500b21485022072cd74b7e49bfd813e09bae778da903b44b7b0ae22b87af4c34cf8bb77dfdef201"),
(hex"304402204f5dd042bfb449c522012a2d461e5a94c9ea3be629c0ab091b0e1f5569eb119c022021411ff8affabab12cd39f0eaa64f1b08fa72ada6f37d1d46c6bde4483d869fb01", hex"3044022043573edb37be815d1b97b90803f601dfc91c25279ccda606ad6515fee721fe57022030ac2883408a2075a47337443eb539062a8ac6b5453befb2b9863d697e35dd8201"),
(hex"3044022030ff3d4d42ef1c3d742164a30ff7b021215e881d9277a52a1720514a4473289502204b090f6b412e8caacb5bcbf295babb075d9d5490e3f7678c289206780f6f0bc901", hex"304502210093fd7dfa3ef6cdf5b94cfadf83022be98062d53cd7097a73947453b210a481eb0220622e63a21b787ea7bb55f01ab6fe503fcb8ef4cb65adce7a264ae014403646fe01")
)
val sigs = c.commitments
.localCommit
.publishableTxs
.htlcTxsAndSigs
.map(data => (Scripts.der(data.localSig), Scripts.der(data.remoteSig)))
assert(ref === sigs)
}
}
object ChannelStateSpec {
val keyManager = new LocalKeyManager(ByteVector32(ByteVector.fill(32)(1)), Block.RegtestGenesisBlock.hash)
val localParams = LocalParams(
keyManager.nodeId,
channelKeyPath = DeterministicWallet.KeyPath(Seq(42L)),
dustLimitSatoshis = Satoshi(546).toLong,
maxHtlcValueInFlightMsat = UInt64(50000000),
channelReserveSatoshis = 10000,
htlcMinimumMsat = 10000,
toSelfDelay = 144,
maxAcceptedHtlcs = 50,
defaultFinalScriptPubKey = ByteVector.empty,
isFunder = true,
globalFeatures = hex"dead",
localFeatures = hex"beef")
val remoteParams = RemoteParams(
nodeId = randomKey.publicKey,
dustLimitSatoshis = Satoshi(546).toLong,
maxHtlcValueInFlightMsat = UInt64(5000000),
channelReserveSatoshis = 10000,
htlcMinimumMsat = 5000,
toSelfDelay = 144,
maxAcceptedHtlcs = 50,
fundingPubKey = PrivateKey(ByteVector32(ByteVector.fill(32)(1)) :+ 1.toByte).publicKey,
revocationBasepoint = PrivateKey(ByteVector.fill(32)(2)).publicKey,
paymentBasepoint = PrivateKey(ByteVector.fill(32)(3)).publicKey,
delayedPaymentBasepoint = PrivateKey(ByteVector.fill(32)(4)).publicKey,
htlcBasepoint = PrivateKey(ByteVector.fill(32)(6)).publicKey,
globalFeatures = hex"dead",
localFeatures = hex"beef")
val paymentPreimages = Seq(
ByteVector32(hex"0000000000000000000000000000000000000000000000000000000000000000"),
ByteVector32(hex"0101010101010101010101010101010101010101010101010101010101010101"),
ByteVector32(hex"0202020202020202020202020202020202020202020202020202020202020202"),
ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"),
ByteVector32(hex"0404040404040404040404040404040404040404040404040404040404040404")
)
val htlcs = Seq(
DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(1000000).amount, Crypto.sha256(paymentPreimages(0)), 500, ByteVector.fill(Sphinx.PacketLength)(0))),
DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(2000000).amount, Crypto.sha256(paymentPreimages(1)), 501, ByteVector.fill(Sphinx.PacketLength)(0))),
DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 30, MilliSatoshi(2000000).amount, Crypto.sha256(paymentPreimages(2)), 502, ByteVector.fill(Sphinx.PacketLength)(0))),
DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 31, MilliSatoshi(3000000).amount, Crypto.sha256(paymentPreimages(3)), 503, ByteVector.fill(Sphinx.PacketLength)(0))),
DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(4000000).amount, Crypto.sha256(paymentPreimages(4)), 504, ByteVector.fill(Sphinx.PacketLength)(0)))
)
val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000")
val fundingAmount = fundingTx.txOut(0).amount
val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey)
val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000000, 70000000), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil))
val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000, 700000), ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"), PrivateKey(ByteVector.fill(32)(4)).publicKey)
val commitments = Commitments(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil),
localNextHtlcId = 32L,
remoteNextHtlcId = 4L,
originChannels = Map(42L -> Local(UUID.randomUUID, None), 15000L -> Relayed(ByteVector32(ByteVector.fill(32)(42)), 43, 11000000L, 10000000L)),
remoteNextCommitInfo = Right(randomKey.publicKey),
commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = ByteVector32.Zeroes)
val channelUpdate = Announcements.makeChannelUpdate(ByteVector32(ByteVector.fill(32)(1)), randomKey, randomKey.publicKey, ShortChannelId(142553), 42, 15, 575, 53, Channel.MAX_FUNDING_SATOSHIS * 1000L)
val normal = DATA_NORMAL(commitments, ShortChannelId(42), true, None, channelUpdate, None, None)
}

View File

@ -24,9 +24,10 @@ import fr.acinq.eclair.channel.{AvailableBalanceChanged, ChannelErrorOccured, Ne
import fr.acinq.eclair.db.sqlite.SqliteAuditDb
import fr.acinq.eclair.db.sqlite.SqliteUtils.{getVersion, using}
import fr.acinq.eclair.payment.{PaymentReceived, PaymentRelayed, PaymentSent}
import fr.acinq.eclair.wire.ChannelCodecs
import fr.acinq.eclair.wire.{ChannelCodecs, ChannelCodecsSpec}
import fr.acinq.eclair._
import org.scalatest.FunSuite
import concurrent.duration._
import scala.compat.Platform
@ -49,7 +50,7 @@ class SqliteAuditDbSpec extends FunSuite {
val e4 = NetworkFeePaid(null, randomKey.publicKey, randomBytes32, Transaction(0, Seq.empty, Seq.empty, 0), Satoshi(42), "mutual")
val e5 = PaymentSent(ChannelCodecs.UNKNOWN_UUID, MilliSatoshi(42000), MilliSatoshi(1000), randomBytes32, randomBytes32, randomBytes32, timestamp = 0)
val e6 = PaymentSent(ChannelCodecs.UNKNOWN_UUID, MilliSatoshi(42000), MilliSatoshi(1000), randomBytes32, randomBytes32, randomBytes32, timestamp = (Platform.currentTime.milliseconds + 10.minutes).toMillis)
val e7 = AvailableBalanceChanged(null, randomBytes32, ShortChannelId(500000, 42, 1), 456123000, ChannelStateSpec.commitments)
val e7 = AvailableBalanceChanged(null, randomBytes32, ShortChannelId(500000, 42, 1), 456123000, ChannelCodecsSpec.commitments)
val e8 = ChannelLifecycleEvent(randomBytes32, randomKey.publicKey, 456123000, true, false, "mutual")
val e9 = ChannelErrorOccured(null, randomBytes32, randomKey.publicKey, null, LocalError(new RuntimeException("oops")), true)
val e10 = ChannelErrorOccured(null, randomBytes32, randomKey.publicKey, null, RemoteError(wire.Error(randomBytes32, "remote oops")), true)

View File

@ -21,6 +21,7 @@ import fr.acinq.eclair.TestConstants
import fr.acinq.eclair.db.sqlite.SqliteUtils.{getVersion, using}
import fr.acinq.eclair.db.sqlite.{SqliteChannelsDb, SqlitePendingRelayDb}
import fr.acinq.eclair.wire.ChannelCodecs.stateDataCodec
import fr.acinq.eclair.wire.ChannelCodecsSpec
import org.scalatest.FunSuite
import org.sqlite.SQLiteException
import scodec.bits.ByteVector
@ -39,7 +40,7 @@ class SqliteChannelsDbSpec extends FunSuite {
val db = new SqliteChannelsDb(sqlite)
new SqlitePendingRelayDb(sqlite) // needed by db.removeChannel
val channel = ChannelStateSpec.normal
val channel = ChannelCodecsSpec.normal
val commitNumber = 42
val paymentHash1 = ByteVector32.Zeroes
@ -78,7 +79,7 @@ class SqliteChannelsDbSpec extends FunSuite {
}
// insert 1 row
val channel = ChannelStateSpec.normal
val channel = ChannelCodecsSpec.normal
val data = stateDataCodec.encode(channel).require.toByteArray
using(sqlite.prepareStatement("INSERT INTO local_channels VALUES (?, ?)")) { statement =>
statement.setBytes(1, channel.channelId.toArray)

View File

@ -19,9 +19,8 @@ package fr.acinq.eclair.io
import akka.actor.{ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.db.ChannelStateSpec
import fr.acinq.eclair.randomBytes32
import fr.acinq.eclair.wire.{TemporaryNodeFailure, UpdateAddHtlc}
import fr.acinq.eclair.wire.{ChannelCodecsSpec, TemporaryNodeFailure, UpdateAddHtlc}
import org.scalatest.FunSuiteLike
import scodec.bits.ByteVector
@ -35,7 +34,7 @@ class HtlcReaperSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
test("init and cleanup") {
val data = ChannelStateSpec.normal
val data = ChannelCodecsSpec.normal
// assuming that data has incoming htlcs 0 and 1, we don't care about the amount/payment_hash/onion fields
val add0 = UpdateAddHtlc(data.channelId, 0, 20000, randomBytes32, 100, ByteVector.empty)

View File

@ -16,12 +16,10 @@
package fr.acinq.eclair.io
import java.net.{Inet4Address, InetSocketAddress}
import akka.actor.{ActorRef, PoisonPill, Terminated}
import java.net.{Inet4Address, InetAddress, InetSocketAddress, ServerSocket}
import akka.actor.{ActorRef, ActorSystem, PoisonPill}
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
import akka.actor.{ActorRef, PoisonPill}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.TestConstants._
@ -29,13 +27,13 @@ import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.EclairWallet
import fr.acinq.eclair.channel.HasCommitments
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.db.ChannelStateSpec
import fr.acinq.eclair.io.Peer._
import fr.acinq.eclair.router.RoutingSyncSpec.makeFakeRoutingInfo
import fr.acinq.eclair.router.{ChannelRangeQueries, ChannelRangeQueriesSpec, Rebroadcast}
import fr.acinq.eclair.wire.{Color, Error, IPv4, NodeAddress, NodeAnnouncement, Ping, Pong}
import fr.acinq.eclair.wire.{ChannelCodecsSpec, Color, Error, IPv4, NodeAddress, NodeAnnouncement, Ping, Pong}
import org.scalatest.{Outcome, Tag}
import scodec.bits.ByteVector
import scala.concurrent.duration._
class PeerSpec extends TestkitBaseClass {
@ -90,7 +88,7 @@ class PeerSpec extends TestkitBaseClass {
test("restore existing channels") { f =>
import f._
val probe = TestProbe()
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, channels = Set(ChannelStateSpec.normal))
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, channels = Set(ChannelCodecsSpec.normal))
probe.send(peer, Peer.GetPeerInfo)
probe.expectMsg(PeerInfo(remoteNodeId, "CONNECTED", Some(fakeIPAddress.socketAddress), 1))
}
@ -138,7 +136,7 @@ class PeerSpec extends TestkitBaseClass {
test("ignore reconnect (no known address)") { f =>
import f._
val probe = TestProbe()
probe.send(peer, Peer.Init(None, Set(ChannelStateSpec.normal)))
probe.send(peer, Peer.Init(None, Set(ChannelCodecsSpec.normal)))
probe.send(peer, Peer.Reconnect)
probe.expectNoMsg()
}
@ -157,13 +155,13 @@ class PeerSpec extends TestkitBaseClass {
// we create a dummy tcp server and update bob's announcement to point to it
val mockServer = new ServerSocket(0, 1, InetAddress.getLocalHost) // port will be assigned automatically
val mockAddress = NodeAddress.fromParts(mockServer.getInetAddress.getHostAddress, mockServer.getLocalPort).get
val mockAddress = NodeAddress.fromParts(mockServer.getInetAddress.getHostAddress, mockServer.getLocalPort).get
val bobAnnouncement = NodeAnnouncement(randomBytes64, ByteVector.empty, 1, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", mockAddress :: Nil)
peer.underlyingActor.nodeParams.db.network.addNode(bobAnnouncement)
val probe = TestProbe()
awaitCond(peer.stateName == INSTANTIATING)
probe.send(peer, Peer.Init(None, Set(ChannelStateSpec.normal)))
probe.send(peer, Peer.Init(None, Set(ChannelCodecsSpec.normal)))
awaitCond(peer.stateName == DISCONNECTED)
// we have auto-reconnect=false so we need to manually tell the peer to reconnect
@ -179,7 +177,7 @@ class PeerSpec extends TestkitBaseClass {
import f._
val probe = TestProbe()
val previouslyKnownAddress = new InetSocketAddress("1.2.3.4", 9735)
probe.send(peer, Peer.Init(Some(previouslyKnownAddress), Set(ChannelStateSpec.normal)))
probe.send(peer, Peer.Init(Some(previouslyKnownAddress), Set(ChannelCodecsSpec.normal)))
probe.send(peer, Peer.Reconnect)
val interval = (peer.underlyingActor.nodeParams.maxReconnectInterval.toSeconds / 2) to peer.underlyingActor.nodeParams.maxReconnectInterval.toSeconds
awaitCond(interval contains peer.stateData.asInstanceOf[DisconnectedData].nextReconnectionDelay.toSeconds)
@ -188,7 +186,7 @@ class PeerSpec extends TestkitBaseClass {
test("reconnect with increasing delays") { f =>
import f._
val probe = TestProbe()
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, channels = Set(ChannelStateSpec.normal))
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, channels = Set(ChannelCodecsSpec.normal))
probe.send(transport.ref, PoisonPill)
awaitCond(peer.stateName === DISCONNECTED)
assert(peer.stateData.asInstanceOf[DisconnectedData].nextReconnectionDelay === (10 seconds))
@ -216,7 +214,7 @@ class PeerSpec extends TestkitBaseClass {
import f._
val probe = TestProbe()
probe.send(peer, Peer.Init(None, Set(ChannelStateSpec.normal)))
probe.send(peer, Peer.Init(None, Set(ChannelCodecsSpec.normal)))
authenticator.send(peer, Authenticator.Authenticated(connection.ref, transport.ref, remoteNodeId, fakeIPAddress.socketAddress, outgoing = true, None))
probe.send(peer, Peer.GetPeerInfo)
@ -230,7 +228,7 @@ class PeerSpec extends TestkitBaseClass {
import f._
val probe = TestProbe()
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, channels = Set(ChannelStateSpec.normal))
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, channels = Set(ChannelCodecsSpec.normal))
probe.send(peer, Peer.GetPeerInfo)
assert(probe.expectMsgType[Peer.PeerInfo].state == "CONNECTED")

View File

@ -2,14 +2,14 @@ package fr.acinq.eclair.io
import java.io.File
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.actor.{ActorRef, ActorSystem}
import akka.testkit.{TestKit, TestProbe}
import fr.acinq.bitcoin.ByteVector64
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.TestConstants._
import fr.acinq.eclair.blockchain.TestWallet
import fr.acinq.eclair.db._
import fr.acinq.eclair.wire.{Color, NodeAddress, NodeAnnouncement}
import fr.acinq.eclair.wire.{ChannelCodecsSpec, Color, NodeAddress, NodeAnnouncement}
import org.mockito.scalatest.IdiomaticMockito
import org.scalatest.FunSuiteLike
import scodec.bits._
@ -31,7 +31,7 @@ class SwitchboardSpec extends TestKit(ActorSystem("test")) with FunSuiteLike wit
}
)
val remoteNodeId = ChannelStateSpec.normal.commitments.remoteParams.nodeId
val remoteNodeId = ChannelCodecsSpec.normal.commitments.remoteParams.nodeId
val authenticator = TestProbe()
val watcher = TestProbe()
val router = TestProbe()
@ -45,7 +45,7 @@ class SwitchboardSpec extends TestKit(ActorSystem("test")) with FunSuiteLike wit
)
// add a channel to the db
nodeParams.db.channels.addOrUpdateChannel(ChannelStateSpec.normal)
nodeParams.db.channels.addOrUpdateChannel(ChannelCodecsSpec.normal)
val switchboard = system.actorOf(Switchboard.props(nodeParams, authenticator.ref, watcher.ref, router.ref, relayer.ref, wallet))

File diff suppressed because one or more lines are too long