1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 02:27:32 +01:00

fixed mutual close, added a dummy findSpendingTx in BlockchainWatcher

This commit is contained in:
pm47 2016-02-11 17:22:30 +01:00
parent 0c349b71cf
commit ac4b154154
6 changed files with 99 additions and 89 deletions

View File

@ -80,7 +80,7 @@ trait Service extends HttpService with Logging {
case JsonRPCBody(_, _, "fulfillhtlc", JString(channel) :: JString(r) :: Nil) =>
sendCommand(channel, CMD_SEND_HTLC_FULFILL(BinaryData(r)))
case JsonRPCBody(_, _, "close", JString(channel) :: Nil) =>
sendCommand(channel, CMD_CLOSE(0))
sendCommand(channel, CMD_CLOSE(Globals.closing_fee))
case _ => Future.failed(new RuntimeException("method not found"))
}

View File

@ -1,11 +1,13 @@
package fr.acinq.eclair.blockchain
import akka.actor.{Cancellable, Actor, ActorLogging}
import fr.acinq.bitcoin.{Transaction, JsonRPCError, BitcoinJsonRPCClient}
import fr.acinq.bitcoin.{BinaryData, Transaction, JsonRPCError, BitcoinJsonRPCClient}
import fr.acinq.eclair.channel.BITCOIN_ANCHOR_SPENT
import grizzled.slf4j.Logging
import org.bouncycastle.util.encoders.Hex
import org.json4s.{FieldSerializer, CustomSerializer}
import org.json4s.JsonAST._
import scala.concurrent.{Future, ExecutionContext}
import scala.concurrent.{Await, Promise, Future, ExecutionContext}
import scala.concurrent.duration._
/**
@ -43,7 +45,7 @@ class PollingWatcher(client: BitcoinJsonRPCClient)(implicit ec: ExecutionContext
} yield {
if (conf.isDefined && !unspent) {
// NOTE : isSpent=!isUnspent only works if the parent transaction actually exists (which we assume to be true)
channel ! event
findSpendingTransaction(client, txId.toString(), outputIndex).map(tx => channel ! (BITCOIN_ANCHOR_SPENT, tx))
self !('remove, w)
} else {}
}
@ -62,18 +64,9 @@ class PollingWatcher(client: BitcoinJsonRPCClient)(implicit ec: ExecutionContext
}
}
object PollingWatcher {
object PollingWatcher extends Logging {
implicit val formats = org.json4s.DefaultFormats + new TransactionSerializer
class TransactionSerializer extends CustomSerializer[Transaction](format => (
{
case JString(x) => Transaction.read(x)
},
{
case x: Transaction => JString(Hex.toHexString(Transaction.write(x)))
}
))
implicit val formats = org.json4s.DefaultFormats
def getTxConfirmations(client: BitcoinJsonRPCClient, txId: String)(implicit ec: ExecutionContext): Future[Option[Int]] =
client.invoke("getrawtransaction", txId, 1) // we choose verbose output to get the number of confirmations
@ -139,13 +132,32 @@ object PollingWatcher {
def publishTransaction(client: BitcoinJsonRPCClient, tx: Transaction)(implicit ec: ExecutionContext): Future[String] =
publishTransaction(client, Hex.toHexString(Transaction.write(tx)))
// TODO : this is very dirty
// we only check the memory pool and the last block, and throw an error if tx was not found
def findSpendingTransaction(client: BitcoinJsonRPCClient, txid: String, outputIndex: Int)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
mempool <- client.invoke("getrawmempool").map(_.extract[List[String]])
bestblockhash <- client.invoke("getbestblockhash").map(_.extract[String])
bestblock <- client.invoke("getblock", bestblockhash).map(b => (b \ "tx").extract[List[String]])
txs <- Future {
for(txid <- mempool ++ bestblock) yield {
Await.result(client.invoke("getrawtransaction", txid).map(json => {
Transaction.read(json.extract[String])
}).recover {
case t: Throwable => Transaction(0, Seq(), Seq(), 0)
}, 20 seconds)
}
}
tx = txs.find(tx => tx.txIn.exists(input => input.outPoint.txid == txid && input.outPoint.index == outputIndex)).getOrElse(throw new RuntimeException("tx not found!"))
} yield tx
}
}
/*object MyTest extends App {
object MyTest extends App {
import ExecutionContext.Implicits.global
implicit val formats = org.json4s.DefaultFormats
val client = new BitcoinJsonRPCClient("foo", "bar")
println(Await.result(BlockchainWatcher.getTxConfirmations(client, "28ec4853f134c416757cda8ef3243df549c823d02c2aa5e3c148557ab04a2aa8"), 10 seconds))
println(Await.result(BlockchainWatcher.isUnspent(client, "c1e932badc68b8d07b714ee87b71dadafad3a3c0058266544ae61fd679481d7a", 0), 10 seconds))
}*/
val client = new BitcoinJsonRPCClient("foo", "bar", port = 18332)
println(Await.result(PollingWatcher.findSpendingTransaction(client, "d9690555e8de580901202975f5a249febfc12fad57fb4d3ff20cd6a7316ff5b3", 1), 10 seconds))
}

View File

@ -2,6 +2,7 @@ package fr.acinq.eclair.blockchain
import akka.actor.ActorRef
import fr.acinq.bitcoin.{Transaction, TxOut, BinaryData}
import fr.acinq.eclair.channel.BlockchainEvent
/**
* Created by PM on 19/01/2016.
@ -10,10 +11,10 @@ import fr.acinq.bitcoin.{Transaction, TxOut, BinaryData}
// @formatter:off
trait Watch
final case class WatchConfirmed(channel: ActorRef, txId: BinaryData, minDepth: Int, event: AnyRef) extends Watch
final case class WatchConfirmedBasedOnOutputs(channel: ActorRef, txIdSpent: BinaryData, txOut: Seq[TxOut], minDepth: Int, event: AnyRef) extends Watch
final case class WatchSpent(channel: ActorRef, txId: BinaryData, outputIndex: Int, minDepth: Int, event: AnyRef) extends Watch
final case class WatchLost(channel: ActorRef, txId: BinaryData, minDepth: Int, event: AnyRef) extends Watch // notify me if confirmation number gets below minDepth
final case class WatchConfirmed(channel: ActorRef, txId: BinaryData, minDepth: Int, event: BlockchainEvent) extends Watch
final case class WatchConfirmedBasedOnOutputs(channel: ActorRef, txIdSpent: BinaryData, txOut: Seq[TxOut], minDepth: Int, event: BlockchainEvent) extends Watch
final case class WatchSpent(channel: ActorRef, txId: BinaryData, outputIndex: Int, minDepth: Int, event: BlockchainEvent) extends Watch
final case class WatchLost(channel: ActorRef, txId: BinaryData, minDepth: Int, event: BlockchainEvent) extends Watch // notify me if confirmation number gets below minDepth
final case class Publish(tx: Transaction)

View File

@ -110,15 +110,15 @@ case object INPUT_NO_MORE_HTLCS
case object INPUT_CLOSE_COMPLETE_TIMEOUT
sealed trait BlockchainEvent
case object BITCOIN_ANCHOR_DEPTHOK
case object BITCOIN_ANCHOR_LOST
case object BITCOIN_ANCHOR_TIMEOUT
case class BITCOIN_ANCHOR_SPENT(tx: Transaction)
case object BITCOIN_ANCHOR_OURCOMMIT_DELAYPASSED
case object BITCOIN_SPEND_THEIRS_DONE
case object BITCOIN_SPEND_OURS_DONE
case object BITCOIN_STEAL_DONE
case object BITCOIN_CLOSE_DONE
case object BITCOIN_ANCHOR_DEPTHOK extends BlockchainEvent
case object BITCOIN_ANCHOR_LOST extends BlockchainEvent
case object BITCOIN_ANCHOR_TIMEOUT extends BlockchainEvent
case object BITCOIN_ANCHOR_SPENT extends BlockchainEvent
case object BITCOIN_ANCHOR_OURCOMMIT_DELAYPASSED extends BlockchainEvent
case object BITCOIN_SPEND_THEIRS_DONE extends BlockchainEvent
case object BITCOIN_SPEND_OURS_DONE extends BlockchainEvent
case object BITCOIN_STEAL_DONE extends BlockchainEvent
case object BITCOIN_CLOSE_DONE extends BlockchainEvent
/*
.d8888b. .d88888b. 888b d888 888b d888 d8888 888b 888 8888888b. .d8888b.
@ -336,7 +336,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
them ! handle_cmd_close(cmd, d.ourParams, d.theirParams, d.commitment)
goto(WAIT_FOR_CLOSE_COMPLETE)
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
@ -370,11 +370,11 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
them ! handle_cmd_close(cmd, d.ourParams, d.theirParams, d.commitment)
goto(WAIT_FOR_CLOSE_COMPLETE)
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(pkt: error, d: CurrentCommitment) =>
@ -395,11 +395,11 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
them ! handle_cmd_close(cmd, d.ourParams, d.theirParams, d.commitment)
goto(WAIT_FOR_CLOSE_COMPLETE)
case Event(BITCOIN_ANCHOR_SPENT(tx), d: DATA_NORMAL) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: DATA_NORMAL) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(pkt: error, d: CurrentCommitment) =>
@ -420,11 +420,11 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
them ! handle_cmd_close(cmd, d.ourParams, d.theirParams, d.commitment)
goto(WAIT_FOR_CLOSE_COMPLETE)
case Event(BITCOIN_ANCHOR_SPENT(tx), d: DATA_NORMAL) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: DATA_NORMAL) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(pkt: error, d: CurrentCommitment) =>
@ -525,15 +525,15 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
them ! handle_cmd_close(cmd, d.ourParams, d.theirParams, d.commitment)
goto(WAIT_FOR_CLOSE_COMPLETE)
case Event(BITCOIN_ANCHOR_SPENT(tx), d: DATA_NORMAL) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: DATA_NORMAL) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
them ! handle_revoked(tx)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, revokedPublished = tx :: Nil)
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(pkt: error, d: CurrentCommitment) =>
@ -577,15 +577,15 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
them ! res
goto(WAIT_FOR_CLOSE_ACK) using DATA_WAIT_FOR_CLOSE_ACK(d.ourParams, d.theirParams, d.shaChain, d.commitment, finalTx)
case Event(BITCOIN_ANCHOR_SPENT(tx), d: DATA_WAIT_FOR_HTLC_ACCEPT) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: DATA_WAIT_FOR_HTLC_ACCEPT) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
them ! handle_revoked(tx)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, revokedPublished = tx :: Nil)
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(pkt: error, d: CurrentCommitment) =>
@ -658,15 +658,15 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
them ! handle_cmd_close(cmd, d.ourParams, d.theirParams, d.commitment)
goto(WAIT_FOR_CLOSE_COMPLETE)
case Event(BITCOIN_ANCHOR_SPENT(tx), d: DATA_WAIT_FOR_UPDATE_SIG) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: DATA_WAIT_FOR_UPDATE_SIG) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
them ! handle_revoked(tx)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, revokedPublished = tx :: Nil)
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(pkt: error, d: CurrentCommitment) =>
@ -697,15 +697,15 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
them ! res
goto(WAIT_FOR_CLOSE_ACK) using DATA_WAIT_FOR_CLOSE_ACK(d.ourParams, d.theirParams, d.shaChain, d.commitment, finalTx)
case Event(BITCOIN_ANCHOR_SPENT(tx), d: DATA_WAIT_FOR_UPDATE_COMPLETE) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: DATA_WAIT_FOR_UPDATE_COMPLETE) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
them ! handle_revoked(tx)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, revokedPublished = tx :: Nil)
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(pkt: error, d: CurrentCommitment) =>
@ -730,6 +730,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
when(WAIT_FOR_CLOSE_COMPLETE) {
case Event(close_channel_complete(theirSig), d: CurrentCommitment) =>
//TODO we should use the closing fee in pkts
val closingState = d.commitment.state.adjust_fees(Globals.closing_fee * 1000, d.ourParams.anchorAmount.isDefined)
val finalTx = makeFinalTx(d.commitment.tx.txIn, ourFinalPubKey, d.theirParams.finalPubKey, closingState)
val signedFinalTx = sign_our_commitment_tx(d.ourParams, d.theirParams, finalTx, theirSig)
@ -746,20 +747,20 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, mutualClosePublished = Some(signedFinalTx))
}
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isMutualClose(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isMutualClose(tx, d.ourParams, d.theirParams, d.commitment)) =>
// it is possible that we received this before the close_channel_complete, we may still receive the latter
log.info(s"mutual close detected: $tx")
stay
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
them ! handle_revoked(tx)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, revokedPublished = tx :: Nil)
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(BITCOIN_CLOSE_DONE, _) => goto(CLOSED)
@ -772,20 +773,20 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
case Event(close_channel_ack(), d: DATA_WAIT_FOR_CLOSE_ACK) =>
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, mutualClosePublished = Some(d.mutualCloseTx))
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isMutualClose(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isMutualClose(tx, d.ourParams, d.theirParams, d.commitment)) =>
// it is possible that we received this before the close_channel_ack, we may still receive the latter
log.info(s"mutual close detected: $tx")
stay
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isTheirCommit(tx, d.ourParams, d.theirParams, d.commitment)) =>
them ! handle_theircommit(tx, d.ourParams, d.theirParams, d.shaChain, d.commitment)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT(tx), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: CurrentCommitment) if (isRevokedCommit(tx)) =>
them ! handle_revoked(tx)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, revokedPublished = tx :: Nil)
case Event(BITCOIN_ANCHOR_SPENT, _) =>
case Event((BITCOIN_ANCHOR_SPENT, _), _) =>
goto(ERR_INFORMATION_LEAK)
case Event(BITCOIN_CLOSE_DONE, _) => goto(CLOSED)
@ -801,26 +802,26 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
*/
when(CLOSING) {
case Event(BITCOIN_ANCHOR_SPENT(tx), d@DATA_CLOSING(ourParams, theirParams, _, commitment, _, _, _, _)) if (isMutualClose(tx, ourParams, theirParams, commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d@DATA_CLOSING(ourParams, theirParams, _, commitment, _, _, _, _)) if (isMutualClose(tx, ourParams, theirParams, commitment)) =>
log.info(s"mutual close detected: $tx")
// wait for BITCOIN_CLOSE_DONE
// should we override the previous tx? (which may be different because of malleability)
stay using d.copy(mutualClosePublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT(tx), DATA_CLOSING(_, _, _, commitment, _, _, _, _)) if (isOurCommit(tx, commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), DATA_CLOSING(_, _, _, commitment, _, _, _, _)) if (isOurCommit(tx, commitment)) =>
log.info(s"our commit detected: $tx")
handle_ourcommit()
stay
case Event(BITCOIN_ANCHOR_SPENT(tx), d@DATA_CLOSING(ourParams, theirParams, shaChain, commitment, _, _, _, _)) if (isTheirCommit(tx, ourParams, theirParams, commitment)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d@DATA_CLOSING(ourParams, theirParams, shaChain, commitment, _, _, _, _)) if (isTheirCommit(tx, ourParams, theirParams, commitment)) =>
handle_theircommit(tx, ourParams, theirParams, shaChain, commitment)
stay using d.copy(theirCommitPublished = Some(tx))
case Event(BITCOIN_ANCHOR_SPENT(tx), d: DATA_CLOSING) if (isRevokedCommit(tx)) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), d: DATA_CLOSING) if (isRevokedCommit(tx)) =>
them ! handle_revoked(tx)
stay using d.copy(revokedPublished = tx +: d.revokedPublished)
case Event(BITCOIN_ANCHOR_SPENT(tx), _) =>
case Event((BITCOIN_ANCHOR_SPENT, tx: Transaction), _) =>
// somebody managed to spend the anchor...
// we're fucked
goto(ERR_INFORMATION_LEAK)
@ -881,13 +882,13 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
888 888 8888888888 88888888 888 8888888888 888 T88b "Y8888P"
*/
def isMutualClose(tx: Transaction, comourParams: OurChannelParams, theirParams: TheirChannelParams, commitment: Commitment): Boolean = {
def isMutualClose(tx: Transaction, ourParams: OurChannelParams, theirParams: TheirChannelParams, commitment: Commitment): Boolean = {
// we rebuild the closing tx as seen by both parties
// TODO : should they be equal ?
val theirFinalTx = makeFinalTx(commitment.tx.txIn, theirParams.finalPubKey, ourFinalPubKey, commitment.state.reverse)
val ourFinalTx = makeFinalTx(commitment.tx.txIn, ourFinalPubKey, theirParams.finalPubKey, commitment.state)
//TODO we should use the closing fee in pkts
val closingState = commitment.state.adjust_fees(Globals.closing_fee * 1000, ourParams.anchorAmount.isDefined)
val finalTx = makeFinalTx(commitment.tx.txIn, theirParams.finalPubKey, ourFinalPubKey, closingState)
// and only compare the outputs
tx.txOut == theirFinalTx.txOut || tx.txOut == ourFinalTx.txOut
tx.txOut == finalTx.txOut
}
def isOurCommit(tx: Transaction, commitment: Commitment): Boolean = tx == commitment.tx
@ -921,27 +922,23 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams) extends Lo
}
def handle_cmd_close(cmd: CMD_CLOSE, ourParams: OurChannelParams, theirParams: TheirChannelParams, commitment: Commitment): close_channel = {
// the only difference between their final tx and ours is the order of the outputs, because state is symmetric
val closingState = commitment.state.adjust_fees(Globals.closing_fee * 1000, ourParams.anchorAmount.isDefined)
val theirFinalTx = makeFinalTx(commitment.tx.txIn, theirParams.finalPubKey, ourFinalPubKey, closingState.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirFinalTx, 0, multiSig2of2(ourCommitPubKey, theirParams.commitPubKey), SIGHASH_ALL, ourParams.commitPrivKey))
val closingState = commitment.state.adjust_fees(cmd.fee * 1000, ourParams.anchorAmount.isDefined)
val finalTx = makeFinalTx(commitment.tx.txIn, ourFinalPubKey, theirParams.finalPubKey, closingState)
val ourSig = bin2signature(Transaction.signInput(finalTx, 0, multiSig2of2(ourCommitPubKey, theirParams.commitPubKey), SIGHASH_ALL, ourParams.commitPrivKey))
val anchorTxId = commitment.tx.txIn(0).outPoint.txid // commit tx only has 1 input, which is the anchor
// we need to watch for BITCOIN_CLOSE_DONE with what we have here, because they may never answer with the fully signed closing tx and still publish it
blockchain ! WatchConfirmedBasedOnOutputs(self, anchorTxId, theirFinalTx.txOut, ourParams.minDepth, BITCOIN_CLOSE_DONE)
close_channel(ourSigForThem, cmd.fee)
blockchain ! WatchConfirmedBasedOnOutputs(self, anchorTxId, finalTx.txOut, ourParams.minDepth, BITCOIN_CLOSE_DONE)
close_channel(ourSig, cmd.fee)
}
def handle_pkt_close(pkt: close_channel, ourParams: OurChannelParams, theirParams: TheirChannelParams, commitment: Commitment): (Transaction, close_channel_complete) = {
// the only difference between their final tx and ours is the order of the outputs, because state is symmetric
val closingState = commitment.state.adjust_fees(Globals.closing_fee * 1000, ourParams.anchorAmount.isDefined)
val theirFinalTx = makeFinalTx(commitment.tx.txIn, theirParams.finalPubKey, ourFinalPubKey, closingState.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirFinalTx, 0, multiSig2of2(ourCommitPubKey, theirParams.commitPubKey), SIGHASH_ALL, ourParams.commitPrivKey))
val ourFinalTx = makeFinalTx(commitment.tx.txIn, ourFinalPubKey, theirParams.finalPubKey, closingState)
val ourSig = Transaction.signInput(ourFinalTx, 0, multiSig2of2(ourCommitPubKey, theirParams.commitPubKey), SIGHASH_ALL, ourParams.commitPrivKey)
val signedFinalTx = ourFinalTx.updateSigScript(0, sigScript2of2(pkt.sig, ourSig, theirParams.commitPubKey, ourCommitPubKey))
val closingState = commitment.state.adjust_fees(pkt.closeFee * 1000, ourParams.anchorAmount.isDefined)
val finalTx = makeFinalTx(commitment.tx.txIn, ourFinalPubKey, theirParams.finalPubKey, closingState)
val ourSig = Transaction.signInput(finalTx, 0, multiSig2of2(ourCommitPubKey, theirParams.commitPubKey), SIGHASH_ALL, ourParams.commitPrivKey)
val signedFinalTx = finalTx.updateSigScript(0, sigScript2of2(pkt.sig, ourSig, theirParams.commitPubKey, ourCommitPubKey))
blockchain ! WatchConfirmed(self, signedFinalTx.txid, ourParams.minDepth, BITCOIN_CLOSE_DONE)
blockchain ! Publish(signedFinalTx)
(signedFinalTx, close_channel_complete(ourSigForThem))
(signedFinalTx, close_channel_complete(ourSig))
}
/**

View File

@ -93,7 +93,7 @@ case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
} else throw new RuntimeException(s"could not find corresponding htlc (r=$r)")
}
def adjust_fees(fee: Int, is_funder: Boolean) : ChannelState = {
def adjust_fees(fee: Long, is_funder: Boolean) : ChannelState = {
if (is_funder) {
val (funder, nonfunder) = ChannelState.adjust_fees(this.us, this.them, fee)
this.copy(us = funder, them = nonfunder)
@ -107,7 +107,7 @@ case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
}
object ChannelState {
def adjust_fees(funder: ChannelOneSide, nonfunder: ChannelOneSide, fee: Int) : (ChannelOneSide, ChannelOneSide) = {
def adjust_fees(funder: ChannelOneSide, nonfunder: ChannelOneSide, fee: Long) : (ChannelOneSide, ChannelOneSide) = {
val nonfunder_fee = Math.min(fee - fee / 2, nonfunder.funds)
val funder_fee = fee - nonfunder_fee
(funder.copy(pay_msat = funder.funds - funder_fee, fee_msat = funder_fee), nonfunder.copy(pay_msat = nonfunder.funds - nonfunder_fee, fee_msat = nonfunder_fee))

View File

@ -32,7 +32,7 @@ class TheirCommitSpec extends TestHelper {
val update_complete(theirRevocationPreimage) = expectMsgClass(classOf[update_complete])
node ! CMD_GETSTATE
expectMsg(NORMAL_HIGHPRIO)
node ! BITCOIN_ANCHOR_SPENT(ourCommitTx)
node ! (BITCOIN_ANCHOR_SPENT, ourCommitTx)
expectMsgClass(classOf[error])
node ! CMD_GETSTATE
expectMsg(CLOSING)
@ -57,7 +57,7 @@ class TheirCommitSpec extends TestHelper {
val update_complete(theirRevocationPreimage) = expectMsgClass(classOf[update_complete])
node ! CMD_GETSTATE
expectMsg(NORMAL_HIGHPRIO)
node ! BITCOIN_ANCHOR_SPENT(previousCommitment.tx)
node ! (BITCOIN_ANCHOR_SPENT, previousCommitment.tx)
expectMsgClass(classOf[error])
node ! CMD_GETSTATE
expectMsg(CLOSING)