diff --git a/eclair-demo/src/main/resources/logback.xml b/eclair-demo/src/main/resources/logback.xml
index 9a9e8409f..27232f2a5 100644
--- a/eclair-demo/src/main/resources/logback.xml
+++ b/eclair-demo/src/main/resources/logback.xml
@@ -6,7 +6,7 @@
false
- return logger.contains("Node");
+ return logger.contains("Node") || logger.contains("BlockchainWatcher");
DENY
ACCEPT
@@ -46,9 +46,25 @@
+
+ System.out
+ false
+
+
+ return ((String) mdc.get("akkaSource")).contains("blockchain");
+
+ DENY
+ ACCEPT
+
+
+ %yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %boldGreen(%msg) %ex{12}%n
+
+
+
+
diff --git a/eclair-demo/src/main/scala/fr/acinq/eclair/BlockchainWatcher.scala b/eclair-demo/src/main/scala/fr/acinq/eclair/BlockchainWatcher.scala
new file mode 100644
index 000000000..073037a58
--- /dev/null
+++ b/eclair-demo/src/main/scala/fr/acinq/eclair/BlockchainWatcher.scala
@@ -0,0 +1,43 @@
+package fr.acinq.eclair
+
+import akka.actor.{ActorRef, Actor, ActorLogging}
+import fr.acinq.bitcoin.{Transaction, BinaryData}
+import fr.acinq.lightning._
+
+// @formatter:off
+
+final case class Watch(txId: BinaryData)
+final case class Unwatch(txId: BinaryData)
+final case class TxConfirmed(txId: BinaryData, blockId: BinaryData, confirmations: Int)
+final case class Publish(tx: Transaction)
+
+// @formatter:on
+
+/**
+ * Created by PM on 28/08/2015.
+ */
+class BlockchainWatcher extends Actor with ActorLogging {
+
+ context.become(watching(Map()))
+
+ override def receive: Receive = ???
+
+ def watching(m: Map[BinaryData, ActorRef]): Receive = {
+ case Watch(txId) =>
+ log.info(s"watching tx $txId for $sender")
+ // instant confirmation for testing
+ (0 until 3) foreach(i => self ! TxConfirmed(txId, "5deedc4c7f4c8e3250a486f340e57a565cda908eef7b7df2c1cd61b8ad6b42e6", i))
+ context.become(watching(m + (txId -> sender)))
+
+ case Unwatch(txId) =>
+ context.become(watching(m - txId))
+
+ case TxConfirmed(txId, blockId, confirmations) if m.contains(txId) =>
+ val channel = m(txId)
+ channel ! BITCOIN_TX_CONFIRMED(blockId, confirmations)
+
+
+ case Publish(tx) =>
+ log.info(s"publishing tx $tx")
+ }
+}
diff --git a/eclair-demo/src/main/scala/fr/acinq/eclair/Boot.scala b/eclair-demo/src/main/scala/fr/acinq/eclair/Boot.scala
index f11aaa891..a3e5cd85a 100644
--- a/eclair-demo/src/main/scala/fr/acinq/eclair/Boot.scala
+++ b/eclair-demo/src/main/scala/fr/acinq/eclair/Boot.scala
@@ -14,21 +14,20 @@ object Boot extends App {
val system = ActorSystem()
- val anchorInput = AnchorInput(1000L, OutPoint("bff676222800bf24bbf32f5a0fc83c4ddd5782f6ba23b4b352b3a6ddf0fe0b95", 0), SignData("76a914763518984abc129ab5825b8c14b6f4c8fa16f9c988ac", Base58Check.decode("cRqkWfx32NcfJjkutHqLrfbuY8HhTeCNLa7NLBpWy4bpk7bEYQYg")._2))
+ val anchorInput = AnchorInput(100100000L, OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1), SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2))
- val alice = system.actorOf(Props(new Node(Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), Hex.decode("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), 1, Some(anchorInput))), name = "alice")
- val bob = system.actorOf(Props(new Node(Hex.decode("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), Hex.decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"), 2, None)), name = "bob")
+ val alice_commit_priv = Base58Check.decode("cQPmcNr6pwBQPyGfab3SksE9nTCtx9ism9T4dkS9dETNU2KKtJHk")._2
+ val alice_final_priv = Base58Check.decode("cUrAtLtV7GGddqdkhUxnbZVDWGJBTducpPoon3eKp9Vnr1zxs6BG")._2
+ val bob_commit_priv = Base58Check.decode("cSUwLtdZ2tht9ZmHhdQue48pfe7tY2GT2TGWJDtjoZgo6FHrubGk")._2
+ val bob_final_priv = Base58Check.decode("cPR7ZgXpUaDPA3GwGceMDS5pfnSm955yvks3yELf3wMJwegsdGTg")._2
+
+ val blockchain = system.actorOf(Props(new BlockchainWatcher), name = "blockchain")
+ val alice = system.actorOf(Props(new Node(blockchain, alice_commit_priv, alice_final_priv, 1, Some(anchorInput))), name = "alice")
+ val bob = system.actorOf(Props(new Node(blockchain, bob_commit_priv, bob_final_priv, 2, None)), name = "bob")
bob.tell(INPUT_NONE, alice)
alice.tell(INPUT_NONE, bob)
- Thread.sleep(500)
- alice ! TxConfirmed(sha256_hash(1, 2, 3, 4), 1)
- bob ! TxConfirmed(sha256_hash(1, 2, 3, 4), 1)
-
- Thread.sleep(500)
- bob ! TxConfirmed(sha256_hash(1, 2, 3, 4), 2)
-
Thread.sleep(1000)
val r = sha256_hash(7, 7, 7, 7)
diff --git a/eclair-demo/src/main/scala/fr/acinq/eclair/Node.scala b/eclair-demo/src/main/scala/fr/acinq/eclair/Node.scala
index ea3caef62..8c9b6e231 100644
--- a/eclair-demo/src/main/scala/fr/acinq/eclair/Node.scala
+++ b/eclair-demo/src/main/scala/fr/acinq/eclair/Node.scala
@@ -11,7 +11,6 @@ import lightning.update_decline_htlc.Reason.{CannotRoute, InsufficientFunds}
import org.bouncycastle.util.encoders.Hex
import scala.util.Try
-import scala.concurrent.duration._
/**
* Created by PM on 20/08/2015.
@@ -45,7 +44,7 @@ case object CLOSED extends State
case object INPUT_NONE
sealed trait BlockchainEvent
-final case class TxConfirmed(blockId: sha256_hash, confirmations: Int) extends BlockchainEvent
+final case class BITCOIN_TX_CONFIRMED(blockId: sha256_hash, confirmations: Int) extends BlockchainEvent
case object BITCOIN_CLOSE_DONE
sealed trait Command
@@ -77,10 +76,10 @@ final case class DATA_WAIT_FOR_UPDATE_COMPLETE(ourParams: ChannelParams, theirPa
// @formatter:on
-class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minDepth: Int, val anchorDataOpt: Option[AnchorInput]) extends LoggingFSM[State, Data] with Stash {
+class Node(val blockchain: ActorRef, val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minDepth: Int, val anchorDataOpt: Option[AnchorInput]) extends LoggingFSM[State, Data] with Stash {
val DEFAULT_delay = locktime(Blocks(10))
- val DEFAULT_commitmentFee = 100
+ val DEFAULT_commitmentFee = 100000
val commitPubKey = bitcoin_pubkey(ByteString.copyFrom(Crypto.publicKeyFromPrivateKey(commitPrivKey.key.toByteArray)))
val finalPubKey = bitcoin_pubkey(ByteString.copyFrom(Crypto.publicKeyFromPrivateKey(finalPrivKey.key.toByteArray)))
@@ -123,10 +122,11 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minD
case Event(open_channel(delay, theirRevocationHash, commitKey, finalKey, WONT_CREATE_ANCHOR, minDepth, commitmentFee), DATA_OPEN_WAIT_FOR_OPEN_WITHANCHOR(ourParams, anchorInput, ourRevocationHashPreimage)) =>
val theirParams = ChannelParams(delay, commitKey, finalKey, minDepth.get, commitmentFee)
val anchorTx = makeAnchorTx(ourParams.commitKey, theirParams.commitKey, anchorInput.amount, anchorInput.previousTxOutput, anchorInput.signData)
+ log.info(s"anchor txid=${anchorTx.hash}")
//TODO : anchorOutputIndex might not always be zero if there are multiple outputs
val anchorOutputIndex = 0
// we fund the channel with the anchor tx, so the money is ours
- val state = ChannelState(them = ChannelOneSide(0, 0, Seq()), us = ChannelOneSide(anchorInput.amount, 0, Seq()))
+ val state = ChannelState(them = ChannelOneSide(0, 0, Seq()), us = ChannelOneSide(anchorInput.amount - DEFAULT_commitmentFee, 0, Seq()))
// we build our commitment tx, leaving it unsigned
val ourCommitTx = makeCommitTx(ourParams.finalKey, theirParams.finalKey, theirParams.delay, anchorTx.hash, anchorOutputIndex, theirRevocationHash, state)
// then we build their commitment tx and sign it
@@ -139,7 +139,7 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minD
when(OPEN_WAIT_FOR_ANCHOR) {
case Event(open_anchor(anchorTxid, anchorOutputIndex, anchorAmount, theirSig), DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, ourRevocationHashPreimage, theirRevocationHash)) =>
// they fund the channel with their anchor tx, so the money is theirs
- val state = ChannelState(them = ChannelOneSide(anchorAmount, 0, Seq()), us = ChannelOneSide(0, 0, Seq()))
+ val state = ChannelState(them = ChannelOneSide(anchorAmount - DEFAULT_commitmentFee, 0, Seq()), us = ChannelOneSide(0, 0, Seq()))
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
val ourCommitTx = makeCommitTx(ourParams.finalKey, theirParams.finalKey, theirParams.delay, anchorTxid, anchorOutputIndex, Crypto.sha256(ourRevocationHashPreimage), state)
// TODO : Transaction.sign(...) should handle multisig
@@ -151,7 +151,7 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minD
val theirCommitTx = makeCommitTx(theirParams.finalKey, ourParams.finalKey, ourParams.delay, anchorTxid, anchorOutputIndex, theirRevocationHash, state.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirCommitTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
them ! open_commit_sig(ourSigForThem)
- // TODO : register for confirmations of anchor tx on the bitcoin network
+ blockchain ! Watch(anchorTxid)
goto(OPEN_WAITING_THEIRANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, CommitmentTx(signedCommitTx, state, ourRevocationHashPreimage, theirRevocationHash))
}
@@ -162,16 +162,17 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minD
val signedCommitTx = newCommitTx.tx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
val ok = Try(Transaction.correctlySpends(signedCommitTx, Map(OutPoint(anchorTx.hash, anchorOutputIndex) -> multiSig2of2(ourParams.commitKey, theirParams.commitKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
// TODO : return Error and close channel if !ok
- log.info(s"publishing anchor tx ${new BinaryData(Transaction.write(anchorTx))}")
+ blockchain ! Watch(anchorTx.hash)
+ blockchain ! Publish(anchorTx)
goto(OPEN_WAITING_OURANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, newCommitTx.copy(tx = signedCommitTx))
}
when(OPEN_WAITING_THEIRANCHOR) {
- case Event(TxConfirmed(blockId, confirmations), DATA_OPEN_WAITING(ourParams, _, _)) if confirmations < ourParams.minDepth =>
+ case Event(BITCOIN_TX_CONFIRMED(blockId, confirmations), DATA_OPEN_WAITING(ourParams, _, _)) if confirmations < ourParams.minDepth =>
log.info(s"got $confirmations confirmation(s) for anchor tx")
stay
- case Event(TxConfirmed(blockId, confirmations), d@DATA_OPEN_WAITING(ourParams, _, _)) if confirmations >= ourParams.minDepth =>
+ case Event(BITCOIN_TX_CONFIRMED(blockId, confirmations), d@DATA_OPEN_WAITING(ourParams, _, _)) if confirmations >= ourParams.minDepth =>
log.info(s"got $confirmations confirmation(s) for anchor tx, minDepth reached")
them ! open_complete(Some(blockId))
unstashAll()
@@ -184,11 +185,11 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minD
}
when(OPEN_WAITING_OURANCHOR) {
- case Event(TxConfirmed(blockId, confirmations), DATA_OPEN_WAITING(ourParams, _, _)) if confirmations < ourParams.minDepth =>
+ case Event(BITCOIN_TX_CONFIRMED(blockId, confirmations), DATA_OPEN_WAITING(ourParams, _, _)) if confirmations < ourParams.minDepth =>
log.info(s"got $confirmations confirmation(s) for anchor tx")
stay
- case Event(TxConfirmed(blockId, confirmations), d@DATA_OPEN_WAITING(ourParams, _, _)) if confirmations >= ourParams.minDepth =>
+ case Event(BITCOIN_TX_CONFIRMED(blockId, confirmations), d@DATA_OPEN_WAITING(ourParams, _, _)) if confirmations >= ourParams.minDepth =>
log.info(s"got $confirmations confirmation(s) for anchor tx, minDepth reached")
them ! open_complete(Some(blockId))
unstashAll()
@@ -277,8 +278,9 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minD
goto(WAIT_FOR_UPDATE_SIG) using DATA_WAIT_FOR_UPDATE_SIG(ourParams, theirParams, p, CommitmentTx(ourCommitTx, newState, ourRevocationHashPreimage, theirRevocationHash))
case Event(CMD_CLOSE(fee), DATA_NORMAL(ourParams, theirParams, CommitmentTx(commitmentTx, state, _, _))) =>
- val finalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state)
- val ourSigForThem = bin2signature(Transaction.signInput(finalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
+ // the only difference between their final tx and ours is the order of the outputs, because state is symmetric
+ val theirFinalTx = makeFinalTx(commitmentTx.txIn, theirParams.finalKey, ourParams.finalKey, state.reverse)
+ val ourSigForThem = bin2signature(Transaction.signInput(theirFinalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
them ! close_channel(ourSigForThem, fee)
goto(WAIT_FOR_CLOSE_COMPLETE)
@@ -287,9 +289,9 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minD
val theirFinalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirFinalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
val ourFinalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state)
- val ourSig = bin2signature(Transaction.signInput(ourFinalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
+ val ourSig = Transaction.signInput(ourFinalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey))
val signedFinaltx = ourFinalTx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
- log.debug(s"final tx : ${Hex.toHexString(Transaction.write(signedFinaltx))}")
+ log.debug(s"*** final tx : ${Hex.toHexString(Transaction.write(signedFinaltx))}")
// ok now we can broadcast the final tx if we want
them ! close_channel_complete(ourSigForThem)
goto(WAIT_FOR_CLOSE_ACK)
@@ -382,4 +384,11 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val minD
case _ => stay
}
+ whenUnhandled {
+ case Event(e@BITCOIN_TX_CONFIRMED(_, confirmations), _) =>
+ log.debug(s"dropped $e")
+ // drops silently, we don't care for confirmations above minDepth
+ stay
+ }
+
}
diff --git a/eclair-demo/src/main/scala/fr/acinq/lightning/package.scala b/eclair-demo/src/main/scala/fr/acinq/lightning/package.scala
index a417dfb51..55afc551f 100644
--- a/eclair-demo/src/main/scala/fr/acinq/lightning/package.scala
+++ b/eclair-demo/src/main/scala/fr/acinq/lightning/package.scala
@@ -15,24 +15,6 @@ import scala.annotation.tailrec
package lightning {
-/*
-
-BOB POV !!
-
-ChannelState(ALICE: ChannelOneSide(pay = 500, fee: Long, htlcs: Seq()), BOB: ChannelOneSide(pay = 500, fee: Long, htlcs = Seq()))
-
-bob received update_add_htlc(100)
-
-OPTION 1 ChannelState(ALICE: ChannelOneSide(pay = 500, fee: Long, htlcs: Seq(update_add_htlc(-100))), BOB: ChannelOneSide(pay = 500, fee: Long, htlcs = Seq(update_add_htlc(100))))
-
-OPTION 2 ChannelState(ALICE: ChannelOneSide(pay = 400, fee: Long, htlcs: Seq()), BOB: ChannelOneSide(pay = 500, fee: Long, htlcs = Seq(update_add_htlc(100))))
-
-bob received r from CAROL and sends a update_complete_htlc to ALICE
-
-ChannelState(ALICE: ChannelOneSide(pay = 400, fee: Long, htlcs: Seq()), BOB: ChannelOneSide(pay = 600, fee: Long, htlcs = Seq()))
-
- */
-
case class ChannelOneSide(pay: Long, fee: Long, htlcs: Seq[update_add_htlc])
case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
@@ -148,7 +130,7 @@ package object lightning {
Protocol.writeUInt64(in.s3, sbos)
Protocol.writeUInt64(in.s4, sbos)
val s = new BigInteger(1, sbos.toByteArray)
- Crypto.encodeSignature(r, s)
+ Crypto.encodeSignature(r, s) :+ SIGHASH_ALL.toByte
}
implicit def locktime2long(in: locktime): Long = in match {