mirror of
https://github.com/ACINQ/eclair.git
synced 2025-01-19 05:33:59 +01:00
* removed the `Direction` class * improved the non-reg test for htlcs - check actual content instead of only success and roundtrip - use randomized data for all fields instead of all-zero - check the remaining data, not only the decoded value (codecs are chained so a regression here will cause the next codec to fail) Co-Authored-By: Bastien Teinturier <31281497+t-bast@users.noreply.github.com>
This commit is contained in:
parent
e8dac7324f
commit
4875d4c879
@ -22,6 +22,7 @@ import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto}
|
||||
import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets}
|
||||
import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx}
|
||||
import fr.acinq.eclair.payment.relay.{Origin, Relayer}
|
||||
import fr.acinq.eclair.transactions.DirectedHtlc._
|
||||
import fr.acinq.eclair.transactions.Transactions._
|
||||
import fr.acinq.eclair.transactions._
|
||||
import fr.acinq.eclair.wire._
|
||||
@ -63,10 +64,13 @@ case class Commitments(channelVersion: ChannelVersion,
|
||||
|
||||
def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight
|
||||
|
||||
def timedOutOutgoingHtlcs(blockheight: Long): Set[UpdateAddHtlc] =
|
||||
(localCommit.spec.htlcs.filter(htlc => htlc.direction == OUT && blockheight >= htlc.add.cltvExpiry.toLong) ++
|
||||
remoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry.toLong) ++
|
||||
remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry.toLong)).getOrElse(Set.empty[DirectedHtlc])).map(_.add)
|
||||
def timedOutOutgoingHtlcs(blockheight: Long): Set[UpdateAddHtlc] = {
|
||||
def expired(add: UpdateAddHtlc) = blockheight >= add.cltvExpiry.toLong
|
||||
|
||||
localCommit.spec.htlcs.collect(outgoing).filter(expired) ++
|
||||
remoteCommit.spec.htlcs.collect(incoming).filter(expired) ++
|
||||
remoteNextCommitInfo.left.toSeq.flatMap(_.nextRemoteCommit.spec.htlcs.collect(incoming).filter(expired).toSet)
|
||||
}
|
||||
|
||||
/**
|
||||
* HTLCs that are close to timing out upstream are potentially dangerous. If we received the pre-image for those
|
||||
@ -75,9 +79,9 @@ case class Commitments(channelVersion: ChannelVersion,
|
||||
* and our HTLC success in case of a force-close.
|
||||
*/
|
||||
def almostTimedOutIncomingHtlcs(blockheight: Long, fulfillSafety: CltvExpiryDelta): Set[UpdateAddHtlc] = {
|
||||
localCommit.spec.htlcs.collect {
|
||||
case htlc if htlc.direction == IN && blockheight >= (htlc.add.cltvExpiry - fulfillSafety).toLong => htlc.add
|
||||
}
|
||||
def nearlyExpired(add: UpdateAddHtlc) = blockheight >= (add.cltvExpiry - fulfillSafety).toLong
|
||||
|
||||
localCommit.spec.htlcs.collect(incoming).filter(nearlyExpired)
|
||||
}
|
||||
|
||||
def addLocalProposal(proposal: UpdateMessage): Commitments = Commitments.addLocalProposal(this, proposal)
|
||||
@ -187,7 +191,7 @@ object Commitments {
|
||||
val remoteCommit1 = commitments1.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit).getOrElse(commitments1.remoteCommit)
|
||||
val reduced = CommitmentSpec.reduce(remoteCommit1.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed)
|
||||
// the HTLC we are about to create is outgoing, but from their point of view it is incoming
|
||||
val outgoingHtlcs = reduced.htlcs.filter(_.direction == IN)
|
||||
val outgoingHtlcs = reduced.htlcs.collect(incoming)
|
||||
|
||||
// note that the funder pays the fee, so if sender != funder, both sides will have to afford this payment
|
||||
val fees = commitTxFee(commitments1.remoteParams.dustLimit, reduced)
|
||||
@ -207,7 +211,7 @@ object Commitments {
|
||||
}
|
||||
|
||||
// NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since outgoingHtlcs is a Set).
|
||||
val htlcValueInFlight = outgoingHtlcs.toSeq.map(_.add.amountMsat).sum
|
||||
val htlcValueInFlight = outgoingHtlcs.toSeq.map(_.amountMsat).sum
|
||||
if (commitments1.remoteParams.maxHtlcValueInFlightMsat < htlcValueInFlight) {
|
||||
// TODO: this should be a specific UPDATE error
|
||||
return Failure(HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.remoteParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight))
|
||||
@ -232,7 +236,7 @@ object Commitments {
|
||||
// let's compute the current commitment *as seen by us* including this change
|
||||
val commitments1 = addRemoteProposal(commitments, add).copy(remoteNextHtlcId = commitments.remoteNextHtlcId + 1)
|
||||
val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed)
|
||||
val incomingHtlcs = reduced.htlcs.filter(_.direction == IN)
|
||||
val incomingHtlcs = reduced.htlcs.collect(incoming)
|
||||
|
||||
// note that the funder pays the fee, so if sender != funder, both sides will have to afford this payment
|
||||
val fees = commitTxFee(commitments1.remoteParams.dustLimit, reduced)
|
||||
@ -251,7 +255,7 @@ object Commitments {
|
||||
}
|
||||
|
||||
// NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since incomingHtlcs is a Set).
|
||||
val htlcValueInFlight = incomingHtlcs.toSeq.map(_.add.amountMsat).sum
|
||||
val htlcValueInFlight = incomingHtlcs.toSeq.map(_.amountMsat).sum
|
||||
if (commitments1.localParams.maxHtlcValueInFlightMsat < htlcValueInFlight) {
|
||||
throw HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.localParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight)
|
||||
}
|
||||
@ -263,16 +267,24 @@ object Commitments {
|
||||
commitments1
|
||||
}
|
||||
|
||||
def getHtlcCrossSigned(commitments: Commitments, directionRelativeToLocal: Direction, htlcId: Long): Option[UpdateAddHtlc] = for {
|
||||
localSigned <- commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit).getOrElse(commitments.remoteCommit).spec.findHtlcById(htlcId, directionRelativeToLocal.opposite)
|
||||
remoteSigned <- commitments.localCommit.spec.findHtlcById(htlcId, directionRelativeToLocal)
|
||||
def getOutgoingHtlcCrossSigned(commitments: Commitments, htlcId: Long): Option[UpdateAddHtlc] = for {
|
||||
localSigned <- commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit).getOrElse(commitments.remoteCommit).spec.findIncomingHtlcById(htlcId)
|
||||
remoteSigned <- commitments.localCommit.spec.findOutgoingHtlcById(htlcId)
|
||||
} yield {
|
||||
require(localSigned.add == remoteSigned.add)
|
||||
localSigned.add
|
||||
}
|
||||
|
||||
def getIncomingHtlcCrossSigned(commitments: Commitments, htlcId: Long): Option[UpdateAddHtlc] = for {
|
||||
localSigned <- commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit).getOrElse(commitments.remoteCommit).spec.findOutgoingHtlcById(htlcId)
|
||||
remoteSigned <- commitments.localCommit.spec.findIncomingHtlcById(htlcId)
|
||||
} yield {
|
||||
require(localSigned.add == remoteSigned.add)
|
||||
localSigned.add
|
||||
}
|
||||
|
||||
def sendFulfill(commitments: Commitments, cmd: CMD_FULFILL_HTLC): Try[(Commitments, UpdateFulfillHtlc)] =
|
||||
getHtlcCrossSigned(commitments, IN, cmd.id) match {
|
||||
getIncomingHtlcCrossSigned(commitments, cmd.id) match {
|
||||
case Some(htlc) if alreadyProposed(commitments.localChanges.proposed, htlc.id) =>
|
||||
// we have already sent a fail/fulfill for this htlc
|
||||
Failure(UnknownHtlcId(commitments.channelId, cmd.id))
|
||||
@ -285,14 +297,14 @@ object Commitments {
|
||||
}
|
||||
|
||||
def receiveFulfill(commitments: Commitments, fulfill: UpdateFulfillHtlc): Try[(Commitments, Origin, UpdateAddHtlc)] =
|
||||
getHtlcCrossSigned(commitments, OUT, fulfill.id) match {
|
||||
getOutgoingHtlcCrossSigned(commitments, fulfill.id) match {
|
||||
case Some(htlc) if htlc.paymentHash == sha256(fulfill.paymentPreimage) => Try((addRemoteProposal(commitments, fulfill), commitments.originChannels(fulfill.id), htlc))
|
||||
case Some(_) => Failure(InvalidHtlcPreimage(commitments.channelId, fulfill.id))
|
||||
case None => Failure(UnknownHtlcId(commitments.channelId, fulfill.id))
|
||||
}
|
||||
|
||||
def sendFail(commitments: Commitments, cmd: CMD_FAIL_HTLC, nodeSecret: PrivateKey): Try[(Commitments, UpdateFailHtlc)] =
|
||||
getHtlcCrossSigned(commitments, IN, cmd.id) match {
|
||||
getIncomingHtlcCrossSigned(commitments, cmd.id) match {
|
||||
case Some(htlc) if alreadyProposed(commitments.localChanges.proposed, htlc.id) =>
|
||||
// we have already sent a fail/fulfill for this htlc
|
||||
Failure(UnknownHtlcId(commitments.channelId, cmd.id))
|
||||
@ -317,7 +329,7 @@ object Commitments {
|
||||
if ((cmd.failureCode & FailureMessageCodecs.BADONION) == 0) {
|
||||
Failure(InvalidFailureCode(commitments.channelId))
|
||||
} else {
|
||||
getHtlcCrossSigned(commitments, IN, cmd.id) match {
|
||||
getIncomingHtlcCrossSigned(commitments, cmd.id) match {
|
||||
case Some(htlc) if alreadyProposed(commitments.localChanges.proposed, htlc.id) =>
|
||||
// we have already sent a fail/fulfill for this htlc
|
||||
Failure(UnknownHtlcId(commitments.channelId, cmd.id))
|
||||
@ -331,7 +343,7 @@ object Commitments {
|
||||
}
|
||||
|
||||
def receiveFail(commitments: Commitments, fail: UpdateFailHtlc): Try[(Commitments, Origin, UpdateAddHtlc)] =
|
||||
getHtlcCrossSigned(commitments, OUT, fail.id) match {
|
||||
getOutgoingHtlcCrossSigned(commitments, fail.id) match {
|
||||
case Some(htlc) => Try((addRemoteProposal(commitments, fail), commitments.originChannels(fail.id), htlc))
|
||||
case None => Failure(UnknownHtlcId(commitments.channelId, fail.id))
|
||||
}
|
||||
@ -341,7 +353,7 @@ object Commitments {
|
||||
if ((fail.failureCode & FailureMessageCodecs.BADONION) == 0) {
|
||||
Failure(InvalidFailureCode(commitments.channelId))
|
||||
} else {
|
||||
getHtlcCrossSigned(commitments, OUT, fail.id) match {
|
||||
getOutgoingHtlcCrossSigned(commitments, fail.id) match {
|
||||
case Some(htlc) => Try((addRemoteProposal(commitments, fail), commitments.originChannels(fail.id), htlc))
|
||||
case None => Failure(UnknownHtlcId(commitments.channelId, fail.id))
|
||||
}
|
||||
@ -396,7 +408,7 @@ object Commitments {
|
||||
if (missing < 0.sat) {
|
||||
Failure(CannotAffordFees(commitments.channelId, missing = -missing, reserve = commitments1.localParams.channelReserve, fees = fees))
|
||||
} else {
|
||||
Success(commitments1)
|
||||
Success(commitments1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -430,7 +442,7 @@ object Commitments {
|
||||
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), remoteNextPerCommitmentPoint))
|
||||
|
||||
// NB: IN/OUT htlcs are inverted because this is the remote commit
|
||||
log.info(s"built remote commit number=${remoteCommit.index + 1} toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx)
|
||||
log.info(s"built remote commit number=${remoteCommit.index + 1} toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.collect(outgoing).map(_.id).mkString(","), spec.htlcs.collect(incoming).map(_.id).mkString(","), remoteCommitTx.tx)
|
||||
|
||||
// don't sign if they don't get paid
|
||||
val commitSig = CommitSig(
|
||||
@ -477,7 +489,7 @@ object Commitments {
|
||||
val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, channelVersion, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec)
|
||||
val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
|
||||
|
||||
log.info(s"built local commit number=${localCommit.index + 1} toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx)
|
||||
log.info(s"built local commit number=${localCommit.index + 1} toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.collect(incoming).map(_.id).mkString(","), spec.htlcs.collect(outgoing).map(_.id).mkString(","), localCommitTx.tx)
|
||||
|
||||
// TODO: should we have optional sig? (original comment: this tx will NOT be signed if our output is empty)
|
||||
|
||||
@ -543,18 +555,18 @@ object Commitments {
|
||||
// same for fails: we need to make sure that they are in neither commitment before propagating the fail upstream
|
||||
case fail: UpdateFailHtlc =>
|
||||
val origin = commitments.originChannels(fail.id)
|
||||
val add = commitments.remoteCommit.spec.findHtlcById(fail.id, IN).map(_.add).get
|
||||
val add = commitments.remoteCommit.spec.findIncomingHtlcById(fail.id).map(_.add).get
|
||||
Relayer.ForwardRemoteFail(fail, origin, add)
|
||||
// same as above
|
||||
case fail: UpdateFailMalformedHtlc =>
|
||||
val origin = commitments.originChannels(fail.id)
|
||||
val add = commitments.remoteCommit.spec.findHtlcById(fail.id, IN).map(_.add).get
|
||||
val add = commitments.remoteCommit.spec.findIncomingHtlcById(fail.id).map(_.add).get
|
||||
Relayer.ForwardRemoteFailMalformed(fail, origin, add)
|
||||
}
|
||||
// the outgoing following htlcs have been completed (fulfilled or failed) when we received this revocation
|
||||
// they have been removed from both local and remote commitment
|
||||
// (since fulfill/fail are sent by remote, they are (1) signed by them, (2) revoked by us, (3) signed by us, (4) revoked by them
|
||||
val completedOutgoingHtlcs = commitments.remoteCommit.spec.htlcs.filter(_.direction == IN).map(_.add.id) -- theirNextCommit.spec.htlcs.filter(_.direction == IN).map(_.add.id)
|
||||
val completedOutgoingHtlcs = commitments.remoteCommit.spec.htlcs.collect(incoming).map(_.id) -- theirNextCommit.spec.htlcs.collect(incoming).map(_.id)
|
||||
// we remove the newly completed htlcs from the origin map
|
||||
val originChannels1 = commitments.originChannels -- completedOutgoingHtlcs
|
||||
val commitments1 = commitments.copy(
|
||||
|
@ -26,6 +26,7 @@ import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets}
|
||||
import fr.acinq.eclair.channel.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL
|
||||
import fr.acinq.eclair.crypto.{Generators, KeyManager}
|
||||
import fr.acinq.eclair.db.ChannelsDb
|
||||
import fr.acinq.eclair.transactions.DirectedHtlc._
|
||||
import fr.acinq.eclair.transactions.Scripts._
|
||||
import fr.acinq.eclair.transactions.Transactions._
|
||||
import fr.acinq.eclair.transactions._
|
||||
@ -835,10 +836,9 @@ object Helpers {
|
||||
// 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 = localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add)
|
||||
outgoingHtlcs.collect {
|
||||
case add if add.paymentHash == sha256(paymentPreimage) => (add, paymentPreimage)
|
||||
}
|
||||
localCommit.spec.htlcs.collect {
|
||||
case OutgoingHtlc(add) if add.paymentHash == sha256(paymentPreimage) => (add, paymentPreimage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -864,14 +864,14 @@ object Helpers {
|
||||
def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] =
|
||||
if (tx.txid == localCommit.publishableTxs.commitTx.tx.txid) {
|
||||
// the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx)
|
||||
(localCommit.spec.htlcs.filter(_.direction == OUT) -- Transactions.trimOfferedHtlcs(localDustLimit, localCommit.spec)).map(_.add)
|
||||
(localCommit.spec.htlcs.collect(outgoing) -- Transactions.trimOfferedHtlcs(localDustLimit, localCommit.spec).map(_.add))
|
||||
} else {
|
||||
// maybe this is a timeout tx, in that case we can resolve and fail the corresponding htlc
|
||||
tx.txIn.map(_.witness match {
|
||||
case ScriptWitness(Seq(ByteVector.empty, remoteSig, localSig, ByteVector.empty, htlcOfferedScript)) =>
|
||||
val paymentHash160 = htlcOfferedScript.slice(109, 109 + 20)
|
||||
log.info(s"extracted paymentHash160=$paymentHash160 from tx=$tx (htlc-timeout)")
|
||||
localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add).filter(add => ripemd160(add.paymentHash) == paymentHash160)
|
||||
localCommit.spec.htlcs.collect(outgoing).filter(add => ripemd160(add.paymentHash) == paymentHash160)
|
||||
case _ => Set.empty
|
||||
}).toSet.flatten
|
||||
}
|
||||
@ -886,14 +886,14 @@ object Helpers {
|
||||
def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] =
|
||||
if (tx.txid == remoteCommit.txid) {
|
||||
// the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx)
|
||||
(remoteCommit.spec.htlcs.filter(_.direction == IN) -- Transactions.trimReceivedHtlcs(remoteDustLimit, remoteCommit.spec)).map(_.add)
|
||||
(remoteCommit.spec.htlcs.collect(incoming) -- Transactions.trimReceivedHtlcs(remoteDustLimit, remoteCommit.spec).map(_.add))
|
||||
} else {
|
||||
// maybe this is a timeout tx, in that case we can resolve and fail the corresponding htlc
|
||||
tx.txIn.map(_.witness match {
|
||||
case ScriptWitness(Seq(remoteSig, ByteVector.empty, htlcReceivedScript)) =>
|
||||
val paymentHash160 = htlcReceivedScript.slice(69, 69 + 20)
|
||||
log.info(s"extracted paymentHash160=$paymentHash160 from tx=$tx (claim-htlc-timeout)")
|
||||
remoteCommit.spec.htlcs.filter(_.direction == IN).map(_.add).filter(add => ripemd160(add.paymentHash) == paymentHash160)
|
||||
remoteCommit.spec.htlcs.collect(incoming).filter { add => ripemd160(add.paymentHash) == paymentHash160 }
|
||||
case _ => Set.empty
|
||||
}).toSet.flatten
|
||||
}
|
||||
@ -906,11 +906,11 @@ object Helpers {
|
||||
*/
|
||||
def onchainOutgoingHtlcs(localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[RemoteCommit], tx: Transaction): Set[UpdateAddHtlc] = {
|
||||
if (localCommit.publishableTxs.commitTx.tx.txid == tx.txid) {
|
||||
localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add)
|
||||
localCommit.spec.htlcs.collect(outgoing)
|
||||
} else if (remoteCommit.txid == tx.txid) {
|
||||
remoteCommit.spec.htlcs.filter(_.direction == IN).map(_.add)
|
||||
remoteCommit.spec.htlcs.collect(incoming)
|
||||
} else if (nextRemoteCommit_opt.map(_.txid).contains(tx.txid)) {
|
||||
nextRemoteCommit_opt.get.spec.htlcs.filter(_.direction == IN).map(_.add)
|
||||
nextRemoteCommit_opt.get.spec.htlcs.collect(incoming)
|
||||
} else {
|
||||
Set.empty
|
||||
}
|
||||
@ -930,14 +930,14 @@ object Helpers {
|
||||
// our commit got confirmed, so any htlc that we signed but they didn't sign will never reach the chain
|
||||
val mostRecentRemoteCommit = nextRemoteCommit_opt.getOrElse(remoteCommit)
|
||||
// NB: from the p.o.v of remote, their incoming htlcs are our outgoing htlcs
|
||||
mostRecentRemoteCommit.spec.htlcs.filter(_.direction == IN).map(_.add) -- localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add)
|
||||
mostRecentRemoteCommit.spec.htlcs.collect(incoming) -- localCommit.spec.htlcs.collect(outgoing)
|
||||
} else if (remoteCommit.txid == tx.txid) {
|
||||
// their commit got confirmed
|
||||
nextRemoteCommit_opt match {
|
||||
case Some(nextRemoteCommit) =>
|
||||
// we had signed a new commitment but they committed the previous one
|
||||
// any htlc that we signed in the new commitment that they didn't sign will never reach the chain
|
||||
nextRemoteCommit.spec.htlcs.filter(_.direction == IN).map(_.add) -- localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add)
|
||||
nextRemoteCommit.spec.htlcs.collect(incoming) -- localCommit.spec.htlcs.collect(outgoing)
|
||||
case None =>
|
||||
// their last commitment got confirmed, so no htlcs will be overridden, they will timeout or be fulfilled on chain
|
||||
Set.empty
|
||||
|
@ -26,7 +26,8 @@ import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.db._
|
||||
import fr.acinq.eclair.payment.Monitoring.Tags
|
||||
import fr.acinq.eclair.payment.{IncomingPacket, PaymentFailed, PaymentSent}
|
||||
import fr.acinq.eclair.transactions.{IN, OUT}
|
||||
import fr.acinq.eclair.transactions.DirectedHtlc.outgoing
|
||||
import fr.acinq.eclair.transactions.OutgoingHtlc
|
||||
import fr.acinq.eclair.wire.{TemporaryNodeFailure, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.{LongToBtcAmount, NodeParams}
|
||||
import scodec.bits.ByteVector
|
||||
@ -83,7 +84,7 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, commandBuffer: ActorRef, in
|
||||
val acked = brokenHtlcs.notRelayed
|
||||
.filter(_.add.channelId == data.channelId) // only consider htlcs related to this channel
|
||||
.filter {
|
||||
case IncomingHtlc(htlc, preimage) if Commitments.getHtlcCrossSigned(data.commitments, IN, htlc.id).isDefined =>
|
||||
case IncomingHtlc(htlc, preimage) if Commitments.getIncomingHtlcCrossSigned(data.commitments, htlc.id).isDefined =>
|
||||
// this htlc is cross signed in the current commitment, we can settle it
|
||||
preimage match {
|
||||
case Some(preimage) =>
|
||||
@ -292,8 +293,7 @@ object PostRestartHtlcCleaner {
|
||||
// we subsequently sign it. That's why we need to look in *their* commitment with direction=OUT.
|
||||
val htlcsIn = channels
|
||||
.flatMap(_.commitments.remoteCommit.spec.htlcs)
|
||||
.filter(_.direction == OUT)
|
||||
.map(_.add)
|
||||
.collect(outgoing)
|
||||
.map(IncomingPacket.decrypt(_, privateKey, features))
|
||||
.collect {
|
||||
// When we're not the final recipient, we'll only consider HTLCs that aren't relayed downstream, so no need to look for a preimage.
|
||||
@ -333,8 +333,7 @@ object PostRestartHtlcCleaner {
|
||||
val channel2Htlc: Set[(ByteVector32, Long)] =
|
||||
channels
|
||||
.flatMap(_.commitments.remoteCommit.spec.htlcs)
|
||||
.filter(_.direction == OUT)
|
||||
.map(htlc => (htlc.add.channelId, htlc.add.id))
|
||||
.collect { case OutgoingHtlc(add) => (add.channelId, add.id) }
|
||||
.toSet
|
||||
|
||||
val pendingRelay: Set[(ByteVector32, Long)] = relayDb.listPendingRelay()
|
||||
|
@ -23,37 +23,55 @@ import fr.acinq.eclair.wire._
|
||||
* Created by PM on 07/12/2016.
|
||||
*/
|
||||
|
||||
// @formatter:off
|
||||
sealed trait Direction { def opposite: Direction }
|
||||
case object IN extends Direction { def opposite = OUT }
|
||||
case object OUT extends Direction { def opposite = IN }
|
||||
// @formatter:on
|
||||
|
||||
sealed trait CommitmentOutput
|
||||
|
||||
object CommitmentOutput {
|
||||
|
||||
case object ToLocal extends CommitmentOutput
|
||||
|
||||
case object ToRemote extends CommitmentOutput
|
||||
|
||||
case class InHtlc(incomingHtlc: IncomingHtlc) extends CommitmentOutput
|
||||
|
||||
case class OutHtlc(outgoingHtlc: OutgoingHtlc) extends CommitmentOutput
|
||||
|
||||
}
|
||||
|
||||
sealed trait DirectedHtlc {
|
||||
def direction: Direction
|
||||
val add: UpdateAddHtlc
|
||||
|
||||
def opposite: DirectedHtlc = this match {
|
||||
case IncomingHtlc(_) => OutgoingHtlc(add)
|
||||
case OutgoingHtlc(_) => IncomingHtlc(add)
|
||||
}
|
||||
|
||||
def direction: String = this match {
|
||||
case IncomingHtlc(_) => "IN"
|
||||
case OutgoingHtlc(_) => "OUT"
|
||||
}
|
||||
}
|
||||
|
||||
case class IncomingHtlc(add: UpdateAddHtlc) extends DirectedHtlc { override def direction: Direction = IN }
|
||||
case class OutgoingHtlc(add: UpdateAddHtlc) extends DirectedHtlc { override def direction: Direction = OUT }
|
||||
object DirectedHtlc {
|
||||
def incoming: PartialFunction[DirectedHtlc, UpdateAddHtlc] = {
|
||||
case h: IncomingHtlc => h.add
|
||||
}
|
||||
|
||||
def outgoing: PartialFunction[DirectedHtlc, UpdateAddHtlc] = {
|
||||
case h: OutgoingHtlc => h.add
|
||||
}
|
||||
}
|
||||
|
||||
case class IncomingHtlc(add: UpdateAddHtlc) extends DirectedHtlc
|
||||
|
||||
case class OutgoingHtlc(add: UpdateAddHtlc) extends DirectedHtlc
|
||||
|
||||
final case class CommitmentSpec(htlcs: Set[DirectedHtlc], feeratePerKw: Long, toLocal: MilliSatoshi, toRemote: MilliSatoshi) {
|
||||
|
||||
def findHtlcById(id: Long, direction: Direction): Option[DirectedHtlc] = htlcs.find(htlc => htlc.add.id == id && htlc.direction == direction)
|
||||
def findIncomingHtlcById(id: Long): Option[IncomingHtlc] = htlcs.collectFirst { case htlc: IncomingHtlc if htlc.add.id == id => htlc }
|
||||
|
||||
val totalFunds = toLocal + toRemote + htlcs.toSeq.map(_.add.amountMsat).sum
|
||||
def findOutgoingHtlcById(id: Long): Option[OutgoingHtlc] = htlcs.collectFirst { case htlc: OutgoingHtlc if htlc.add.id == id => htlc }
|
||||
|
||||
val totalFunds: MilliSatoshi = toLocal + toRemote + htlcs.toSeq.map(_.add.amountMsat).sum
|
||||
}
|
||||
|
||||
object CommitmentSpec {
|
||||
@ -62,54 +80,60 @@ object CommitmentSpec {
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def addHtlc(spec: CommitmentSpec, direction: Direction, update: UpdateAddHtlc): CommitmentSpec = {
|
||||
val htlc = direction match {
|
||||
case IN => IncomingHtlc(update)
|
||||
case OUT => OutgoingHtlc(update)
|
||||
}
|
||||
direction match {
|
||||
case OUT => spec.copy(toLocal = spec.toLocal - htlc.add.amountMsat, htlcs = spec.htlcs + htlc)
|
||||
case IN => spec.copy(toRemote = spec.toRemote - htlc.add.amountMsat, htlcs = spec.htlcs + htlc)
|
||||
def addHtlc(spec: CommitmentSpec, directedHtlc: DirectedHtlc): CommitmentSpec = {
|
||||
directedHtlc match {
|
||||
case OutgoingHtlc(add) => spec.copy(toLocal = spec.toLocal - add.amountMsat, htlcs = spec.htlcs + directedHtlc)
|
||||
case IncomingHtlc(add) => spec.copy(toRemote = spec.toRemote - add.amountMsat, htlcs = spec.htlcs + directedHtlc)
|
||||
}
|
||||
}
|
||||
|
||||
// OUT means we are sending an UpdateFulfillHtlc message which means that we are fulfilling an HTLC that they sent
|
||||
def fulfillHtlc(spec: CommitmentSpec, direction: Direction, htlcId: Long): CommitmentSpec = {
|
||||
spec.findHtlcById(htlcId, direction.opposite) match {
|
||||
case Some(htlc) if direction == OUT => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
case Some(htlc) if direction == IN => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
def fulfillIncomingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = {
|
||||
spec.findIncomingHtlcById(htlcId) match {
|
||||
case Some(htlc) => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
case None => throw new RuntimeException(s"cannot find htlc id=$htlcId")
|
||||
}
|
||||
}
|
||||
|
||||
// OUT means we are sending an UpdateFailHtlc message which means that we are failing an HTLC that they sent
|
||||
def failHtlc(spec: CommitmentSpec, direction: Direction, htlcId: Long): CommitmentSpec = {
|
||||
spec.findHtlcById(htlcId, direction.opposite) match {
|
||||
case Some(htlc) if direction == OUT => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
case Some(htlc) if direction == IN => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
def fulfillOutgoingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = {
|
||||
spec.findOutgoingHtlcById(htlcId) match {
|
||||
case Some(htlc) => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
case None => throw new RuntimeException(s"cannot find htlc id=$htlcId")
|
||||
}
|
||||
}
|
||||
|
||||
def failIncomingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = {
|
||||
spec.findIncomingHtlcById(htlcId) match {
|
||||
case Some(htlc) => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
case None => throw new RuntimeException(s"cannot find htlc id=$htlcId")
|
||||
}
|
||||
}
|
||||
|
||||
def failOutgoingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = {
|
||||
spec.findOutgoingHtlcById(htlcId) match {
|
||||
case Some(htlc) => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
case None => throw new RuntimeException(s"cannot find htlc id=$htlcId")
|
||||
}
|
||||
}
|
||||
|
||||
def reduce(localCommitSpec: CommitmentSpec, localChanges: List[UpdateMessage], remoteChanges: List[UpdateMessage]): CommitmentSpec = {
|
||||
val spec1 = localChanges.foldLeft(localCommitSpec) {
|
||||
case (spec, u: UpdateAddHtlc) => addHtlc(spec, OUT, u)
|
||||
case (spec, u: UpdateAddHtlc) => addHtlc(spec, OutgoingHtlc(u))
|
||||
case (spec, _) => spec
|
||||
}
|
||||
val spec2 = remoteChanges.foldLeft(spec1) {
|
||||
case (spec, u: UpdateAddHtlc) => addHtlc(spec, IN, u)
|
||||
case (spec, u: UpdateAddHtlc) => addHtlc(spec, IncomingHtlc(u))
|
||||
case (spec, _) => spec
|
||||
}
|
||||
val spec3 = localChanges.foldLeft(spec2) {
|
||||
case (spec, u: UpdateFulfillHtlc) => fulfillHtlc(spec, OUT, u.id)
|
||||
case (spec, u: UpdateFailHtlc) => failHtlc(spec, OUT, u.id)
|
||||
case (spec, u: UpdateFailMalformedHtlc) => failHtlc(spec, OUT, u.id)
|
||||
case (spec, u: UpdateFulfillHtlc) => fulfillIncomingHtlc(spec, u.id)
|
||||
case (spec, u: UpdateFailHtlc) => failIncomingHtlc(spec, u.id)
|
||||
case (spec, u: UpdateFailMalformedHtlc) => failIncomingHtlc(spec, u.id)
|
||||
case (spec, _) => spec
|
||||
}
|
||||
val spec4 = remoteChanges.foldLeft(spec3) {
|
||||
case (spec, u: UpdateFulfillHtlc) => fulfillHtlc(spec, IN, u.id)
|
||||
case (spec, u: UpdateFailHtlc) => failHtlc(spec, IN, u.id)
|
||||
case (spec, u: UpdateFailMalformedHtlc) => failHtlc(spec, IN, u.id)
|
||||
case (spec, u: UpdateFulfillHtlc) => fulfillOutgoingHtlc(spec, u.id)
|
||||
case (spec, u: UpdateFailHtlc) => failOutgoingHtlc(spec, u.id)
|
||||
case (spec, u: UpdateFailMalformedHtlc) => failOutgoingHtlc(spec, u.id)
|
||||
case (spec, _) => spec
|
||||
}
|
||||
val spec5 = (localChanges ++ remoteChanges).foldLeft(spec4) {
|
||||
|
@ -88,14 +88,9 @@ object ChannelCodecs extends Logging {
|
||||
("htlcBasepoint" | publicKey) ::
|
||||
("features" | combinedFeaturesCodec)).as[RemoteParams]
|
||||
|
||||
val directionCodec: Codec[Direction] = Codec[Direction](
|
||||
(dir: Direction) => bool.encode(dir == IN),
|
||||
(wire: BitVector) => bool.decode(wire).map(_.map(b => if (b) IN else OUT))
|
||||
)
|
||||
|
||||
val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(directionCodec)
|
||||
.typecase(IN, updateAddHtlcCodec.as[IncomingHtlc])
|
||||
.typecase(OUT, updateAddHtlcCodec.as[OutgoingHtlc])
|
||||
val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool)
|
||||
.typecase(true, updateAddHtlcCodec.as[IncomingHtlc])
|
||||
.typecase(false, updateAddHtlcCodec.as[OutgoingHtlc])
|
||||
|
||||
def setCodec[T](codec: Codec[T]): Codec[Set[T]] = Codec[Set[T]](
|
||||
(elems: Set[T]) => listOfN(uint16, codec).encode(elems.toList),
|
||||
|
@ -35,8 +35,9 @@ import fr.acinq.eclair.io.Peer
|
||||
import fr.acinq.eclair.payment.relay.Relayer._
|
||||
import fr.acinq.eclair.payment.relay.{CommandBuffer, Origin}
|
||||
import fr.acinq.eclair.router.Announcements
|
||||
import fr.acinq.eclair.transactions.DirectedHtlc.{incoming, outgoing}
|
||||
import fr.acinq.eclair.transactions.Transactions
|
||||
import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, htlcSuccessWeight, htlcTimeoutWeight, weight2fee}
|
||||
import fr.acinq.eclair.transactions.{IN, OUT, Transactions}
|
||||
import fr.acinq.eclair.wire.{AnnouncementSignatures, ChannelUpdate, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass, randomBytes32, _}
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
@ -720,7 +721,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
// bob replies immediately with a signature
|
||||
bob2alice.expectMsgType[CommitSig]
|
||||
|
||||
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == IN))
|
||||
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.collect(incoming).exists(_.id == htlc.id))
|
||||
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1)
|
||||
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocal == initialState.commitments.localCommit.spec.toLocal)
|
||||
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.acked.size == 0)
|
||||
@ -745,7 +746,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
bob2alice.expectMsgType[CommitSig]
|
||||
bob2alice.forward(alice)
|
||||
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == OUT))
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.collect(outgoing).exists(_.id == htlc.id))
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1)
|
||||
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocal == initialState.commitments.localCommit.spec.toLocal)
|
||||
}
|
||||
@ -822,7 +823,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc1.id && h.direction == IN))
|
||||
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.collect(incoming).exists(_.id == htlc1.id))
|
||||
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 2)
|
||||
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocal == initialState.commitments.localCommit.spec.toLocal)
|
||||
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx.txOut.count(_.amount == 50000.sat) == 2)
|
||||
|
@ -23,7 +23,7 @@ import java.util.concurrent.CountDownLatch
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Stash}
|
||||
import fr.acinq.bitcoin.ByteVector32
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.transactions.{IN, OUT}
|
||||
import fr.acinq.eclair.transactions.{IncomingHtlc, OutgoingHtlc}
|
||||
import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, TestConstants, TestUtils}
|
||||
|
||||
/**
|
||||
@ -132,15 +132,15 @@ class SynchronizationPipe(latch: CountDownLatch) extends Actor with ActorLogging
|
||||
val l = List(
|
||||
"LOCAL COMMITS:",
|
||||
s" Commit ${d.commitments.localCommit.index}:",
|
||||
s" Offered htlcs: ${localCommit.spec.htlcs.filter(_.direction == OUT).map(h => (h.add.id, h.add.amountMsat)).mkString(" ")}",
|
||||
s" Received htlcs: ${localCommit.spec.htlcs.filter(_.direction == IN).map(h => (h.add.id, h.add.amountMsat)).mkString(" ")}",
|
||||
s" Offered htlcs: ${localCommit.spec.htlcs.collect { case OutgoingHtlc(add) => (add.id, add.amountMsat) }.mkString(" ")}",
|
||||
s" Received htlcs: ${localCommit.spec.htlcs.collect { case IncomingHtlc(add) => (add.id, add.amountMsat) }.mkString(" ")}",
|
||||
s" Balance us: ${localCommit.spec.toLocal}",
|
||||
s" Balance them: ${localCommit.spec.toRemote}",
|
||||
s" Fee rate: ${localCommit.spec.feeratePerKw}",
|
||||
"REMOTE COMMITS:",
|
||||
s" Commit ${remoteCommit.index}:",
|
||||
s" Offered htlcs: ${remoteCommit.spec.htlcs.filter(_.direction == OUT).map(h => (h.add.id, h.add.amountMsat)).mkString(" ")}",
|
||||
s" Received htlcs: ${remoteCommit.spec.htlcs.filter(_.direction == IN).map(h => (h.add.id, h.add.amountMsat)).mkString(" ")}",
|
||||
s" Offered htlcs: ${remoteCommit.spec.htlcs.collect { case OutgoingHtlc(add) => (add.id, add.amountMsat) }.mkString(" ")}",
|
||||
s" Received htlcs: ${remoteCommit.spec.htlcs.collect { case IncomingHtlc(add) => (add.id, add.amountMsat) }.mkString(" ")}",
|
||||
s" Balance us: ${remoteCommit.spec.toLocal}",
|
||||
s" Balance them: ${remoteCommit.spec.toRemote}",
|
||||
s" Fee rate: ${remoteCommit.spec.feeratePerKw}")
|
||||
|
@ -27,7 +27,7 @@ import fr.acinq.eclair.payment.OutgoingPacket.buildCommand
|
||||
import fr.acinq.eclair.payment.PaymentPacketSpec._
|
||||
import fr.acinq.eclair.payment.relay.{CommandBuffer, Origin, Relayer}
|
||||
import fr.acinq.eclair.router.ChannelHop
|
||||
import fr.acinq.eclair.transactions.{DirectedHtlc, Direction, IN, IncomingHtlc, OUT, OutgoingHtlc}
|
||||
import fr.acinq.eclair.transactions.{DirectedHtlc, IncomingHtlc, OutgoingHtlc}
|
||||
import fr.acinq.eclair.wire.Onion.FinalLegacyPayload
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{CltvExpiry, LongToBtcAmount, NodeParams, TestConstants, TestkitBaseClass, randomBytes32}
|
||||
@ -80,19 +80,19 @@ class PostRestartHtlcCleanerSpec extends TestkitBaseClass {
|
||||
val trampolineRelayed = Origin.TrampolineRelayed((channelId_ab_1, 0L) :: (channelId_ab_2, 2L) :: Nil, None)
|
||||
|
||||
val htlc_ab_1 = Seq(
|
||||
buildHtlc(0, channelId_ab_1, trampolineRelayedPaymentHash, IN),
|
||||
buildHtlc(1, channelId_ab_1, randomBytes32, IN), // not relayed
|
||||
buildHtlc(2, channelId_ab_1, randomBytes32, OUT),
|
||||
buildHtlc(3, channelId_ab_1, randomBytes32, OUT),
|
||||
buildHtlc(4, channelId_ab_1, randomBytes32, IN), // not relayed
|
||||
buildHtlc(5, channelId_ab_1, relayedPaymentHash, IN)
|
||||
buildHtlcIn(0, channelId_ab_1, trampolineRelayedPaymentHash),
|
||||
buildHtlcIn(1, channelId_ab_1, randomBytes32), // not relayed
|
||||
buildHtlcOut(2, channelId_ab_1, randomBytes32),
|
||||
buildHtlcOut(3, channelId_ab_1, randomBytes32),
|
||||
buildHtlcIn(4, channelId_ab_1, randomBytes32), // not relayed
|
||||
buildHtlcIn(5, channelId_ab_1, relayedPaymentHash)
|
||||
)
|
||||
val htlc_ab_2 = Seq(
|
||||
buildHtlc(0, channelId_ab_2, randomBytes32, IN), // not relayed
|
||||
buildHtlc(1, channelId_ab_2, randomBytes32, OUT),
|
||||
buildHtlc(2, channelId_ab_2, trampolineRelayedPaymentHash, IN),
|
||||
buildHtlc(3, channelId_ab_2, randomBytes32, OUT),
|
||||
buildHtlc(4, channelId_ab_2, randomBytes32, IN) // not relayed
|
||||
buildHtlcIn(0, channelId_ab_2, randomBytes32), // not relayed
|
||||
buildHtlcOut(1, channelId_ab_2, randomBytes32),
|
||||
buildHtlcIn(2, channelId_ab_2, trampolineRelayedPaymentHash),
|
||||
buildHtlcOut(3, channelId_ab_2, randomBytes32),
|
||||
buildHtlcIn(4, channelId_ab_2, randomBytes32) // not relayed
|
||||
)
|
||||
|
||||
val channels = Seq(
|
||||
@ -365,15 +365,15 @@ object PostRestartHtlcCleanerSpec {
|
||||
val (preimage1, preimage2) = (randomBytes32, randomBytes32)
|
||||
val (paymentHash1, paymentHash2) = (Crypto.sha256(preimage1), Crypto.sha256(preimage2))
|
||||
|
||||
def buildHtlc(htlcId: Long, channelId: ByteVector32, paymentHash: ByteVector32, direction: Direction): DirectedHtlc = {
|
||||
def buildHtlc(htlcId: Long, channelId: ByteVector32, paymentHash: ByteVector32): UpdateAddHtlc = {
|
||||
val (cmd, _) = buildCommand(Upstream.Local(UUID.randomUUID()), paymentHash, hops, FinalLegacyPayload(finalAmount, finalExpiry))
|
||||
val add = UpdateAddHtlc(channelId, htlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion)
|
||||
direction match {
|
||||
case IN => IncomingHtlc(add)
|
||||
case OUT => OutgoingHtlc(add)
|
||||
}
|
||||
UpdateAddHtlc(channelId, htlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion)
|
||||
}
|
||||
|
||||
def buildHtlcIn(htlcId: Long, channelId: ByteVector32, paymentHash: ByteVector32): DirectedHtlc = IncomingHtlc(buildHtlc(htlcId, channelId, paymentHash))
|
||||
|
||||
def buildHtlcOut(htlcId: Long, channelId: ByteVector32, paymentHash: ByteVector32): DirectedHtlc = OutgoingHtlc(buildHtlc(htlcId, channelId, paymentHash))
|
||||
|
||||
def buildFinalHtlc(htlcId: Long, channelId: ByteVector32, paymentHash: ByteVector32): DirectedHtlc = {
|
||||
val (cmd, _) = buildCommand(Upstream.Local(UUID.randomUUID()), paymentHash, ChannelHop(a, TestConstants.Bob.nodeParams.nodeId, channelUpdate_ab) :: Nil, FinalLegacyPayload(finalAmount, finalExpiry))
|
||||
IncomingHtlc(UpdateAddHtlc(channelId, htlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion))
|
||||
@ -432,15 +432,15 @@ object PostRestartHtlcCleanerSpec {
|
||||
def setupTrampolinePayments(nodeParams: NodeParams): TrampolinePaymentTest = {
|
||||
// Upstream HTLCs.
|
||||
val htlc_ab_1 = Seq(
|
||||
buildHtlc(0, channelId_ab_1, paymentHash1, IN),
|
||||
buildHtlc(2, channelId_ab_1, randomBytes32, OUT), // ignored
|
||||
buildHtlc(3, channelId_ab_1, randomBytes32, OUT), // ignored
|
||||
buildHtlc(5, channelId_ab_1, paymentHash2, IN)
|
||||
buildHtlcIn(0, channelId_ab_1, paymentHash1),
|
||||
buildHtlcOut(2, channelId_ab_1, randomBytes32), // ignored
|
||||
buildHtlcOut(3, channelId_ab_1, randomBytes32), // ignored
|
||||
buildHtlcIn(5, channelId_ab_1, paymentHash2)
|
||||
)
|
||||
val htlc_ab_2 = Seq(
|
||||
buildHtlc(1, channelId_ab_2, randomBytes32, OUT), // ignored
|
||||
buildHtlc(7, channelId_ab_2, paymentHash1, IN),
|
||||
buildHtlc(9, channelId_ab_2, randomBytes32, OUT) // ignored
|
||||
buildHtlcOut(1, channelId_ab_2, randomBytes32), // ignored
|
||||
buildHtlcIn(7, channelId_ab_2, paymentHash1),
|
||||
buildHtlcOut(9, channelId_ab_2, randomBytes32) // ignored
|
||||
)
|
||||
|
||||
val origin_1 = Origin.TrampolineRelayed((channelId_ab_1, 0L) :: (channelId_ab_2, 7L) :: Nil, None)
|
||||
@ -448,18 +448,18 @@ object PostRestartHtlcCleanerSpec {
|
||||
|
||||
// Downstream HTLCs.
|
||||
val htlc_bc_1 = Seq(
|
||||
buildHtlc(1, channelId_bc_1, randomBytes32, IN), // ignored
|
||||
buildHtlc(6, channelId_bc_1, paymentHash1, OUT),
|
||||
buildHtlc(8, channelId_bc_1, paymentHash2, OUT)
|
||||
buildHtlcIn(1, channelId_bc_1, randomBytes32), // ignored
|
||||
buildHtlcOut(6, channelId_bc_1, paymentHash1),
|
||||
buildHtlcOut(8, channelId_bc_1, paymentHash2)
|
||||
)
|
||||
val htlc_bc_2 = Seq(
|
||||
buildHtlc(0, channelId_bc_2, randomBytes32, IN), // ignored
|
||||
buildHtlc(1, channelId_bc_2, paymentHash2, OUT)
|
||||
buildHtlcIn(0, channelId_bc_2, randomBytes32), // ignored
|
||||
buildHtlcOut(1, channelId_bc_2, paymentHash2)
|
||||
)
|
||||
val htlc_bc_3 = Seq(
|
||||
buildHtlc(3, channelId_bc_3, randomBytes32, IN), // ignored
|
||||
buildHtlc(4, channelId_bc_3, paymentHash2, OUT),
|
||||
buildHtlc(5, channelId_bc_3, randomBytes32, IN) // ignored
|
||||
buildHtlcIn(3, channelId_bc_3, randomBytes32), // ignored
|
||||
buildHtlcOut(4, channelId_bc_3, paymentHash2),
|
||||
buildHtlcIn(5, channelId_bc_3, randomBytes32) // ignored
|
||||
)
|
||||
|
||||
val downstream_1_1 = UpdateAddHtlc(channelId_bc_1, 6L, finalAmount, paymentHash1, finalExpiry, TestConstants.emptyOnionPacket)
|
||||
|
@ -160,18 +160,18 @@ class TestVectorsSpec extends FunSuite with Logging {
|
||||
OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 3000000 msat, Crypto.sha256(paymentPreimages(3)), CltvExpiry(503), TestConstants.emptyOnionPacket)),
|
||||
IncomingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 4000000 msat, Crypto.sha256(paymentPreimages(4)), CltvExpiry(504), TestConstants.emptyOnionPacket))
|
||||
)
|
||||
val htlcScripts = htlcs.map(htlc => htlc.direction match {
|
||||
case OUT => Scripts.htlcOffered(Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.revocation_pubkey, Crypto.ripemd160(htlc.add.paymentHash))
|
||||
case IN => Scripts.htlcReceived(Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.revocation_pubkey, Crypto.ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)
|
||||
})
|
||||
val htlcScripts = htlcs.map {
|
||||
case OutgoingHtlc(add) => Scripts.htlcOffered(Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.revocation_pubkey, Crypto.ripemd160(add.paymentHash))
|
||||
case IncomingHtlc(add) => Scripts.htlcReceived(Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.revocation_pubkey, Crypto.ripemd160(add.paymentHash), add.cltvExpiry)
|
||||
}
|
||||
|
||||
def dir2string(dir: Direction) = dir match {
|
||||
case IN => "remote->local"
|
||||
case OUT => "local->remote"
|
||||
def dir2string(htlc: DirectedHtlc) = htlc match {
|
||||
case _: IncomingHtlc => "remote->local"
|
||||
case _: OutgoingHtlc => "local->remote"
|
||||
}
|
||||
|
||||
for (i <- 0 until htlcs.length) {
|
||||
logger.info(s"htlc $i direction: ${dir2string(htlcs(i).direction)}")
|
||||
logger.info(s"htlc $i direction: ${dir2string(htlcs(i))}")
|
||||
logger.info(s"htlc $i amount_msat: ${htlcs(i).add.amountMsat}")
|
||||
logger.info(s"htlc $i expiry: ${htlcs(i).add.cltvExpiry}")
|
||||
logger.info(s"htlc $i payment_preimage: ${paymentPreimages(i)}")
|
||||
@ -204,13 +204,13 @@ class TestVectorsSpec extends FunSuite with Logging {
|
||||
logger.info(s"# base commitment transaction fee = ${baseFee.toLong}")
|
||||
val actualFee = fundingAmount - commitTx.tx.txOut.map(_.amount).sum
|
||||
logger.info(s"# actual commitment transaction fee = ${actualFee.toLong}")
|
||||
commitTx.tx.txOut.map(txOut => {
|
||||
commitTx.tx.txOut.foreach(txOut => {
|
||||
txOut.publicKeyScript.length match {
|
||||
case 22 => logger.info(s"# to-remote amount ${txOut.amount.toLong} P2WPKH(${Remote.payment_privkey.publicKey})")
|
||||
case 34 =>
|
||||
val index = htlcScripts.indexWhere(s => Script.write(Script.pay2wsh(s)) == txOut.publicKeyScript)
|
||||
if (index == -1) logger.info(s"# to-local amount ${txOut.amount.toLong} wscript ${Script.write(Scripts.toLocalDelayed(Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey))}")
|
||||
else logger.info(s"# HTLC ${if (htlcs(index).direction == OUT) "offered" else "received"} amount ${txOut.amount.toLong} wscript ${Script.write(htlcScripts(index))}")
|
||||
else logger.info(s"# HTLC ${if (htlcs(index).isInstanceOf[OutgoingHtlc]) "offered" else "received"} amount ${txOut.amount.toLong} wscript ${Script.write(htlcScripts(index))}")
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -391,11 +391,9 @@ class TransactionsSpec extends FunSuite with Logging {
|
||||
case Failure(t) => fail(t)
|
||||
}
|
||||
|
||||
def htlc(direction: Direction, amount: Satoshi): DirectedHtlc = direction match {
|
||||
case IN => IncomingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, amount.toMilliSatoshi, ByteVector32.Zeroes, CltvExpiry(144), TestConstants.emptyOnionPacket))
|
||||
case OUT => OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, amount.toMilliSatoshi, ByteVector32.Zeroes, CltvExpiry(144), TestConstants.emptyOnionPacket))
|
||||
}
|
||||
def htlcIn(amount: Satoshi): DirectedHtlc = IncomingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, amount.toMilliSatoshi, ByteVector32.Zeroes, CltvExpiry(144), TestConstants.emptyOnionPacket))
|
||||
|
||||
def htlcOut(amount: Satoshi): DirectedHtlc = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, amount.toMilliSatoshi, ByteVector32.Zeroes, CltvExpiry(144), TestConstants.emptyOnionPacket))
|
||||
|
||||
test("BOLT 2 fee tests") {
|
||||
|
||||
@ -424,8 +422,8 @@ class TransactionsSpec extends FunSuite with Logging {
|
||||
val htlcs = htlcRegex.findAllIn(s).map(l => {
|
||||
val htlcRegex(direction, amount) = l
|
||||
direction match {
|
||||
case "offered" => htlc(OUT, Satoshi(amount.toLong))
|
||||
case "received" => htlc(IN, Satoshi(amount.toLong))
|
||||
case "offered" => htlcOut(Satoshi(amount.toLong))
|
||||
case "received" => htlcIn(Satoshi(amount.toLong))
|
||||
}
|
||||
}).toSet
|
||||
TestSetup(name, dustLimit, CommitmentSpec(htlcs = htlcs, feeratePerKw = feerate_per_kw.toLong, toLocal = MilliSatoshi(to_local_msat.toLong), toRemote = MilliSatoshi(to_remote_msat.toLong)), Satoshi(fee.toLong))
|
||||
|
@ -131,11 +131,6 @@ class ChannelCodecsSpec extends FunSuite {
|
||||
assert(withGlobalFeaturesDecoded.features === hex"028a")
|
||||
}
|
||||
|
||||
test("encode/decode direction") {
|
||||
assert(directionCodec.decodeValue(directionCodec.encode(IN).require).require === IN)
|
||||
assert(directionCodec.decodeValue(directionCodec.encode(OUT).require).require === OUT)
|
||||
}
|
||||
|
||||
test("encode/decode htlc") {
|
||||
val add = UpdateAddHtlc(
|
||||
channelId = randomBytes32,
|
||||
@ -150,6 +145,37 @@ class ChannelCodecsSpec extends FunSuite {
|
||||
assert(htlcCodec.decodeValue(htlcCodec.encode(htlc2).require).require === htlc2)
|
||||
}
|
||||
|
||||
test("backward compatibility of htlc codec") {
|
||||
// these encoded HTLC were produced by a previous version of the codec (at commit 8932785e001ddfe32839b3f83468ea19cf00b289)
|
||||
val encodedHtlc1 = hex"89d5618930919bd77c07ea3931e7791010a9637c3d06e091b2dad38f21bbcffa00000000384ffa48000000003dbf35101f8724fbd36096ea5f462be20d20bc2f93ebc2c8a3f00b7a78c5158b5a0e9f6b1666923e800f1d073e2adcfba5904f1b8234af1c43a6e84a862a044d15f33addf8d41b3cfb7f96d815d2248322aeadd0ce7bacbcc44e611f66c35c439423c099e678c077203d5fc415ec37b798c6c74c9eed0806e6cb20f2b613855772c086cee60642b3b9c3919627c877a62a57dcf6ce003bedc53a8b7a0d7aa91b2304ef8b512fe1a9a043e410d7cd3009edffcb5d4c05cfb545aced4afea8bbe26c5ff492602edf9d4eb731541e60e48fd1ae5e33b04a614346fb16e09ccd9bcb8907fe9fc287757ea9280a03462299e950a274c1dc53fbae8c421e67d7de35709eda0f11bcd417c0f215667e8b8ccae1035d0281214af25bf690102b180e5d4b57323d02ab5cee5d3669b4300539d02eff553143f085cd70e5428b7af3262418aa7664d56c3fd29c00a2f88a6a5ee9685f45b6182c45d492b2170b092e4b5891247bcffe82623b86637bec291cca1dc729f5747d842ecdf2fc24eaf95c522cbebe9a841e7cff837e715b689f0b366b92a2850875636962ba42863ab6df12ee938ada6e6ae795f8b4fbe81adea478caa9899fed0d6ccdf7a2173b69b2d3ff1b93c82c08c4da63b426d2f94912109997e8ee5830c5ffe3b60c97438ae1521a2956e73a9a60f16dc13a5e6565904e04bf66ceda3db693fc7a0c6ad4f7dc8cb7f1ef54527c11589b7c35ce5b20e7f23a0ab107a406fa747435ff08096a7533a8ab7f5d3630d5c20c9161101f922c76079497e00e3ca62bce033b2bb065ea1733c50b5a06492d2b46715812003f29a8754b5dc1649082893e7be76550c58d98e81556e4ddf20a244f363bc23e756c95224335d0eeccd3da06a9161c4c72ae3d93afe902a806eadd2167d15c04cf3028fc61d0843bd270fd702a2c5af889ab5bc79a294847914f8dd409a9b990a96397d9046c385ca9810fb7c7b2c61491c67264257a601be7fe8c47a859b56af41caf06be7ea1cdb540719fc3bc2603675b79fd36a6f2911043b78da9f186d2a01f1209d0d91508e8ebecce09fd72823d0c166542f6d059fa8725d9d719a2532289c88f7a291a6bbe01f5b1f83cc2232d716f7dfc6a103fb8637d759aab939aaa278cffe04a64f4142564113080276bee7d3ec62e3f887838e3821f0dd713337972df994160edc29ccb9b9630c41a9ec7c994cbef2501a610e1c3684e697df230fd6f6f10526c9446e8307a1fb7e4988cdf7fc8aa32c8a09206113d8247aaae42e3942c0ffd291d67837d2c88231c85882667582eca1d2566134c4ee1301de8e1637f09467b473ba3e353992488048bd34b26dcc6f6f474751b7ac5bbad468c64eda2aeabfe6a92150a4faab142229d7934c4a24427441850d0deae5db802b02940435f39ceaa85e2d3d2269510881ab26926c3167487aa138d38b9cf650f59f0aa0b84297479271c2009cde61e5c58c26bf8a15aba86869af83941ec14972d93b6ae4a6ecf6584238150a61487d6bd394db40a10d710fd2d065850e52ea6536a74d88947448221c1ce493fecbf2070998e04d5263935488c2935f2d3afed4d0fc7472c03e652f928e6a18f78029043f219f652d992e104529149a978e5c660c0081fe6a179dbe62dcb597f3b4e497c6049b0255f8f306e4b18c97c339c98270abf86a4eb1af93b14d880eeda203bb3ba5b6e3113d0e003f8e55f3d446bd4dcda686b357ca0adf1fe25390767a40ff086a9258d04c19b0474488aaafac321f087d2bd0dc0e056ad9f5b5afa5f3d82bc3f18b33de9044529637fed05879f6bd440f331c06008dd38c2fb822c22fc4201e97f9ef9fc351807c045dece147d19fd01a68604c3cb6b5e0db1b4d1ebe387670021067d94206fbdc9ed33ac1f49d87f961cb5d44f48805e55f8637ca3de4ec9dd969944ed61de45970b7ef96d9f313a41de1cae380e0fe4b56729f275e2a0a87403c90e80"
|
||||
val encodedHtlc2 = hex"09d5618930919bd77c07ea3931e7791010a9637c3d06e091b2dad38f21bbcffa00000000384ffa48000000003dbf35101f8724fbd36096ea5f462be20d20bc2f93ebc2c8a3f00b7a78c5158b5a0e9f6b1666923e800f1d073e2adcfba5904f1b8234af1c43a6e84a862a044d15f33addf8d41b3cfb7f96d815d2248322aeadd0ce7bacbcc44e611f66c35c439423c099e678c077203d5fc415ec37b798c6c74c9eed0806e6cb20f2b613855772c086cee60642b3b9c3919627c877a62a57dcf6ce003bedc53a8b7a0d7aa91b2304ef8b512fe1a9a043e410d7cd3009edffcb5d4c05cfb545aced4afea8bbe26c5ff492602edf9d4eb731541e60e48fd1ae5e33b04a614346fb16e09ccd9bcb8907fe9fc287757ea9280a03462299e950a274c1dc53fbae8c421e67d7de35709eda0f11bcd417c0f215667e8b8ccae1035d0281214af25bf690102b180e5d4b57323d02ab5cee5d3669b4300539d02eff553143f085cd70e5428b7af3262418aa7664d56c3fd29c00a2f88a6a5ee9685f45b6182c45d492b2170b092e4b5891247bcffe82623b86637bec291cca1dc729f5747d842ecdf2fc24eaf95c522cbebe9a841e7cff837e715b689f0b366b92a2850875636962ba42863ab6df12ee938ada6e6ae795f8b4fbe81adea478caa9899fed0d6ccdf7a2173b69b2d3ff1b93c82c08c4da63b426d2f94912109997e8ee5830c5ffe3b60c97438ae1521a2956e73a9a60f16dc13a5e6565904e04bf66ceda3db693fc7a0c6ad4f7dc8cb7f1ef54527c11589b7c35ce5b20e7f23a0ab107a406fa747435ff08096a7533a8ab7f5d3630d5c20c9161101f922c76079497e00e3ca62bce033b2bb065ea1733c50b5a06492d2b46715812003f29a8754b5dc1649082893e7be76550c58d98e81556e4ddf20a244f363bc23e756c95224335d0eeccd3da06a9161c4c72ae3d93afe902a806eadd2167d15c04cf3028fc61d0843bd270fd702a2c5af889ab5bc79a294847914f8dd409a9b990a96397d9046c385ca9810fb7c7b2c61491c67264257a601be7fe8c47a859b56af41caf06be7ea1cdb540719fc3bc2603675b79fd36a6f2911043b78da9f186d2a01f1209d0d91508e8ebecce09fd72823d0c166542f6d059fa8725d9d719a2532289c88f7a291a6bbe01f5b1f83cc2232d716f7dfc6a103fb8637d759aab939aaa278cffe04a64f4142564113080276bee7d3ec62e3f887838e3821f0dd713337972df994160edc29ccb9b9630c41a9ec7c994cbef2501a610e1c3684e697df230fd6f6f10526c9446e8307a1fb7e4988cdf7fc8aa32c8a09206113d8247aaae42e3942c0ffd291d67837d2c88231c85882667582eca1d2566134c4ee1301de8e1637f09467b473ba3e353992488048bd34b26dcc6f6f474751b7ac5bbad468c64eda2aeabfe6a92150a4faab142229d7934c4a24427441850d0deae5db802b02940435f39ceaa85e2d3d2269510881ab26926c3167487aa138d38b9cf650f59f0aa0b84297479271c2009cde61e5c58c26bf8a15aba86869af83941ec14972d93b6ae4a6ecf6584238150a61487d6bd394db40a10d710fd2d065850e52ea6536a74d88947448221c1ce493fecbf2070998e04d5263935488c2935f2d3afed4d0fc7472c03e652f928e6a18f78029043f219f652d992e104529149a978e5c660c0081fe6a179dbe62dcb597f3b4e497c6049b0255f8f306e4b18c97c339c98270abf86a4eb1af93b14d880eeda203bb3ba5b6e3113d0e003f8e55f3d446bd4dcda686b357ca0adf1fe25390767a40ff086a9258d04c19b0474488aaafac321f087d2bd0dc0e056ad9f5b5afa5f3d82bc3f18b33de9044529637fed05879f6bd440f331c06008dd38c2fb822c22fc4201e97f9ef9fc351807c045dece147d19fd01a68604c3cb6b5e0db1b4d1ebe387670021067d94206fbdc9ed33ac1f49d87f961cb5d44f48805e55f8637ca3de4ec9dd969944ed61de45970b7ef96d9f313a41de1cae380e0fe4b56729f275e2a0a87403c90e80"
|
||||
|
||||
val ref = UpdateAddHtlc(
|
||||
ByteVector32(hex"13aac312612337aef80fd47263cef2202152c6f87a0dc12365b5a71e43779ff4"),
|
||||
1889531024,
|
||||
2071882272 msat,
|
||||
ByteVector32(hex"3f0e49f7a6c12dd4be8c57c41a41785f27d7859147e016f4f18a2b16b41d3ed6"),
|
||||
CltvExpiry(751641725),
|
||||
OnionRoutingPacket(
|
||||
0,
|
||||
hex"1e3a0e7c55b9f74b209e3704695e38874dd0950c54089a2be675bbf1a83679f6ff",
|
||||
hex"2db02ba44906455d5ba19cf75979889cc23ecd86b88728478133ccf180ee407abf882bd86f6f318d8e993dda100dcd9641e56c270aaee5810d9dcc0c85677387232c4f90ef4c54afb9ed9c0077db8a7516f41af552364609df16a25fc3534087c821af9a6013dbff96ba980b9f6a8b59da95fd5177c4d8bfe924c05dbf3a9d6e62a83cc1c91fa35cbc676094c2868df62dc1399b3797120ffd3f850eeafd525014068c4533d2a144e983b8a7f75d18843ccfafbc6ae13db41e2379a82f81e42accfd171995c206ba05024295e4b7ed202056301cba96ae647a0556b9dcba6cd368600a73a05dfeaa6287e10b9ae1ca8516f5e64c483154ecc9aad87fa5380145f114d4bdd2d0be8b6c30588ba925642e16125c96b12248f79ffd04c4770cc6f7d85239943b8e53eae8fb085d9be5f849d5f2b8a4597d7d35083cf9ff06fce2b6d13e166cd725450a10eac6d2c574850c756dbe25dd2715b4dcd5cf2bf169f7d035bd48f19553133fda1ad99bef442e76d365a7fe372790581189b4c7684da5f2922421332fd1dcb0618bffc76c192e8715c2a43452adce7534c1e2db8274bccacb209c097ecd9db47b6d27f8f418d5a9efb9196fe3dea8a4f822b136f86b9cb641cfe47415620f480df4e8e86bfe1012d4ea675156feba6c61ab841922c2203f2458ec0f292fc01c794c579c06765760cbd42e678a16b40c925a568ce2b024007e5350ea96bb82c92105127cf7cecaa18b1b31d02aadc9bbe414489e6c77847cead92a44866ba1dd99a7b40d522c3898e55c7b275fd205500dd5ba42cfa2b8099e6051f8c3a10877a4e1fae05458b5f11356b78f3452908f229f1ba81353732152c72fb208d870b953021f6f8f658c29238ce4c84af4c037cffd188f50b36ad5e8395e0d7cfd439b6a80e33f87784c06ceb6f3fa6d4de52220876f1b53e30da5403e2413a1b22a11d1d7d99c13fae5047a182cca85eda0b3f50e4bb3ae3344a64513911ef45234d77c03eb63f07984465ae2defbf8d4207f70c6faeb35572735544f19ffc094c9e8284ac82261004ed7dcfa7d8c5c7f10f071c7043e1bae2666f2e5bf3282c1db853997372c6188353d8f932997de4a034c21c386d09cd2fbe461fadede20a4d9288dd060f43f6fc93119beff9154659141240c227b048f555c85c728581ffa523acf06fa591046390b104cceb05d943a4acc26989dc2603bd1c2c6fe128cf68e7747c6a73249100917a6964db98dede8e8ea36f58b775a8d18c9db455d57fcd5242a149f556284453af2698944884e8830a1a1bd5cbb700560528086be739d550bc5a7a44d2a21103564d24d862ce90f54271a71739eca1eb3e154170852e8f24e3840139bcc3cb8b184d7f142b5750d0d35f07283d8292e5b276d5c94dd9ecb084702a14c290fad7a729b681421ae21fa5a0cb0a1ca5d4ca6d4e9b1128e890443839c927fd97e40e1331c09aa4c726a9118526be5a75fda9a1f8e8e5807cca5f251cd431ef0052087e433eca5b325c208a5229352f1cb8cc180103fcd42f3b7cc5b96b2fe769c92f8c093604abf1e60dc963192f86739304e157f0d49d635f27629b101ddb440776774b6dc6227a1c007f1cabe7a88d7a9b9b4d0d66af9415be3fc4a720ecf481fe10d524b1a09833608e8911555f58643e10fa57a1b81c0ad5b3eb6b5f4be7b05787e31667bd2088a52c6ffda0b0f3ed7a881e66380c011ba7185f7045845f88403d2ff3df3f86a300f808bbd9c28fa33fa034d0c098796d6bc1b6369a3d7c70ece00420cfb2840df7b93da67583e93b0ff2c396ba89e9100bcabf0c6f947bc9d93bb2d3289",
|
||||
ByteVector32(hex"dac3bc8b2e16fdf2db3e627483bc395c701c1fc96ace53e4ebc54150e807921d"))
|
||||
)
|
||||
val remaining = bin"0000000" // 7 bits remainder because the direction is encoded with 1 bit and we are dealing with bytes
|
||||
|
||||
val DecodeResult(h1, r1) = htlcCodec.decode(encodedHtlc1.toBitVector).require
|
||||
val DecodeResult(h2, r2) = htlcCodec.decode(encodedHtlc2.toBitVector).require
|
||||
|
||||
assert(h1 == IncomingHtlc(ref))
|
||||
assert(h2 == OutgoingHtlc(ref))
|
||||
assert(r1 == remaining)
|
||||
assert(r2 == remaining)
|
||||
|
||||
assert(htlcCodec.encode(h1).require.bytes === encodedHtlc1)
|
||||
assert(htlcCodec.encode(h2).require.bytes === encodedHtlc2)
|
||||
}
|
||||
|
||||
test("encode/decode commitment spec") {
|
||||
val add1 = UpdateAddHtlc(
|
||||
channelId = randomBytes32,
|
||||
@ -214,20 +240,6 @@ class ChannelCodecsSpec extends FunSuite {
|
||||
assert(spentMapCodec.decodeValue(spentMapCodec.encode(map).require).require === map)
|
||||
}
|
||||
|
||||
test("backward compatibility of htlc codecs") {
|
||||
// these encoded HTLC were produced by a previous version of the codec (at commit 8932785e001ddfe32839b3f83468ea19cf00b289)
|
||||
val encodedHtlc1 = hex"89d5618930919bd77c07ea3931e7791010a9637c3d06e091b2dad38f21bbcffa00000000384ffa48000000003dbf35101f8724fbd36096ea5f462be20d20bc2f93ebc2c8a3f00b7a78c5158b5a0e9f6b1666923e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
val encodedHtlc2 = hex"09d5618930919bd77c07ea3931e7791010a9637c3d06e091b2dad38f21bbcffa00000000384ffa48000000003dbf35101f8724fbd36096ea5f462be20d20bc2f93ebc2c8a3f00b7a78c5158b5a0e9f6b1666923e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
val h1 = htlcCodec.decodeValue(encodedHtlc1.toBitVector).require
|
||||
val h2 = htlcCodec.decodeValue(encodedHtlc2.toBitVector).require
|
||||
|
||||
assert(h1.direction == IN)
|
||||
assert(h2.direction == OUT)
|
||||
assert(htlcCodec.encode(h1).require.bytes === encodedHtlc1)
|
||||
assert(htlcCodec.encode(h2).require.bytes === encodedHtlc2)
|
||||
}
|
||||
|
||||
test("basic serialization test (NORMAL)") {
|
||||
val data = normal
|
||||
val bin = ChannelCodecs.DATA_NORMAL_Codec.encode(data).require
|
||||
@ -590,4 +602,4 @@ object ChannelCodecsSpec {
|
||||
new InputInfoSerializer
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +28,13 @@ import fr.acinq.eclair.crypto.ShaChain
|
||||
import fr.acinq.eclair.db.{IncomingPaymentStatus, OutgoingPaymentStatus}
|
||||
import fr.acinq.eclair.payment._
|
||||
import fr.acinq.eclair.router.RouteResponse
|
||||
import fr.acinq.eclair.transactions.Direction
|
||||
import fr.acinq.eclair.transactions.DirectedHtlc
|
||||
import fr.acinq.eclair.transactions.Transactions.{InputInfo, TransactionWithInputInfo}
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, UInt64}
|
||||
import org.json4s.JsonAST._
|
||||
import org.json4s.{CustomKeySerializer, CustomSerializer, TypeHints, jackson}
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.json4s.{CustomKeySerializer, CustomSerializer, DefaultFormats, Extraction, TypeHints, jackson}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
@ -208,10 +209,16 @@ class NodeAddressSerializer extends CustomSerializer[NodeAddress](_ => ( {
|
||||
case n: NodeAddress => JString(HostAndPort.fromParts(n.socketAddress.getHostString, n.socketAddress.getPort).toString)
|
||||
}))
|
||||
|
||||
class DirectionSerializer extends CustomSerializer[Direction](_ => ( {
|
||||
class DirectedHtlcSerializer extends CustomSerializer[DirectedHtlc](_ => ( {
|
||||
null
|
||||
}, {
|
||||
case d: Direction => JString(d.toString)
|
||||
case h: DirectedHtlc => new JObject(List(("direction", JString(h.direction)), ("add", Extraction.decompose(h.add)(
|
||||
DefaultFormats +
|
||||
new ByteVector32Serializer +
|
||||
new ByteVectorSerializer +
|
||||
new PublicKeySerializer +
|
||||
new MilliSatoshiSerializer +
|
||||
new CltvExpirySerializer))))
|
||||
}))
|
||||
|
||||
class PaymentRequestSerializer extends CustomSerializer[PaymentRequest](_ => ( {
|
||||
@ -308,7 +315,7 @@ object JsonSupport extends Json4sSupport {
|
||||
new ThrowableSerializer +
|
||||
new FailureMessageSerializer +
|
||||
new NodeAddressSerializer +
|
||||
new DirectionSerializer +
|
||||
new DirectedHtlcSerializer +
|
||||
new PaymentRequestSerializer +
|
||||
new JavaUUIDSerializer +
|
||||
CustomTypeHints.incomingPaymentStatus +
|
||||
|
@ -22,8 +22,8 @@ import java.util.UUID
|
||||
import fr.acinq.bitcoin.{ByteVector32, OutPoint, Transaction}
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.payment.{PaymentRequest, PaymentSettlingOnChain}
|
||||
import fr.acinq.eclair.transactions.{IN, OUT}
|
||||
import fr.acinq.eclair.wire.{NodeAddress, Tor2, Tor3}
|
||||
import fr.acinq.eclair.transactions.{IncomingHtlc, OutgoingHtlc}
|
||||
import fr.acinq.eclair.wire.{NodeAddress, OnionRoutingPacket, Tor2, Tor3, UpdateAddHtlc}
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
import scodec.bits._
|
||||
|
||||
@ -60,9 +60,25 @@ class JsonSerializersSpec extends FunSuite with Matchers {
|
||||
JsonSupport.serialization.write(tor3)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc.onion:9999""""
|
||||
}
|
||||
|
||||
test("Direction serialization") {
|
||||
JsonSupport.serialization.write(IN)(org.json4s.DefaultFormats + new DirectionSerializer) shouldBe s""""IN""""
|
||||
JsonSupport.serialization.write(OUT)(org.json4s.DefaultFormats + new DirectionSerializer) shouldBe s""""OUT""""
|
||||
test("DirectedHtlc serialization") {
|
||||
val add = UpdateAddHtlc(
|
||||
channelId = ByteVector32(hex"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"),
|
||||
id = 926,
|
||||
amountMsat = 12365.msat,
|
||||
paymentHash = ByteVector32(hex"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692"),
|
||||
cltvExpiry = CltvExpiry(621500),
|
||||
onionRoutingPacket = OnionRoutingPacket(
|
||||
version = 0,
|
||||
publicKey = hex"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b",
|
||||
payload = hex"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63f8cbe9f5e537e6e518d7998f11c40277d551bee036d227a421f344c0185b4533660d200acbc4f4aa6148e29f813bb537e950a79ba961b80ccaa6ad808cb88f858ee73f8b1f129d3214d194f76fc011c46e18c2caec0fcb69715e79cb381e449e5be20281e0aaa92defa089c98b7e29c22181f2d1af9f5fe2a37ede4ac3163b123aa72318201b1128b17053c381e7bf111620dfb7ea3dcc5c28cafdd9cb7bb1a4202b64199aa02af145c563ba1b9f10288203ce2666f486aacb2ee385dc0b67915864c95174dfaac1e0ea195329d1741cd1febb4b49b33f84e0d10a5ec8ee0a1a94f73abf081ec69bb863edfeb46d24ce424b025ac3f593a7ba9419ec9b17fb39f0fbeac80e0898f95b6709cbc95f7c097e22e3a6ca0efbc947bbcd4a9077f6bd9daba25b18bb16179fca9d9cb2ce49fc7cbd2de589237926cacb87ea60e2cc60a90b47575517921b5529b8a95823dd0c3d02a7747d74c4ca927ba6b70c06c1c1ef27e14d371e8dd8f5d9380a65b08ae1e6384f9b3575c5d7278de22ce80e63612a27f3b3f45dbe32ee855185293c719e5a7203a682a08fd810c46fa12b67e61349831f8fae3f558090ea988e1a22ec877b790ea09169055529247c4dd597857aad74eaeb3a5879e96453e681e213f2796ed704d620509f34f91d9d16f881fd397e2836a0a4d2f1bcd230067f7acb5381a2b17e8c5135e38c4d258afbe4f69ac7ad39b789e99686ee926b3ad31b98993673313b7b18a4faaea238d8055824fde7339a791fc7777ef28cc4a1a5d177b3c3882ced4921c6cd85ae82e1fe13fe680ae432a9918ce37b15f88d4d18fb16b69e5369d18c204aaf7ee49b830bf2328380e6ad96e8f6a9e01bc2c97ffbaa402d5406dc7b83c6eb5d515ffc3bea8c42cf299c9e2bea693515246c6ff859d33ba6c4da4c4c1706e0c6b4a574e927f02eb92b7d56722cff80c3e6f6b98d1c84cb576abdcc27a6bc7b110fc2ac5fead57f05ad854c3331ce1ff94c0dc97303540ee797d71566497af09f20e3554d467528e1fed8e69438171072fe2deca3979a8f5ec9043b9bc4da921b095c29dc0294148c1b7001dafda4c48600d1194f745e6d0689c561bf19d20758c4d25fac64d81780607a4106e220ef546fc4026af7b9da8defb2fe3c21d48798ac67c794fb40aabe44618a8911673466be06808c6f54a772b87bcfafb4d120a9bebffb8051bf24bb332eaa769cf175c1aadb0186f8946dc32513fd81fe1a61bfb860886bdd070359a43e06e74607d300bd2e01a3f1ee900c4039e8db742170228db61ef0c77724c49d1573144564a80cc1ebc0449b34f84be35187ceba3fbc2facf5ad1f1e15945e3c6c236579aca7bc97e4cc76a3310022693b64008562b254a7d11c0086813e551c4817bbb72a1d6fbfc84d326ce973651200f80aa8ab0976c53c390249ca8e7e5ec21b80e70c3e0205983d313b28a5d1b9d9149501e05d3257c8ae88c6308e9e00feeab19121d1032a582e68ca1f9f64a1fd91cb5d8613b985fd4be22a4d5c14a132c20811a75ee3cc61de0b3fbc3254d61995d086603032269888b942ec0971ad26ea4b8df1746c5ec1de904ddeb5045abc0a6ede9d6a199ed0782cb69efa3a4dc00747553dbef12fb8299ca364b4cdf2ac3eba03b0d8b273684116bba4458b5717bc4aca5406901173a89b3643ccc076f22779fccf1ad69981e24eef18c711a2d58dfe5834b41f9e7166d54dc8628e754baaca1cbb7db8256f88ebc889de6078ba83a1af14a4",
|
||||
hmac = ByteVector32(hex"9442626f72c475963dbddf8a57ab2cef3013eb3d6a5e8afbea9e631dac4481f5")
|
||||
)
|
||||
)
|
||||
|
||||
val expectedIn = """{"direction":"IN","add":{"channelId":"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f","id":926,"amountMsat":12365,"paymentHash":"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692","cltvExpiry":621500,"onionRoutingPacket":{"version":0,"publicKey":"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","payload":"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63f8cbe9f5e537e6e518d7998f11c40277d551bee036d227a421f344c0185b4533660d200acbc4f4aa6148e29f813bb537e950a79ba961b80ccaa6ad808cb88f858ee73f8b1f129d3214d194f76fc011c46e18c2caec0fcb69715e79cb381e449e5be20281e0aaa92defa089c98b7e29c22181f2d1af9f5fe2a37ede4ac3163b123aa72318201b1128b17053c381e7bf111620dfb7ea3dcc5c28cafdd9cb7bb1a4202b64199aa02af145c563ba1b9f10288203ce2666f486aacb2ee385dc0b67915864c95174dfaac1e0ea195329d1741cd1febb4b49b33f84e0d10a5ec8ee0a1a94f73abf081ec69bb863edfeb46d24ce424b025ac3f593a7ba9419ec9b17fb39f0fbeac80e0898f95b6709cbc95f7c097e22e3a6ca0efbc947bbcd4a9077f6bd9daba25b18bb16179fca9d9cb2ce49fc7cbd2de589237926cacb87ea60e2cc60a90b47575517921b5529b8a95823dd0c3d02a7747d74c4ca927ba6b70c06c1c1ef27e14d371e8dd8f5d9380a65b08ae1e6384f9b3575c5d7278de22ce80e63612a27f3b3f45dbe32ee855185293c719e5a7203a682a08fd810c46fa12b67e61349831f8fae3f558090ea988e1a22ec877b790ea09169055529247c4dd597857aad74eaeb3a5879e96453e681e213f2796ed704d620509f34f91d9d16f881fd397e2836a0a4d2f1bcd230067f7acb5381a2b17e8c5135e38c4d258afbe4f69ac7ad39b789e99686ee926b3ad31b98993673313b7b18a4faaea238d8055824fde7339a791fc7777ef28cc4a1a5d177b3c3882ced4921c6cd85ae82e1fe13fe680ae432a9918ce37b15f88d4d18fb16b69e5369d18c204aaf7ee49b830bf2328380e6ad96e8f6a9e01bc2c97ffbaa402d5406dc7b83c6eb5d515ffc3bea8c42cf299c9e2bea693515246c6ff859d33ba6c4da4c4c1706e0c6b4a574e927f02eb92b7d56722cff80c3e6f6b98d1c84cb576abdcc27a6bc7b110fc2ac5fead57f05ad854c3331ce1ff94c0dc97303540ee797d71566497af09f20e3554d467528e1fed8e69438171072fe2deca3979a8f5ec9043b9bc4da921b095c29dc0294148c1b7001dafda4c48600d1194f745e6d0689c561bf19d20758c4d25fac64d81780607a4106e220ef546fc4026af7b9da8defb2fe3c21d48798ac67c794fb40aabe44618a8911673466be06808c6f54a772b87bcfafb4d120a9bebffb8051bf24bb332eaa769cf175c1aadb0186f8946dc32513fd81fe1a61bfb860886bdd070359a43e06e74607d300bd2e01a3f1ee900c4039e8db742170228db61ef0c77724c49d1573144564a80cc1ebc0449b34f84be35187ceba3fbc2facf5ad1f1e15945e3c6c236579aca7bc97e4cc76a3310022693b64008562b254a7d11c0086813e551c4817bbb72a1d6fbfc84d326ce973651200f80aa8ab0976c53c390249ca8e7e5ec21b80e70c3e0205983d313b28a5d1b9d9149501e05d3257c8ae88c6308e9e00feeab19121d1032a582e68ca1f9f64a1fd91cb5d8613b985fd4be22a4d5c14a132c20811a75ee3cc61de0b3fbc3254d61995d086603032269888b942ec0971ad26ea4b8df1746c5ec1de904ddeb5045abc0a6ede9d6a199ed0782cb69efa3a4dc00747553dbef12fb8299ca364b4cdf2ac3eba03b0d8b273684116bba4458b5717bc4aca5406901173a89b3643ccc076f22779fccf1ad69981e24eef18c711a2d58dfe5834b41f9e7166d54dc8628e754baaca1cbb7db8256f88ebc889de6078ba83a1af14a4","hmac":"9442626f72c475963dbddf8a57ab2cef3013eb3d6a5e8afbea9e631dac4481f5"}}}"""
|
||||
|
||||
JsonSupport.serialization.write(IncomingHtlc(add))(org.json4s.DefaultFormats + new DirectedHtlcSerializer) shouldBe expectedIn
|
||||
JsonSupport.serialization.write(OutgoingHtlc(add))(org.json4s.DefaultFormats + new DirectedHtlcSerializer) shouldBe expectedIn.replace("IN", "OUT")
|
||||
}
|
||||
|
||||
test("Payment Request") {
|
||||
|
Loading…
Reference in New Issue
Block a user