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:
parent
5028d924db
commit
a164088163
5 changed files with 95 additions and 46 deletions
|
@ -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>
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue