1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-19 18:10:42 +01:00

anchor is now created, funded and pushed through bitcoind

This commit is contained in:
sstone 2016-02-05 14:41:48 +01:00
parent 07053b7d22
commit bdbd733e16
11 changed files with 50 additions and 24 deletions

View File

@ -35,7 +35,7 @@ object Boot extends App with Logging {
val api = system.actorOf(Props(new ServiceActor {
override val register: ActorRef = Boot.register
override def connect(addr: InetSocketAddress): Unit = system.actorOf(Props(classOf[Client], addr))
override def connect(addr: InetSocketAddress, amount: Long): Unit = system.actorOf(Props(classOf[Client], addr, amount))
}), "api")
// start a new HTTP server on port 8080 with our service actor as the handler

View File

@ -22,7 +22,7 @@ object Demo extends App {
val system = ActorSystem()
implicit val timeout = Timeout(30 seconds)
val anchorInput = AnchorInput(100100000L, OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1), SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2))
val anchorInput = AnchorInput(100100000L) //, OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1), SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2))
val alice_commit_priv = Base58Check.decode("cQPmcNr6pwBQPyGfab3SksE9nTCtx9ism9T4dkS9dETNU2KKtJHk")._2
val alice_final_priv = Base58Check.decode("cUrAtLtV7GGddqdkhUxnbZVDWGJBTducpPoon3eKp9Vnr1zxs6BG")._2

View File

