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 833a6d142..6e9b76b57 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 @@ -173,7 +173,9 @@ class ZmqWatcher(client: ExtendedBitcoinClient)(implicit ec: ExecutionContext = case w: WatchConfirmed => self ! TickNewBlock - case w => log.warning(s"ignoring $w (not implemented)") + case w: WatchLost => () // TODO: not implemented + + case w => log.warning(s"ignoring $w") } log.debug(s"adding watch $w for $sender") context.watch(w.channel) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index bb6076fb2..31d38bc48 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -467,15 +467,15 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // note: spec would allow us to keep sending new htlcs after having received their shutdown (and not sent ours) // but we want to converge as fast as possible and they would probably not route them anyway val error = NoMoreHtlcsClosingInProgress(d.channelId) - handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(d.channelUpdate))) + handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(d.channelUpdate)), c) case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) => Try(Commitments.sendAdd(d.commitments, c, origin(c))) match { case Success(Right((commitments1, add))) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending add - case Success(Left(error)) => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(d.channelUpdate))) - case Failure(cause) => handleCommandError(AddHtlcFailed(d.channelId, cause, origin(c), Some(d.channelUpdate))) + case Success(Left(error)) => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(d.channelUpdate)), c) + case Failure(cause) => handleCommandError(AddHtlcFailed(d.channelId, cause, origin(c), Some(d.channelUpdate)), c) } case Event(add: UpdateAddHtlc, d: DATA_NORMAL) => @@ -489,7 +489,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, fulfill)) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending fulfill - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(fulfill: UpdateFulfillHtlc, d: DATA_NORMAL) => @@ -506,7 +506,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, fail)) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending fail - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(c: CMD_FAIL_MALFORMED_HTLC, d: DATA_NORMAL) => @@ -514,7 +514,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, fail)) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending fail - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(fail: UpdateFailHtlc, d: DATA_NORMAL) => @@ -540,7 +540,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, fee)) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending fee - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(fee: UpdateFee, d: DATA_NORMAL) => @@ -549,7 +549,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(CMD_SIGN, d: DATA_NORMAL) => + case Event(c@CMD_SIGN, d: DATA_NORMAL) => d.commitments.remoteNextCommitInfo match { case _ if !Commitments.localHasChanges(d.commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") @@ -560,7 +560,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}") commitments1.localChanges.signed.collect { case u: UpdateFulfillHtlc => relayer ! AckFulfillCmd(u.channelId, u.id) } handleCommandSuccess(sender, store(d.copy(commitments = commitments1))) sending commit - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") @@ -609,15 +609,15 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(revocation)) } - case Event(CMD_CLOSE(localScriptPubKey_opt), d: DATA_NORMAL) => + case Event(c@CMD_CLOSE(localScriptPubKey_opt), d: DATA_NORMAL) => val localScriptPubKey = localScriptPubKey_opt.getOrElse(d.commitments.localParams.defaultFinalScriptPubKey) if (d.localShutdown.isDefined) - handleCommandError(ClosingAlreadyInProgress((d.channelId))) + handleCommandError(ClosingAlreadyInProgress((d.channelId)), c) else if (Commitments.localHasUnsignedOutgoingHtlcs(d.commitments)) // TODO: simplistic behavior, we could also sign-then-close - handleCommandError(CannotCloseWithUnsignedOutgoingHtlcs((d.channelId))) + handleCommandError(CannotCloseWithUnsignedOutgoingHtlcs((d.channelId)), c) else if (!Closing.isValidFinalScriptPubkey(localScriptPubKey)) - handleCommandError(InvalidFinalScript(d.channelId)) + handleCommandError(InvalidFinalScript(d.channelId), c) else { val shutdown = Shutdown(d.channelId, localScriptPubKey) handleCommandSuccess(sender, store(d.copy(localShutdown = Some(shutdown)))) sending shutdown @@ -720,7 +720,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu d.channelAnnouncement match { case None => require(d.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${d.shortChannelId.toHexString} remote=${remoteAnnSigs.shortChannelId.toHexString}") - log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId}") + log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId.toHexString}") import d.commitments.{localParams, remoteParams} val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, localParams.nodeId, remoteParams.nodeId, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) // we use GOTO instead of stay because we want to fire transitions @@ -791,7 +791,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, fulfill)) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending fulfill - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(fulfill: UpdateFulfillHtlc, d: DATA_SHUTDOWN) => @@ -808,7 +808,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, fail)) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending fail - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(c: CMD_FAIL_MALFORMED_HTLC, d: DATA_SHUTDOWN) => @@ -816,7 +816,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, fail)) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending fail - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(fail: UpdateFailHtlc, d: DATA_SHUTDOWN) => @@ -842,7 +842,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, fee)) => if (c.commit) self ! CMD_SIGN handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending fee - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(fee: UpdateFee, d: DATA_SHUTDOWN) => @@ -851,7 +851,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(CMD_SIGN, d: DATA_SHUTDOWN) => + case Event(c@CMD_SIGN, d: DATA_SHUTDOWN) => d.commitments.remoteNextCommitInfo match { case _ if !Commitments.localHasChanges(d.commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") @@ -862,7 +862,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}") commitments1.localChanges.signed.collect { case u: UpdateFulfillHtlc => relayer ! AckFulfillCmd(u.channelId, u.id) } handleCommandSuccess(sender, store(d.copy(commitments = commitments1))) sending commit - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") @@ -932,7 +932,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_SHUTDOWN) => handleRemoteSpentOther(tx, d) - case Event(CMD_CLOSE(_), d: DATA_SHUTDOWN) => handleCommandError(ClosingAlreadyInProgress(d.channelId)) + case Event(c: CMD_CLOSE, d: DATA_SHUTDOWN) => handleCommandError(ClosingAlreadyInProgress(d.channelId), c) case Event(e: Error, d: DATA_SHUTDOWN) => handleRemoteError(e, d) @@ -968,7 +968,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_NEGOTIATING) => handleRemoteSpentOther(tx, d) - case Event(CMD_CLOSE(_), d: DATA_NEGOTIATING) => handleCommandError(ClosingAlreadyInProgress(d.channelId)) + case Event(c: CMD_CLOSE, d: DATA_NEGOTIATING) => handleCommandError(ClosingAlreadyInProgress(d.channelId), c) case Event(e: Error, d: DATA_NEGOTIATING) => handleRemoteError(e, d) @@ -998,7 +998,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu remoteCommitPublished1 } stay using store(d.copy(commitments = commitments1, localCommitPublished = localCommitPublished1, remoteCommitPublished = remoteCommitPublished1, nextRemoteCommitPublished = nextRemoteCommitPublished1)) - case Failure(cause) => handleCommandError(cause) + case Failure(cause) => handleCommandError(cause, c) } case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_CLOSING) => @@ -1036,41 +1036,45 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.warning(s"processing BITCOIN_OUTPUT_SPENT with txid=${tx.txid} tx=$tx") require(tx.txIn.size == 1, s"htlc tx should only have 1 input") val witness = tx.txIn(0).witness - val extracted = witness match { + val extracted_opt = witness match { case ScriptWitness(Seq(localSig, paymentPreimage, htlcOfferedScript)) if paymentPreimage.size == 32 => - log.warning(s"extracted preimage=$paymentPreimage from tx=$tx (claim-htlc-success)") - paymentPreimage + log.info(s"extracted preimage=$paymentPreimage from tx=$tx (claim-htlc-success)") + Some(paymentPreimage) case ScriptWitness(Seq(BinaryData.empty, remoteSig, localSig, paymentPreimage, htlcReceivedScript)) if paymentPreimage.size == 32 => - log.warning(s"extracted preimage=$paymentPreimage from tx=$tx (htlc-success)") - paymentPreimage + log.info(s"extracted preimage=$paymentPreimage from tx=$tx (htlc-success)") + Some(paymentPreimage) case ScriptWitness(Seq(BinaryData.empty, remoteSig, localSig, BinaryData.empty, htlcOfferedScript)) => val paymentHash160 = BinaryData(htlcOfferedScript.slice(109, 109 + 20)) - log.warning(s"extracted paymentHash160=$paymentHash160 from tx=$tx (htlc-timeout)") - paymentHash160 + log.info(s"extracted paymentHash160=$paymentHash160 from tx=$tx (htlc-timeout)") + Some(paymentHash160) case ScriptWitness(Seq(remoteSig, BinaryData.empty, htlcReceivedScript)) => val paymentHash160 = BinaryData(htlcReceivedScript.slice(69, 69 + 20)) - log.warning(s"extracted paymentHash160=$paymentHash160 from tx=$tx (claim-htlc-timeout)") - paymentHash160 + log.info(s"extracted paymentHash160=$paymentHash160 from tx=$tx (claim-htlc-timeout)") + Some(paymentHash160) + case _ => + // this is not an htlc witness (we don't watch only htlc outputs) + None } - // we only consider htlcs in our local commitment, because we only care about outgoing htlcs, which disappear first in the remote commitment - // if an outgoing htlc is in the remote commitment, then: - // - either it is in the local commitment (it was never fulfilled) - // - or we have already received the fulfill and forwarded it upstream - val outgoingHtlcs = d.commitments.localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add) - outgoingHtlcs.collect { - case add if add.paymentHash == sha256(extracted) => - val origin = d.commitments.originChannels(add.id) - log.warning(s"found a match between preimage=$extracted and origin=$origin: htlc was fulfilled") - // let's just pretend we received the preimage from the counterparty - relayer ! ForwardFulfill(UpdateFulfillHtlc(add.channelId, add.id, extracted), origin) - case add if ripemd160(add.paymentHash) == extracted => - val origin = d.commitments.originChannels(add.id) - log.warning(s"found a match between paymentHash160=$extracted and origin=$origin: htlc timed out") - relayer ! Status.Failure(AddHtlcFailed(d.channelId, HtlcTimedout(d.channelId), origin, None)) + extracted_opt map { extracted => + // we only consider htlcs in our local commitment, because we only care about outgoing htlcs, which disappear first in the remote commitment + // if an outgoing htlc is in the remote commitment, then: + // - either it is in the local commitment (it was never fulfilled) + // - or we have already received the fulfill and forwarded it upstream + val outgoingHtlcs = d.commitments.localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add) + outgoingHtlcs.collect { + case add if add.paymentHash == sha256(extracted) => + val origin = d.commitments.originChannels(add.id) + log.warning(s"found a match between preimage=$extracted and origin=$origin: htlc was fulfilled") + // let's just pretend we received the preimage from the counterparty + relayer ! ForwardFulfill(UpdateFulfillHtlc(add.channelId, add.id, extracted), origin) + case add if ripemd160(add.paymentHash) == extracted => + val origin = d.commitments.originChannels(add.id) + log.warning(s"found a match between paymentHash160=$extracted and origin=$origin: htlc timed out") + relayer ! Status.Failure(AddHtlcFailed(d.channelId, HtlcTimedout(d.channelId), origin, None)) + } + // TODO: should we handle local htlcs here as well? currently timed out htlcs that we sent will never have an answer + // TODO: we do not handle the case where htlcs transactions end up being unconfirmed this can happen if an htlc-success tx is published right before a htlc timed out } - // TODO: should we handle local htlcs here as well? currently timed out htlcs that we sent will never have an answer - // TODO: we do not handle the case where htlcs transactions end up being unconfirmed this can happen if an htlc-success tx is published right before a htlc timed out - stay case Event(WatchEventConfirmed(BITCOIN_TX_CONFIRMED(tx), _, _), d: DATA_CLOSING) => @@ -1087,11 +1091,23 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val revokedCommitDone = revokedCommitPublished1.map(Closing.isRevokedCommitDone(_)).exists(_ == true) // we only need one revoked commit done // finally, if one of the unilateral closes is done, we move to CLOSED state, otherwise we stay (note that we don't store the state) val d1 = d.copy(localCommitPublished = localCommitPublished1, remoteCommitPublished = remoteCommitPublished1, nextRemoteCommitPublished = nextRemoteCommitPublished1, revokedCommitPublished = revokedCommitPublished1) - if (mutualCloseDone || localCommitDone || remoteCommitDone || nextRemoteCommitDone || revokedCommitDone) { - log.info(s"channel closed (mutualClose=$mutualCloseDone localCommit=$localCommitDone remoteCommit=$remoteCommitDone nextRemoteCommit=$nextRemoteCommitDone revokedCommit=$revokedCommitDone)") - goto(CLOSED) using d1 + val closeType_opt = if (mutualCloseDone) { + Some("mutual") + } else if (localCommitDone) { + Some("local") + } else if (remoteCommitDone || nextRemoteCommitDone) { + Some("remote") + } else if (revokedCommitDone) { + Some("revoked") } else { - stay using d1 + None + } + closeType_opt match { + case Some(closeType) => + log.info(s"channel closed type=$closeType") + goto(CLOSED) using d1 + case None => + stay using d1 } case Event(_: ChannelReestablish, d: DATA_CLOSING) => @@ -1102,7 +1118,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val error = Error(d.channelId, exc.getMessage.getBytes) stay sending error - case Event(CMD_CLOSE(_), d: DATA_CLOSING) => handleCommandError(ClosingAlreadyInProgress(d.channelId)) + case Event(c: CMD_CLOSE, d: DATA_CLOSING) => handleCommandError(ClosingAlreadyInProgress(d.channelId), c) case Event(e: Error, d: DATA_CLOSING) => handleRemoteError(e, d) @@ -1139,7 +1155,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu ) goto(SYNCING) sending channelReestablish - case Event(c@CMD_CLOSE(_), d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "can't do a mutual close while disconnected"), d, Some(c)) replying "ok" + case Event(c: CMD_CLOSE, d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "can't do a mutual close while disconnected"), d, Some(c)) replying "ok" case Event(c@CurrentBlockCount(count), d: HasCommitments) if d.commitments.hasTimedoutOutgoingHtlcs(count) => // note: this can only happen if state is NORMAL or SHUTDOWN @@ -1162,7 +1178,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu goto(WAIT_FOR_FUNDING_CONFIRMED) case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_LOCKED) => - log.info(s"re-sending fundingLocked") + log.debug(s"re-sending fundingLocked") val nextPerCommitmentPoint = Generators.perCommitPoint(d.commitments.localParams.shaSeed, 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked @@ -1171,7 +1187,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu if (channelReestablish.nextLocalCommitmentNumber == 1 && d.commitments.localCommit.index == 0) { // If next_local_commitment_number is 1 in both the channel_reestablish it sent and received, then the node MUST retransmit funding_locked, otherwise it MUST NOT - log.info(s"re-sending fundingLocked") + log.debug(s"re-sending fundingLocked") val nextPerCommitmentPoint = Generators.perCommitPoint(d.commitments.localParams.shaSeed, 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) forwarder ! fundingLocked @@ -1181,7 +1197,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu d.localShutdown.map { case localShutdown => - log.info(s"re-sending localShutdown") + log.debug(s"re-sending localShutdown") forwarder ! localShutdown } @@ -1223,7 +1239,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu forwarder ! d.localClosingSigned goto(NEGOTIATING) - case Event(c@CMD_CLOSE(_), d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "can't do a mutual close while syncing"), d, Some(c)) + case Event(c: CMD_CLOSE, d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "can't do a mutual close while syncing"), d, Some(c)) case Event(c@CurrentBlockCount(count), d: HasCommitments) if d.commitments.hasTimedoutOutgoingHtlcs(count) => handleLocalError(HtlcTimedout(d.channelId), d, Some(c)) @@ -1273,8 +1289,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.info(s"rejecting htlc request in state=$stateName") val error = ChannelUnavailable(d.channelId) d match { - case normal: DATA_NORMAL => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(normal.channelUpdate))) // can happen if we are in OFFLINE or SYNCING state (channelUpdate will have enable=false) - case _ => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), None)) // we don't provide a channel_update: this will be a permanent channel failure + case normal: DATA_NORMAL => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(normal.channelUpdate)), c) // can happen if we are in OFFLINE or SYNCING state (channelUpdate will have enable=false) + case _ => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), None), c) // we don't provide a channel_update: this will be a permanent channel failure } // we only care about this event in NORMAL and SHUTDOWN state, and we never unregister to the event stream @@ -1343,16 +1359,21 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu stay using newData replying "ok" } - def handleCommandError(cause: Throwable) = { + def handleCommandError(cause: Throwable, cmd: Command) = { + log.error(s"${cause.getMessage} while processing cmd=${cmd.getClass.getSimpleName} in state=$stateName") cause match { - case _: ChannelException => log.error(s"$cause") - case _ => log.error(cause, "") + case _: ChannelException => () + case _ => log.error(cause, s"msg=$cmd stateData=$stateData ") } stay replying Status.Failure(cause) } def handleLocalError(cause: Throwable, d: HasCommitments, msg: Option[Any]) = { - log.error(cause, s"error while processing msg=${msg.getOrElse("n/a")} in state=$stateData ") + log.error(s"${cause.getMessage} while processing msg=${msg.getOrElse("n/a").getClass.getSimpleName} in state=$stateName") + cause match { + case _: ChannelException => () + case _ => log.error(cause, s"msg=${msg.getOrElse("n/a")} stateData=$stateData ") + } val error = Error(d.channelId, cause.getMessage.getBytes) spendLocalCurrent(d) sending error } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index af08f7e81..d786d2fa4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -216,8 +216,11 @@ object Helpers { def generateTx(desc: String)(attempt: Try[TransactionWithInputInfo])(implicit log: LoggingAdapter): Option[TransactionWithInputInfo] = { attempt match { case Success(txinfo) => - log.warning(s"tx generation success: desc=$desc txid=${txinfo.tx.txid} amount=${txinfo.tx.txOut.map(_.amount.amount).sum} tx=${txinfo.tx}") + log.info(s"tx generation success: desc=$desc txid=${txinfo.tx.txid} amount=${txinfo.tx.txOut.map(_.amount.amount).sum} tx=${txinfo.tx}") Some(txinfo) + case Failure(t: TxGenerationSkipped) => + log.info(s"tx generation skipped: desc=$desc reason: ${t.getMessage}") + None case Failure(t) => log.warning(s"tx generation failure: desc=$desc reason: ${t.getMessage}") None diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/TransportHandler.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/TransportHandler.scala index 25e719d76..dbee05f4c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/TransportHandler.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/TransportHandler.scala @@ -131,15 +131,15 @@ class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], co whenUnhandled { case Event(ErrorClosed(cause), _) => - log.debug(s"tcp connection error: $cause") + log.info(s"tcp connection error: $cause") stop(FSM.Normal) case Event(PeerClosed, _) => - log.debug(s"connection closed") + log.info(s"connection closed") stop(FSM.Normal) case Event(Terminated(actor), _) if actor == connection => - log.debug(s"connection terminated, stopping the transport") + log.info(s"connection terminated, stopping the transport") // this can be the connection or the listener, either way it is a cause of death stop(FSM.Normal) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index f4712525f..d5aad271a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -58,8 +58,6 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, previousKnownAddress val h = channels.filter(_._2 == actor).map(_._1) log.info(s"channel closed: channelId=${h.mkString("/")}") stay using d.copy(channels = channels -- h) - - case Event(_: Rebroadcast, _) => stay // ignored } when(INITIALIZING) { @@ -187,7 +185,7 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, previousKnownAddress stay case Event(Terminated(actor), ConnectedData(address_opt, transport, _, channels)) if actor == transport => - log.warning(s"lost connection to $remoteNodeId") + log.info(s"lost connection to $remoteNodeId") channels.values.foreach(_ ! INPUT_DISCONNECTED) goto(DISCONNECTED) using DisconnectedData(address_opt, channels) @@ -224,6 +222,8 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, previousKnownAddress case Event(GetPeerInfo, d) => sender ! PeerInfo(remoteNodeId, stateName.toString, d.address_opt, d.channels.values.toSet.size) // we use toSet to dedup because a channel can have a TemporaryChannelId + a ChannelId stay + + case Event(_: Rebroadcast, _) => stay // ignored } onTransition { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala index 04c1ef05f..59546316d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala @@ -62,7 +62,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR case _: ChannelStateChanged => () case LocalChannelUpdate(_, channelId, shortChannelId, remoteNodeId, _, channelUpdate) => - log.info(s"updating channel_update for channelId=$channelId shortChannelId=${shortChannelId.toHexString} remoteNodeId=$remoteNodeId channelUpdate=$channelUpdate ") + log.debug(s"updating channel_update for channelId=$channelId shortChannelId=${shortChannelId.toHexString} remoteNodeId=$remoteNodeId channelUpdate=$channelUpdate ") context become main(channelUpdates + (channelUpdate.shortChannelId -> channelUpdate)) case LocalChannelDown(_, channelId, shortChannelId, _) => @@ -70,49 +70,68 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR context become main(channelUpdates - shortChannelId) case ForwardAdd(add) => + log.debug(s"received forwarding request for htlc #${add.id} paymentHash=${add.paymentHash} from channelId=${add.channelId} ") Sphinx.parsePacket(nodeParams.privateKey, add.paymentHash, add.onionRoutingPacket) - .map(parsedPacket => (LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(parsedPacket.payload.data)), parsedPacket.nextPacket, parsedPacket.sharedSecret)) match { - case Success((Attempt.Successful(DecodeResult(perHopPayload, _)), nextPacket, _)) if nextPacket.isLastPacket => - log.info(s"looks like we are the final recipient of htlc #${add.id}") - perHopPayload match { + .flatMap { + case Sphinx.ParsedPacket(payload, nextPacket, sharedSecret) => + LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(payload.data)) match { + case Attempt.Successful(DecodeResult(perHopPayload, _)) => Success((perHopPayload, nextPacket, sharedSecret)) + case Attempt.Failure(cause) => Failure(new RuntimeException(cause.messageWithContext)) + } + } match { + case Success((perHopPayload, nextPacket, _)) if nextPacket.isLastPacket => + val cmd = perHopPayload match { case PerHopPayload(_, finalAmountToForward, _) if finalAmountToForward > add.amountMsat => - sender ! CMD_FAIL_HTLC(add.id, Right(FinalIncorrectHtlcAmount(add.amountMsat)), commit = true) + Left(CMD_FAIL_HTLC(add.id, Right(FinalIncorrectHtlcAmount(add.amountMsat)), commit = true)) case PerHopPayload(_, _, finalOutgoingCltvValue) if finalOutgoingCltvValue != add.expiry => - sender ! CMD_FAIL_HTLC(add.id, Right(FinalIncorrectCltvExpiry(add.expiry)), commit = true) + Left(CMD_FAIL_HTLC(add.id, Right(FinalIncorrectCltvExpiry(add.expiry)), commit = true)) case _ if add.expiry < Globals.blockCount.get() + 3 => // TODO: check hardcoded value - sender ! CMD_FAIL_HTLC(add.id, Right(FinalExpiryTooSoon), commit = true) + Left(CMD_FAIL_HTLC(add.id, Right(FinalExpiryTooSoon), commit = true)) case _ => - paymentHandler forward add + Right(add) } - case Success((Attempt.Successful(DecodeResult(perHopPayload, _)), nextPacket, _)) => - channelUpdates.get(perHopPayload.channel_id) match { + cmd match { + case Left(cmdFail) => + log.info(s"rejecting htlc #${add.id} paymentHash=${add.paymentHash} from channelId=${add.channelId} reason=${cmdFail.reason}") + sender ! cmdFail + case Right(addHtlc) => + log.debug(s"forwarding htlc #${add.id} paymentHash=${add.paymentHash} to payment-handler") + paymentHandler forward addHtlc + } + case Success((perHopPayload, nextPacket, _)) => + val cmd = channelUpdates.get(perHopPayload.channel_id) match { case None => // if we don't (yet?) have a channel_update for the next channel, we consider the channel doesn't exist // TODO: use a different channel to the same peer instead? - sender ! CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true) + Left(CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true)) case Some(channelUpdate) if !Announcements.isEnabled(channelUpdate.flags) => - sender ! CMD_FAIL_HTLC(add.id, Right(ChannelDisabled(channelUpdate.flags, channelUpdate)), commit = true) + Left(CMD_FAIL_HTLC(add.id, Right(ChannelDisabled(channelUpdate.flags, channelUpdate)), commit = true)) case Some(channelUpdate) if add.amountMsat < channelUpdate.htlcMinimumMsat => - sender ! CMD_FAIL_HTLC(add.id, Right(AmountBelowMinimum(add.amountMsat, channelUpdate)), commit = true) + Left(CMD_FAIL_HTLC(add.id, Right(AmountBelowMinimum(add.amountMsat, channelUpdate)), commit = true)) case Some(channelUpdate) if add.expiry != perHopPayload.outgoingCltvValue + channelUpdate.cltvExpiryDelta => - sender ! CMD_FAIL_HTLC(add.id, Right(IncorrectCltvExpiry(add.expiry, channelUpdate)), commit = true) + Left(CMD_FAIL_HTLC(add.id, Right(IncorrectCltvExpiry(add.expiry, channelUpdate)), commit = true)) case Some(channelUpdate) if add.expiry < Globals.blockCount.get() + 3 => // TODO: hardcoded value - sender ! CMD_FAIL_HTLC(add.id, Right(ExpiryTooSoon(channelUpdate)), commit = true) + Left(CMD_FAIL_HTLC(add.id, Right(ExpiryTooSoon(channelUpdate)), commit = true)) case _ => - log.info(s"forwarding htlc #${add.id} to shortChannelId=${perHopPayload.channel_id.toHexString}") - register ! Register.ForwardShortId(perHopPayload.channel_id, CMD_ADD_HTLC(perHopPayload.amtToForward, add.paymentHash, perHopPayload.outgoingCltvValue, nextPacket.serialize, upstream_opt = Some(add), commit = true)) + Right(CMD_ADD_HTLC(perHopPayload.amtToForward, add.paymentHash, perHopPayload.outgoingCltvValue, nextPacket.serialize, upstream_opt = Some(add), commit = true)) + } + cmd match { + case Left(cmdFail) => + log.info(s"rejecting htlc #${add.id} paymentHash=${add.paymentHash} from channelId=${add.channelId} to shortChannelId=${perHopPayload.channel_id.toHexString} reason=${cmdFail.reason}") + sender ! cmdFail + case Right(cmdAdd) => + log.info(s"forwarding htlc #${add.id} paymentHash=${add.paymentHash} from channelId=${add.channelId} to shortChannelId=${perHopPayload.channel_id.toHexString}") + register ! Register.ForwardShortId(perHopPayload.channel_id, cmdAdd) } - case Success((Attempt.Failure(cause), _, _)) => - log.error(s"couldn't parse payload: $cause") - sender ! CMD_FAIL_HTLC(add.id, Right(PermanentNodeFailure), commit = true) case Failure(t) => - log.error(t, "couldn't parse onion: ") - // we cannot even parse the onion packet - sender ! CMD_FAIL_MALFORMED_HTLC(add.id, Crypto.sha256(add.onionRoutingPacket), failureCode = FailureMessageCodecs.BADONION, commit = true) + log.warning(s"couldn't parse onion: reason=${t.getMessage}") + val cmdFail = CMD_FAIL_MALFORMED_HTLC(add.id, Crypto.sha256(add.onionRoutingPacket), failureCode = FailureMessageCodecs.BADONION, commit = true) + log.info(s"rejecting htlc #${add.id} paymentHash=${add.paymentHash} from channelId=${add.channelId} reason=malformed onionHash=${cmdFail.onionHash} failureCode=${cmdFail.failureCode}") + sender ! cmdFail } case Status.Failure(Register.ForwardShortIdFailure(Register.ForwardShortId(shortChannelId, CMD_ADD_HTLC(_, _, _, _, Some(add), _)))) => - log.warning(s"couldn't resolve downstream channel $shortChannelId, failing htlc #${add.id}") + log.warning(s"couldn't resolve downstream channel ${shortChannelId.toHexString}, failing htlc #${add.id}") register ! Register.Forward(add.channelId, CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true)) case Status.Failure(AddHtlcFailed(_, error, Local(Some(sender)), _)) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index ac1d91cb0..00f95b139 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -132,28 +132,28 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data] // let's check that the output is indeed a P2WSH multisig 2-of-2 of nodeid1 and nodeid2) val fundingOutputScript = write(pay2wsh(Scripts.multiSig2of2(PublicKey(c.bitcoinKey1), PublicKey(c.bitcoinKey2)))) if (tx.txOut.size < outputIndex + 1) { - log.error(s"invalid script for shortChannelId=${c.shortChannelId}: txid=${tx.txid} does not have outputIndex=$outputIndex ann=$c") + log.error(s"invalid script for shortChannelId=${c.shortChannelId.toHexString}: txid=${tx.txid} does not have outputIndex=$outputIndex ann=$c") None } else if (fundingOutputScript != tx.txOut(outputIndex).publicKeyScript) { - log.error(s"invalid script for shortChannelId=${c.shortChannelId} txid=${tx.txid} ann=$c") + log.error(s"invalid script for shortChannelId=${c.shortChannelId.toHexString} txid=${tx.txid} ann=$c") None } else { watcher ! WatchSpentBasic(self, tx, outputIndex, BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT(c.shortChannelId)) // TODO: check feature bit set - log.debug(s"added channel channelId=${c.shortChannelId}") + log.debug(s"added channel channelId=${c.shortChannelId.toHexString}") context.system.eventStream.publish(ChannelDiscovered(c, tx.txOut(outputIndex).amount)) db.addChannel(c) Some(c) } case IndividualResult(c, Some(tx), false) => // TODO: vulnerability if they flood us with spent funding tx? - log.warning(s"ignoring shortChannelId=${c.shortChannelId} tx=${tx.txid} (funding tx not found in utxo)") + log.warning(s"ignoring shortChannelId=${c.shortChannelId.toHexString} tx=${tx.txid} (funding tx not found in utxo)") // there may be a record if we have just restarted db.removeChannel(c.shortChannelId) None case IndividualResult(c, None, _) => // TODO: blacklist? - log.warning(s"could not retrieve tx for shortChannelId=${c.shortChannelId}") + log.warning(s"could not retrieve tx for shortChannelId=${c.shortChannelId.toHexString}") None } @@ -221,12 +221,12 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data] stay case Event(c: ChannelAnnouncement, d) => - log.debug(s"received channel announcement for shortChannelId=${c.shortChannelId} nodeId1=${c.nodeId1} nodeId2=${c.nodeId2}") + log.debug(s"received channel announcement for shortChannelId=${c.shortChannelId.toHexString} nodeId1=${c.nodeId1} nodeId2=${c.nodeId2}") if (d.channels.containsKey(c.shortChannelId) || d.awaiting.exists(_.shortChannelId == c.shortChannelId) || d.stash.contains(c)) { log.debug(s"ignoring $c (duplicate)") stay } else if (!Announcements.checkSigs(c)) { - log.error(s"bad signature for announcement $c") + log.warning(s"bad signature for announcement $c") sender ! Error(Peer.CHANNELID_ZERO, "bad announcement sig!!!".getBytes()) stay } else { @@ -239,7 +239,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data] log.debug(s"ignoring announcement $n (old timestamp or duplicate)") stay } else if (!Announcements.checkSig(n)) { - log.error(s"bad signature for announcement $n") + log.warning(s"bad signature for announcement $n") sender ! Error(Peer.CHANNELID_ZERO, "bad announcement sig!!!".getBytes()) stay } else if (d.nodes.containsKey(n.nodeId)) { @@ -256,7 +256,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data] log.debug(s"stashing $n") stay using d.copy(stash = d.stash :+ n, origins = d.origins + (n -> sender)) } else { - log.warning(s"ignoring $n (no related channel found)") + log.debug(s"ignoring $n (no related channel found)") // there may be a record if we have just restarted db.removeNode(n.nodeId) stay @@ -271,7 +271,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data] log.debug(s"ignoring $u (old timestamp or duplicate)") stay } else if (!Announcements.checkSig(u, desc.a)) { - log.error(s"bad signature for announcement shortChannelId=${u.shortChannelId.toHexString} $u") + log.warning(s"bad signature for announcement shortChannelId=${u.shortChannelId.toHexString} $u") sender ! Error(Peer.CHANNELID_ZERO, "bad announcement sig!!!".getBytes()) stay } else if (d.updates.contains(desc)) { @@ -296,7 +296,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data] log.debug(s"ignoring $u (old timestamp or duplicate)") stay } else if (!Announcements.checkSig(u, desc.a)) { - log.error(s"bad signature for announcement shortChannelId=${u.shortChannelId.toHexString} $u") + log.warning(s"bad signature for announcement shortChannelId=${u.shortChannelId.toHexString} $u") sender ! Error(Peer.CHANNELID_ZERO, "bad announcement sig!!!".getBytes()) stay } else if (d.privateUpdates.contains(desc)) { @@ -309,7 +309,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data] stay using d.copy(privateUpdates = d.privateUpdates + (desc -> u)) } } else { - log.warning(s"ignoring announcement $u (unknown channel)") + log.debug(s"ignoring announcement $u (unknown channel)") stay } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/ThrottleForwarder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/ThrottleForwarder.scala index 5f503fc63..d370ba3d5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/ThrottleForwarder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/ThrottleForwarder.scala @@ -23,9 +23,9 @@ class ThrottleForwarder(target: ActorRef, messages: Iterable[Any], chunkSize: In override def receive = group(messages) - def group(messages: Iterable[Any]): Receive = { + def group(remaining: Iterable[Any]): Receive = { case Tick => - messages.splitAt(chunkSize) match { + remaining.splitAt(chunkSize) match { case (Nil, _) => clock.cancel() log.debug(s"sent messages=${messages.size} with chunkSize=$chunkSize and delay=$delay") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index c6c2b0757..d2ef3770a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -37,6 +37,11 @@ object Transactions { case class MainPenaltyTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo case class HtlcPenaltyTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo case class ClosingTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo + + sealed trait TxGenerationSkipped extends RuntimeException + case object OutputNotFound extends RuntimeException(s"output not found (probably trimmed)") with TxGenerationSkipped + case object AmountBelowDustLimit extends RuntimeException(s"amount is below dust limit") with TxGenerationSkipped + // @formatter:on /** @@ -133,30 +138,31 @@ object Transactions { */ def getCommitTxNumber(commitTx: Transaction, isFunder: Boolean, localPaymentBasePoint: Point, remotePaymentBasePoint: Point): Long = { val blind = obscuredCommitTxNumber(0, isFunder, localPaymentBasePoint, remotePaymentBasePoint) - val obscured = decodeTxNumber(commitTx.txIn(0).sequence, commitTx.lockTime) + val obscured = decodeTxNumber(commitTx.txIn.head.sequence, commitTx.lockTime) obscured ^ blind } /** * This is a trick to split and encode a 48-bit txnumber into the sequence and locktime fields of a tx * - * @param txnumber + * @param txnumber commitment number * @return (sequence, locktime) */ - def encodeTxNumber(txnumber: Long) = { + def encodeTxNumber(txnumber: Long): (Long, Long) = { require(txnumber <= 0xffffffffffffL, "txnumber must be lesser than 48 bits long") (0x80000000L | (txnumber >> 24), (txnumber & 0xffffffL) | 0x20000000) } - def decodeTxNumber(sequence: Long, locktime: Long) = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) + def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): CommitTx = { val commitFee = commitTxFee(localDustLimit, spec) - val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = localIsFunder match { - case true => (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) - case false => (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - commitFee) + val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) + } else { + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - commitFee) } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway val toLocalDelayedOutput_opt = if (toLocalAmount >= localDustLimit) Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) else None @@ -183,9 +189,10 @@ object Transactions { val redeemScript = htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.paymentHash)) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript) - require(outputIndex >= 0, "output not found (was trimmed?)") val amount = MilliSatoshi(htlc.amountMsat) - fee - require(amount >= localDustLimit, "amount lesser than dust limit") + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) HtlcTimeoutTx(input, Transaction( version = 2, @@ -199,9 +206,10 @@ object Transactions { val redeemScript = htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.paymentHash), htlc.expiry) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript) - require(outputIndex >= 0, "output not found (was trimmed?)") val amount = MilliSatoshi(htlc.amountMsat) - fee - require(amount >= localDustLimit, "amount lesser than dust limit") + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) HtlcSuccessTx(input, Transaction( version = 2, @@ -223,10 +231,11 @@ object Transactions { val redeemScript = htlcOffered(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, ripemd160(htlc.paymentHash)) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript) - require(outputIndex >= 0, "output not found (was trimmed?)") val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) val amount = input.txOut.amount - fee - require(amount >= localDustLimit, "amount lesser than dust limit") + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } ClaimHtlcSuccessTx(input, Transaction( version = 2, txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil, @@ -239,10 +248,11 @@ object Transactions { val redeemScript = htlcReceived(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, ripemd160(htlc.paymentHash), htlc.expiry) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript) - require(outputIndex >= 0, "output not found (was trimmed?)") val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) val amount = input.txOut.amount - fee - require(amount >= localDustLimit, "amount lesser than dust limit") + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } ClaimHtlcTimeoutTx(input, Transaction( version = 2, txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil, @@ -255,10 +265,11 @@ object Transactions { val redeemScript = Script.pay2pkh(localPaymentPubkey) val pubkeyScript = write(pay2wpkh(localPaymentPubkey)) val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript) - require(outputIndex >= 0, "output not found (was trimmed?)") val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) val amount = input.txOut.amount - fee - require(amount >= localDustLimit, "amount lesser than dust limit") + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } ClaimP2WPKHOutputTx(input, Transaction( version = 2, txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil, @@ -271,10 +282,11 @@ object Transactions { val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript) - require(outputIndex >= 0, "output not found (was trimmed?)") val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) val amount = input.txOut.amount - fee - require(amount >= localDustLimit, "amount lesser than dust limit") + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } ClaimDelayedOutputTx(input, Transaction( version = 2, txIn = TxIn(input.outPoint, Array.emptyByteArray, toLocalDelay) :: Nil, @@ -287,10 +299,11 @@ object Transactions { val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript) - require(outputIndex >= 0, "output not found (was trimmed?)") val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) val amount = input.txOut.amount - fee - require(amount >= localDustLimit, "amount lesser than dust limit") + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } MainPenaltyTx(input, Transaction( version = 2, txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil, @@ -301,11 +314,12 @@ object Transactions { def makeHtlcPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi): HtlcPenaltyTx = ??? def makeClosingTx(commitTxInput: InputInfo, localScriptPubKey: BinaryData, remoteScriptPubKey: BinaryData, localIsFunder: Boolean, dustLimit: Satoshi, closingFee: Satoshi, spec: CommitmentSpec): ClosingTx = { - require(spec.htlcs.size == 0, "there shouldn't be any pending htlcs") + require(spec.htlcs.isEmpty, "there shouldn't be any pending htlcs") - val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = localIsFunder match { - case true => (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - closingFee, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) - case false => (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - closingFee) + val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - closingFee, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) + } else { + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - closingFee) } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway val toLocalOutput_opt = if (toLocalAmount >= dustLimit) Some(TxOut(toLocalAmount, localScriptPubKey)) else None @@ -319,7 +333,14 @@ object Transactions { ClosingTx(commitTxInput, LexicographicalOrdering.sort(tx)) } - def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: BinaryData): Int = tx.txOut.indexWhere(_.publicKeyScript == pubkeyScript) + def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: BinaryData): Int = { + val outputIndex = tx.txOut.indexWhere(_.publicKeyScript == pubkeyScript) + if (outputIndex >= 0) { + outputIndex + } else { + throw OutputNotFound + } + } def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: Seq[ScriptElt]): Int = findPubKeyScriptIndex(tx, write(pubkeyScript)) @@ -336,7 +357,7 @@ object Transactions { } def sign(txinfo: TransactionWithInputInfo, key: PrivateKey): BinaryData = { - require(txinfo.tx.txIn.size == 1, "only one input allowed") + require(txinfo.tx.txIn.lengthCompare(1) == 0, "only one input allowed") sign(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, txinfo.input.txOut.amount, key) } @@ -386,7 +407,7 @@ object Transactions { } def checkSpendable(txinfo: TransactionWithInputInfo): Try[Unit] = - Try(Transaction.correctlySpends(txinfo.tx, Map(txinfo.tx.txIn(0).outPoint -> txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + Try(Transaction.correctlySpends(txinfo.tx, Map(txinfo.tx.txIn.head.outPoint -> txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) def checkSig(txinfo: TransactionWithInputInfo, sig: BinaryData, pubKey: PublicKey): Boolean = { val data = Transaction.hashForSigning(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, SIGHASH_ALL, txinfo.input.txOut.amount, SIGVERSION_WITNESS_V0) diff --git a/eclair-node/src/main/resources/logback.xml b/eclair-node/src/main/resources/logback.xml index dc73202bf..4d5f81c63 100644 --- a/eclair-node/src/main/resources/logback.xml +++ b/eclair-node/src/main/resources/logback.xml @@ -17,10 +17,6 @@ - - - - diff --git a/eclair-node/src/main/resources/logback_colors.xml b/eclair-node/src/main/resources/logback_colors.xml index e94c5023f..16a34d08c 100644 --- a/eclair-node/src/main/resources/logback_colors.xml +++ b/eclair-node/src/main/resources/logback_colors.xml @@ -13,18 +13,7 @@ System.out false - %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %yellow(%msg) %ex{12}%n - - - - - - System.out - false - - %boldYellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %yellow(%msg) - %ex{12}%n - + %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %yellow(%msg) %ex{12}%n @@ -32,8 +21,7 @@ System.out false - %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %red(%msg) %ex{12}%n - + %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %red(%msg) %ex{12}%n @@ -41,9 +29,7 @@ System.out false - %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} channelId=%X{channelId} - %blue(%msg) - %ex{12}%n - + %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} channelId=%X{channelId} - %blue(%msg) %ex{12}%n @@ -51,8 +37,7 @@ System.out false - %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %cyan(%msg) %ex{12}%n - + %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %cyan(%msg) %ex{12}%n @@ -60,8 +45,7 @@ System.out false - %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %green(%msg) %ex{12}%n - + %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %green(%msg) %ex{12}%n @@ -69,8 +53,7 @@ System.out false - %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %magenta(%msg) %ex{12}%n - + %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %magenta(%msg) %ex{12}%n @@ -78,19 +61,19 @@ - + - + - + - + @@ -98,7 +81,7 @@ - +