1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 18:50:43 +01:00

relayer now extracts preimages from the blockchain

This commit is contained in:
pm47 2017-02-07 14:04:12 +01:00
parent 08281dc109
commit ba515a921a
8 changed files with 171 additions and 47 deletions

View File

@ -100,7 +100,7 @@ class PeerWatcher(client: ExtendedBitcoinClient, blockCount: Long)(implicit ec:
}
def publish(tx: Transaction) = client.publishTransaction(tx).onFailure {
case t: Throwable => log.error(t, s"cannot publish tx ${BinaryData(Transaction.write(tx))}")
case t: Throwable => log.error(s"cannot publish tx: reason=${t.getMessage} tx=${BinaryData(Transaction.write(tx))}")
}
}

View File

@ -259,7 +259,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
val commitInput = localCommitTx.input
blockchain ! WatchSpent(self, commitInput.outPoint.txid, commitInput.outPoint.index.toInt, BITCOIN_FUNDING_SPENT) // TODO: should we wait for an acknowledgment from the watcher?
blockchain ! WatchConfirmed(self, commitInput.outPoint.txid, params.minimumDepth, BITCOIN_FUNDING_DEPTHOK)
blockchain ! PublishAsap(fundingTx)
publishTx(fundingTx, "funding-tx")
val commitments = Commitments(params.localParams, params.remoteParams,
LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), remoteCommit,
LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil),
@ -303,21 +304,21 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, _), d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) => handleInformationLeak(d)
case Event(cmd: CMD_CLOSE, d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) =>
blockchain ! PublishAsap(d.commitments.localCommit.publishableTxs.commitTx.tx)
blockchain ! WatchConfirmed(self, d.commitments.localCommit.publishableTxs.commitTx.tx.txid, d.params.minimumDepth, BITCOIN_CLOSE_DONE)
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
publishTx(commitTx, "commit-tx")
blockchain ! WatchConfirmed(self, commitTx.txid, d.params.minimumDepth, BITCOIN_CLOSE_DONE)
// there can't be htlcs at this stage
val localCommitPublished = LocalCommitPublished(d.commitments.localCommit.publishableTxs.commitTx.tx, None, Nil, Nil, Nil)
val localCommitPublished = LocalCommitPublished(commitTx, None, Nil, Nil, Nil)
goto(CLOSING) using DATA_CLOSING(d.commitments, localCommitPublished = Some(localCommitPublished))
case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) =>
log.error(s"peer sent $e, closing connection")
// see bolt #2: A node MUST fail the connection if it receives an err message
val localCommitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
blockchain ! PublishAsap(localCommitTx)
blockchain ! WatchConfirmed(self, localCommitTx.txid, d.params.minimumDepth, BITCOIN_LOCALCOMMIT_DONE)
log.error(s"peer sent $e, closing connection") // see bolt #2: A node MUST fail the connection if it receives an err message
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
publishTx(commitTx, "commit-tx")
blockchain ! WatchConfirmed(self, commitTx.txid, d.params.minimumDepth, BITCOIN_LOCALCOMMIT_DONE)
// there can't be htlcs at this stage
// TODO: LocalCommitPublished.claimDelayedOutputTx should be defined
val localCommitPublished = LocalCommitPublished(d.commitments.localCommit.publishableTxs.commitTx.tx, None, Nil, Nil, Nil)
val localCommitPublished = LocalCommitPublished(commitTx, None, Nil, Nil, Nil)
goto(CLOSING) using DATA_CLOSING(d.commitments, localCommitPublished = Some(localCommitPublished))
})
@ -353,11 +354,12 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, _), d: DATA_NORMAL) => handleInformationLeak(d)
case Event(cmd: CMD_CLOSE, d: DATA_NORMAL) =>
blockchain ! PublishAsap(d.commitments.localCommit.publishableTxs.commitTx.tx)
blockchain ! WatchConfirmed(self, d.commitments.localCommit.publishableTxs.commitTx.tx.txid, d.params.minimumDepth, BITCOIN_CLOSE_DONE)
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
publishTx(commitTx, "commit-tx")
blockchain ! WatchConfirmed(self, commitTx.txid, d.params.minimumDepth, BITCOIN_CLOSE_DONE)
// there can't be htlcs at this stage
// TODO: LocalCommitPublished.claimDelayedOutputTx should be defined
val localCommitPublished = LocalCommitPublished(d.commitments.localCommit.publishableTxs.commitTx.tx, None, Nil, Nil, Nil)
val localCommitPublished = LocalCommitPublished(commitTx, None, Nil, Nil, Nil)
goto(CLOSING) using DATA_CLOSING(d.commitments, localCommitPublished = Some(localCommitPublished))
case Event(e: Error, d: DATA_NORMAL) => handleRemoteError(e, d)
@ -777,25 +779,38 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
spendLocalCurrent(d)
}
def publishTx(tx: Transaction, desc: String): Unit = {
log.info(s"publishing desc=$desc tx=${Transaction.write(tx)}")
blockchain ! PublishAsap(tx)
}
def publishMutualClosing(mutualClosing: Transaction) = {
log.info(s"closingTxId=${mutualClosing.txid}")
blockchain ! PublishAsap(mutualClosing)
publishTx(mutualClosing, "mutual-closing")
// TODO: hardcoded mindepth
blockchain ! WatchConfirmed(self, mutualClosing.txid, 3, BITCOIN_CLOSE_DONE)
}
def spendLocalCurrent(d: HasCommitments) = {
val tx = d.commitments.localCommit.publishableTxs.commitTx.tx
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
blockchain ! PublishAsap(tx)
publishTx(commitTx, "local-commit")
// TODO hardcoded mindepth + shouldn't we watch the claim tx instead?
blockchain ! WatchConfirmed(self, tx.txid, 3, BITCOIN_LOCALCOMMIT_DONE)
blockchain ! WatchConfirmed(self, commitTx.txid, 3, BITCOIN_LOCALCOMMIT_DONE)
val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(d.commitments, tx)
localCommitPublished.claimMainDelayedOutputTx.foreach(tx => blockchain ! PublishAsap(tx))
localCommitPublished.htlcSuccessTxs.foreach(tx => blockchain ! PublishAsap(tx))
localCommitPublished.htlcTimeoutTxs.foreach(tx => blockchain ! PublishAsap(tx))
localCommitPublished.claimHtlcDelayedTx.foreach(tx => blockchain ! PublishAsap(tx))
val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(d.commitments, commitTx)
localCommitPublished.claimMainDelayedOutputTx.foreach(tx => publishTx(tx, "claim-main-delayed-output"))
localCommitPublished.htlcSuccessTxs.foreach(tx => publishTx(tx, "htlc-success"))
localCommitPublished.htlcTimeoutTxs.foreach(tx => publishTx(tx, "htlc-timeout"))
localCommitPublished.claimHtlcDelayedTx.foreach(tx => publishTx(tx, "claim-htlc-delayed"))
// we also watch the htlc-timeout outputs in order to extract payment preimages
localCommitPublished.htlcTimeoutTxs.foreach(tx => {
require(tx.txIn.size == 1, s"an htlc-timeout tx must have exactly 1 input (has ${tx.txIn.size})")
val outpoint = tx.txIn(0).outPoint
log.info(s"watching output ${outpoint.index} of commit tx ${outpoint.txid}")
blockchain ! WatchSpent(relayer, outpoint.txid, outpoint.index.toInt, BITCOIN_HTLC_SPENT)
})
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(localCommitPublished = Some(localCommitPublished))
@ -805,17 +820,25 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
goto(CLOSING) using nextData
}
def handleRemoteSpentCurrent(tx: Transaction, d: HasCommitments) = {
log.warning(s"they published their current commit in txid=${tx.txid}")
require(tx.txid == d.commitments.remoteCommit.txid, "txid mismatch")
def handleRemoteSpentCurrent(commitTx: Transaction, d: HasCommitments) = {
log.warning(s"they published their current commit in txid=${commitTx.txid}")
require(commitTx.txid == d.commitments.remoteCommit.txid, "txid mismatch")
// TODO hardcoded mindepth + shouldn't we watch the claim tx instead?
blockchain ! WatchConfirmed(self, tx.txid, 3, BITCOIN_REMOTECOMMIT_DONE)
blockchain ! WatchConfirmed(self, commitTx.txid, 3, BITCOIN_REMOTECOMMIT_DONE)
val remoteCommitPublished = Helpers.Closing.claimCurrentRemoteCommitTxOutputs(d.commitments, tx)
remoteCommitPublished.claimMainOutputTx.foreach(tx => blockchain ! PublishAsap(tx))
remoteCommitPublished.claimHtlcSuccessTxs.foreach(tx => blockchain ! PublishAsap(tx))
remoteCommitPublished.claimHtlcTimeoutTxs.foreach(tx => blockchain ! PublishAsap(tx))
val remoteCommitPublished = Helpers.Closing.claimCurrentRemoteCommitTxOutputs(d.commitments, commitTx)
remoteCommitPublished.claimMainOutputTx.foreach(tx => publishTx(tx, "claim-main-output"))
remoteCommitPublished.claimHtlcSuccessTxs.foreach(tx => publishTx(tx, "claim-htlc-success"))
remoteCommitPublished.claimHtlcTimeoutTxs.foreach(tx => publishTx(tx, "claim-htlc-timeout"))
// we also watch the htlc-sent outputs in order to extract payment preimages
remoteCommitPublished.claimHtlcTimeoutTxs.foreach(tx => {
require(tx.txIn.size == 1, s"a claim-htlc-timeout tx must have exactly 1 input (has ${tx.txIn.size})")
val outpoint = tx.txIn(0).outPoint
log.info(s"watching output ${outpoint.index} of commit tx ${outpoint.txid}")
blockchain ! WatchSpent(relayer, outpoint.txid, outpoint.index.toInt, BITCOIN_HTLC_SPENT)
})
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(remoteCommitPublished = Some(remoteCommitPublished))
@ -836,11 +859,11 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
// TODO hardcoded mindepth + shouldn't we watch the claim tx instead?
blockchain ! WatchConfirmed(self, tx.txid, 3, BITCOIN_PUNISHMENT_DONE)
revokedCommitPublished.claimMainOutputTx.foreach(tx => blockchain ! PublishAsap(tx))
revokedCommitPublished.mainPunishmentTx.foreach(tx => blockchain ! PublishAsap(tx))
revokedCommitPublished.claimHtlcTimeoutTxs.foreach(tx => blockchain ! PublishAsap(tx))
revokedCommitPublished.htlcTimeoutTxs.foreach(tx => blockchain ! PublishAsap(tx))
revokedCommitPublished.htlcPunishmentTxs.foreach(tx => blockchain ! PublishAsap(tx))
revokedCommitPublished.claimMainOutputTx.foreach(tx => publishTx(tx, "claim-main-output"))
revokedCommitPublished.mainPunishmentTx.foreach(tx => publishTx(tx, "main-punishment"))
revokedCommitPublished.claimHtlcTimeoutTxs.foreach(tx => publishTx(tx, "claim-htlc-timeout"))
revokedCommitPublished.htlcTimeoutTxs.foreach(tx => publishTx(tx, "htlc-timeout"))
revokedCommitPublished.htlcPunishmentTxs.foreach(tx => publishTx(tx, "htlc-punishment"))
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(revokedCommitPublished = closing.revokedCommitPublished :+ revokedCommitPublished)
@ -858,9 +881,10 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
// this is never supposed to happen !!
log.error(s"our funding tx ${d.commitments.anchorId} was spent !!")
// TODO! channel id
them ! Error(0, "Anchor has been spent".getBytes)
them ! Error(0, "Funding tx has been spent".getBytes)
// TODO: not enough
blockchain ! PublishAsap(d.commitments.localCommit.publishableTxs.commitTx.tx)
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
publishTx(commitTx, "commit-tx")
goto(ERR_INFORMATION_LEAK)
}

View File

@ -66,6 +66,7 @@ case object BITCOIN_FUNDING_DEPTHOK extends BitcoinEvent
case object BITCOIN_FUNDING_LOST extends BitcoinEvent
case object BITCOIN_FUNDING_TIMEOUT extends BitcoinEvent
case object BITCOIN_FUNDING_SPENT extends BitcoinEvent
case object BITCOIN_HTLC_SPENT extends BitcoinEvent
case object BITCOIN_LOCALCOMMIT_DONE extends BitcoinEvent
case object BITCOIN_REMOTECOMMIT_DONE extends BitcoinEvent
case object BITCOIN_PUNISHMENT_DONE extends BitcoinEvent

View File

@ -1,9 +1,9 @@
package fr.acinq.eclair.payment
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import fr.acinq.bitcoin.BinaryData
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.eclair.Globals
import fr.acinq.bitcoin.Crypto.{PrivateKey, sha256}
import fr.acinq.bitcoin.{BinaryData, ScriptWitness, Transaction}
import fr.acinq.eclair.blockchain.WatchEventSpent
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.wire._
@ -119,6 +119,31 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
log.warning(s"no origin found for htlc $add")
}
case WatchEventSpent(BITCOIN_HTLC_SPENT, tx) =>
// when a remote or local commitment tx containing outgoing htlcs is published on the network,
// we watch it in order to extract payment preimage if funds are pulled by the counterparty
// we can then use these preimages to fulfill origin htlcs
tx.txIn
.map(_.witness)
.map {
case ScriptWitness(localSig :: paymentPreimage :: htlcOfferedScript :: Nil) =>
log.info(s"extracted preimage=$paymentPreimage from tx=${Transaction.write(tx)}")
Some(paymentPreimage)
case ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: paymentPreimage :: htlcOfferedScript :: Nil) =>
log.info(s"extracted preimage=$paymentPreimage from tx=${Transaction.write(tx)}")
Some(paymentPreimage)
case _ =>
None
}
.flatten
.map { preimage =>
bindings.collect {
case b@(upstream, Relayed(downstream)) if downstream.paymentHash == sha256(preimage) =>
log.info(s"found a match between preimage=$preimage and origin htlc for $b")
self ! (upstream, UpdateFulfillHtlc(upstream.channelId, upstream.id, preimage))
}
}
case 'upstreams => sender ! upstreams
}
}

