diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 86cc13e091..24c402d04c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair import com.typesafe.config.{Config, ConfigFactory, ConfigValueType} +import fr.acinq.bitcoin.BlockHeader import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi} import fr.acinq.eclair.Setup.Seeds @@ -57,6 +58,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager, onChainKeyManager_opt: Option[OnChainKeyManager], instanceId: UUID, // a unique instance ID regenerated after each restart private val blockHeight: AtomicLong, + private val blockHeader: AtomicReference[BlockHeader], private val feerates: AtomicReference[FeeratesPerKw], alias: String, color: Color, @@ -100,6 +102,8 @@ case class NodeParams(nodeKeyManager: NodeKeyManager, def currentBlockHeight: BlockHeight = BlockHeight(blockHeight.get) + def currentBlockHeader: BlockHeader = blockHeader.get() + def currentFeerates: FeeratesPerKw = feerates.get() /** Only to be used in tests. */ @@ -215,7 +219,7 @@ object NodeParams extends Logging { def makeNodeParams(config: Config, instanceId: UUID, nodeKeyManager: NodeKeyManager, channelKeyManager: ChannelKeyManager, onChainKeyManager_opt: Option[OnChainKeyManager], - torAddress_opt: Option[NodeAddress], database: Databases, blockHeight: AtomicLong, feerates: AtomicReference[FeeratesPerKw], + torAddress_opt: Option[NodeAddress], database: Databases, blockHeight: AtomicLong, blockHeader: AtomicReference[BlockHeader], feerates: AtomicReference[FeeratesPerKw], pluginParams: Seq[PluginParams] = Nil): NodeParams = { // check configuration for keys that have been renamed val deprecatedKeyPaths = Map( @@ -482,6 +486,7 @@ object NodeParams extends Logging { onChainKeyManager_opt = onChainKeyManager_opt, instanceId = instanceId, blockHeight = blockHeight, + blockHeader = blockHeader, feerates = feerates, alias = nodeAlias, color = Color(color(0), color(1), color(2)), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index b6ca12c5e5..38899845b8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -22,6 +22,7 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorRefOps, ClassicActorSystem import akka.actor.{ActorRef, ActorSystem, Props, SupervisorStrategy, typed} import akka.pattern.after import akka.util.Timeout +import fr.acinq.bitcoin.BlockHeader import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, BlockId, ByteVector32, Satoshi, Script, addressToPublicKeyScript} import fr.acinq.eclair.Setup.Seeds @@ -110,6 +111,11 @@ class Setup(val datadir: File, */ val blockHeight = new AtomicLong(0) + /** + * This holds the latest block header we've received. + */ + val blockHeader = new AtomicReference[BlockHeader](null) + /** * This holds the current feerates, in satoshi-per-kilobytes. * The value is read by all actors, hence it needs to be thread-safe. @@ -134,7 +140,7 @@ class Setup(val datadir: File, case "password" => BitcoinJsonRPCAuthMethod.UserPassword(config.getString("bitcoind.rpcuser"), config.getString("bitcoind.rpcpassword")) } - case class BitcoinStatus(version: Int, chainHash: BlockHash, initialBlockDownload: Boolean, verificationProgress: Double, blockCount: Long, headerCount: Long, unspentAddresses: List[String]) + case class BitcoinStatus(version: Int, chainHash: BlockHash, initialBlockDownload: Boolean, verificationProgress: Double, blockCount: Long, headerCount: Long, latestHeader: BlockHeader, unspentAddresses: List[String]) def getBitcoinStatus(bitcoinClient: BasicBitcoinJsonRPCClient): Future[BitcoinStatus] = for { json <- bitcoinClient.invoke("getblockchaininfo").recover { case e => throw BitcoinRPCConnectionException(e) } @@ -150,6 +156,8 @@ class Setup(val datadir: File, // NB: bitcoind confusingly returns the blockId instead of the blockHash. chainHash <- bitcoinClient.invoke("getblockhash", 0).map(_.extract[String]).map(s => BlockId(ByteVector32.fromValidHex(s))).map(BlockHash(_)) bitcoinVersion <- bitcoinClient.invoke("getnetworkinfo").map(json => json \ "version").map(_.extract[Int]) + blockHash <- bitcoinClient.invoke("getblockhash", blocks).map(_.extract[String]) + latestHeader <- bitcoinClient.invoke("getblockheader", blockHash, /* verbose */ false).map(_.extract[String]).map(BlockHeader.read) unspentAddresses <- bitcoinClient.invoke("listunspent").recover { _ => if (wallet.isEmpty && wallets.length > 1) throw BitcoinDefaultWalletException(wallets) else throw BitcoinWalletNotLoadedException(wallet.getOrElse(""), wallets) } .collect { case JArray(values) => values @@ -162,7 +170,7 @@ class Setup(val datadir: File, case "signet" => bitcoinClient.invoke("getrawtransaction", "ff1027486b628b2d160859205a3401fb2ee379b43527153b0b50a92c17ee7955") // coinbase of #5000 case "regtest" => Future.successful(()) } - } yield BitcoinStatus(bitcoinVersion, chainHash, ibd, progress, blocks, headers, unspentAddresses) + } yield BitcoinStatus(bitcoinVersion, chainHash, ibd, progress, blocks, headers, latestHeader, unspentAddresses) def pollBitcoinStatus(bitcoinClient: BasicBitcoinJsonRPCClient): Future[BitcoinStatus] = { getBitcoinStatus(bitcoinClient).transformWith { @@ -197,8 +205,9 @@ class Setup(val datadir: File, assert(bitcoinStatus.verificationProgress > 0.999, s"bitcoind should be synchronized (progress=${bitcoinStatus.verificationProgress})") assert(bitcoinStatus.headerCount - bitcoinStatus.blockCount <= 1, s"bitcoind should be synchronized (headers=${bitcoinStatus.headerCount} blocks=${bitcoinStatus.blockCount})") } - logger.info(s"current blockchain height=${bitcoinStatus.blockCount}") + logger.info(s"current blockchain height=${bitcoinStatus.blockCount} header=${ByteVector(BlockHeader.write(bitcoinStatus.latestHeader)).toHex}") blockHeight.set(bitcoinStatus.blockCount) + blockHeader.set(bitcoinStatus.latestHeader) (bitcoinClient, bitcoinStatus.chainHash) } @@ -206,7 +215,7 @@ class Setup(val datadir: File, logger.info(s"connecting to database with instanceId=$instanceId") val databases = Databases.init(config.getConfig("db"), instanceId, chaindir, db) - val nodeParams = NodeParams.makeNodeParams(config, instanceId, nodeKeyManager, channelKeyManager, onChainKeyManager_opt, initTor(), databases, blockHeight, feeratesPerKw, pluginParams) + val nodeParams = NodeParams.makeNodeParams(config, instanceId, nodeKeyManager, channelKeyManager, onChainKeyManager_opt, initTor(), databases, blockHeight, blockHeader, feeratesPerKw, pluginParams) logger.info(s"nodeid=${nodeParams.nodeId} alias=${nodeParams.alias}") assert(bitcoinChainHash == nodeParams.chainHash, s"chainHash mismatch (conf=${nodeParams.chainHash} != bitcoind=$bitcoinChainHash)") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala index f65769062a..b91db1a05a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala @@ -16,6 +16,7 @@ package fr.acinq.eclair.blockchain +import fr.acinq.bitcoin.BlockHeader import fr.acinq.bitcoin.scalacompat.{BlockId, Transaction} import fr.acinq.eclair.BlockHeight import fr.acinq.eclair.blockchain.fee.FeeratesPerKw @@ -30,6 +31,6 @@ case class NewBlock(blockId: BlockId) extends BlockchainEvent case class NewTransaction(tx: Transaction) extends BlockchainEvent -case class CurrentBlockHeight(blockHeight: BlockHeight) extends BlockchainEvent +case class CurrentBlockHeight(blockHeight: BlockHeight, blockHeader_opt: Option[BlockHeader]) extends BlockchainEvent case class CurrentFeerates(feeratesPerKw: FeeratesPerKw) extends BlockchainEvent diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala index 2ec66eaf1d..b3791be447 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.blockchain.bitcoind import akka.actor.typed.eventstream.EventStream import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} +import fr.acinq.bitcoin.BlockHeader import fr.acinq.bitcoin.scalacompat._ import fr.acinq.eclair.blockchain.Monitoring.Metrics import fr.acinq.eclair.blockchain._ @@ -60,7 +61,7 @@ object ZmqWatcher { private case object TickBlockTimeout extends Command private case class GetBlockCountFailed(t: Throwable) extends Command private case class CheckBlockHeight(current: BlockHeight) extends Command - private case class PublishBlockHeight(current: BlockHeight) extends Command + private case class PublishBlockHeight(current: BlockHeight, header_opt: Option[BlockHeader]) extends Command private case class ProcessNewBlock(blockId: BlockId) extends Command private case class ProcessNewTransaction(tx: Transaction) extends Command @@ -275,9 +276,12 @@ private class ZmqWatcher(nodeParams: NodeParams, blockHeight: AtomicLong, client Behaviors.same case TickNewBlock => - context.pipeToSelf(client.getBlockHeight()) { + context.pipeToSelf(for { + height <- client.getBlockHeight() + header_opt <- client.getBlockHeader(height) + } yield (height, header_opt)) { case Failure(t) => GetBlockCountFailed(t) - case Success(currentHeight) => PublishBlockHeight(currentHeight) + case Success((currentHeight, header_opt)) => PublishBlockHeight(currentHeight, header_opt) } // TODO: beware of the herd effect KamonExt.timeFuture(Metrics.NewBlockCheckConfirmedDuration.withoutTags()) { @@ -288,10 +292,10 @@ private class ZmqWatcher(nodeParams: NodeParams, blockHeight: AtomicLong, client } Behaviors.same - case PublishBlockHeight(currentHeight) => + case PublishBlockHeight(currentHeight, header_opt) => log.debug("setting blockHeight={}", currentHeight) blockHeight.set(currentHeight.toLong) - context.system.eventStream ! EventStream.Publish(CurrentBlockHeight(currentHeight)) + context.system.eventStream ! EventStream.Publish(CurrentBlockHeight(currentHeight, header_opt)) Behaviors.same case TriggerEvent(replyTo, watch, event) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala index f6a8daa965..0dac490481 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala @@ -19,14 +19,13 @@ package fr.acinq.eclair.blockchain.bitcoind.rpc import fr.acinq.bitcoin.psbt.Psbt import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat._ -import fr.acinq.bitcoin.{Bech32, Block, SigHash} +import fr.acinq.bitcoin.{Bech32, Block, BlockHeader, SigHash} import fr.acinq.eclair.ShortChannelId.coordinates import fr.acinq.eclair.blockchain.OnChainWallet import fr.acinq.eclair.blockchain.OnChainWallet.{FundTransactionResponse, MakeFundingTxResponse, OnChainBalance, ProcessPsbtResponse} import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{GetTxWithMetaResponse, UtxoStatus, ValidateResult} import fr.acinq.eclair.blockchain.fee.{FeeratePerKB, FeeratePerKw} import fr.acinq.eclair.crypto.keymanager.OnChainKeyManager -import fr.acinq.eclair.json.SatoshiSerializer import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol.ChannelAnnouncement import fr.acinq.eclair.{BlockHeight, TimestampSecond, TxCoordinates} @@ -655,6 +654,13 @@ class BitcoinCoreClient(val rpcClient: BitcoinJsonRPCClient, val onChainKeyManag case JInt(count) => BlockHeight(count.toLong) } + def getBlockHeader(height: BlockHeight)(implicit ec: ExecutionContext): Future[Option[BlockHeader]] = + rpcClient.invoke("getblockhash", height.toLong) + .collect { case JString(blockHash) => blockHash } + .flatMap(blockHash => rpcClient.invoke("getblockheader", blockHash, /* verbose */ false)) + .collect { case JString(blockHeader) => Some(BlockHeader.read(blockHeader)) } + .recover { case _ => None } + def validate(c: ChannelAnnouncement)(implicit ec: ExecutionContext): Future[ValidateResult] = { val TxCoordinates(blockHeight, txIndex, outputIndex) = coordinates(c.shortChannelId) for { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala index 8b333d41ff..6929db6e3a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala @@ -18,7 +18,8 @@ package fr.acinq.eclair.io import akka.actor.{ActorRef, FSM, OneForOneStrategy, PoisonPill, Props, Stash, SupervisorStrategy, Terminated} import akka.event.Logging.MDC -import fr.acinq.bitcoin.scalacompat.{BlockHash, ByteVector32} +import fr.acinq.bitcoin.BlockHeader +import fr.acinq.bitcoin.scalacompat.BlockHash import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.Logs.LogCategory import fr.acinq.eclair.crypto.Noise.KeyPair @@ -28,7 +29,7 @@ import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{FSMDiagnosticActorLogging, FeatureCompatibilityResult, Features, InitFeature, Logs, TimestampMilli, TimestampSecond} +import fr.acinq.eclair.{BlockHeight, FSMDiagnosticActorLogging, Features, InitFeature, Logs, TimestampMilli, TimestampSecond} import scodec.Attempt import scodec.bits.ByteVector @@ -103,14 +104,16 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A } when(BEFORE_INIT) { - case Event(InitializeConnection(peer, chainHash, localFeatures, doSync), d: BeforeInitData) => + case Event(InitializeConnection(peer, chainHash, currentBlockHeight, currentBlockHeader, localFeatures, doSync), d: BeforeInitData) => d.transport ! TransportHandler.Listener(self) Metrics.PeerConnectionsConnecting.withTag(Tags.ConnectionState, Tags.ConnectionStates.Initializing).increment() log.debug(s"using features=$localFeatures") - val localInit = d.pendingAuth.address match { - case remoteAddress if !d.pendingAuth.outgoing && conf.sendRemoteAddressInit && NodeAddress.isPublicIPAddress(remoteAddress) => protocol.Init(localFeatures, TlvStream(InitTlv.Networks(chainHash :: Nil), InitTlv.RemoteAddress(remoteAddress))) - case _ => protocol.Init(localFeatures, TlvStream(InitTlv.Networks(chainHash :: Nil))) - } + val tlvs = TlvStream(Set( + Some(InitTlv.Networks(chainHash :: Nil)), + if (!d.pendingAuth.outgoing && conf.sendRemoteAddressInit && NodeAddress.isPublicIPAddress(d.pendingAuth.address)) Some(InitTlv.RemoteAddress(d.pendingAuth.address)) else None, + Some(InitTlv.LatestBlockHeader(currentBlockHeight, currentBlockHeader)) + ).flatten[InitTlv]) + val localInit = protocol.Init(localFeatures, tlvs) d.transport ! localInit startSingleTimer(INIT_TIMER, InitTimeout, conf.initTimeout) unstashAll() // unstash remote init if it already arrived @@ -130,6 +133,8 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A log.info(s"peer is using features=${remoteInit.features}, networks=${remoteInit.networks.mkString(",")}") remoteInit.remoteAddress_opt.foreach(address => log.info("peer reports that our IP address is {} (public={})", address.toString, NodeAddress.isPublicIPAddress(address))) + remoteInit.currentBlockHeight_opt.foreach(height => log.info("peer reports that the current block height is {}", height.toLong)) + remoteInit.currentBlockHeader_opt.foreach(header => log.info("peer reports that the current block header is {}", ByteVector(BlockHeader.write(header)).toHex)) val featureGraphErr_opt = Features.validateFeatureGraph(remoteInit.features) val featuresCompatibilityResult = Features.testCompatible(d.localInit.features, remoteInit.features) @@ -574,7 +579,7 @@ object PeerConnection { def outgoing: Boolean = remoteNodeId_opt.isDefined // if this is an outgoing connection, we know the node id in advance } case class Authenticated(peerConnection: ActorRef, remoteNodeId: PublicKey, outgoing: Boolean) extends RemoteTypes - case class InitializeConnection(peer: ActorRef, chainHash: BlockHash, features: Features[InitFeature], doSync: Boolean) extends RemoteTypes + case class InitializeConnection(peer: ActorRef, chainHash: BlockHash, currentBlockHeight: BlockHeight, currentBlockHeader: BlockHeader, features: Features[InitFeature], doSync: Boolean) extends RemoteTypes case class ConnectionReady(peerConnection: ActorRef, remoteNodeId: PublicKey, address: NodeAddress, outgoing: Boolean, localInit: protocol.Init, remoteInit: protocol.Init) extends RemoteTypes sealed trait ConnectionResult extends RemoteTypes diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Switchboard.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Switchboard.scala index 0c5d4bb3d0..7c410f177a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Switchboard.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Switchboard.scala @@ -102,7 +102,7 @@ class Switchboard(nodeParams: NodeParams, peerFactory: Switchboard.PeerFactory) val hasChannels = peersWithChannels.contains(authenticated.remoteNodeId) // if the peer is whitelisted, we sync with them, otherwise we only sync with peers with whom we have at least one channel val doSync = nodeParams.syncWhitelist.contains(authenticated.remoteNodeId) || (nodeParams.syncWhitelist.isEmpty && hasChannels) - authenticated.peerConnection ! PeerConnection.InitializeConnection(peer, nodeParams.chainHash, features, doSync) + authenticated.peerConnection ! PeerConnection.InitializeConnection(peer, nodeParams.chainHash, nodeParams.currentBlockHeight, nodeParams.currentBlockHeader, features, doSync) if (!hasChannels && !authenticated.outgoing) { incomingConnectionsTracker ! TrackIncomingConnection(authenticated.remoteNodeId) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggerer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggerer.scala index 5adccac806..c181329b2f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggerer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggerer.scala @@ -113,7 +113,7 @@ private class AsyncPaymentTriggerer(context: ActorContext[Command]) { case (remoteNodeId, peer) => peer.cancel(paymentHash).map(peer1 => remoteNodeId -> peer1) } watching(peers1) - case WrappedCurrentBlockHeight(CurrentBlockHeight(currentBlockHeight)) => + case WrappedCurrentBlockHeight(CurrentBlockHeight(currentBlockHeight, _)) => val peers1 = peers.flatMap { case (remoteNodeId, peer) => peer.update(currentBlockHeight).map(peer1 => remoteNodeId -> peer1) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala index 4fb6e472cb..5394bb60d1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala @@ -89,8 +89,8 @@ object EclairInternalsSerializer { val messageRouteParamsCodec: Codec[MessageRouteParams] = ( ("maxRouteLength" | int32) :: (("baseFactor" | double) :: - ("ageFactor" | double) :: - ("capacityFactor" | double)).as[Graph.MessagePath.WeightRatios]).as[MessageRouteParams] + ("ageFactor" | double) :: + ("capacityFactor" | double)).as[Graph.MessagePath.WeightRatios]).as[MessageRouteParams] val routerConfCodec: Codec[RouterConf] = ( ("watchSpentWindow" | finiteDurationCodec) :: @@ -150,6 +150,8 @@ object EclairInternalsSerializer { def initializeConnectionCodec(system: ExtendedActorSystem): Codec[PeerConnection.InitializeConnection] = ( ("peer" | actorRefCodec(system)) :: ("chainHash" | blockHash) :: + ("currentBlockHeight" | blockHeight) :: + ("currentBlockHeader" | blockHeader) :: ("features" | variableSizeBytes(uint16, initFeaturesCodec)) :: ("doSync" | bool(8))).as[PeerConnection.InitializeConnection] diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 812cf0f8a6..e0d439e15e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -16,6 +16,7 @@ package fr.acinq.eclair.wire.protocol +import fr.acinq.bitcoin.BlockHeader import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{BlockHash, ByteVector32, ByteVector64, Satoshi, Transaction, TxHash, TxId} import fr.acinq.eclair.blockchain.fee.FeeratePerKw @@ -170,6 +171,8 @@ object CommonCodecs { val txCodec: Codec[Transaction] = bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)) + val blockHeader: Codec[BlockHeader] = bytes(80).xmap(b => BlockHeader.read(b.toArray), h => ByteVector(BlockHeader.write(h))) + def zeropaddedstring(size: Int): Codec[String] = fixedSizeBytes(size, utf8).xmap(s => s.takeWhile(_ != '\u0000'), s => s) /** diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 8ce1e52191..94601558a4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -58,6 +58,8 @@ sealed trait HtlcFailureMessage extends HtlcSettlementMessage // <- not in the s case class Init(features: Features[InitFeature], tlvStream: TlvStream[InitTlv] = TlvStream.empty) extends SetupMessage { val networks = tlvStream.get[InitTlv.Networks].map(_.chainHashes).getOrElse(Nil) val remoteAddress_opt = tlvStream.get[InitTlv.RemoteAddress].map(_.address) + val currentBlockHeight_opt = tlvStream.get[InitTlv.LatestBlockHeader].map(_.blockHeight) + val currentBlockHeader_opt = tlvStream.get[InitTlv.LatestBlockHeader].map(_.blockHeader) } case class Warning(channelId: ByteVector32, data: ByteVector, tlvStream: TlvStream[WarningTlv] = TlvStream.empty) extends SetupMessage with HasChannelId { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/SetupAndControlTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/SetupAndControlTlv.scala index 6938381cb9..512bd01fc1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/SetupAndControlTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/SetupAndControlTlv.scala @@ -16,8 +16,9 @@ package fr.acinq.eclair.wire.protocol +import fr.acinq.bitcoin.BlockHeader import fr.acinq.bitcoin.scalacompat.BlockHash -import fr.acinq.eclair.UInt64 +import fr.acinq.eclair.{BlockHeight, UInt64} import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.TlvCodecs.{tlvField, tlvStream} import scodec.Codec @@ -41,6 +42,9 @@ object InitTlv { */ case class RemoteAddress(address: NodeAddress) extends InitTlv + /** We let our peer know the latest block header we've seen, to allow them to detect eclipse attacks. */ + case class LatestBlockHeader(blockHeight: BlockHeight, blockHeader: BlockHeader) extends InitTlv + } object InitTlvCodecs { @@ -49,10 +53,12 @@ object InitTlvCodecs { private val networks: Codec[Networks] = tlvField(list(blockHash)) private val remoteAddress: Codec[RemoteAddress] = tlvField(nodeaddress) + private val latestBlockHeader: Codec[LatestBlockHeader] = tlvField(blockHeight :: blockHeader) val initTlvCodec = tlvStream(discriminated[InitTlv].by(varint) .typecase(UInt64(1), networks) .typecase(UInt64(3), remoteAddress) + .typecase(UInt64(32411), latestBlockHeader) ) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala index 1bd052c264..a630edd8e5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala @@ -38,11 +38,12 @@ class StartupSpec extends AnyFunSuite { def makeNodeParamsWithDefaults(conf: Config): NodeParams = { val blockCount = new AtomicLong(0) + val blockHeader = new AtomicReference(TestConstants.defaultBlockHeader) val feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)) val nodeKeyManager = new LocalNodeKeyManager(randomBytes32(), chainHash = Block.TestnetGenesisBlock.hash) val channelKeyManager = new LocalChannelKeyManager(randomBytes32(), chainHash = Block.TestnetGenesisBlock.hash) val db = TestDatabases.inMemoryDb() - NodeParams.makeNodeParams(conf, UUID.fromString("01234567-0123-4567-89ab-0123456789ab"), nodeKeyManager, channelKeyManager, None, None, db, blockCount, feerates) + NodeParams.makeNodeParams(conf, UUID.fromString("01234567-0123-4567-89ab-0123456789ab"), nodeKeyManager, channelKeyManager, None, None, db, blockCount, blockHeader, feerates) } test("check configuration") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 10400356ea..9a2e7c096f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair import akka.actor.ActorRef +import fr.acinq.bitcoin.BlockHeader import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Satoshi, SatoshiLong} import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features._ @@ -46,6 +47,7 @@ import scala.concurrent.duration._ object TestConstants { val defaultBlockHeight = 400_000 + val defaultBlockHeader = BlockHeader.read("00601d3455bb9fbd966b3ea2dc42d0c22722e4c0c1729fad17210100000000000000000055087fab0c8f3f89f8bcfd4df26c504d81b0a88e04907161838c0c53001af09135edbd64943805175e955e06") val fundingSatoshis: Satoshi = 1_000_000 sat val nonInitiatorFundingSatoshis: Satoshi = 500_000 sat val initiatorPushAmount: MilliSatoshi = 200_000_000L msat @@ -86,6 +88,7 @@ object TestConstants { channelKeyManager, onChainKeyManager_opt = None, blockHeight = new AtomicLong(defaultBlockHeight), + blockHeader = new AtomicReference(defaultBlockHeader), feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)), alias = "alice", color = Color(1, 2, 3), @@ -256,6 +259,7 @@ object TestConstants { channelKeyManager, onChainKeyManager_opt = None, blockHeight = new AtomicLong(defaultBlockHeight), + blockHeader = new AtomicReference(defaultBlockHeader), feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)), alias = "bob", color = Color(4, 5, 6), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala index fd091a0803..4203e3ce61 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala @@ -23,7 +23,7 @@ import fr.acinq.bitcoin import fr.acinq.bitcoin.psbt.{Psbt, UpdateFailure} import fr.acinq.bitcoin.scalacompat.Crypto.{PublicKey, der2compact} import fr.acinq.bitcoin.scalacompat.{Block, Btc, BtcDouble, Crypto, DeterministicWallet, MilliBtcDouble, MnemonicCode, OP_DROP, OP_PUSHDATA, OutPoint, Satoshi, SatoshiLong, Script, ScriptWitness, Transaction, TxId, TxIn, TxOut, addressFromPublicKeyScript, addressToPublicKeyScript, computeBIP84Address, computeP2PkhAddress, computeP2WpkhAddress} -import fr.acinq.bitcoin.{Bech32, SigHash, SigVersion} +import fr.acinq.bitcoin.{Bech32, BlockHeader, SigHash, SigVersion} import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair.blockchain.OnChainWallet.{FundTransactionResponse, MakeFundingTxResponse, OnChainBalance, ProcessPsbtResponse} import fr.acinq.eclair.blockchain.WatcherSpec.{createSpendManyP2WPKH, createSpendP2WPKH} @@ -1708,6 +1708,25 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A sender.expectMsgType[FundTransactionResponse] } + test("get block header") { + val sender = TestProbe() + val bitcoinClient = makeBitcoinCoreClient() + + bitcoinClient.getBlockHeight().pipeTo(sender.ref) + val height = sender.expectMsgType[BlockHeight] + bitcoinClient.getBlockHeader(height).pipeTo(sender.ref) + val Some(header) = sender.expectMsgType[Option[BlockHeader]] + + generateBlocks(1) + + bitcoinClient.getBlockHeight().pipeTo(sender.ref) + assert(sender.expectMsgType[BlockHeight] == height + 1) + bitcoinClient.getBlockHeader(height + 1).pipeTo(sender.ref) + val Some(nextHeader) = sender.expectMsgType[Option[BlockHeader]] + assert(nextHeader.hash != header.hash) + assert(nextHeader.hashPreviousBlock == header.hash) + } + } class BitcoinCoreClientWithEclairSignerSpec extends BitcoinCoreClientSpec { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala index 3cc68fb47e..4bab239fb7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala @@ -145,11 +145,14 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind // When the watcher starts, it broadcasts the current height. val block1 = listener.expectMsgType[CurrentBlockHeight] assert(blockHeight.get() == block1.blockHeight.toLong) + assert(block1.blockHeader_opt.nonEmpty) listener.expectNoMessage(100 millis) generateBlocks(1) - assert(listener.expectMsgType[CurrentBlockHeight].blockHeight == block1.blockHeight + 1) - assert(blockHeight.get() == block1.blockHeight.toLong + 1) + val block2 = listener.expectMsgType[CurrentBlockHeight] + assert(block2.blockHeight == block1.blockHeight + 1) + assert(blockHeight.get() == block2.blockHeight.toLong) + assert(block2.blockHeader_opt.get.hashPreviousBlock == block1.blockHeader_opt.get.hash) listener.expectNoMessage(100 millis) generateBlocks(5) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/FinalTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/FinalTxPublisherSpec.scala index 8d8d59f9b8..27e6d22778 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/FinalTxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/FinalTxPublisherSpec.scala @@ -69,7 +69,7 @@ class FinalTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bi def createBlocks(blockCount: Int, probe: TestProbe): Unit = { generateBlocks(blockCount) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) } test("publish transaction with time locks") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala index 5c5b6ac84a..ed468f6dc2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala @@ -367,7 +367,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w wallet.publishTransaction(remoteCommit.tx).pipeTo(probe.ref) probe.expectMsg(remoteCommit.tx.txid) generateBlocks(1) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val result = probe.expectMsgType[TxRejected] assert(result.cmd == anchorTx) @@ -423,7 +423,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(targetFee * 0.9 <= actualFee && actualFee <= targetFee * 1.1, s"actualFee=$actualFee targetFee=$targetFee") generateBlocks(5) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val result = probe.expectMsgType[TxConfirmed] assert(result.cmd == anchorTx) assert(result.tx.txIn.map(_.outPoint.txid).contains(commitTx.tx.txid)) @@ -468,7 +468,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(targetFee * 0.9 <= actualFee && actualFee <= targetFee * 1.1, s"actualFee=$actualFee targetFee=$targetFee") generateBlocks(5) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val result = probe.expectMsgType[TxConfirmed] assert(result.cmd == anchorTx) assert(result.tx.txIn.map(_.outPoint.txid).contains(commitTx.txid)) @@ -510,7 +510,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(targetFee * 0.9 <= actualFee && actualFee <= targetFee * 1.1, s"actualFee=$actualFee targetFee=$targetFee") generateBlocks(5) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val result = probe.expectMsgType[TxConfirmed] assert(result.cmd == anchorTx) assert(result.tx.txIn.map(_.outPoint.txid).contains(commitTx.tx.txid)) @@ -563,7 +563,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(targetFee * 0.9 <= actualFee && actualFee <= targetFee * 1.1, s"actualFee=$actualFee targetFee=$targetFee") generateBlocks(5) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val result = probe.expectMsgType[TxConfirmed] assert(result.cmd == anchorTx) assert(result.tx.txIn.map(_.outPoint.txid).contains(commitTx.tx.txid)) @@ -617,7 +617,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(targetFee * 0.9 <= actualFee && actualFee <= targetFee * 1.1, s"actualFee=$actualFee targetFee=$targetFee") generateBlocks(5) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val result = probe.expectMsgType[TxConfirmed] assert(result.cmd == anchorTx) assert(result.tx.txIn.map(_.outPoint.txid).contains(commitTx.tx.txid)) @@ -642,7 +642,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // A new block is found, but we still have time and the feerate hasn't changed, so we don't bump the fees. // Note that we don't generate blocks, so the transactions are still unconfirmed. - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5, None)) probe.expectNoMessage(500 millis) val mempoolTxs2 = getMempool() assert(mempoolTxs.map(_.txid).toSet == mempoolTxs2.map(_.txid).toSet) @@ -673,7 +673,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // A new block is found, and the feerate has increased for our block target, so we bump the fees. val newFeerate = FeeratePerKw(5000 sat) setFeerate(newFeerate, blockTarget = 12) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5, None)) val anchorTxId2 = listener.expectMsgType[TransactionPublished].tx.txid assert(!isInMempool(mempoolAnchorTx1.txid)) val mempoolTxs2 = getMempoolTxs(2) @@ -717,7 +717,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(anchorTx1.txid == anchorTxId1) // A new block is found, and the feerate has increased for our block target, so we bump the fees. - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 15)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 15, None)) val anchorTxId2 = listener.expectMsgType[TransactionPublished].tx.txid assert(!isInMempool(anchorTx1.txid)) val anchorTx2 = getMempool().filter(_.txid != commitTx.tx.txid).head @@ -756,7 +756,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w system.eventStream.publish(NotifyNodeOperator(NotificationsLogger.Info, "ping")) assert(probe.msgAvailable) }, max = 30 seconds) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 15)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 15, None)) probe.fishForMessage() { case nno: NotifyNodeOperator => nno.severity != NotificationsLogger.Info case _ => false @@ -790,7 +790,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // A new block is found, and the feerate has increased for our block target, but we can't use our unconfirmed input. system.eventStream.subscribe(probe.ref, classOf[NotifyNodeOperator]) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 15)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 15, None)) probe.expectMsgType[NotifyNodeOperator] val mempoolTxs2 = getMempool() assert(mempoolTxs1.map(_.txid).toSet + walletTx.txid == mempoolTxs2.map(_.txid).toSet) @@ -826,7 +826,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // The confirmation target has changed (probably because we learnt a payment preimage). // We should now use the high feerate, which corresponds to that new target. publisher ! UpdateConfirmationTarget(ConfirmationTarget.Absolute(aliceBlockHeight() + 15)) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight())) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight(), None)) val anchorTxId2 = listener.expectMsgType[TransactionPublished].tx.txid awaitAssert(assert(!isInMempool(mempoolAnchorTx1.txid)), interval = 200 millis, max = 30 seconds) val mempoolTxs2 = getMempoolTxs(2) @@ -870,7 +870,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // the first publishing attempt succeeds generateBlocks(5) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) assert(probe.expectMsgType[TxConfirmed].cmd == anchorTx) } } @@ -1062,7 +1062,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(htlcSuccessTx.fees <= htlcSuccess.txInfo.input.txOut.amount) generateBlocks(4) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val htlcSuccessResult = probe.expectMsgType[TxConfirmed] assert(htlcSuccessResult.cmd == htlcSuccess) assert(htlcSuccessResult.tx.txIn.map(_.outPoint.txid).contains(commitTx.txid)) @@ -1082,7 +1082,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w htlcTimeoutPublisher ! Publish(probe.ref, htlcTimeout) alice2blockchain.expectNoMessage(100 millis) generateBlocks(144) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) setFeerate(targetFeerate) // the feerate is higher than what it was when the channel force-closed val w = alice2blockchain.expectMsgType[WatchParentTxConfirmed] w.replyTo ! WatchParentTxConfirmedTriggered(currentBlockHeight(probe), 0, commitTx) @@ -1092,7 +1092,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(htlcTimeoutTx.fees <= htlcTimeout.txInfo.input.txOut.amount) generateBlocks(4) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val htlcTimeoutResult = probe.expectMsgType[TxConfirmed] assert(htlcTimeoutResult.cmd == htlcTimeout) assert(htlcTimeoutResult.tx.txIn.map(_.outPoint.txid).contains(commitTx.txid)) @@ -1239,7 +1239,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // New blocks are found, which makes us aim for a more aggressive block target, so we bump the fees. val targetFeerate = FeeratePerKw(25_000 sat) setFeerate(targetFeerate, blockTarget = 6) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 15)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 15, None)) val htlcSuccessTxId2 = listener.expectMsgType[TransactionPublished].tx.txid assert(!isInMempool(htlcSuccessTx1.txid)) val htlcSuccessTx2 = getMempoolTxs(1).head @@ -1275,7 +1275,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // New blocks are found, which makes us aim for a more aggressive block target, so we bump the fees. val targetFeerate = FeeratePerKw(10_000 sat) setFeerate(targetFeerate, blockTarget = 2) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 10)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 10, None)) val htlcSuccessTxId2 = listener.expectMsgType[TransactionPublished].tx.txid awaitAssert(assert(!isInMempool(htlcSuccessTx1.txid)), interval = 200 millis, max = 30 seconds) val htlcSuccessTx2 = getMempoolTxs(1).head @@ -1309,7 +1309,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // We are only 6 blocks away from the confirmation target, so we bump the fees at each new block. (1 to 3).foreach(i => { - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + i)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + i, None)) val bumpedHtlcSuccessTxId = listener.expectMsgType[TransactionPublished].tx.txid assert(!isInMempool(htlcSuccessTx.txid)) val bumpedHtlcSuccessTx = getMempoolTxs(1).head @@ -1335,7 +1335,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val htlcTimeoutPublisher = createPublisher() htlcTimeoutPublisher ! Publish(probe.ref, htlcTimeout) generateBlocks(144) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val w = alice2blockchain.expectMsgType[WatchParentTxConfirmed] w.replyTo ! WatchParentTxConfirmedTriggered(currentBlockHeight(probe), 0, commitTx) val htlcTimeoutTxId1 = listener.expectMsgType[TransactionPublished].tx.txid @@ -1345,7 +1345,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // A new block is found, and we've already reached the confirmation target, so we bump the fees. setFeerate(feerate, fastest = feerate * 1.2) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 145)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 145, None)) val htlcTimeoutTxId2 = listener.expectMsgType[TransactionPublished].tx.txid assert(!isInMempool(htlcTimeoutTx1.txid)) val htlcTimeoutTx2 = getMempoolTxs(1).head @@ -1413,7 +1413,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // the first publishing attempt succeeds generateBlocks(5) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) assert(probe.expectMsgType[TxConfirmed].cmd == htlcSuccess) publisher1 ! Stop } @@ -1552,7 +1552,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(claimHtlcSuccessTargetFee * 0.9 <= claimHtlcSuccessTx.fees && claimHtlcSuccessTx.fees <= claimHtlcSuccessTargetFee * 1.1, s"actualFee=${claimHtlcSuccessTx.fees} targetFee=$claimHtlcSuccessTargetFee") generateBlocks(4) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val claimHtlcSuccessResult = probe.expectMsgType[TxConfirmed] assert(claimHtlcSuccessResult.cmd == claimHtlcSuccess) assert(claimHtlcSuccessResult.tx.txIn.map(_.outPoint.txid).contains(remoteCommitTx.txid)) @@ -1572,7 +1572,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w claimHtlcTimeoutPublisher ! Publish(probe.ref, claimHtlcTimeout) alice2blockchain.expectNoMessage(100 millis) generateBlocks(144) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) setFeerate(targetFeerate) // the feerate is higher than what it was when the channel force-closed val w = alice2blockchain.expectMsgType[WatchParentTxConfirmed] w.replyTo ! WatchParentTxConfirmedTriggered(currentBlockHeight(probe), 0, remoteCommitTx) @@ -1581,7 +1581,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(claimHtlcTimeoutTargetFee * 0.9 <= claimHtlcTimeoutTx.fees && claimHtlcTimeoutTx.fees <= claimHtlcTimeoutTargetFee * 1.1, s"actualFee=${claimHtlcTimeoutTx.fees} targetFee=$claimHtlcTimeoutTargetFee") generateBlocks(4) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val claimHtlcTimeoutResult = probe.expectMsgType[TxConfirmed] assert(claimHtlcTimeoutResult.cmd == claimHtlcTimeout) assert(claimHtlcTimeoutResult.tx.txIn.map(_.outPoint.txid).contains(remoteCommitTx.txid)) @@ -1646,7 +1646,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val claimHtlcSuccessTargetFee = Transactions.weight2fee(targetFeerate, claimHtlcSuccessTx.weight.toInt) assert(claimHtlcSuccessTargetFee * 0.9 <= claimHtlcSuccessTx.fees && claimHtlcSuccessTx.fees <= claimHtlcSuccessTargetFee * 1.1, s"actualFee=${claimHtlcSuccessTx.fees} targetFee=$claimHtlcSuccessTargetFee") generateBlocks(4) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val claimHtlcSuccessResult = probe.expectMsgType[TxConfirmed] assert(claimHtlcSuccessResult.cmd == claimHtlcSuccess) assert(claimHtlcSuccessResult.tx.txIn.map(_.outPoint.txid).contains(remoteCommitTx.txid)) @@ -1657,13 +1657,13 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w claimHtlcTimeoutPublisher ! Publish(probe.ref, claimHtlcTimeout) alice2blockchain.expectNoMessage(100 millis) generateBlocks(144) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val claimHtlcTimeoutTx = getMempoolTxs(1).head val claimHtlcTimeoutTargetFee = Transactions.weight2fee(targetFeerate, claimHtlcTimeoutTx.weight.toInt) assert(claimHtlcTimeoutTargetFee * 0.9 <= claimHtlcTimeoutTx.fees && claimHtlcTimeoutTx.fees <= claimHtlcTimeoutTargetFee * 1.1, s"actualFee=${claimHtlcTimeoutTx.fees} targetFee=$claimHtlcTimeoutTargetFee") generateBlocks(4) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val claimHtlcTimeoutResult = probe.expectMsgType[TxConfirmed] assert(claimHtlcTimeoutResult.cmd == claimHtlcTimeout) assert(claimHtlcTimeoutResult.tx.txIn.map(_.outPoint.txid).contains(remoteCommitTx.txid)) @@ -1698,7 +1698,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val claimHtlcTimeoutPublisher = createPublisher() claimHtlcTimeoutPublisher ! Publish(probe.ref, claimHtlcTimeout) generateBlocks(144) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) + system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe), None)) val w2 = alice2blockchain.expectMsgType[WatchParentTxConfirmed] w2.replyTo ! WatchParentTxConfirmedTriggered(currentBlockHeight(probe), 0, remoteCommitTx) val result2 = probe.expectMsgType[TxRejected] @@ -1728,7 +1728,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(listener.expectMsgType[TransactionPublished].tx.txid == claimHtlcSuccessTx1.txid) setFeerate(targetFeerate, fastest = targetFeerate) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5, None)) val claimHtlcSuccessTxId2 = listener.expectMsgType[TransactionPublished].tx.txid assert(!isInMempool(claimHtlcSuccessTx1.txid)) val claimHtlcSuccessTx2 = getMempoolTxs(1).head @@ -1746,13 +1746,13 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val claimHtlcTimeoutPublisher = createPublisher() claimHtlcTimeoutPublisher ! Publish(probe.ref, claimHtlcTimeout) generateBlocks(144) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 144)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 144, None)) assert(probe.expectMsgType[TxConfirmed].tx.txid == finalHtlcSuccessTx.txid) // the claim-htlc-success is now confirmed val claimHtlcTimeoutTx1 = getMempoolTxs(1).head assert(listener.expectMsgType[TransactionPublished].tx.txid == claimHtlcTimeoutTx1.txid) setFeerate(targetFeerate, fastest = targetFeerate) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 145)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 145, None)) val claimHtlcTimeoutTxId2 = listener.expectMsgType[TransactionPublished].tx.txid assert(!isInMempool(claimHtlcTimeoutTx1.txid)) val claimHtlcTimeoutTx2 = getMempoolTxs(1).head @@ -1786,7 +1786,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // New blocks are found and the feerate is higher, but the htlc would become dust, so we don't bump the fees. setFeerate(FeeratePerKw(50_000 sat)) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5)) + system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5, None)) probe.expectNoMessage(500 millis) val mempoolTxs = getMempool() assert(mempoolTxs.map(_.txid).toSet == Set(claimHtlcSuccessTx.txid)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala index 2e2cbf577d..938df365f2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala @@ -226,7 +226,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { // We don't retry until a new block is found. factory.expectNoMessage(100 millis) - system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200))) + system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200), None)) val attempt2 = factory.expectMsgType[FinalTxPublisherSpawned] assert(attempt2.actor.expectMsgType[FinalTxPublisher.Publish].cmd == cmd) } @@ -247,7 +247,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { // We automatically retry the failed attempt once a new block is found (we may have more funds now): factory.expectNoMessage(100 millis) - system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200))) + system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200), None)) val attempt2 = factory.expectMsgType[ReplaceableTxPublisherSpawned] assert(attempt2.actor.expectMsgType[ReplaceableTxPublisher.Publish].cmd == cmd) } @@ -273,7 +273,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { attempt2.actor.expectMsg(FinalTxPublisher.Stop) factory.expectNoMessage(100 millis) - system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200))) + system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200), None)) val attempt3 = factory.expectMsgType[FinalTxPublisherSpawned] assert(attempt3.actor.expectMsgType[publish.FinalTxPublisher.Publish].cmd == cmd2) factory.expectNoMessage(100 millis) @@ -292,7 +292,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { attempt.actor.expectMsg(FinalTxPublisher.Stop) // We don't retry, even after a new block has been found: - system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200))) + system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200), None)) factory.expectNoMessage(100 millis) } @@ -311,7 +311,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { factory.expectNoMessage(100 millis) // We retry when a new block is found: - system.eventStream.publish(CurrentBlockHeight(nodeParams.currentBlockHeight + 1)) + system.eventStream.publish(CurrentBlockHeight(nodeParams.currentBlockHeight + 1, None)) val attempt2 = factory.expectMsgType[ReplaceableTxPublisherSpawned] assert(attempt2.actor.expectMsgType[ReplaceableTxPublisher.Publish].cmd == cmd) } @@ -329,7 +329,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { attempt.actor.expectMsg(FinalTxPublisher.Stop) // We don't retry, even after a new block has been found: - system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200))) + system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200), None)) factory.expectNoMessage(100 millis) } @@ -346,7 +346,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { attempt.actor.expectMsg(FinalTxPublisher.Stop) // We don't retry, even after a new block has been found: - system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200))) + system.eventStream.publish(CurrentBlockHeight(BlockHeight(8200), None)) factory.expectNoMessage(100 millis) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala index 1b1367d24c..d757f39fbc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala @@ -475,7 +475,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture import f._ val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx val currentBlock = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].waitingSince + 10 - alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock)) + alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock, None)) // Alice republishes the highest feerate funding tx. assert(aliceListener.expectMsgType[TransactionPublished].tx.txid == fundingTx.txid) alice2bob.expectNoMessage(100 millis) @@ -489,7 +489,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture val currentBlock = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].waitingSince + 10 alice ! INPUT_DISCONNECTED awaitCond(alice.stateName == OFFLINE) - alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock)) + alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock, None)) // Alice republishes the highest feerate funding tx. assert(aliceListener.expectMsgType[TransactionPublished].tx.txid == fundingTx.txid) alice2bob.expectNoMessage(100 millis) @@ -502,7 +502,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx val currentBlock = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].waitingSince + 10 wallet.doubleSpent = Set(fundingTx.txid) - alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock)) + alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock, None)) alice2bob.expectMsgType[Error] alice2blockchain.expectNoMessage(100 millis) awaitCond(wallet.rolledback.map(_.txid) == Seq(fundingTx.txid)) @@ -517,7 +517,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture alice ! INPUT_DISCONNECTED awaitCond(alice.stateName == OFFLINE) wallet.doubleSpent = Set(fundingTx.txid) - alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock)) + alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock, None)) alice2bob.expectMsgType[Error] alice2blockchain.expectNoMessage(100 millis) awaitCond(wallet.rolledback.map(_.txid) == Seq(fundingTx.txid)) @@ -528,7 +528,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture test("recv CurrentBlockCount (funding timeout reached)", Tag(ChannelStateTestsTags.DualFunding), Tag("no-funding-contribution")) { f => import f._ val timeoutBlock = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].waitingSince + Channel.FUNDING_TIMEOUT_FUNDEE + 1 - bob ! ProcessCurrentBlockHeight(CurrentBlockHeight(timeoutBlock)) + bob ! ProcessCurrentBlockHeight(CurrentBlockHeight(timeoutBlock, None)) bob2alice.expectMsgType[Error] bob2blockchain.expectNoMessage(100 millis) bobListener.expectMsgType[ChannelAborted] @@ -540,7 +540,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture val timeoutBlock = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].waitingSince + Channel.FUNDING_TIMEOUT_FUNDEE + 1 bob ! INPUT_DISCONNECTED awaitCond(bob.stateName == OFFLINE) - bob ! ProcessCurrentBlockHeight(CurrentBlockHeight(timeoutBlock)) + bob ! ProcessCurrentBlockHeight(CurrentBlockHeight(timeoutBlock, None)) bob2alice.expectMsgType[Error] bob2blockchain.expectNoMessage(100 millis) bobListener.expectMsgType[ChannelAborted] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index c665bedd75..feafc4afbe 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -210,21 +210,21 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF test("recv CurrentBlockCount (funder)") { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] - alice ! CurrentBlockHeight(initialState.waitingSince + Channel.FUNDING_TIMEOUT_FUNDEE + 1) + alice ! CurrentBlockHeight(initialState.waitingSince + Channel.FUNDING_TIMEOUT_FUNDEE + 1, None) alice2bob.expectNoMessage(100 millis) } test("recv CurrentBlockCount (funding timeout not reached)") { f => import f._ val initialState = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] - bob ! CurrentBlockHeight(initialState.waitingSince + Channel.FUNDING_TIMEOUT_FUNDEE - 1) + bob ! CurrentBlockHeight(initialState.waitingSince + Channel.FUNDING_TIMEOUT_FUNDEE - 1, None) bob2alice.expectNoMessage(100 millis) } test("recv CurrentBlockCount (funding timeout reached)") { f => import f._ val initialState = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] - bob ! CurrentBlockHeight(initialState.waitingSince + Channel.FUNDING_TIMEOUT_FUNDEE + 1) + bob ! CurrentBlockHeight(initialState.waitingSince + Channel.FUNDING_TIMEOUT_FUNDEE + 1, None) bob2alice.expectMsgType[Error] listener.expectMsgType[ChannelAborted] awaitCond(bob.stateName == CLOSED) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala index d39afd2ecb..7ec852a3b9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala @@ -433,7 +433,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL val htlcTimeoutTx = aliceCommit.htlcTxsAndRemoteSigs.head.htlcTx.tx // the HTLC times out, alice needs to close the channel - alice ! CurrentBlockHeight(add.cltvExpiry.blockHeight) + alice ! CurrentBlockHeight(add.cltvExpiry.blockHeight, None) assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == commitTx.txid) alice2blockchain.expectMsgType[PublishTx] // main delayed assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == htlcTimeoutTx.txid) @@ -457,7 +457,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL val htlcSuccessTx = bobCommit.htlcTxsAndRemoteSigs.head.htlcTx.tx // bob does not force-close unless there is a pending preimage for the incoming htlc - bob ! CurrentBlockHeight(add.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt) + bob ! CurrentBlockHeight(add.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt, None) bob2blockchain.expectNoMessage(100 millis) // bob receives the fulfill for htlc, which is ignored because the channel is quiescent @@ -465,7 +465,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL safeSend(bob, Seq(fulfillHtlc)) // the HTLC timeout from alice is near, bob needs to close the channel to avoid an on-chain race condition - bob ! CurrentBlockHeight(add.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt) + bob ! CurrentBlockHeight(add.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt, None) // bob publishes a first set of force-close transactions assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == commitTx.txid) bob2blockchain.expectMsgType[PublishTx] // main delayed diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index caf4fd2f42..3afb90c17a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -2844,7 +2844,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test begins val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - alice ! CurrentBlockHeight(BlockHeight(400143)) + alice ! CurrentBlockHeight(BlockHeight(400143), None) awaitCond(alice.stateData == initialState) } @@ -2856,7 +2856,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test begins val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val aliceCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - alice ! CurrentBlockHeight(BlockHeight(400145)) + alice ! CurrentBlockHeight(BlockHeight(400145), None) assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid) alice2blockchain.expectMsgType[PublishTx] // main delayed alice2blockchain.expectMsgType[PublishTx] // htlc timeout @@ -2884,7 +2884,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! CMD_FULFILL_HTLC(htlc.id, r, commit = true) bob2alice.expectMsgType[UpdateFulfillHtlc] bob2alice.expectMsgType[CommitSig] - bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt) + bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt, None) val ChannelErrorOccurred(_, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccurred] assert(isFatal) @@ -2918,7 +2918,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! CMD_FULFILL_HTLC(htlc.id, r, commit = false) bob2alice.expectMsgType[UpdateFulfillHtlc] bob2alice.expectNoMessage(500 millis) - bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt) + bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt, None) val ChannelErrorOccurred(_, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccurred] assert(isFatal) @@ -2956,7 +2956,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice) alice2bob.expectMsgType[RevokeAndAck] alice2bob.forward(bob) - bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt) + bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt, None) val ChannelErrorOccurred(_, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccurred] assert(isFatal) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index fbeebfdb32..7611aefa8f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -595,7 +595,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // We simulate a pending fulfill on that HTLC but not relayed. // When it is close to expiring upstream, we should close the channel. bob.underlyingActor.nodeParams.db.pendingCommands.addSettlementCommand(initialState.channelId, CMD_FULFILL_HTLC(htlc.id, r, commit = true)) - bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - bob.underlyingActor.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt) + bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - bob.underlyingActor.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt, None) val ChannelErrorOccurred(_, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccurred] assert(isFatal) @@ -626,7 +626,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // We simulate a pending failure on that HTLC. // Even if we get close to expiring upstream we shouldn't close the channel, because we have nothing to lose. bob ! CMD_FAIL_HTLC(htlc.id, Right(IncorrectOrUnknownPaymentDetails(0 msat, BlockHeight(0)))) - bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - bob.underlyingActor.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt) + bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - bob.underlyingActor.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt, None) bob2blockchain.expectNoMessage(250 millis) alice2blockchain.expectNoMessage(250 millis) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 1d6006b02e..79474bfb5a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -642,7 +642,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv CurrentBlockCount (no htlc timed out)") { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - alice ! CurrentBlockHeight(BlockHeight(400143)) + alice ! CurrentBlockHeight(BlockHeight(400143), None) awaitCond(alice.stateData == initialState) } @@ -650,7 +650,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit import f._ val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val aliceCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - alice ! CurrentBlockHeight(BlockHeight(400145)) + alice ! CurrentBlockHeight(BlockHeight(400145), None) assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid) // commit tx alice2blockchain.expectMsgType[PublishTx] // main delayed alice2blockchain.expectMsgType[PublishTx] // htlc timeout 1 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index 1fcbada4a6..f5c155bc75 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -30,7 +30,7 @@ import fr.acinq.eclair.payment.relay.{ChannelRelayer, PostRestartHtlcCleaner, Re import fr.acinq.eclair.payment.send.PaymentInitiator import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.IPAddress -import fr.acinq.eclair.{BlockHeight, MilliSatoshi, NodeParams, SubscriptionsComplete, TestBitcoinCoreClient, TestDatabases} +import fr.acinq.eclair.{BlockHeight, MilliSatoshi, NodeParams, SubscriptionsComplete, TestBitcoinCoreClient, TestConstants, TestDatabases} import org.scalatest.concurrent.{Eventually, IntegrationPatience} import org.scalatest.{Assertions, EitherValues} @@ -74,6 +74,7 @@ object MinimalNodeFixture extends Assertions with Eventually with IntegrationPat torAddress_opt = None, database = TestDatabases.inMemoryDb(), blockHeight = new AtomicLong(400_000), + blockHeader = new AtomicReference(TestConstants.defaultBlockHeader), feerates = new AtomicReference(FeeratesPerKw.single(FeeratePerKw(253 sat))) ).modify(_.alias).setTo(alias) .modify(_.chainHash).setTo(Block.RegtestGenesisBlock.hash) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala index e07e6251a5..b882324958 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala @@ -74,10 +74,12 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi probe.send(peerConnection, PeerConnection.PendingAuth(connection.ref, Some(remoteNodeId), address, origin_opt = None, transport_opt = Some(transport.ref), isPersistent = isPersistent)) transport.send(peerConnection, TransportHandler.HandshakeCompleted(remoteNodeId)) switchboard.expectMsg(PeerConnection.Authenticated(peerConnection, remoteNodeId, outgoing = true)) - probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, aliceParams.chainHash, aliceParams.features.initFeatures(), doSync)) + probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, aliceParams.chainHash, BlockHeight(42), TestConstants.defaultBlockHeader, aliceParams.features.initFeatures(), doSync)) transport.expectMsgType[TransportHandler.Listener] val localInit = transport.expectMsgType[protocol.Init] assert(localInit.networks == List(Block.RegtestGenesisBlock.hash)) + assert(localInit.currentBlockHeight_opt.contains(BlockHeight(42))) + assert(localInit.currentBlockHeader_opt.contains(TestConstants.defaultBlockHeader)) transport.send(peerConnection, remoteInit) transport.expectMsgType[TransportHandler.ReadAck] if (doSync) { @@ -102,10 +104,10 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi probe.send(peerConnection, incomingConnection) transport.send(peerConnection, TransportHandler.HandshakeCompleted(remoteNodeId)) switchboard.expectMsg(PeerConnection.Authenticated(peerConnection, remoteNodeId, outgoing = false)) - probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, nodeParams.features.initFeatures(), doSync = false)) + probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, BlockHeight(42), TestConstants.defaultBlockHeader, nodeParams.features.initFeatures(), doSync = false)) transport.expectMsgType[TransportHandler.Listener] val localInit = transport.expectMsgType[protocol.Init] - assert(localInit.remoteAddress_opt == Some(fakeIPAddress)) + assert(localInit.remoteAddress_opt.contains(fakeIPAddress)) } test("handle connection closed during authentication") { f => @@ -134,7 +136,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi probe.watch(peerConnection) probe.send(peerConnection, PeerConnection.PendingAuth(connection.ref, Some(remoteNodeId), address, origin_opt = Some(origin.ref), transport_opt = Some(transport.ref), isPersistent = true)) transport.send(peerConnection, TransportHandler.HandshakeCompleted(remoteNodeId)) - probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, nodeParams.features.initFeatures(), doSync = true)) + probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, BlockHeight(42), TestConstants.defaultBlockHeader, nodeParams.features.initFeatures(), doSync = true)) probe.expectTerminated(peerConnection, nodeParams.peerConnectionConf.initTimeout / transport.testKitSettings.TestTimeFactor + 1.second) // we don't want dilated time here origin.expectMsg(PeerConnection.ConnectionResult.InitializationFailed("initialization timed out")) peer.expectMsg(ConnectionDown(peerConnection)) @@ -147,7 +149,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi probe.watch(transport.ref) probe.send(peerConnection, PeerConnection.PendingAuth(connection.ref, Some(remoteNodeId), address, origin_opt = Some(origin.ref), transport_opt = Some(transport.ref), isPersistent = true)) transport.send(peerConnection, TransportHandler.HandshakeCompleted(remoteNodeId)) - probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, nodeParams.features.initFeatures(), doSync = true)) + probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, BlockHeight(42), TestConstants.defaultBlockHeader, nodeParams.features.initFeatures(), doSync = true)) transport.expectMsgType[TransportHandler.Listener] transport.expectMsgType[protocol.Init] transport.send(peerConnection, LightningMessageCodecs.initCodec.decode(hex"0000 00050100000000".bits).require.value) @@ -164,7 +166,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi probe.watch(transport.ref) probe.send(peerConnection, PeerConnection.PendingAuth(connection.ref, Some(remoteNodeId), address, origin_opt = Some(origin.ref), transport_opt = Some(transport.ref), isPersistent = true)) transport.send(peerConnection, TransportHandler.HandshakeCompleted(remoteNodeId)) - probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, nodeParams.features.initFeatures(), doSync = true)) + probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, BlockHeight(42), TestConstants.defaultBlockHeader, nodeParams.features.initFeatures(), doSync = true)) transport.expectMsgType[TransportHandler.Listener] transport.expectMsgType[protocol.Init] transport.send(peerConnection, LightningMessageCodecs.initCodec.decode(hex"00050100000000 0000".bits).require.value) @@ -181,7 +183,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi probe.watch(transport.ref) probe.send(peerConnection, PeerConnection.PendingAuth(connection.ref, Some(remoteNodeId), address, origin_opt = Some(origin.ref), transport_opt = Some(transport.ref), isPersistent = true)) transport.send(peerConnection, TransportHandler.HandshakeCompleted(remoteNodeId)) - probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, nodeParams.features.initFeatures(), doSync = true)) + probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, BlockHeight(42), TestConstants.defaultBlockHeader, nodeParams.features.initFeatures(), doSync = true)) transport.expectMsgType[TransportHandler.Listener] transport.expectMsgType[protocol.Init] // remote activated MPP but forgot payment secret @@ -199,7 +201,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi probe.watch(transport.ref) probe.send(peerConnection, PeerConnection.PendingAuth(connection.ref, Some(remoteNodeId), address, origin_opt = Some(origin.ref), transport_opt = Some(transport.ref), isPersistent = true)) transport.send(peerConnection, TransportHandler.HandshakeCompleted(remoteNodeId)) - probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, nodeParams.features.initFeatures(), doSync = true)) + probe.send(peerConnection, PeerConnection.InitializeConnection(peer.ref, nodeParams.chainHash, BlockHeight(42), TestConstants.defaultBlockHeader, nodeParams.features.initFeatures(), doSync = true)) transport.expectMsgType[TransportHandler.Listener] transport.expectMsgType[protocol.Init] transport.send(peerConnection, protocol.Init(Bob.nodeParams.features.initFeatures(), TlvStream(InitTlv.Networks(Block.LivenetGenesisBlock.hash :: Block.SignetGenesisBlock.hash :: Nil)))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerReadyNotifierSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerReadyNotifierSpec.scala index 4d2dad7808..3d19314bf7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerReadyNotifierSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerReadyNotifierSpec.scala @@ -65,11 +65,11 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load assert(switchboard.expectMessageType[Switchboard.GetPeerInfo].remoteNodeId == remoteNodeId) // We haven't reached the timeout yet. - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(99))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(99), None)) probe.expectNoMessage(100 millis) // We exceed the timeout (we've missed blocks). - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(110))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(110), None)) probe.expectMessage(PeerUnavailable(remoteNodeId)) } @@ -199,7 +199,7 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load val request = switchboard.expectMessageType[Switchboard.GetPeerInfo] request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) peer.expectMessageType[Peer.GetPeerChannels] - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100), None)) probe.expectMessage(PeerUnavailable(remoteNodeId)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggererSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggererSpec.scala index 223b7af750..f237ccd026 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggererSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggererSpec.scala @@ -44,15 +44,15 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. assert(switchboard.expectMessageType[GetPeerInfo].remoteNodeId == remoteNodeId) // We haven't reached the timeout yet. - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(99))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(99), None)) probe.expectNoMessage(100 millis) // We exceed the timeout (we've missed blocks). - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(110))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(110), None)) probe.expectMessage(AsyncPaymentTimeout) // Only get the timeout message once. - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(111))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(111), None)) probe.expectNoMessage(100 millis) } @@ -79,7 +79,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. triggerer ! Watch(probe.ref, remoteNodeId, paymentHash = ByteVector32.Zeroes, timeout = BlockHeight(100)) // We trigger one timeout messages when we reach the timeout - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100), None)) probe.expectMessage(AsyncPaymentTimeout) probe.expectNoMessage(100 millis) @@ -90,7 +90,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. triggerer ! Watch(probe2.ref, remoteNodeId, paymentHash = ByteVector32.Zeroes, timeout = BlockHeight(100)) // We get two timeout messages when we reach the timeout - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100), None)) probe.expectMessage(AsyncPaymentTimeout) probe2.expectMessage(AsyncPaymentTimeout) } @@ -145,7 +145,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. triggerer ! Watch(probe2.ref, remoteNodeId, paymentHash = ByteVector32.One, timeout = BlockHeight(101)) // First watch times out - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100), None)) probe.expectMessage(AsyncPaymentTimeout) // Second watch succeeds @@ -173,7 +173,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, Set(TestProbe().ref.toClassic)) // First remote node times out - system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100))) + system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100), None)) probe.expectMessage(AsyncPaymentTimeout) // Second remote node connects and triggers watch diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 685bea7074..b9510bf458 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -93,6 +93,13 @@ class LightningMessageCodecsSpec extends AnyFunSuite { } } + test("encode block header in init") { + val init = Init(Features.empty, TlvStream(InitTlv.Networks(Block.LivenetGenesisBlock.hash :: Nil), InitTlv.LatestBlockHeader(BlockHeight(800_000), TestConstants.defaultBlockHeader))) + val expected = hex"0000 0000 01206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 fd7e9b54000c350000601d3455bb9fbd966b3ea2dc42d0c22722e4c0c1729fad17210100000000000000000055087fab0c8f3f89f8bcfd4df26c504d81b0a88e04907161838c0c53001af09135edbd64943805175e955e06" + assert(initCodec.encode(init).require.bytes == expected) + assert(initCodec.decode(expected.bits).require.value == init) + } + test("encode/decode warning") { val testCases = Seq( Warning("") -> hex"000100000000000000000000000000000000000000000000000000000000000000000000",