mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-20 02:27:32 +01:00
Do not reply to htlc settlement commands (#1527)
The replies are always ignored currently anyway. A new trait `NoReplyTo` has been introduced. Those commands have a particular workflow because they are persisted in the pending relay db.
This commit is contained in:
parent
d0011005a0
commit
2fc118c291
@ -1854,12 +1854,15 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
}
|
||||
|
||||
def handleCommandSuccess(c: Command, newData: Data) = {
|
||||
val replyTo = c match {
|
||||
case hasReplyTo: HasReplyTo if hasReplyTo.replyTo != ActorRef.noSender => hasReplyTo.replyTo
|
||||
case _ => sender
|
||||
val replyTo_opt = c match {
|
||||
case _: NoReplyTo => None
|
||||
case hasReplyTo: HasReplyTo if hasReplyTo.replyTo != ActorRef.noSender => Some(hasReplyTo.replyTo)
|
||||
case _ => Some(sender)
|
||||
}
|
||||
replyTo_opt.foreach { replyTo =>
|
||||
val channelId = Helpers.getChannelId(newData)
|
||||
replyTo ! RES_SUCCESS(c, channelId)
|
||||
}
|
||||
val channelId = Helpers.getChannelId(newData)
|
||||
replyTo ! RES_SUCCESS(c, channelId)
|
||||
stay using newData
|
||||
}
|
||||
|
||||
|
@ -157,11 +157,12 @@ object Origin {
|
||||
|
||||
sealed trait Command
|
||||
sealed trait HasReplyTo { this: Command => def replyTo: ActorRef }
|
||||
sealed trait HasHtlcId { this: Command => def id: Long }
|
||||
final case class CMD_FULFILL_HTLC(id: Long, r: ByteVector32, commit: Boolean = false) extends Command with HasHtlcId
|
||||
final case class CMD_FAIL_HTLC(id: Long, reason: Either[ByteVector, FailureMessage], commit: Boolean = false) extends Command with HasHtlcId
|
||||
final case class CMD_FAIL_MALFORMED_HTLC(id: Long, onionHash: ByteVector32, failureCode: Int, commit: Boolean = false) extends Command with HasHtlcId
|
||||
sealed trait NoReplyTo { this: Command => }
|
||||
final case class CMD_ADD_HTLC(replyTo: ActorRef, amount: MilliSatoshi, paymentHash: ByteVector32, cltvExpiry: CltvExpiry, onion: OnionRoutingPacket, origin: Origin.Hot, commit: Boolean = false) extends Command with HasReplyTo
|
||||
sealed trait HtlcSettlementCommand extends Command with NoReplyTo { def id: Long }
|
||||
final case class CMD_FULFILL_HTLC(id: Long, r: ByteVector32, commit: Boolean = false) extends HtlcSettlementCommand
|
||||
final case class CMD_FAIL_HTLC(id: Long, reason: Either[ByteVector, FailureMessage], commit: Boolean = false) extends HtlcSettlementCommand
|
||||
final case class CMD_FAIL_MALFORMED_HTLC(id: Long, onionHash: ByteVector32, failureCode: Int, commit: Boolean = false) extends HtlcSettlementCommand
|
||||
final case class CMD_UPDATE_FEE(feeratePerKw: FeeratePerKw, commit: Boolean = false) extends Command
|
||||
case object CMD_SIGN extends Command
|
||||
sealed trait CloseCommand extends Command
|
||||
|
@ -38,11 +38,11 @@ import fr.acinq.eclair.wire.{UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulf
|
||||
*/
|
||||
trait PendingRelayDb extends Closeable {
|
||||
|
||||
def addPendingRelay(channelId: ByteVector32, cmd: Command with HasHtlcId): Unit
|
||||
def addPendingRelay(channelId: ByteVector32, cmd: HtlcSettlementCommand): Unit
|
||||
|
||||
def removePendingRelay(channelId: ByteVector32, htlcId: Long): Unit
|
||||
|
||||
def listPendingRelay(channelId: ByteVector32): Seq[Command with HasHtlcId]
|
||||
def listPendingRelay(channelId: ByteVector32): Seq[HtlcSettlementCommand]
|
||||
|
||||
def listPendingRelay(): Set[(ByteVector32, Long)]
|
||||
|
||||
@ -54,16 +54,14 @@ object PendingRelayDb {
|
||||
* in a database because we don't want to lose preimages, or to forget to fail
|
||||
* incoming htlcs, which would lead to unwanted channel closings.
|
||||
*/
|
||||
def safeSend(register: ActorRef, db: PendingRelayDb, replyTo: ActorRef, channelId: ByteVector32, cmd: Command with HasHtlcId): Unit = {
|
||||
register ! Register.Forward(replyTo, channelId, cmd)
|
||||
def safeSend(register: ActorRef, db: PendingRelayDb, channelId: ByteVector32, cmd: HtlcSettlementCommand): Unit = {
|
||||
// htlc settlement commands don't have replyTo
|
||||
register ! Register.Forward(ActorRef.noSender, channelId, cmd)
|
||||
// we store the command in a db (note that this happens *after* forwarding the command to the channel, so we don't add latency)
|
||||
db.addPendingRelay(channelId, cmd)
|
||||
}
|
||||
|
||||
def safeSend(register: ActorRef, db: PendingRelayDb, channelId: ByteVector32, cmd: Command with HasHtlcId)(implicit ctx: ActorContext): Unit =
|
||||
safeSend(register, db, ctx.self, channelId, cmd)
|
||||
|
||||
def ackCommand(db: PendingRelayDb, channelId: ByteVector32, cmd: Command with HasHtlcId): Unit = {
|
||||
def ackCommand(db: PendingRelayDb, channelId: ByteVector32, cmd: HtlcSettlementCommand): Unit = {
|
||||
db.removePendingRelay(channelId, cmd.id)
|
||||
}
|
||||
|
||||
@ -79,7 +77,7 @@ object PendingRelayDb {
|
||||
db.removePendingRelay(u.channelId, u.id)
|
||||
}
|
||||
|
||||
def getPendingFailsAndFulfills(db: PendingRelayDb, channelId: ByteVector32)(implicit log: LoggingAdapter): Seq[Command with HasHtlcId] = {
|
||||
def getPendingFailsAndFulfills(db: PendingRelayDb, channelId: ByteVector32)(implicit log: LoggingAdapter): Seq[HtlcSettlementCommand] = {
|
||||
db.listPendingRelay(channelId)
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ package fr.acinq.eclair.db.pg
|
||||
|
||||
|
||||
import fr.acinq.bitcoin.ByteVector32
|
||||
import fr.acinq.eclair.channel.{Command, HasHtlcId}
|
||||
import fr.acinq.eclair.channel.{Command, HtlcSettlementCommand}
|
||||
import fr.acinq.eclair.db.Monitoring.Metrics.withMetrics
|
||||
import fr.acinq.eclair.db.PendingRelayDb
|
||||
import fr.acinq.eclair.db.pg.PgUtils._
|
||||
@ -44,7 +44,7 @@ class PgPendingRelayDb(implicit ds: DataSource, lock: DatabaseLock) extends Pend
|
||||
}
|
||||
}
|
||||
|
||||
override def addPendingRelay(channelId: ByteVector32, cmd: Command with HasHtlcId): Unit = withMetrics("pending-relay/add") {
|
||||
override def addPendingRelay(channelId: ByteVector32, cmd: HtlcSettlementCommand): Unit = withMetrics("pending-relay/add") {
|
||||
withLock { pg =>
|
||||
using(pg.prepareStatement("INSERT INTO pending_relay VALUES (?, ?, ?) ON CONFLICT DO NOTHING")) { statement =>
|
||||
statement.setString(1, channelId.toHex)
|
||||
@ -65,7 +65,7 @@ class PgPendingRelayDb(implicit ds: DataSource, lock: DatabaseLock) extends Pend
|
||||
}
|
||||
}
|
||||
|
||||
override def listPendingRelay(channelId: ByteVector32): Seq[Command with HasHtlcId] = withMetrics("pending-relay/list-channel") {
|
||||
override def listPendingRelay(channelId: ByteVector32): Seq[HtlcSettlementCommand] = withMetrics("pending-relay/list-channel") {
|
||||
withLock { pg =>
|
||||
using(pg.prepareStatement("SELECT htlc_id, data FROM pending_relay WHERE channel_id=?")) { statement =>
|
||||
statement.setString(1, channelId.toHex)
|
||||
|
@ -19,7 +19,7 @@ package fr.acinq.eclair.db.sqlite
|
||||
import java.sql.Connection
|
||||
|
||||
import fr.acinq.bitcoin.ByteVector32
|
||||
import fr.acinq.eclair.channel.{Command, HasHtlcId}
|
||||
import fr.acinq.eclair.channel.{Command, HtlcSettlementCommand}
|
||||
import fr.acinq.eclair.db.Monitoring.Metrics.withMetrics
|
||||
import fr.acinq.eclair.db.PendingRelayDb
|
||||
import fr.acinq.eclair.wire.CommandCodecs.cmdCodec
|
||||
@ -40,7 +40,7 @@ class SqlitePendingRelayDb(sqlite: Connection) extends PendingRelayDb {
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS pending_relay (channel_id BLOB NOT NULL, htlc_id INTEGER NOT NULL, data BLOB NOT NULL, PRIMARY KEY(channel_id, htlc_id))")
|
||||
}
|
||||
|
||||
override def addPendingRelay(channelId: ByteVector32, cmd: Command with HasHtlcId): Unit = withMetrics("pending-relay/add") {
|
||||
override def addPendingRelay(channelId: ByteVector32, cmd: HtlcSettlementCommand): Unit = withMetrics("pending-relay/add") {
|
||||
using(sqlite.prepareStatement("INSERT OR IGNORE INTO pending_relay VALUES (?, ?, ?)")) { statement =>
|
||||
statement.setBytes(1, channelId.toArray)
|
||||
statement.setLong(2, cmd.id)
|
||||
@ -57,7 +57,7 @@ class SqlitePendingRelayDb(sqlite: Connection) extends PendingRelayDb {
|
||||
}
|
||||
}
|
||||
|
||||
override def listPendingRelay(channelId: ByteVector32): Seq[Command with HasHtlcId] = withMetrics("pending-relay/list-channel") {
|
||||
override def listPendingRelay(channelId: ByteVector32): Seq[HtlcSettlementCommand] = withMetrics("pending-relay/list-channel") {
|
||||
using(sqlite.prepareStatement("SELECT data FROM pending_relay WHERE channel_id=?")) { statement =>
|
||||
statement.setBytes(1, channelId.toArray)
|
||||
val rs = statement.executeQuery()
|
||||
|
@ -87,10 +87,6 @@ class MultiPartPaymentFSM(nodeParams: NodeParams, paymentHash: ByteVector32, tot
|
||||
stay
|
||||
}
|
||||
|
||||
whenUnhandled {
|
||||
case Event(_: RES_SUCCESS[_], _) => stay
|
||||
}
|
||||
|
||||
onTransition {
|
||||
case WAITING_FOR_HTLC -> WAITING_FOR_HTLC => () // don't do anything if we stay in that state
|
||||
case WAITING_FOR_HTLC -> _ => cancelTimer(PaymentTimeout.toString)
|
||||
|
@ -77,7 +77,7 @@ object ChannelRelay {
|
||||
}
|
||||
}
|
||||
|
||||
def translateRelayFailure(originHtlcId: Long, fail: HtlcResult.Fail): channel.Command with channel.HasHtlcId = {
|
||||
def translateRelayFailure(originHtlcId: Long, fail: HtlcResult.Fail): channel.Command with channel.HtlcSettlementCommand = {
|
||||
fail match {
|
||||
case f: HtlcResult.RemoteFail => CMD_FAIL_HTLC(originHtlcId, Left(f.fail.reason), commit = true)
|
||||
case f: HtlcResult.RemoteFailMalformed => CMD_FAIL_MALFORMED_HTLC(originHtlcId, f.fail.onionHash, f.fail.failureCode, commit = true)
|
||||
@ -154,9 +154,9 @@ class ChannelRelay private(nodeParams: NodeParams,
|
||||
safeSendAndStop(o.originChannelId, cmd)
|
||||
}
|
||||
|
||||
def safeSendAndStop(channelId: ByteVector32, cmd: channel.Command with channel.HasHtlcId): Behavior[Command] = {
|
||||
def safeSendAndStop(channelId: ByteVector32, cmd: channel.Command with channel.HtlcSettlementCommand): Behavior[Command] = {
|
||||
// NB: we are not using an adapter here because we are stopping anyway so we won't be there to get the result
|
||||
PendingRelayDb.safeSend(register, nodeParams.db.pendingRelay, context.system.deadLetters.toClassic, channelId, cmd)
|
||||
PendingRelayDb.safeSend(register, nodeParams.db.pendingRelay, channelId, cmd)
|
||||
Behaviors.stopped
|
||||
}
|
||||
|
||||
|
@ -310,7 +310,7 @@ class NodeRelay private(nodeParams: NodeParams,
|
||||
private def rejectHtlc(htlcId: Long, channelId: ByteVector32, amount: MilliSatoshi, failure: Option[FailureMessage] = None): Unit = {
|
||||
val failureMessage = failure.getOrElse(IncorrectOrUnknownPaymentDetails(amount, nodeParams.currentBlockHeight))
|
||||
val cmd = CMD_FAIL_HTLC(htlcId, Right(failureMessage), commit = true)
|
||||
PendingRelayDb.safeSend(register, nodeParams.db.pendingRelay, context.system.deadLetters.toClassic, channelId, cmd)
|
||||
PendingRelayDb.safeSend(register, nodeParams.db.pendingRelay, channelId, cmd)
|
||||
}
|
||||
|
||||
private def rejectPayment(upstream: Upstream.Trampoline, failure: Option[FailureMessage]): Unit = {
|
||||
@ -320,7 +320,7 @@ class NodeRelay private(nodeParams: NodeParams,
|
||||
|
||||
private def fulfillPayment(upstream: Upstream.Trampoline, paymentPreimage: ByteVector32): Unit = upstream.adds.foreach(add => {
|
||||
val cmd = CMD_FULFILL_HTLC(add.id, paymentPreimage, commit = true)
|
||||
PendingRelayDb.safeSend(register, nodeParams.db.pendingRelay, context.system.deadLetters.toClassic, add.channelId, cmd)
|
||||
PendingRelayDb.safeSend(register, nodeParams.db.pendingRelay, add.channelId, cmd)
|
||||
})
|
||||
|
||||
private def success(upstream: Upstream.Trampoline, fulfilledUpstream: Boolean, paymentSent: PaymentSent): Unit = {
|
||||
|
@ -115,8 +115,6 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
|
||||
handleDownstreamFailure(brokenHtlcs, o, htlc, fail)
|
||||
|
||||
case GetBrokenHtlcs => sender ! brokenHtlcs
|
||||
|
||||
case _: RES_SUCCESS[_] => // ignoring responses from channels
|
||||
}
|
||||
|
||||
private def handleDownstreamFulfill(brokenHtlcs: BrokenHtlcs, origin: Origin.Cold, fulfilledHtlc: UpdateAddHtlc, paymentPreimage: ByteVector32): Unit =
|
||||
|
@ -89,8 +89,6 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym
|
||||
case o: Origin.Hot => o.replyTo ! r
|
||||
}
|
||||
|
||||
case _: RES_SUCCESS[_] => () // ignoring responses from channels
|
||||
|
||||
case g: GetOutgoingChannels => channelRelayer ! ChannelRelayer.GetOutgoingChannels(sender, g)
|
||||
|
||||
case GetChildActors(replyTo) => replyTo ! ChildActors(postRestartCleaner, channelRelayer, nodeRelayer)
|
||||
|
@ -40,7 +40,7 @@ object CommandCodecs {
|
||||
("failureCode" | uint16) ::
|
||||
("commit" | provide(false))).as[CMD_FAIL_MALFORMED_HTLC]
|
||||
|
||||
val cmdCodec: Codec[Command with HasHtlcId] = discriminated[Command with HasHtlcId].by(uint16)
|
||||
val cmdCodec: Codec[HtlcSettlementCommand] = discriminated[HtlcSettlementCommand].by(uint16)
|
||||
.typecase(0, cmdFulfillCodec)
|
||||
.typecase(1, cmdFailCodec)
|
||||
.typecase(2, cmdFailMalformedCodec)
|
||||
|
@ -158,7 +158,6 @@ trait StateTestsHelperMethods extends TestKitBase with FixtureTestSuite with Par
|
||||
def fulfillHtlc(id: Long, R: ByteVector32, s: TestFSMRef[State, Data, Channel], r: TestFSMRef[State, Data, Channel], s2r: TestProbe, r2s: TestProbe): Unit = {
|
||||
val sender = TestProbe()
|
||||
sender.send(s, CMD_FULFILL_HTLC(id, R))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
val fulfill = s2r.expectMsgType[UpdateFulfillHtlc]
|
||||
s2r.forward(r)
|
||||
awaitCond(r.stateData.asInstanceOf[HasCommitments].commitments.remoteChanges.proposed.contains(fulfill))
|
||||
|
@ -739,7 +739,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
val (r, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
// we listen to channel_update events
|
||||
val listener = TestProbe()
|
||||
@ -1115,7 +1114,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
sender.send(bob, CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure)))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
bob2alice.forward(alice)
|
||||
sender.send(bob, CMD_SIGN)
|
||||
@ -1144,7 +1142,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
sender.send(bob, CMD_FAIL_MALFORMED_HTLC(htlc.id, Sphinx.PaymentPacket.hash(htlc.onionRoutingPacket), FailureMessageCodecs.BADONION))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_MALFORMED_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc]
|
||||
bob2alice.forward(alice)
|
||||
sender.send(bob, CMD_SIGN)
|
||||
@ -1239,7 +1236,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
// actual test begins
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
awaitCond(bob.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(
|
||||
@ -1301,7 +1297,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
val (r, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
|
||||
// actual test begins
|
||||
@ -1391,7 +1386,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
// actual test begins
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
sender.send(bob, CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure)))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
awaitCond(bob.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(
|
||||
@ -1442,7 +1436,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
// actual test begins
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
sender.send(bob, CMD_FAIL_MALFORMED_HTLC(htlc.id, Sphinx.PaymentPacket.hash(htlc.onionRoutingPacket), FailureMessageCodecs.BADONION))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_MALFORMED_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc]
|
||||
awaitCond(bob.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(
|
||||
@ -1487,7 +1480,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
sender.send(bob, CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure)))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
|
||||
// actual test begins
|
||||
@ -1521,7 +1513,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
// Bob fails the HTLC because he cannot parse it
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
sender.send(bob, CMD_FAIL_MALFORMED_HTLC(htlc.id, Sphinx.PaymentPacket.hash(htlc.onionRoutingPacket), FailureMessageCodecs.BADONION))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_MALFORMED_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc]
|
||||
bob2alice.forward(alice)
|
||||
|
||||
@ -1601,7 +1592,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
// Bob receives a failure with a completely invalid onion error (missing mac)
|
||||
sender.send(bob, CMD_FAIL_HTLC(htlc.id, Left(ByteVector.fill(260)(42))))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
assert(fail.id === htlc.id)
|
||||
// We should rectify the packet length before forwarding upstream.
|
||||
@ -2065,7 +2055,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
val HtlcSuccessTx(_, htlcSuccessTx, _) = initialState.commitments.localCommit.publishableTxs.htlcTxsAndSigs.head.txinfo
|
||||
|
||||
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = true))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeout).toLong))
|
||||
|
||||
@ -2100,7 +2089,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
val HtlcSuccessTx(_, htlcSuccessTx, _) = initialState.commitments.localCommit.publishableTxs.htlcTxsAndSigs.head.txinfo
|
||||
|
||||
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = false))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeout).toLong))
|
||||
|
||||
@ -2135,7 +2123,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||
val HtlcSuccessTx(_, htlcSuccessTx, _) = initialState.commitments.localCommit.publishableTxs.htlcTxsAndSigs.head.txinfo
|
||||
|
||||
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = true))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
bob2alice.forward(alice)
|
||||
bob2alice.expectMsgType[CommitSig]
|
||||
|
@ -121,7 +121,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||
val sender = TestProbe()
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
|
||||
sender.send(bob, CMD_FULFILL_HTLC(0, r1))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
awaitCond(bob.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(
|
||||
@ -206,7 +205,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||
val sender = TestProbe()
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
|
||||
sender.send(bob, CMD_FAIL_HTLC(1, Right(PermanentChannelFailure)))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
awaitCond(bob.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(
|
||||
@ -238,7 +236,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||
val sender = TestProbe()
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
|
||||
sender.send(bob, CMD_FAIL_MALFORMED_HTLC(1, Crypto.sha256(ByteVector.empty), FailureMessageCodecs.BADONION))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_MALFORMED_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc]
|
||||
awaitCond(bob.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(
|
||||
@ -357,7 +354,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
sender.send(bob, CMD_FULFILL_HTLC(0, r1))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
sender.send(bob, CMD_SIGN)
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_SIGN.type]]
|
||||
@ -376,7 +372,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
sender.send(bob, CMD_FULFILL_HTLC(0, r1))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
bob2alice.forward(alice)
|
||||
sender.send(bob, CMD_SIGN)
|
||||
@ -453,7 +448,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||
val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val sender = TestProbe()
|
||||
sender.send(bob, CMD_FULFILL_HTLC(0, r1))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FULFILL_HTLC]]
|
||||
bob2alice.expectMsgType[UpdateFulfillHtlc]
|
||||
bob2alice.forward(alice)
|
||||
sender.send(bob, CMD_SIGN)
|
||||
@ -493,7 +487,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
sender.send(bob, CMD_FAIL_HTLC(1, Right(PermanentChannelFailure)))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
bob2alice.forward(alice)
|
||||
sender.send(bob, CMD_SIGN)
|
||||
@ -519,7 +512,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
sender.send(bob, CMD_FAIL_MALFORMED_HTLC(1, Crypto.sha256(ByteVector.view("should be htlc.onionRoutingPacket".getBytes())), FailureMessageCodecs.BADONION))
|
||||
sender.expectMsgType[RES_SUCCESS[CMD_FAIL_MALFORMED_HTLC]]
|
||||
val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc]
|
||||
bob2alice.forward(alice)
|
||||
sender.send(bob, CMD_SIGN)
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package fr.acinq.eclair.payment
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import akka.actor.Status.Failure
|
||||
import akka.testkit.{TestActorRef, TestProbe}
|
||||
import fr.acinq.bitcoin.{ByteVector32, Crypto}
|
||||
@ -369,8 +370,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||
|
||||
val commands = f.register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: f.register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: Nil
|
||||
assert(commands.toSet === Set(
|
||||
Register.Forward(handler, ByteVector32.One, CMD_FAIL_HTLC(0, Right(PaymentTimeout), commit = true)),
|
||||
Register.Forward(handler, ByteVector32.One, CMD_FAIL_HTLC(1, Right(PaymentTimeout), commit = true))
|
||||
Register.Forward(ActorRef.noSender, ByteVector32.One, CMD_FAIL_HTLC(0, Right(PaymentTimeout), commit = true)),
|
||||
Register.Forward(ActorRef.noSender, ByteVector32.One, CMD_FAIL_HTLC(1, Right(PaymentTimeout), commit = true))
|
||||
))
|
||||
awaitCond({
|
||||
f.sender.send(handler, GetPendingPayments)
|
||||
@ -379,7 +380,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||
|
||||
// Extraneous HTLCs should be failed.
|
||||
f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr1.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 42, 200 msat, pr1.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket)), Some(PaymentTimeout)))
|
||||
f.register.expectMsg(Register.Forward(handler, ByteVector32.One, CMD_FAIL_HTLC(42, Right(PaymentTimeout), commit = true)))
|
||||
f.register.expectMsg(Register.Forward(ActorRef.noSender, ByteVector32.One, CMD_FAIL_HTLC(42, Right(PaymentTimeout), commit = true)))
|
||||
|
||||
// The payment should still be pending in DB.
|
||||
val Some(incomingPayment) = nodeParams.db.payments.getIncomingPayment(pr1.paymentHash)
|
||||
@ -401,12 +402,12 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||
val add3 = add2.copy(id = 43)
|
||||
f.sender.send(handler, IncomingPacket.FinalPacket(add3, Onion.createMultiPartPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, pr.paymentSecret.get)))
|
||||
|
||||
f.register.expectMsg(Register.Forward(handler, add2.channelId, CMD_FAIL_HTLC(add2.id, Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)), commit = true)))
|
||||
f.register.expectMsg(Register.Forward(ActorRef.noSender, add2.channelId, CMD_FAIL_HTLC(add2.id, Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)), commit = true)))
|
||||
val cmd1 = f.register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]]
|
||||
assert(cmd1.message.id === add1.id)
|
||||
assert(cmd1.channelId === add1.channelId)
|
||||
assert(Crypto.sha256(cmd1.message.r) === pr.paymentHash)
|
||||
f.register.expectMsg(Register.Forward(handler, add3.channelId, CMD_FULFILL_HTLC(add3.id, cmd1.message.r, commit = true)))
|
||||
f.register.expectMsg(Register.Forward(ActorRef.noSender, add3.channelId, CMD_FULFILL_HTLC(add3.id, cmd1.message.r, commit = true)))
|
||||
|
||||
val paymentReceived = f.eventListener.expectMsgType[PaymentReceived]
|
||||
assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0))) === PaymentReceived(pr.paymentHash, PartialPayment(800 msat, ByteVector32.One, 0) :: PartialPayment(200 msat, ByteVector32.Zeroes, 0) :: Nil))
|
||||
@ -420,7 +421,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||
|
||||
// Extraneous HTLCs should be fulfilled.
|
||||
f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 44, 200 msat, pr.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket)), None))
|
||||
f.register.expectMsg(Register.Forward(handler, ByteVector32.One, CMD_FULFILL_HTLC(44, cmd1.message.r, commit = true)))
|
||||
f.register.expectMsg(Register.Forward(ActorRef.noSender, ByteVector32.One, CMD_FULFILL_HTLC(44, cmd1.message.r, commit = true)))
|
||||
assert(f.eventListener.expectMsgType[PaymentReceived].amount === 200.msat)
|
||||
val received2 = nodeParams.db.payments.getIncomingPayment(pr.paymentHash)
|
||||
assert(received2.get.status.asInstanceOf[IncomingPaymentStatus.Received].amount === 1200.msat)
|
||||
@ -439,7 +440,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||
|
||||
val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, pr.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket)
|
||||
f.sender.send(handler, IncomingPacket.FinalPacket(add1, Onion.createMultiPartPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, pr.paymentSecret.get)))
|
||||
f.register.expectMsg(Register.Forward(handler, ByteVector32.One, CMD_FAIL_HTLC(0, Right(PaymentTimeout), commit = true)))
|
||||
f.register.expectMsg(Register.Forward(ActorRef.noSender, ByteVector32.One, CMD_FAIL_HTLC(0, Right(PaymentTimeout), commit = true)))
|
||||
awaitCond({
|
||||
f.sender.send(handler, GetPendingPayments)
|
||||
f.sender.expectMsgType[PendingPayments].paymentHashes.isEmpty
|
||||
@ -454,7 +455,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||
assert(cmd1.channelId === add2.channelId)
|
||||
assert(cmd1.message.id === 2)
|
||||
assert(Crypto.sha256(cmd1.message.r) === pr.paymentHash)
|
||||
f.register.expectMsg(Register.Forward(handler, add3.channelId, CMD_FULFILL_HTLC(5, cmd1.message.r, commit = true)))
|
||||
f.register.expectMsg(Register.Forward(ActorRef.noSender, add3.channelId, CMD_FULFILL_HTLC(5, cmd1.message.r, commit = true)))
|
||||
|
||||
val paymentReceived = f.eventListener.expectMsgType[PaymentReceived]
|
||||
assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0))) === PaymentReceived(pr.paymentHash, PartialPayment(300 msat, ByteVector32.One, 0) :: PartialPayment(700 msat, ByteVector32.Zeroes, 0) :: Nil))
|
||||
@ -501,7 +502,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||
val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket)
|
||||
sender.send(normalHandler, IncomingPacket.FinalPacket(add, payload))
|
||||
|
||||
f.register.expectMsg(Register.Forward(normalHandler, add.channelId, CMD_FAIL_HTLC(add.id, Right(IncorrectOrUnknownPaymentDetails(42000 msat, nodeParams.currentBlockHeight)), commit = true)))
|
||||
f.register.expectMsg(Register.Forward(ActorRef.noSender, add.channelId, CMD_FAIL_HTLC(add.id, Right(IncorrectOrUnknownPaymentDetails(42000 msat, nodeParams.currentBlockHeight)), commit = true)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(paymentHash) === None)
|
||||
}
|
||||
}
|
||||
|
@ -413,8 +413,8 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||
val origin_2 = Origin.TrampolineRelayedCold(upstream_2.adds.map(u => (u.channelId, u.id)).toList)
|
||||
sender.send(relayer, RES_ADD_SETTLED(origin_2, htlc_2_2, HtlcResult.OnChainFulfill(preimage2)))
|
||||
register.expectMsgAllOf(
|
||||
Register.Forward(replyTo = postRestartHtlcCleaner, channelId_ab_1, CMD_FULFILL_HTLC(5, preimage2, commit = true)),
|
||||
Register.Forward(replyTo = postRestartHtlcCleaner, channelId_ab_2, CMD_FULFILL_HTLC(9, preimage2, commit = true))
|
||||
Register.Forward(replyTo = ActorRef.noSender, channelId_ab_1, CMD_FULFILL_HTLC(5, preimage2, commit = true)),
|
||||
Register.Forward(replyTo = ActorRef.noSender, channelId_ab_2, CMD_FULFILL_HTLC(9, preimage2, commit = true))
|
||||
)
|
||||
|
||||
// Payment 3 should not be failed: we are still waiting for on-chain confirmation.
|
||||
@ -448,7 +448,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||
val postRestartHtlcCleaner = sender.expectMsgType[Relayer.ChildActors].postRestartCleaner
|
||||
|
||||
sender.send(relayer, buildForwardFulfill(testCase.downstream, testCase.origin, preimage1))
|
||||
register.expectMsg(Register.Forward(postRestartHtlcCleaner, testCase.origin.originChannelId, CMD_FULFILL_HTLC(testCase.origin.originHtlcId, preimage1, commit = true)))
|
||||
register.expectMsg(Register.Forward(ActorRef.noSender, testCase.origin.originChannelId, CMD_FULFILL_HTLC(testCase.origin.originHtlcId, preimage1, commit = true)))
|
||||
eventListener.expectMsgType[ChannelPaymentRelayed]
|
||||
|
||||
sender.send(relayer, buildForwardFulfill(testCase.downstream, testCase.origin, preimage1))
|
||||
@ -471,7 +471,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||
sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.origin_1))
|
||||
val fails = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: Nil
|
||||
assert(fails.toSet === testCase.origin_1.htlcs.map {
|
||||
case (channelId, htlcId) => Register.Forward(postRestartHtlcCleaner, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure), commit = true))
|
||||
case (channelId, htlcId) => Register.Forward(ActorRef.noSender, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure), commit = true))
|
||||
}.toSet)
|
||||
|
||||
sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.origin_1))
|
||||
@ -483,7 +483,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||
|
||||
sender.send(relayer, buildForwardFail(testCase.downstream_2_3, testCase.origin_2))
|
||||
register.expectMsg(testCase.origin_2.htlcs.map {
|
||||
case (channelId, htlcId) => Register.Forward(postRestartHtlcCleaner, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure), commit = true))
|
||||
case (channelId, htlcId) => Register.Forward(ActorRef.noSender, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure), commit = true))
|
||||
}.head)
|
||||
|
||||
register.expectNoMsg(100 millis)
|
||||
@ -505,7 +505,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||
sender.send(relayer, buildForwardFulfill(testCase.downstream_1_1, testCase.origin_1, preimage1))
|
||||
val fulfills = register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]] :: register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]] :: Nil
|
||||
assert(fulfills.toSet === testCase.origin_1.htlcs.map {
|
||||
case (channelId, htlcId) => Register.Forward(postRestartHtlcCleaner, channelId, CMD_FULFILL_HTLC(htlcId, preimage1, commit = true))
|
||||
case (channelId, htlcId) => Register.Forward(ActorRef.noSender, channelId, CMD_FULFILL_HTLC(htlcId, preimage1, commit = true))
|
||||
}.toSet)
|
||||
|
||||
sender.send(relayer, buildForwardFulfill(testCase.downstream_1_1, testCase.origin_1, preimage1))
|
||||
@ -514,7 +514,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||
// This payment has 3 downstream HTLCs, but we should fulfill upstream as soon as we receive the preimage.
|
||||
sender.send(relayer, buildForwardFulfill(testCase.downstream_2_1, testCase.origin_2, preimage2))
|
||||
register.expectMsg(testCase.origin_2.htlcs.map {
|
||||
case (channelId, htlcId) => Register.Forward(postRestartHtlcCleaner, channelId, CMD_FULFILL_HTLC(htlcId, preimage2, commit = true))
|
||||
case (channelId, htlcId) => Register.Forward(ActorRef.noSender, channelId, CMD_FULFILL_HTLC(htlcId, preimage2, commit = true))
|
||||
}.head)
|
||||
|
||||
sender.send(relayer, buildForwardFulfill(testCase.downstream_2_2, testCase.origin_2, preimage2))
|
||||
@ -538,7 +538,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||
|
||||
sender.send(relayer, buildForwardFulfill(testCase.downstream_2_2, testCase.origin_2, preimage2))
|
||||
register.expectMsg(testCase.origin_2.htlcs.map {
|
||||
case (channelId, htlcId) => Register.Forward(postRestartHtlcCleaner, channelId, CMD_FULFILL_HTLC(htlcId, preimage2, commit = true))
|
||||
case (channelId, htlcId) => Register.Forward(ActorRef.noSender, channelId, CMD_FULFILL_HTLC(htlcId, preimage2, commit = true))
|
||||
}.head)
|
||||
|
||||
sender.send(relayer, buildForwardFail(testCase.downstream_2_3, testCase.origin_2))
|
||||
|
@ -375,7 +375,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
||||
val u_disabled = createLocalUpdate(shortId1, enabled = false)
|
||||
val downstream_htlc = UpdateAddHtlc(channelId1, 7, outgoingAmount, paymentHash, outgoingExpiry, emptyOnionPacket)
|
||||
|
||||
case class TestCase(result: HtlcResult, cmd: channel.Command with HasHtlcId)
|
||||
case class TestCase(result: HtlcResult, cmd: channel.HtlcSettlementCommand)
|
||||
|
||||
val testCases = Seq(
|
||||
TestCase(HtlcResult.RemoteFail(UpdateFailHtlc(channelId1, downstream_htlc.id, hex"deadbeef")), CMD_FAIL_HTLC(r.add.id, Left(hex"deadbeef"), commit = true)),
|
||||
|
@ -27,7 +27,7 @@ import org.scalatest.funsuite.AnyFunSuite
|
||||
class CommandCodecsSpec extends AnyFunSuite {
|
||||
|
||||
test("encode/decode all channel messages") {
|
||||
val msgs: List[Command with HasHtlcId] =
|
||||
val msgs: List[HtlcSettlementCommand] =
|
||||
CMD_FULFILL_HTLC(1573L, randomBytes32) ::
|
||||
CMD_FAIL_HTLC(42456L, Left(randomBytes(145))) ::
|
||||
CMD_FAIL_HTLC(253, Right(TemporaryNodeFailure)) ::
|
||||
|
Loading…
Reference in New Issue
Block a user