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

Merge branch 'master' into wip-api

This commit is contained in:
pm47 2016-01-27 17:46:44 +01:00
commit 4403e50464
13 changed files with 150 additions and 76 deletions

View File

@ -39,7 +39,9 @@
</encoder>
</appender>
<root level="DEBUG">
<logger name="fr.acinq.eclair" level="DEBUG" />
<root level="INFO">
<appender-ref ref="CHANNEL"/>
<appender-ref ref="BLOCKCHAIN"/>
<appender-ref ref="CONSOLE"/>

View File

@ -26,7 +26,7 @@ class PollingWatcher(client: BitcoinJsonRPCClient)(implicit ec: ExecutionContext
case w: Watch if !watches.contains(w) =>
log.info(s"adding watch $w for $sender")
val cancellable = context.system.scheduler.schedule(2 seconds, 2 minutes)(w match {
val cancellable = context.system.scheduler.schedule(2 seconds, 30 seconds)(w match {
case w@WatchConfirmed(channel, txId, minDepth, event) =>
getTxConfirmations(client, txId.toString).map(_ match {
case Some(confirmations) if confirmations >= minDepth =>
@ -35,13 +35,14 @@ class PollingWatcher(client: BitcoinJsonRPCClient)(implicit ec: ExecutionContext
case _ => {}
})
case w@WatchSpent(channel, txId, outputIndex, minDepth, event) =>
isUnspent(client, txId.toString, outputIndex).map(_ match {
case false =>
for {
conf <- getTxConfirmations(client, txId.toString)
unspent <- isUnspent(client, txId.toString, outputIndex)
} yield {if (conf.isDefined && !unspent) {
// NOTE : isSpent=!isUnspent only works if the parent transaction actually exists (which we assume to be true)
channel ! event
self !('remove, w)
case _ => {}
})
} else {}}
})
context.become(watching(watches + (w -> cancellable)))

View File

@ -245,9 +245,9 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
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)
log.info(s"anchor txid=${anchorTx.hash}")
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, 0, Seq()))
val state = ChannelState(them = ChannelOneSide(0, 0, Seq()), us = ChannelOneSide((anchorInput.amount - ourParams.commitmentFee) * 1000, 0, 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)
@ -257,14 +257,15 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
}
when(OPEN_WAIT_FOR_ANCHOR) {
case Event(open_anchor(anchorTxid, anchorOutputIndex, anchorAmount, theirSig), DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash)) =>
case Event(open_anchor(anchorTxHash, anchorOutputIndex, anchorAmount, theirSig), DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash)) =>
val anchorTxid = anchorTxHash.reverse //see https://github.com/ElementsProject/lightning/issues/17
// they fund the channel with their anchor tx, so the money is theirs
val state = ChannelState(them = ChannelOneSide(anchorAmount - ourParams.commitmentFee, 0, Seq()), us = ChannelOneSide(0, 0, Seq()))
val state = ChannelState(them = ChannelOneSide((anchorAmount - ourParams.commitmentFee) * 1000, 0, Seq()), us = ChannelOneSide(0, 0, Seq()))
val ourRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, 0))
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
val (ourCommitTx, ourSigForThem) = sign_their_commitment_tx(ourParams, theirParams, TxIn(OutPoint(anchorTxid, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, state, ourRevocationHash, theirRevocationHash)
val (ourCommitTx, ourSigForThem) = sign_their_commitment_tx(ourParams, theirParams, TxIn(OutPoint(anchorTxHash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, state, ourRevocationHash, theirRevocationHash)
val signedCommitTx = sign_our_commitment_tx(ourParams, theirParams, ourCommitTx, theirSig)
val ok = Try(Transaction.correctlySpends(signedCommitTx, Map(OutPoint(anchorTxid, anchorOutputIndex) -> anchorPubkeyScript(ourCommitPubKey, theirParams.commitPubKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
val ok = Try(Transaction.correctlySpends(signedCommitTx, Map(OutPoint(anchorTxHash, anchorOutputIndex) -> anchorPubkeyScript(ourCommitPubKey, theirParams.commitPubKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
ok match {
case false =>
them ! error(Some("Bad signature"))
@ -288,8 +289,8 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
them ! error(Some("Bad signature"))
goto(CLOSED)
case true =>
blockchain ! WatchConfirmed(self, anchorTx.hash, ourParams.minDepth, BITCOIN_ANCHOR_DEPTHOK)
blockchain ! WatchSpent(self, anchorTx.hash, anchorOutputIndex, 0, BITCOIN_ANCHOR_SPENT)
blockchain ! WatchConfirmed(self, anchorTx.txid, ourParams.minDepth, BITCOIN_ANCHOR_DEPTHOK)
blockchain ! WatchSpent(self, anchorTx.txid, anchorOutputIndex, 0, BITCOIN_ANCHOR_SPENT)
blockchain ! Publish(anchorTx)
goto(OPEN_WAITING_OURANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, ShaChain.init, commitment.copy(tx = signedCommitTx))
}
@ -299,7 +300,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
when(OPEN_WAITING_THEIRANCHOR) {
case Event(BITCOIN_ANCHOR_DEPTHOK, DATA_OPEN_WAITING(ourParams, theirParams, shaChain, commitment)) =>
val anchorTxId = commitment.tx.txIn(0).outPoint.hash // commit tx only has 1 input, which is the anchor
val anchorTxId = commitment.tx.txIn(0).outPoint.txid // commit tx only has 1 input, which is the anchor
blockchain ! WatchLost(self, anchorTxId, ourParams.minDepth, BITCOIN_ANCHOR_LOST)
them ! open_complete(None)
unstashAll()
@ -337,7 +338,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
when(OPEN_WAITING_OURANCHOR) {
case Event(BITCOIN_ANCHOR_DEPTHOK, DATA_OPEN_WAITING(ourParams, theirParams, shaChain, commitment)) =>
val anchorTxId = commitment.tx.txIn(0).outPoint.hash // commit tx only has 1 input, which is the anchor
val anchorTxId = commitment.tx.txIn(0).outPoint.txid // commit tx only has 1 input, which is the anchor
blockchain ! WatchLost(self, anchorTxId, ourParams.minDepth, BITCOIN_ANCHOR_LOST)
them ! open_complete(None)
unstashAll()
@ -448,7 +449,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
case Event(htlc@update_add_htlc(theirRevocationHash, _, _, _), DATA_NORMAL(ourParams, theirParams, shaChain, commitment)) =>
commitment.state.htlc_receive(htlc) match {
case newState if (newState.them.pay < 0) =>
case newState if (newState.them.pay_msat < 0) =>
// insufficient funds
them ! update_decline_htlc(InsufficientFunds(funding(None)))
stay
@ -727,7 +728,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, ourCommitPublished = Some(d.commitment.tx))
case true =>
them ! close_channel_ack()
blockchain ! WatchConfirmed(self, signedFinalTx.hash, d.ourParams.minDepth, BITCOIN_CLOSE_DONE)
blockchain ! WatchConfirmed(self, signedFinalTx.txid, d.ourParams.minDepth, BITCOIN_CLOSE_DONE)
blockchain ! Publish(signedFinalTx)
goto(CLOSING) using DATA_CLOSING(d.ourParams, d.theirParams, d.shaChain, d.commitment, mutualClosePublished = Some(signedFinalTx))
}
@ -906,7 +907,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
// the only difference between their final tx and ours is the order of the outputs, because state is symmetric
val theirFinalTx = makeFinalTx(commitment.tx.txIn, theirParams.finalPubKey, ourFinalPubKey, commitment.state.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirFinalTx, 0, multiSig2of2(ourCommitPubKey, theirParams.commitPubKey), SIGHASH_ALL, ourParams.commitPrivKey))
val anchorTxId = commitment.tx.txIn(0).outPoint.hash // commit tx only has 1 input, which is the anchor
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)
@ -919,7 +920,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
val ourFinalTx = makeFinalTx(commitment.tx.txIn, ourFinalPubKey, theirParams.finalPubKey, commitment.state)
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))
blockchain ! WatchConfirmed(self, signedFinalTx.hash, ourParams.minDepth, BITCOIN_CLOSE_DONE)
blockchain ! WatchConfirmed(self, signedFinalTx.txid, ourParams.minDepth, BITCOIN_CLOSE_DONE)
blockchain ! Publish(signedFinalTx)
(signedFinalTx, close_channel_complete(ourSigForThem))
}

View File

@ -7,7 +7,7 @@ import lightning.{sha256_hash, update_add_htlc}
/**
* Created by PM on 19/01/2016.
*/
case class ChannelOneSide(pay: Long, fee: Long, htlcs: Seq[update_add_htlc])
case class ChannelOneSide(pay_msat: Long, fee_msat: Long, htlcs: Seq[update_add_htlc])
case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
/**
@ -23,7 +23,7 @@ case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
* @param delta as seen by us, if delta > 0 we increase our balance
* @return the update channel state
*/
def update(delta: Long): ChannelState = this.copy(them = them.copy(pay = them.pay - delta), us = us.copy(pay = us.pay + delta))
def update(delta: Long): ChannelState = this.copy(them = them.copy(pay_msat = them.pay_msat - delta), us = us.copy(pay_msat = us.pay_msat + delta))
/**
* Update our state when we send an htlc
@ -31,7 +31,7 @@ case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
* @param htlc
* @return
*/
def htlc_receive(htlc: update_add_htlc): ChannelState = this.copy(them = them.copy(pay = them.pay - htlc.amountMsat), us = us.copy(htlcs = us.htlcs :+ htlc))
def htlc_receive(htlc: update_add_htlc): ChannelState = this.copy(them = them.copy(pay_msat = them.pay_msat - htlc.amountMsat), us = us.copy(htlcs = us.htlcs :+ htlc))
/**
* Update our state when we receive an htlc
@ -39,7 +39,7 @@ case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
* @param htlc
* @return
*/
def htlc_send(htlc: update_add_htlc): ChannelState = this.copy(them = them.copy(htlcs = them.htlcs :+ htlc), us = us.copy(pay = us.pay - htlc.amountMsat))
def htlc_send(htlc: update_add_htlc): ChannelState = this.copy(them = them.copy(htlcs = them.htlcs :+ htlc), us = us.copy(pay_msat = us.pay_msat - htlc.amountMsat))
/**
* We remove an existing htlc (can be because of a timeout, or a routing failure)
@ -52,12 +52,12 @@ case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
// TODO not optimized
val htlc = us.htlcs.find(_.rHash == rHash).get
// we were the receiver of this htlc
this.copy(them = them.copy(pay = them.pay + htlc.amountMsat), us = us.copy(htlcs = us.htlcs.filterNot(_ == htlc)))
this.copy(them = them.copy(pay_msat = them.pay_msat + htlc.amountMsat), us = us.copy(htlcs = us.htlcs.filterNot(_ == htlc)))
} else if (them.htlcs.find(_.rHash == rHash).isDefined) {
// TODO not optimized
val htlc = them.htlcs.find(_.rHash == rHash).get
// we were the sender of this htlc
this.copy(them = them.copy(htlcs = them.htlcs.filterNot(_ == htlc)), us = us.copy(pay = us.pay + htlc.amountMsat))
this.copy(them = them.copy(htlcs = them.htlcs.filterNot(_ == htlc)), us = us.copy(pay_msat = us.pay_msat + htlc.amountMsat))
} else throw new RuntimeException(s"could not find corresponding htlc (rHash=$rHash)")
}
@ -66,14 +66,30 @@ case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
// TODO not optimized
val htlc = us.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).get
// we were the receiver of this htlc
this.copy(us = us.copy(pay = us.pay + htlc.amountMsat, htlcs = us.htlcs.filterNot(_ == htlc)))
val prev_fee = this.us.fee_msat + this.them.fee_msat
val new_us_amount_nofee = us.pay_msat + htlc.amountMsat + us.fee_msat
val new_us_fee = Math.min(prev_fee / 2, new_us_amount_nofee)
val new_them_fee = prev_fee - new_us_fee
val new_us_amount = new_us_amount_nofee - new_us_fee
val new_them_amount = them.pay_msat + them.fee_msat - new_them_fee
this.copy(
us = us.copy(pay_msat = new_us_amount, htlcs = us.htlcs.filterNot(_ == htlc), fee_msat = new_us_fee),
them = them.copy(pay_msat = new_them_amount, fee_msat = new_them_fee))
} else if (them.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).isDefined) {
// TODO not optimized
val htlc = them.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).get
// we were the sender of this htlc
this.copy(them = them.copy(pay = them.pay + htlc.amountMsat, htlcs = them.htlcs.filterNot(_ == htlc)))
val prev_fee = this.us.fee_msat + this.them.fee_msat
val new_them_amount_nofee = them.pay_msat + htlc.amountMsat + them.fee_msat
val new_them_fee = Math.min(prev_fee / 2, new_them_amount_nofee)
val new_us_fee = prev_fee - new_them_fee
val new_them_amount = new_them_amount_nofee - new_them_fee
val new_us_amount = us.pay_msat + us.fee_msat - new_us_fee
this.copy(
us = us.copy(pay_msat = new_us_amount, fee_msat = new_us_fee),
them = them.copy(pay_msat = new_them_amount, htlcs = them.htlcs.filterNot(_ == htlc), fee_msat = new_them_fee))
} else throw new RuntimeException(s"could not find corresponding htlc (r=$r)")
}
def prettyString(): String = s"pay_us=${us.pay} htlcs_us=${us.htlcs.map(_.amountMsat).sum} pay_them=${them.pay} htlcs_them=${them.htlcs.map(_.amountMsat).sum} total=${us.pay + us.htlcs.map(_.amountMsat).sum + them.pay + them.htlcs.map(_.amountMsat).sum}"
def prettyString(): String = s"pay_us=${us.pay_msat} htlcs_us=${us.htlcs.map(_.amountMsat).sum} pay_them=${them.pay_msat} htlcs_them=${them.htlcs.map(_.amountMsat).sum} total=${us.pay_msat + us.htlcs.map(_.amountMsat).sum + them.pay_msat + them.htlcs.map(_.amountMsat).sum}"
}

View File

@ -3,20 +3,33 @@ package fr.acinq.eclair.channel
import fr.acinq.bitcoin.Crypto._
import fr.acinq.bitcoin._
import fr.acinq.eclair._
import lightning.{update_add_htlc, open_anchor, open_channel}
import lightning.locktime.Locktime.{Seconds, Blocks}
import lightning.{locktime, update_add_htlc, open_anchor, open_channel}
/**
* Created by PM on 21/01/2016.
*/
object Scripts {
def locktime2long_csv(in: locktime): Long = in match {
case locktime(Blocks(blocks)) => blocks
// FIXME : we adopt Element's alpha convention while BIP68 is not enabled
case locktime(Seconds(seconds)) => 500000000 + seconds
}
def locktime2long_cltv(in: locktime): Long = in match {
case locktime(Blocks(blocks)) => blocks
case locktime(Seconds(seconds)) => seconds
}
def isLess(a: Seq[Byte], b: Seq[Byte]): Boolean = memcmp(a.dropWhile(_ == 0).toList, b.dropWhile(_ == 0).toList) < 0
def lessThan(output1: TxOut, output2: TxOut) : Boolean = (output1, output2) match {
def lessThan(output1: TxOut, output2: TxOut): Boolean = (output1, output2) match {
case (TxOut(amount1, script1), TxOut(amount2, script2)) if amount1 == amount2 => memcmp(script1.toList, script2.toList) < 0
case (TxOut(amount1, _), TxOut(amount2, _)) => amount1 < amount2
}
def permuteOutputs(tx: Transaction) : Transaction = tx.copy(txOut = tx.txOut.sortWith(lessThan))
def permuteOutputs(tx: Transaction): Transaction = tx.copy(txOut = tx.txOut.sortWith(lessThan))
def multiSig2of2(pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = if (isLess(pubkey1, pubkey2))
BinaryData(Script.createMultiSigMofN(2, Seq(pubkey1, pubkey2)))
@ -49,7 +62,7 @@ object Scripts {
(signedTx, 0)
}
def anchorPubkeyScript(pubkey1: BinaryData, pubkey2: BinaryData) : BinaryData = Script.write(pay2sh(multiSig2of2(pubkey1, pubkey2)))
def anchorPubkeyScript(pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = Script.write(pay2sh(multiSig2of2(pubkey1, pubkey2)))
def redeemSecretOrDelay(delayedKey: BinaryData, lockTime: Long, keyIfSecretKnown: BinaryData, hashOfSecret: BinaryData): Seq[ScriptElt] = {
// @formatter:off
@ -63,7 +76,7 @@ object Scripts {
// @formatter:on
}
def scriptPubKeyHtlcSend(ourkey: BinaryData, theirkey: BinaryData, value: Long, htlc_abstimeout: Long, locktime: Long, commit_revoke: BinaryData, rhash: BinaryData): Seq[ScriptElt] = {
def scriptPubKeyHtlcSend(ourkey: BinaryData, theirkey: BinaryData, htlc_abstimeout: Long, locktime: Long, rhash: BinaryData, commit_revoke: BinaryData): Seq[ScriptElt] = {
// @formatter:off
OP_HASH160 :: OP_DUP ::
OP_PUSHDATA(ripemd160(rhash)) :: OP_EQUAL ::
@ -77,7 +90,7 @@ object Scripts {
// @formatter:on
}
def scriptPubKeyHtlcReceive(ourkey: BinaryData, theirkey: BinaryData, value: Long, htlc_abstimeout: Long, locktime: Long, commit_revoke: BinaryData, rhash: BinaryData): Seq[ScriptElt] = {
def scriptPubKeyHtlcReceive(ourkey: BinaryData, theirkey: BinaryData, htlc_abstimeout: Long, locktime: Long, rhash: BinaryData, commit_revoke: BinaryData): Seq[ScriptElt] = {
// @formatter:off
OP_HASH160 :: OP_DUP ::
OP_PUSHDATA(ripemd160(rhash)) :: OP_EQUAL ::
@ -94,28 +107,29 @@ object Scripts {
// @formatter:on
}
def makeCommitTx(ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: Long, anchorTxId: BinaryData, anchorOutputIndex: Int, revocationHash: BinaryData, channelState: ChannelState): Transaction =
def makeCommitTx(ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, anchorTxId: BinaryData, anchorOutputIndex: Int, revocationHash: BinaryData, channelState: ChannelState): Transaction =
makeCommitTx(inputs = TxIn(OutPoint(anchorTxId, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourFinalKey, theirFinalKey, theirDelay, revocationHash, channelState)
// this way it is easy to reuse the inputTx of an existing commitmentTx
def makeCommitTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: Long, revocationHash: BinaryData, channelState: ChannelState): Transaction = {
val redeemScript = redeemSecretOrDelay(ourFinalKey, theirDelay, theirFinalKey, revocationHash: BinaryData)
def makeCommitTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, revocationHash: BinaryData, channelState: ChannelState): Transaction = {
val redeemScript = redeemSecretOrDelay(ourFinalKey, locktime2long_csv(theirDelay), theirFinalKey, revocationHash: BinaryData)
val tx = Transaction(
version = 1,
txIn = inputs,
txOut = Seq(
TxOut(amount = channelState.us.pay, publicKeyScript = pay2sh(redeemScript)),
TxOut(amount = channelState.them.pay, publicKeyScript = pay2sh(OP_PUSHDATA(theirFinalKey) :: OP_CHECKSIG :: Nil))
// TODO : is that the correct way to handle sub-satoshi balances ?
TxOut(amount = channelState.us.pay_msat / 1000, publicKeyScript = pay2sh(redeemScript)),
TxOut(amount = channelState.them.pay_msat / 1000, publicKeyScript = pay2sh(OP_PUSHDATA(theirFinalKey) :: OP_CHECKSIG :: Nil))
),
lockTime = 0)
val sendOuts = channelState.them.htlcs.map(htlc => {
TxOut(htlc.amountMsat, pay2sh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, htlc.amountMsat, htlc.expiry, theirDelay, htlc.rHash, htlc.revocationHash)))
})
val receiveOuts = channelState.us.htlcs.map(htlc => {
TxOut(htlc.amountMsat, pay2sh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, htlc.amountMsat, htlc.expiry, theirDelay, htlc.rHash, htlc.revocationHash)))
})
val sendOuts = channelState.them.htlcs.map(htlc =>
TxOut(htlc.amountMsat / 1000, pay2sh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
)
val receiveOuts = channelState.us.htlcs.map(htlc =>
TxOut(htlc.amountMsat / 1000, pay2sh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
)
val tx1 = tx.copy(txOut = tx.txOut ++ sendOuts ++ receiveOuts)
permuteOutputs(tx1)
}
@ -136,8 +150,8 @@ object Scripts {
version = 1,
txIn = inputs,
txOut = Seq(
TxOut(amount = channelState.them.pay, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(theirFinalKey) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
TxOut(amount = channelState.us.pay, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(ourFinalKey) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)),
TxOut(amount = channelState.them.pay_msat / 1000, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(theirFinalKey) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
TxOut(amount = channelState.us.pay_msat / 1000, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(ourFinalKey) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)),
lockTime = 0))
}
@ -145,7 +159,7 @@ object Scripts {
def initialFunding(a: open_channel, b: open_channel, anchor: open_anchor, fee: Long): ChannelState = {
require(isFunder(a) ^ isFunder(b))
val (c1, c2) = ChannelOneSide(pay = anchor.amount - fee, fee = fee, Seq.empty[update_add_htlc]) -> ChannelOneSide(0, 0, Seq.empty[update_add_htlc])
val (c1, c2) = ChannelOneSide(pay_msat = anchor.amount - fee, fee_msat = fee, Seq.empty[update_add_htlc]) -> ChannelOneSide(0, 0, Seq.empty[update_add_htlc])
if (isFunder(a)) ChannelState(c1, c2) else ChannelState(c2, c1)
}

View File

@ -7,7 +7,9 @@ import akka.io.Tcp.{Received, Write}
import akka.util.ByteString
import com.trueaccord.scalapb.GeneratedMessage
import fr.acinq.bitcoin._
import fr.acinq.eclair.Globals._
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.PollingWatcher
import fr.acinq.eclair.channel.{INPUT_NONE, Channel, OurChannelParams, AnchorInput}
import fr.acinq.eclair.crypto.LightningCrypto._
import fr.acinq.eclair.io.AuthHandler.Secrets
@ -72,6 +74,7 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, our_anchor: Boolean) ext
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 channel_params = OurChannelParams(Globals.default_locktime, Globals.commit_priv, Globals.final_priv, Globals.default_mindepth, Globals.default_commitfee, "sha-seed".getBytes())
val blockchain = context.system.actorOf(Props(new PollingWatcher(bitcoin_client)), name = "blockchain")
val channel = context.system.actorOf(Props(new Channel(blockchain, channel_params, anchorInput_opt)), name = "alice")
channel ! INPUT_NONE
goto(IO_NORMAL) using Normal(channel, s)

View File

@ -14,11 +14,11 @@ class Server extends Actor with ActorLogging {
import Tcp._
import context.system
IO(Tcp) ! Bind(self, new InetSocketAddress("localhost", 57776))
IO(Tcp) ! Bind(self, new InetSocketAddress("192.168.1.43", 45000))
def receive = {
case b @ Bound(localAddress) =>
// do some logging or setup ...
log.info(s"bound on $b")
case CommandFailed(_: Bind) => context stop self

View File

@ -72,12 +72,6 @@ package object eclair {
Crypto.encodeSignature(r, s) :+ SIGHASH_ALL.toByte
}
implicit def locktime2long(in: locktime): Long = in match {
case locktime(Blocks(blocks)) => blocks
// FIXME : we adopt Element's alpha convention while BIP68 is not enabled
case locktime(Seconds(seconds)) => 500000000 + seconds
}
@tailrec
def memcmp(a: List[Byte], b: List[Byte]): Int = (a, b) match {
case (x, y) if (x.length != y.length) => x.length - y.length

View File

@ -0,0 +1,39 @@
package fr.acinq.eclair
import fr.acinq.bitcoin.Crypto
import fr.acinq.eclair.channel.{ChannelOneSide, ChannelState}
import lightning.locktime.Locktime.Blocks
import lightning.{locktime, sha256_hash, update_add_htlc}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by PM on 26/01/2016.
*/
@RunWith(classOf[JUnitRunner])
class ChannelStateSpec extends FunSuite {
test("fee management") {
val state_0 = ChannelState(
us = ChannelOneSide(pay_msat = 950000000, fee_msat = 50000000, htlcs = Seq()),
them = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs = Seq())
)
val r = sha256_hash(7, 7, 7, 7)
val rHash = Crypto.sha256(r)
val htlc = update_add_htlc(rHash, 100000000, rHash, locktime(Blocks(1)))
val state_1 = state_0.htlc_send(htlc)
assert(state_1 === ChannelState(
us = ChannelOneSide(pay_msat = 850000000, fee_msat = 50000000, htlcs = Seq()),
them = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs = Seq(htlc))
))
val state_2 = state_1.htlc_fulfill(r)
assert(state_2 === ChannelState(
us = ChannelOneSide(pay_msat = 875000000, fee_msat = 25000000, htlcs = Seq()),
them = ChannelOneSide(pay_msat = 75000000, fee_msat = 25000000, htlcs = Seq())
))
}
}

View File

@ -8,6 +8,7 @@ import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class ClaimReceivedHtlcSpec extends FunSuite {
object Alice {
val (_, commitKey) = Base58Check.decode("cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g")
val (_, finalKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA")
@ -32,7 +33,7 @@ class ClaimReceivedHtlcSpec extends FunSuite {
val revokeCommitHash = Crypto.sha256(revokeCommit)
}
val htlcScript = scriptPubKeyHtlcReceive(Alice.finalPubKey, Bob.finalPubKey, 10, 1000, 2000, Bob.revokeCommitHash, Bob.Rhash)
val htlcScript = scriptPubKeyHtlcReceive(Alice.finalPubKey, Bob.finalPubKey, 1000, 2000, Bob.Rhash, Bob.revokeCommitHash)
// this tx sends money to our HTLC
val tx = Transaction(
@ -55,13 +56,16 @@ class ClaimReceivedHtlcSpec extends FunSuite {
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
lockTime = 2000)
val sig = Transaction.signInput(tx1, 0, Script.write(htlcScript), SIGHASH_ALL, Alice.finalKey)
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Bob.R) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
val tx2 = tx1.updateSigScript(0, Script.write(sigScript))
val sig = Transaction.signInput(tx1, 0, Script.write(htlcScript), SIGHASH_ALL, Alice.finalKey)
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Bob.R) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
val tx2 = tx1.updateSigScript(0, Script.write(sigScript))
val runner = new Script.Runner(new Script.Context(tx2, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
assert(result)
val runner = new Script.Runner(
new Script.Context(tx2, 0),
ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY
)
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
assert(result)
}
test("Blob can spend this HTLC after a delay") {
@ -72,13 +76,13 @@ class ClaimReceivedHtlcSpec extends FunSuite {
lockTime = 2000)
val sig = Transaction.signInput(tx1, 0, Script.write(htlcScript), SIGHASH_ALL, Bob.finalKey)
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Array.emptyByteArray) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
val tx2 = tx1.updateSigScript(0, Script.write(sigScript))
val sig = Transaction.signInput(tx1, 0, Script.write(htlcScript), SIGHASH_ALL, Bob.finalKey)
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Array.emptyByteArray) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
val tx2 = tx1.updateSigScript(0, Script.write(sigScript))
val runner = new Script.Runner(new Script.Context(tx2, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
assert(result)
val runner = new Script.Runner(new Script.Context(tx2, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
assert(result)
}
test("Blob can spend this HTLC right away if he knows the revocation hash") {

View File

@ -31,7 +31,7 @@ class ClaimSentHtlcSpec extends FunSuite {
val revokeCommitHash = Crypto.sha256(revokeCommit)
}
val htlcScript = scriptPubKeyHtlcSend(Alice.finalPubKey, Bob.finalPubKey, 10, 1000, 2000, Alice.revokeCommitHash, Alice.Rhash)
val htlcScript = scriptPubKeyHtlcSend(Alice.finalPubKey, Bob.finalPubKey, 1000, 2000, Alice.revokeCommitHash, Alice.Rhash)
// this tx sends money to our HTLC
val tx = Transaction(

View File

@ -95,7 +95,7 @@ class ProtocolSpec extends FlatSpec {
txIn = TxIn(OutPoint(commitTx, 0), Array.emptyByteArray, 0xffffffffL) :: Nil,
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(Bob.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
lockTime = 0)
val redeemScript = redeemSecretOrDelay(ours.finalKey, theirs.delay, theirs.finalKey, Bob.H)
val redeemScript = redeemSecretOrDelay(ours.finalKey, locktime2long_csv(theirs.delay), theirs.finalKey, Bob.H)
val sig = Transaction.signInput(tx, 0, Script.write(redeemScript), SIGHASH_ALL, Bob.finalKey)
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Bob.R) :: OP_PUSHDATA(Script.write(redeemScript)) :: Nil
tx.updateSigScript(0, Script.write(sigScript))

View File

@ -57,12 +57,12 @@ abstract class TestHelper(_system: ActorSystem) extends TestKit(_system) with Im
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)
// 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, 0, Seq()))
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
val ourCommitTx = makeCommitTx(ourFinalPubKey, theirParams.finalPubKey, theirParams.delay, anchorTx.hash, anchorOutputIndex, ourRevocationHash, state)
val ourCommitTx = makeCommitTx(ourFinalPubKey, theirParams.finalPubKey, ourParams.delay, anchorTx.hash, anchorOutputIndex, ourRevocationHash, state)
channelDesc = channelDesc.copy(ourCommitment = Some(Commitment(0, ourCommitTx, state, theirRevocationHash)))
// then we build their commitment tx and sign it
val theirCommitTx = makeCommitTx(theirParams.finalPubKey, ourFinalPubKey, ourParams.delay, anchorTx.hash, anchorOutputIndex, theirRevocationHash, state.reverse)
val theirCommitTx = makeCommitTx(theirParams.finalPubKey, ourFinalPubKey, theirParams.delay, anchorTx.hash, anchorOutputIndex, theirRevocationHash, state.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirCommitTx, 0, multiSig2of2(ourCommitPubKey, theirParams.commitPubKey), SIGHASH_ALL, ourParams.commitPrivKey))
node ! CMD_GETSTATE // node is in OPEN_WAIT_FOR_ANCHOR
if (expectMsgClass(classOf[State]) == targetState) return (node, channelDesc)
@ -98,7 +98,7 @@ abstract class TestHelper(_system: ActorSystem) extends TestKit(_system) with Im
node ! open_channel(ourParams.delay, ourRevocationHash, ourCommitPubKey, ourFinalPubKey, WONT_CREATE_ANCHOR, Some(ourParams.minDepth), ourParams.commitmentFee)
val their_open_anchor = expectMsgClass(classOf[open_anchor])
// we fund the channel with the anchor tx, so the money is ours
val state = ChannelState(them = ChannelOneSide(their_open_anchor.amount - ourParams.commitmentFee, 0, Seq()), us = ChannelOneSide(0, 0, Seq()))
val state = ChannelState(them = ChannelOneSide(their_open_anchor.amount * 1000 - ourParams.commitmentFee * 1000, ourParams.commitmentFee * 1000, Seq()), us = ChannelOneSide(0, 0, Seq()))
// we build our commitment tx, leaving it unsigned
val ourCommitTx = makeCommitTx(ourFinalPubKey, theirParams.finalPubKey, theirParams.delay, their_open_anchor.txid, their_open_anchor.outputIndex, ourRevocationHash, state)
channelDesc = channelDesc.copy(ourCommitment = Some(Commitment(0, ourCommitTx, state, theirRevocationHash)))