@ -10,7 +10,7 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
// @formatter:off
case class CreateChannel(connection: ActorRef, our_anchor: Boolean)
case class CreateChannel(connection: ActorRef, our_anchor: Boolean, amount: Long = 0)
case class GetChannels()
case class RegisterChannel(nodeId: String, state: ChannelState)
// @formatter:on
@ -30,7 +30,7 @@ class RegisterActor extends Actor with ActorLogging {
override def receive: Receive = ???
def main(channels: Map[String, ChannelState]): Receive = {
case CreateChannel(connection, our_anchor) => context.actorOf(Props(classOf[AuthHandler], connection, Boot.blockchain, our_anchor), name = s"handler-${i = i + 1; i}")
case CreateChannel(connection, our_anchor, amount) => context.actorOf(Props(classOf[AuthHandler], connection, Boot.blockchain, our_anchor, amount), name = s"handler-${i = i + 1; i}")
case GetChannels =>
val s = sender()
Future.sequence(context.children.map(c => c ? CMD_GETINFO)).map(s ! _)

View File

@ -53,7 +53,7 @@ trait Service extends HttpService with Logging {
implicit val formats = org.json4s.DefaultFormats + new BinaryDataSerializer + new StateSerializer + new Sha256Serializer
implicit val timeout = Timeout(30 seconds)
def connect(addr: InetSocketAddress): Unit
def connect(addr: InetSocketAddress, amount: Long): Unit // amount in satoshis
def register: ActorRef
def sendCommand(channel: String, cmd: Command): Future[String] = {
@ -71,8 +71,7 @@ trait Service extends HttpService with Logging {
val json = parse(body).extract[JsonRPCBody]
val f_res: Future[AnyRef] = json match {
case JsonRPCBody(_, _, "connect", JString(host) :: JInt(port) :: JInt(anchor_amount) :: Nil) =>
//TODO : anchor_amount not implemented
connect(new InetSocketAddress(host, port.toInt))
connect(new InetSocketAddress(host, port.toInt), anchor_amount.toLong)
Future.successful("")
case JsonRPCBody(_, _, "list", _) =>
(register ? GetChannels).mapTo[Iterable[RES_GETINFO]]

View File

@ -56,6 +56,9 @@ class PollingWatcher(client: BitcoinJsonRPCClient)(implicit ec: ExecutionContext
case Publish(tx) =>
log.info(s"publishing tx $tx")
PollingWatcher.publishTransaction(client, tx).onFailure {
case t: Throwable => log.error(t, s"cannot publish tx ${Hex.toHexString(Transaction.write(tx))}")
}
}
}
@ -128,6 +131,14 @@ object PollingWatcher {
def signTransaction(client: BitcoinJsonRPCClient, tx: Transaction)(implicit ec: ExecutionContext): Future[SignTransactionResponse] =
signTransaction(client, Hex.toHexString(Transaction.write(tx)))
def publishTransaction(client: BitcoinJsonRPCClient, hex: String)(implicit ec: ExecutionContext): Future[String] =
client.invoke("sendrawtransaction", hex).map {
case JString(txid) => txid
}
def publishTransaction(client: BitcoinJsonRPCClient, tx: Transaction)(implicit ec: ExecutionContext): Future[String] =
publishTransaction(client, Hex.toHexString(Transaction.write(tx)))
}
/*object MyTest extends App {

View File

@ -45,7 +45,7 @@ object Anchor extends App {
val finalPub = Crypto.publicKeyFromPrivateKey(finalPriv)
}
val amount = (0.5 * Coin).asInstanceOf[Long]
val amount = (0.06 * Coin).asInstanceOf[Long]
val config = ConfigFactory.load()
val bitcoind = new BitcoinJsonRPCClient(
@ -54,14 +54,14 @@ object Anchor extends App {
host = config.getString("eclair.bitcoind.address"),
port = config.getInt("eclair.bitcoind.port"))
val (anchorTx, pos) = Await.result(makeAnchorTx(bitcoind, Alice.commitPub, Bob.commitPub, amount), 10 seconds)
val (anchorTx, pos) = (Transaction.read("010000000164345d5e5f6d3f8489740bc4e3f8bf8686b2ac221af48f0a2e88f601496182f1010000006a47304402204db8a977f275e74c92d28b18d82f09b5291111d0435cb3e653268a1d35dbbe02022074ada818ff9ea49ec613132423c42a5eecc223dbaa6ec1719a8de75539f659ca012103b8e06b059d35f1a3447ed834d265cb194f0f67dc50da30d18e569a40af697565feffffff02808d5b000000000017a91408bc5c0400edc31cbca9204fe1b8463b8c912f0187d99f0602000000001976a9148cda43313910281fe08a9d1659249e1f97152f8588ac00000000"), 0) //Await.result(makeAnchorTx(bitcoind, Alice.commitPub, Bob.commitPub, amount), 10 seconds)
println(anchorTx)
println(Hex.toHexString(Transaction.write(anchorTx)))
println(s"anchor tx: ${Hex.toHexString(Transaction.write(anchorTx))}")
bitcoind.client.close()
val spending = Transaction(version = 1,
txIn = TxIn(OutPoint(anchorTx, pos), Array.emptyByteArray, 0xffffffffL) :: Nil,
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.commitPub)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
txOut = TxOut(amount - 10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.commitPub)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
lockTime = 0)
val redeemScript = Scripts.multiSig2of2(Alice.commitPub, Bob.commitPub)
@ -70,4 +70,5 @@ object Anchor extends App {
val scriptSig = Scripts.sigScript2of2(sig1, sig2, Alice.commitPub, Bob.commitPub)
val signedTx = spending.updateSigScript(0, scriptSig)
Transaction.correctlySpends(signedTx, Seq(anchorTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
println(s"spending tx: ${Hex.toHexString(Transaction.write(signedTx))}")
}

View File

@ -1,6 +1,7 @@
package fr.acinq.eclair.channel
import akka.actor.{ActorRef, LoggingFSM, Stash}
import akka.pattern.pipe
import com.google.protobuf.ByteString
import fr.acinq.bitcoin._
import fr.acinq.eclair._
@ -10,8 +11,9 @@ import Scripts._
import lightning._
import lightning.open_channel.anchor_offer.{WILL_CREATE_ANCHOR, WONT_CREATE_ANCHOR}
import lightning.update_decline_htlc.Reason.{CannotRoute, InsufficientFunds}
import org.bouncycastle.util.encoders.Hex
import scala.util.Try
import scala.util.{Failure, Success, Try}
/**
* Created by PM on 20/08/2015.
@ -153,7 +155,7 @@ final case class RES_GETINFO(name: String, state: State, data: Data)
sealed trait Data
case object Nothing extends Data
final case class AnchorInput(amount: Long, previousTxOutput: OutPoint, signData: SignData) extends Data
final case class AnchorInput(amount: Long) extends Data
final case class OurChannelParams(delay: locktime, commitPrivKey: BinaryData, finalPrivKey: BinaryData, minDepth: Int, commitmentFee: Long, shaSeed: BinaryData) {
val commitPubKey: BinaryData = Crypto.publicKeyFromPrivateKey(commitPrivKey)
val finalPubKey: BinaryData = Crypto.publicKeyFromPrivateKey(finalPrivKey)
@ -171,6 +173,7 @@ trait CurrentCommitment {
final case class DATA_OPEN_WAIT_FOR_OPEN_NOANCHOR(ourParams: OurChannelParams) extends Data
final case class DATA_OPEN_WAIT_FOR_OPEN_WITHANCHOR(ourParams: OurChannelParams, anchorInput: AnchorInput) extends Data
final case class DATA_OPEN_WITH_ANCHOR_WAIT_FOR_ANCHOR(ourParams: OurChannelParams, theirParams: TheirChannelParams, theirRevocationHash: BinaryData) extends Data
final case class DATA_OPEN_WAIT_FOR_ANCHOR(ourParams: OurChannelParams, theirParams: TheirChannelParams, theirRevocationHash: sha256_hash) extends Data
final case class DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorTx: Transaction, anchorOutputIndex: Int, newCommitmentUnsigned: Commitment) extends Data
final case class DATA_OPEN_WAITING(ourParams: OurChannelParams, theirParams: TheirChannelParams, shaChain: ShaChain, commitment: Commitment) extends Data with CurrentCommitment
@ -188,6 +191,7 @@ final case class DATA_CLOSING(ourParams: OurChannelParams, theirParams: TheirCha
// @formatter:on
class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchorDataOpt: Option[AnchorInput]) extends LoggingFSM[State, Data] with Stash {
import context.dispatcher
val ourCommitPubKey = bitcoin_pubkey(ByteString.copyFrom(params.commitPubKey))
val ourFinalPubKey = bitcoin_pubkey(ByteString.copyFrom(params.finalPubKey))
@ -249,13 +253,17 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
when(OPEN_WAIT_FOR_OPEN_WITHANCHOR) {
case Event(open_channel(delay, theirRevocationHash, commitKey, finalKey, WONT_CREATE_ANCHOR, minDepth, commitmentFee), DATA_OPEN_WAIT_FOR_OPEN_WITHANCHOR(ourParams, anchorInput)) =>
val theirParams = TheirChannelParams(delay, commitKey, finalKey, minDepth, commitmentFee)
val (anchorTx, anchorOutputIndex) = makeAnchorTx(ourCommitPubKey, theirParams.commitPubKey, anchorInput.amount, anchorInput.previousTxOutput, anchorInput.signData)
Anchor.makeAnchorTx(Globals.bitcoin_client, ourCommitPubKey, theirParams.commitPubKey, anchorInput.amount).pipeTo(self)
stay using DATA_OPEN_WITH_ANCHOR_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash)
case Event((anchorTx: Transaction, anchorOutputIndex: Int), DATA_OPEN_WITH_ANCHOR_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash)) =>
log.info(s"anchor txid=${anchorTx.txid}")
// 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 - ourParams.commitmentFee) * 1000, ourParams.commitmentFee * 1000, Seq()))
val amount = anchorTx.txOut(anchorOutputIndex).amount
val state = ChannelState(them = ChannelOneSide(0, 0, Seq()), us = ChannelOneSide((amount - ourParams.commitmentFee) * 1000, ourParams.commitmentFee * 1000, Seq()))
val ourRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, 0))
val (ourCommitTx, ourSigForThem) = sign_their_commitment_tx(ourParams, theirParams, TxIn(OutPoint(anchorTx.hash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, state, ourRevocationHash, theirRevocationHash)
them ! open_anchor(anchorTx.hash, anchorOutputIndex, anchorInput.amount, ourSigForThem)
log.info(s"our commit tx: ${Hex.toHexString(Transaction.write(ourCommitTx))}")
them ! open_anchor(anchorTx.hash, anchorOutputIndex, amount, ourSigForThem)
goto(OPEN_WAIT_FOR_COMMIT_SIG) using DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, Commitment(0, ourCommitTx, state, theirRevocationHash))
case Event(CMD_CLOSE(_), _) => goto(CLOSED)

View File

@ -36,7 +36,7 @@ case object IO_NORMAL extends State
// @formatter:on
class AuthHandler(them: ActorRef, blockchain: ActorRef, our_anchor: Boolean) extends LoggingFSM[State, Data] with Stash {
class AuthHandler(them: ActorRef, blockchain: ActorRef, our_anchor: Boolean, amount: Long) extends LoggingFSM[State, Data] with Stash {
import AuthHandler._
@ -71,7 +71,10 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, our_anchor: Boolean) ext
log.info(s"their_nodeid: ${BinaryData(auth.nodeId.key.toByteArray)}")
assert(Crypto.verifySignature(Crypto.hash256(session_key.pub), signature2bin(auth.sessionSig), pubkey2bin(auth.nodeId)), "auth failed")
log.info(s"initializing channel actor")
val anchorInput_opt = if (our_anchor) Some(AnchorInput(1000000L, OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1), SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2))) else None
val anchorInput_opt = if (our_anchor)
Some(AnchorInput(amount))
else
None
val channel_params = OurChannelParams(Globals.default_locktime, Globals.commit_priv, Globals.final_priv, Globals.default_mindepth, Globals.default_commitfee, "sha-seed".getBytes())
val channel = context.actorOf(Props(new Channel(blockchain, channel_params, anchorInput_opt)), name = "channel")
channel ! INPUT_NONE

View File

@ -9,7 +9,7 @@ import fr.acinq.eclair.{CreateChannel, Boot}
/**
* Created by PM on 27/10/2015.
*/
class Client(remote: InetSocketAddress) extends Actor with ActorLogging {
class Client(remote: InetSocketAddress, amount: Long) extends Actor with ActorLogging {
import Tcp._
import context.system
@ -22,7 +22,7 @@ class Client(remote: InetSocketAddress) extends Actor with ActorLogging {
case c@Connected(remote, local) =>
log.info(s"connected to $remote")
val connection = sender()
Boot.register ! CreateChannel(connection, true)
Boot.register ! CreateChannel(connection, true, amount)
// TODO : kill this actor ?
}
}

View File

@ -4,6 +4,7 @@ import java.net.InetSocketAddress
import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import akka.io.{IO, Tcp}
import com.typesafe.config.ConfigFactory
import fr.acinq.eclair.{CreateChannel, Boot}
/**
@ -32,7 +33,8 @@ class Server(address: InetSocketAddress) extends Actor with ActorLogging {
object Server extends App {
implicit val system = ActorSystem("system")
val server = system.actorOf(Props[Server], "server")
val config = ConfigFactory.load()
val server = system.actorOf(Server.props(config.getString("eclair.server.address"), config.getInt("eclair.server.port")), "server")
def props(address: InetSocketAddress): Props = Props(classOf[Server], address)
def props(address: String, port: Int): Props = props(new InetSocketAddress(address, port))

View File

@ -27,7 +27,9 @@ abstract class TestHelper(_system: ActorSystem) extends TestKit(_system) with Im
TestKit.shutdownActorSystem(system)
}
val anchorInput = AnchorInput(100100000L, OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1), SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2))
val anchorInput = AnchorInput(100100000L)
val previousTxOutput = OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1)
val signData = SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2)
val our_commitkey_priv = Base58Check.decode("cQPmcNr6pwBQPyGfab3SksE9nTCtx9ism9T4dkS9dETNU2KKtJHk")._2
val our_finalkey_priv = Base58Check.decode("cUrAtLtV7GGddqdkhUxnbZVDWGJBTducpPoon3eKp9Vnr1zxs6BG")._2
@ -55,7 +57,7 @@ abstract class TestHelper(_system: ActorSystem) extends TestKit(_system) with Im
if (expectMsgClass(classOf[State]) == targetState) return (node, channelDesc)
channelDesc = channelDesc.copy(ourParams = Some(ourParams))
node ! open_channel(ourParams.delay, ourRevocationHash, ourCommitPubKey, ourFinalPubKey, WILL_CREATE_ANCHOR, Some(ourParams.minDepth), ourParams.commitmentFee)
val (anchorTx, anchorOutputIndex) = makeAnchorTx(ourCommitPubKey, theirParams.commitPubKey, anchorInput.amount, anchorInput.previousTxOutput, anchorInput.signData)
val (anchorTx, anchorOutputIndex) = makeAnchorTx(ourCommitPubKey, theirParams.commitPubKey, anchorInput.amount, previousTxOutput, signData)
// 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 * 1000- ourParams.commitmentFee * 1000, ourParams.commitmentFee * 1000, Seq()))
// we build our commitment tx, leaving it unsigned