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:
commit
4403e50464
@ -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"/>
|
||||
|
@ -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)))
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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}"
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
))
|
||||
}
|
||||
|
||||
}
|
@ -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") {
|
||||
|
@ -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(
|
||||
|
@ -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))
|
||||
|
@ -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)))
|
||||
|
Loading…
Reference in New Issue
Block a user