1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 22:25:26 +01:00

added a blockchain watcher

This commit is contained in:
pm47 2015-08-28 15:34:36 +02:00
parent 5028d924db
commit a164088163
5 changed files with 95 additions and 46 deletions

View file

@ -6,7 +6,7 @@
<withJansi>false</withJansi>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
<expression>return logger.contains("Node");</expression>
<expression>return logger.contains("Node") || logger.contains("BlockchainWatcher");</expression>
</evaluator>
<OnMatch>DENY</OnMatch>
<OnMismatch>ACCEPT</OnMismatch>
@ -46,9 +46,25 @@
</encoder>
</appender>
<appender name="BLOCKCHAIN" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<withJansi>false</withJansi>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
<expression>return ((String) mdc.get("akkaSource")).contains("blockchain");</expression>
</evaluator>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<encoder>
<pattern>%yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %boldGreen(%msg) %ex{12}%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="ALICE"/>
<appender-ref ref="BOB"/>
<appender-ref ref="BLOCKCHAIN"/>
<appender-ref ref="CONSOLE"/>
</root>

View file

@ -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")
}
}

View file

@ -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)

View file

@ -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
}
}

View file

@ -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 {