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:
parent
08281dc109
commit
ba515a921a
@ -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))}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user