View File

@ -864,10 +864,13 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
claimHtlcTx.txOut(0).amount
}).sum
alice2blockchain.expectNoMsg(1 second)
// at best we have a little less than 450 000 + 250 000 + 100 000 + 50 000 = 850000 (because fees)
assert(amountClaimed == Satoshi(831510))
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_HTLC_SPENT)
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_HTLC_SPENT)
alice2blockchain.expectNoMsg(1 second)
awaitCond(alice.stateName == CLOSING)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcSuccessTxs.size == 1)
@ -966,7 +969,6 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// - 3 txes for each htlc
// - 3 txes for each delayed output of the claimed htlc
val claimTxs = for (i <- 0 until 7) yield alice2blockchain.expectMsgType[PublishAsap].tx
alice2blockchain.expectNoMsg(1 second)
// the main delayed output spends the commitment transaction
Transaction.correctlySpends(claimTxs(0), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
@ -981,6 +983,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
Transaction.correctlySpends(claimTxs(5), claimTxs(2) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
Transaction.correctlySpends(claimTxs(6), claimTxs(3) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_HTLC_SPENT)
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_HTLC_SPENT)
alice2blockchain.expectNoMsg(1 second)
awaitCond(alice.stateName == CLOSING)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined)
val localCommitPublished = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get

View File

@ -394,10 +394,13 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
claimHtlcTx.txOut(0).amount
}).sum
alice2blockchain.expectNoMsg(1 second)
// htlc will timeout and be eventually refunded so we have a little less than fundingSatoshis - pushMsat = 1000000 - 200000 = 800000 (because fees)
assert(amountClaimed == Satoshi(784950))
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_HTLC_SPENT)
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_HTLC_SPENT)
alice2blockchain.expectNoMsg(1 second)
awaitCond(alice.stateName == CLOSING)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcSuccessTxs.size == 0)
@ -459,7 +462,6 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// - 2 txes for each htlc
// - 2 txes for each delayed output of the claimed htlc
val claimTxs = for (i <- 0 until 5) yield alice2blockchain.expectMsgType[PublishAsap].tx
alice2blockchain.expectNoMsg(1 second)
// the main delayed output spends the commitment transaction
Transaction.correctlySpends(claimTxs(0), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
@ -472,6 +474,10 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
Transaction.correctlySpends(claimTxs(3), claimTxs(1) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
Transaction.correctlySpends(claimTxs(4), claimTxs(2) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_HTLC_SPENT)
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_HTLC_SPENT)
alice2blockchain.expectNoMsg(1 second)
awaitCond(alice.stateName == CLOSING)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined)
}

View File

@ -1,6 +1,6 @@
package fr.acinq.eclair.payment
import fr.acinq.bitcoin.BinaryData
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.payment.PaymentLifecycle._
import fr.acinq.eclair.randomKey
@ -149,7 +149,8 @@ object HtlcGenerationSpec {
Hop(d, e, channelUpdate_de) :: Nil
val finalAmountMsat = 42000000L
val paymentHash = BinaryData("42" * 32)
val paymentPreimage = BinaryData("42" * 32)
val paymentHash = Crypto.sha256(paymentPreimage)
val currentBlockCount = 420000
val expiry_de = currentBlockCount + defaultHtlcExpiry

View File

@ -3,9 +3,12 @@ package fr.acinq.eclair.payment
import akka.actor.ActorRef
import akka.testkit.TestProbe
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{OutPoint, Transaction, TxIn}
import fr.acinq.eclair.TestkitBaseClass
import fr.acinq.eclair.blockchain.WatchEventSpent
import fr.acinq.eclair.channel._
import fr.acinq.eclair.payment.PaymentLifecycle.buildCommand
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -262,4 +265,62 @@ class RelayerSpec extends TestkitBaseClass {
eventListener.expectMsgType[PaymentFailed]
}
test("extract a payment preimage from an onchain tx (extract from witnessHtlcSuccess script)") { case (relayer, paymentHandler, eventListener) =>
val sender = TestProbe()
val channel_ab = TestProbe()
val channel_bc = TestProbe()
val add_ab = {
val cmd = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
// and then manually build an htlc
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, ChannelChangedState(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null)))
sender.send(relayer, add_ab)
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
sender.send(relayer, Binding(add_bc, Relayed(add_ab)))
// actual test starts here
val tx = Transaction(version = 0, txIn = TxIn(outPoint = OutPoint("22" * 32, 0), signatureScript = "", sequence = 0, witness = Scripts.witnessHtlcSuccess("11" * 70, "22" * 70, paymentPreimage, "33" * 130)) :: Nil, txOut = Nil, lockTime = 0)
sender.send(relayer, WatchEventSpent(BITCOIN_HTLC_SPENT, tx))
channel_ab.expectMsg(CMD_SIGN)
val cmd_ab = channel_ab.expectMsgType[CMD_FULFILL_HTLC]
channel_ab.expectMsg(CMD_SIGN)
assert(cmd_ab.id === add_ab.id)
}
test("extract a payment preimage from an onchain tx (extract from witnessClaimHtlcSuccessFromCommitTx script)") { case (relayer, paymentHandler, eventListener) =>
val sender = TestProbe()
val channel_ab = TestProbe()
val channel_bc = TestProbe()
val add_ab = {
val cmd = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
// and then manually build an htlc
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, ChannelChangedState(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null)))
sender.send(relayer, add_ab)
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
sender.send(relayer, Binding(add_bc, Relayed(add_ab)))
// actual test starts here
val tx = Transaction(version = 0, txIn = TxIn(outPoint = OutPoint("22" * 32, 0), signatureScript = "", sequence = 0, witness = Scripts.witnessClaimHtlcSuccessFromCommitTx("11" * 70, paymentPreimage, "33" * 130)) :: Nil, txOut = Nil, lockTime = 0)
sender.send(relayer, WatchEventSpent(BITCOIN_HTLC_SPENT, tx))
channel_ab.expectMsg(CMD_SIGN)
val cmd_ab = channel_ab.expectMsgType[CMD_FULFILL_HTLC]
channel_ab.expectMsg(CMD_SIGN)
assert(cmd_ab.id === add_ab.id)
}
}