mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 06:35:11 +01:00
merged from wip-bolt2
This commit is contained in:
commit
4a859bdd3a
37 changed files with 1185 additions and 350 deletions
|
@ -52,7 +52,7 @@ trait Service extends Logging {
|
|||
}
|
||||
|
||||
val route =
|
||||
path(RestPath) { path =>
|
||||
pathSingleSlash {
|
||||
post {
|
||||
entity(as[String]) {
|
||||
body =>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package fr.acinq.eclair.blockchain
|
||||
|
||||
import fr.acinq.bitcoin.{BinaryData, BitcoinJsonRPCClient, JsonRPCError, Protocol, Satoshi, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.channel
|
||||
import fr.acinq.eclair.channel.Scripts
|
||||
import org.bouncycastle.util.encoders.Hex
|
||||
|
@ -12,7 +12,7 @@ import scala.concurrent.{Await, ExecutionContext, Future}
|
|||
/**
|
||||
* Created by PM on 26/04/2016.
|
||||
*/
|
||||
class ExtendedBitcoinClient(client: BitcoinJsonRPCClient) {
|
||||
class ExtendedBitcoinClient(val client: BitcoinJsonRPCClient) {
|
||||
|
||||
implicit val formats = org.json4s.DefaultFormats
|
||||
|
||||
|
@ -56,7 +56,7 @@ class ExtendedBitcoinClient(client: BitcoinJsonRPCClient) {
|
|||
case class FundTransactionResponse(tx: Transaction, changepos: Int, fee: Double)
|
||||
|
||||
def fundTransaction(hex: String)(implicit ec: ExecutionContext): Future[FundTransactionResponse] = {
|
||||
client.invoke("fundrawtransaction", hex.take(4) + "0000" + hex.drop(4)).map(json => {
|
||||
client.invoke("fundrawtransaction", hex /*hex.take(4) + "0000" + hex.drop(4)*/).map(json => {
|
||||
val JString(hex) = json \ "hex"
|
||||
val JInt(changepos) = json \ "changepos"
|
||||
val JDouble(fee) = json \ "fee"
|
||||
|
@ -119,4 +119,24 @@ class ExtendedBitcoinClient(client: BitcoinJsonRPCClient) {
|
|||
future
|
||||
}
|
||||
|
||||
def makeAnchorTx(fundingPriv: BinaryData, ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Btc)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
|
||||
val pub = Crypto.publicKeyFromPrivateKey(fundingPriv)
|
||||
val script = Script.write(Scripts.pay2sh(Scripts.pay2wpkh(pub)))
|
||||
val address = Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, script)
|
||||
val future = for {
|
||||
id <- sendFromAccount("", address, amount.amount.toDouble)
|
||||
tx <- getTransaction(id)
|
||||
Some(pos) = Scripts.findPublicKeyScriptIndex(tx, script)
|
||||
output = tx.txOut(pos)
|
||||
anchorOutputScript = channel.Scripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)
|
||||
tx1 = Transaction(version = 2, txIn = TxIn(OutPoint(tx, pos), Nil, 0xffffffffL) :: Nil, txOut = TxOut(amount, anchorOutputScript) :: Nil, lockTime = 0)
|
||||
pubKeyScript = Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
sig = Transaction.signInput(tx1, 0, pubKeyScript, SIGHASH_ALL, output.amount.toLong, 1, fundingPriv)
|
||||
witness = ScriptWitness(Seq(sig, pub))
|
||||
tx2 = tx1.copy(witness = Seq(witness))
|
||||
Some(pos1) = Scripts.findPublicKeyScriptIndex(tx2, anchorOutputScript)
|
||||
} yield(tx2, pos1)
|
||||
|
||||
future
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class PollingWatcher(client: ExtendedBitcoinClient)(implicit ec: ExecutionContex
|
|||
|
||||
case w: Watch if !watches.contains(w) =>
|
||||
log.info(s"adding watch $w for $sender")
|
||||
val cancellable = context.system.scheduler.schedule(2 seconds, 10 seconds)(w match {
|
||||
val cancellable = context.system.scheduler.schedule(100 milliseconds, 10 seconds)(w match {
|
||||
case w@WatchConfirmed(channel, txId, minDepth, event) =>
|
||||
client.getTxConfirmations(txId.toString).map(_ match {
|
||||
case Some(confirmations) if confirmations >= minDepth =>
|
||||
|
|
|
@ -63,64 +63,46 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
|||
case Event((anchorTx: Transaction, anchorOutputIndex: Int), DATA_OPEN_WITH_ANCHOR_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash, theirNextRevocationHash)) =>
|
||||
log.info(s"anchor txid=${anchorTx.txid}")
|
||||
val amount = anchorTx.txOut(anchorOutputIndex).amount.toLong
|
||||
// FIXME. fees and amount are wrong
|
||||
val spec = CommitmentSpec(Set.empty[Htlc], theirParams.initialFeeRate, amount * 1000, 0)
|
||||
val theirTx = makeTheirTx(ourParams, theirParams, TxIn(OutPoint(anchorTx.hash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, theirRevocationHash, spec)
|
||||
val ourSig = sign(ourParams, theirParams, amount, theirTx)
|
||||
them ! open_anchor(anchorTx.hash, anchorOutputIndex, amount, ourSig)
|
||||
goto(OPEN_WAIT_FOR_COMMIT_SIG) using DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, TheirCommit(0, spec, theirRevocationHash), theirNextRevocationHash)
|
||||
val theirSpec = CommitmentSpec(Set.empty[Htlc], feeRate = theirParams.initialFeeRate, initial_amount_us_msat = 0, initial_amount_them_msat = amount * 1000, amount_us_msat = 0, amount_them_msat = amount * 1000)
|
||||
them ! open_anchor(anchorTx.hash, anchorOutputIndex, amount)
|
||||
goto(OPEN_WAIT_FOR_COMMIT_SIG) using DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, TheirCommit(0, theirSpec, theirRevocationHash), theirNextRevocationHash)
|
||||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
}
|
||||
|
||||
when(OPEN_WAIT_FOR_ANCHOR) {
|
||||
case Event(open_anchor(anchorTxHash, anchorOutputIndex, anchorAmount, theirSig), DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash, theirNextRevocationHash)) =>
|
||||
case Event(open_anchor(anchorTxHash, anchorOutputIndex, anchorAmount), DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash, theirNextRevocationHash)) =>
|
||||
val anchorTxid = anchorTxHash.reverse //see https://github.com/ElementsProject/lightning/issues/17
|
||||
val anchorOutput = TxOut(Satoshi(anchorAmount), publicKeyScript = Scripts.anchorPubkeyScript(ourParams.commitPubKey, theirParams.commitPubKey))
|
||||
|
||||
// they fund the channel with their anchor tx, so the money is theirs
|
||||
val ourSpec = {
|
||||
val fee = ChannelState.computeFee(ourParams.initialFeeRate, 0)
|
||||
val pay_msat = (anchorAmount - fee) * 1000
|
||||
val fee_msat = fee * 1000
|
||||
CommitmentSpec(Set.empty[Htlc], fee_msat, amount_us = 0, amount_them = pay_msat)
|
||||
}
|
||||
|
||||
val theirSpec = {
|
||||
val fee = ChannelState.computeFee(theirParams.initialFeeRate, 0)
|
||||
val pay_msat = (anchorAmount - fee) * 1000
|
||||
val fee_msat = fee * 1000
|
||||
CommitmentSpec(Set.empty[Htlc], fee_msat, amount_us = pay_msat, amount_them = 0)
|
||||
}
|
||||
val ourSpec = CommitmentSpec(Set.empty[Htlc], feeRate = ourParams.initialFeeRate, initial_amount_them_msat = anchorAmount * 1000, initial_amount_us_msat = 0, amount_them_msat = anchorAmount * 1000, amount_us_msat = 0)
|
||||
val theirSpec = CommitmentSpec(Set.empty[Htlc], feeRate = theirParams.initialFeeRate, initial_amount_them_msat = 0, initial_amount_us_msat = anchorAmount * 1000, amount_them_msat = 0, amount_us_msat = anchorAmount * 1000)
|
||||
|
||||
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
|
||||
val ourRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, 0))
|
||||
val ourTx = makeOurTx(ourParams, theirParams, TxIn(OutPoint(anchorTxHash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourRevocationHash, ourSpec)
|
||||
val ourSig = sign(ourParams, theirParams, anchorAmount, ourTx)
|
||||
val signedTx = addSigs(ourParams, theirParams, anchorAmount: Long, ourTx, ourSig, theirSig)
|
||||
checksig(ourParams, theirParams, anchorOutput, signedTx) match {
|
||||
case false =>
|
||||
them ! error(Some("Bad signature"))
|
||||
goto(CLOSED)
|
||||
case true =>
|
||||
val theirTx = makeTheirTx(ourParams, theirParams, TxIn(OutPoint(anchorTxHash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, theirRevocationHash, theirSpec)
|
||||
val ourSigForThem = sign(ourParams, theirParams, anchorAmount, theirTx)
|
||||
them ! open_commit_sig(ourSigForThem)
|
||||
blockchain ! WatchConfirmed(self, anchorTxid, ourParams.minDepth, BITCOIN_ANCHOR_DEPTHOK)
|
||||
blockchain ! WatchSpent(self, anchorTxid, anchorOutputIndex, 0, BITCOIN_ANCHOR_SPENT)
|
||||
goto(OPEN_WAITING_THEIRANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, ShaChain.init, OurCommit(0, ourSpec, signedTx), TheirCommit(0, theirSpec, theirRevocationHash), theirNextRevocationHash, None, anchorOutput)
|
||||
}
|
||||
val theirTx = makeTheirTx(ourParams, theirParams, TxIn(OutPoint(anchorTxHash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, theirRevocationHash, theirSpec)
|
||||
log.info(s"signing their tx: $theirTx")
|
||||
val ourSigForThem = sign(ourParams, theirParams, anchorAmount, theirTx)
|
||||
them ! open_commit_sig(ourSigForThem)
|
||||
blockchain ! WatchConfirmed(self, anchorTxid, ourParams.minDepth, BITCOIN_ANCHOR_DEPTHOK)
|
||||
blockchain ! WatchSpent(self, anchorTxid, anchorOutputIndex, 0, BITCOIN_ANCHOR_SPENT)
|
||||
// FIXME: ourTx is not signed by them and cannot be published
|
||||
goto(OPEN_WAITING_THEIRANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, ShaChain.init, OurCommit(0, ourSpec, ourTx), TheirCommit(0, theirSpec, theirRevocationHash), theirNextRevocationHash, None, anchorOutput)
|
||||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
}
|
||||
|
||||
when(OPEN_WAIT_FOR_COMMIT_SIG) {
|
||||
case Event(open_commit_sig(theirSig), DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, commitment, theirNextRevocationHash)) =>
|
||||
val spec = commitment.spec
|
||||
case Event(open_commit_sig(theirSig), DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, theirCommitment, theirNextRevocationHash)) =>
|
||||
val anchorAmount = anchorTx.txOut(anchorOutputIndex).amount.toLong
|
||||
val theirSpec = theirCommitment.spec
|
||||
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
|
||||
val ourRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, 0))
|
||||
val ourTx = makeOurTx(ourParams, theirParams, TxIn(OutPoint(anchorTx, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourRevocationHash, spec)
|
||||
val anchorAmount = anchorTx.txOut(anchorOutputIndex).amount.toLong
|
||||
val ourSpec = CommitmentSpec(Set.empty[Htlc], feeRate = theirParams.initialFeeRate, initial_amount_us_msat = anchorAmount * 1000, initial_amount_them_msat = 0, amount_us_msat = anchorAmount * 1000, amount_them_msat = 0)
|
||||
val ourTx = makeOurTx(ourParams, theirParams, TxIn(OutPoint(anchorTx, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourRevocationHash, ourSpec)
|
||||
log.info(s"checking our tx: $ourTx")
|
||||
val ourSig = sign(ourParams, theirParams, anchorAmount, ourTx)
|
||||
val signedTx: Transaction = addSigs(ourParams, theirParams, anchorAmount, ourTx, ourSig, theirSig)
|
||||
val anchorOutput = anchorTx.txOut(anchorOutputIndex)
|
||||
|
@ -132,7 +114,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
|||
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, OurCommit(0, spec, signedTx), commitment, theirNextRevocationHash, None, anchorOutput)
|
||||
goto(OPEN_WAITING_OURANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, ShaChain.init, OurCommit(0, ourSpec, signedTx), theirCommitment, theirNextRevocationHash, None, anchorOutput)
|
||||
}
|
||||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
|
@ -144,7 +126,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
|||
them ! open_complete(None)
|
||||
deferred.map(self ! _)
|
||||
//TODO htlcIdx should not be 0 when resuming connection
|
||||
goto(OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR) using DATA_NORMAL(ourParams, theirParams, shaChain, 0, ourCommit, theirCommit, OurChanges(Nil, Nil), TheirChanges(Nil), Some(theirNextRevocationHash), anchorOutput)
|
||||
goto(OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR) using DATA_NORMAL(ourParams, theirParams, shaChain, 0, ourCommit, theirCommit, OurChanges(Nil, Nil, Nil), TheirChanges(Nil, Nil), Some(theirNextRevocationHash), anchorOutput)
|
||||
|
||||
case Event(msg@open_complete(blockId_opt), d: DATA_OPEN_WAITING) =>
|
||||
log.info(s"received their open_complete, deferring message")
|
||||
|
@ -182,7 +164,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
|||
them ! open_complete(None)
|
||||
deferred.map(self ! _)
|
||||
//TODO htlcIdx should not be 0 when resuming connection
|
||||
goto(OPEN_WAIT_FOR_COMPLETE_OURANCHOR) using DATA_NORMAL(ourParams, theirParams, shaChain, 0, ourCommit, theirCommit, OurChanges(Nil, Nil), TheirChanges(Nil), Some(theirNextRevocationHash), anchorOutput)
|
||||
goto(OPEN_WAIT_FOR_COMPLETE_OURANCHOR) using DATA_NORMAL(ourParams, theirParams, shaChain, 0, ourCommit, theirCommit, OurChanges(Nil, Nil, Nil), TheirChanges(Nil, Nil), Some(theirNextRevocationHash), anchorOutput)
|
||||
|
||||
case Event(msg@open_complete(blockId_opt), d: DATA_OPEN_WAITING) =>
|
||||
log.info(s"received their open_complete, deferring message")
|
||||
|
@ -295,26 +277,26 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
|||
// TODO: nodeIds are ignored
|
||||
stay using d.copy(theirChanges = theirChanges.copy(proposed = theirChanges.proposed :+ htlc))
|
||||
|
||||
case Event(CMD_FULFILL_HTLC(id, r), d@DATA_NORMAL(_, _, _, _, _, theirCommit, ourChanges, _, _, _)) =>
|
||||
theirCommit.spec.htlcs.find(h => h.direction == IN && h.id == id) match {
|
||||
case Some(htlc) if htlc.rHash == bin2sha256(sha256(r)) =>
|
||||
case Event(CMD_FULFILL_HTLC(id, r), d@DATA_NORMAL(_, _, _, _, _, theirCommit, ourChanges, theirChanges, _, _)) =>
|
||||
theirChanges.acked.collectFirst { case u: update_add_htlc if u.id == id => u } match {
|
||||
case Some(htlc) if htlc.rHash == bin2sha256(Crypto.sha256(r)) =>
|
||||
val fulfill = update_fulfill_htlc(id, r)
|
||||
them ! fulfill
|
||||
stay using d.copy(ourChanges = ourChanges.copy(proposed = ourChanges.proposed :+ fulfill))
|
||||
case Some(htlc) => throw new RuntimeException(s"invalid preimage for htlc id=$id")
|
||||
case Some(htlc) => throw new RuntimeException(s"invalid htlc preimage for htlc $id")
|
||||
case None => throw new RuntimeException(s"unknown htlc id=$id")
|
||||
}
|
||||
|
||||
case Event(fulfill@update_fulfill_htlc(id, r), d@DATA_NORMAL(_, _, _, _, ourCommit, _, _, theirChanges, _, _)) =>
|
||||
ourCommit.spec.htlcs.find(h => h.direction == OUT && h.id == id) match {
|
||||
case Some(htlc) if htlc.rHash == bin2sha256(sha256(r)) =>
|
||||
case Event(fulfill@update_fulfill_htlc(id, r), d@DATA_NORMAL(_, _, _, _, ourCommit, _, ourChanges, theirChanges, _, _)) =>
|
||||
ourChanges.acked.collectFirst { case u: update_add_htlc if u.id == id => u } match {
|
||||
case Some(htlc) if htlc.rHash == bin2sha256(Crypto.sha256(r)) =>
|
||||
stay using d.copy(theirChanges = theirChanges.copy(proposed = theirChanges.proposed :+ fulfill))
|
||||
case Some(htlc) => throw new RuntimeException(s"invalid preimage for htlc id=$id") // TODO : we should fail the channel
|
||||
case Some(htlc) => throw new RuntimeException(s"invalid htlc preimage for htlc $id")
|
||||
case None => throw new RuntimeException(s"unknown htlc id=$id") // TODO : we should fail the channel
|
||||
}
|
||||
|
||||
case Event(CMD_FAIL_HTLC(id, reason), d@DATA_NORMAL(_, _, _, _, _, theirCommit, ourChanges, _, _, _)) =>
|
||||
theirCommit.spec.htlcs.find(h => h.direction == IN && h.id == id) match {
|
||||
case Event(CMD_FAIL_HTLC(id, reason), d@DATA_NORMAL(_, _, _, _, _, theirCommit, ourChanges, theirChanges, _, _)) =>
|
||||
theirChanges.acked.collectFirst { case u: update_add_htlc if u.id == id => u } match {
|
||||
case Some(htlc) =>
|
||||
val fail = update_fail_htlc(id, fail_reason(ByteString.copyFromUtf8(reason)))
|
||||
them ! fail
|
||||
|
@ -322,42 +304,55 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
|||
case None => throw new RuntimeException(s"unknown htlc id=$id")
|
||||
}
|
||||
|
||||
case Event(fail@update_fail_htlc(id, reason), d@DATA_NORMAL(_, _, _, _, ourCommit, _, _, theirChanges, _, _)) =>
|
||||
ourCommit.spec.htlcs.find(h => h.direction == OUT && h.id == id) match {
|
||||
case Event(fail@update_fail_htlc(id, reason), d@DATA_NORMAL(_, _, _, _, ourCommit, _, ourChanges, theirChanges, _, _)) =>
|
||||
ourChanges.acked.collectFirst { case u: update_add_htlc if u.id == id => u } match {
|
||||
case Some(htlc) =>
|
||||
stay using d.copy(theirChanges = theirChanges.copy(proposed = theirChanges.proposed :+ fail))
|
||||
case None => throw new RuntimeException(s"unknown htlc id=$id") // TODO : we should fail the channel
|
||||
}
|
||||
|
||||
case Event(CMD_SIGN, d@DATA_NORMAL(ourParams, theirParams, _, _, ourCommit, theirCommit, ourChanges, theirChanges, theirNextRevocationHash_opt, anchorOutput)) =>
|
||||
// sign all our proposals + their acked proposals
|
||||
// their commitment now includes all our changes + their acked changes
|
||||
theirNextRevocationHash_opt match {
|
||||
case Some(theirNextRevocationHash) =>
|
||||
val spec = reduce(theirCommit.spec, Nil, ourChanges.proposed)
|
||||
val spec = reduce(theirCommit.spec, theirChanges.acked, ourChanges.acked ++ ourChanges.signed ++ ourChanges.proposed)
|
||||
val theirTx = makeTheirTx(ourParams, theirParams, ourCommit.publishableTx.txIn, theirNextRevocationHash, spec)
|
||||
val ourSig = sign(ourParams, theirParams, anchorOutput.amount.toLong, theirTx)
|
||||
them ! update_commit(ourSig)
|
||||
stay using d.copy(theirCommit = TheirCommit(theirCommit.index + 1, spec, theirNextRevocationHash), ourChanges = ourChanges.copy(proposed = Nil, signed = ourChanges.proposed))
|
||||
stay using d.copy(theirCommit = TheirCommit(theirCommit.index + 1, spec, theirNextRevocationHash), ourChanges = ourChanges.copy(proposed = Nil, signed = ourChanges.signed ++ ourChanges.proposed), theirNextRevocationHash = None)
|
||||
case None => throw new RuntimeException(s"cannot send two update_commit in a row (must wait for revocation)")
|
||||
}
|
||||
|
||||
case Event(msg@update_commit(theirSig), d@DATA_NORMAL(ourParams, theirParams, shaChain, _, ourCommit, theirCommit, _, theirChanges, _, anchorOutput)) =>
|
||||
val spec = reduce(ourCommit.spec, theirChanges.proposed, Nil)
|
||||
val ourRevocationPreimage = ShaChain.shaChainFromSeed(ourParams.shaSeed, ourCommit.index)
|
||||
val ourRevocationHash = Crypto.sha256(ourRevocationPreimage)
|
||||
val ourTx = makeOurTx(ourParams, theirParams, ourCommit.publishableTx.txIn, ourRevocationHash, spec)
|
||||
case Event(msg@update_commit(theirSig), d@DATA_NORMAL(ourParams, theirParams, shaChain, _, ourCommit, theirCommit, ourChanges, theirChanges, _, anchorOutput)) =>
|
||||
// we've received a signature
|
||||
// ack all their changes
|
||||
// our commitment now includes all theirs changes + our acked changes
|
||||
val spec = reduce(ourCommit.spec, ourChanges.acked, theirChanges.acked ++ theirChanges.proposed)
|
||||
val ourNextRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, ourCommit.index + 1))
|
||||
val ourTx = makeOurTx(ourParams, theirParams, ourCommit.publishableTx.txIn, ourNextRevocationHash, spec)
|
||||
val ourSig = sign(ourParams, theirParams, anchorOutput.amount.toLong, ourTx)
|
||||
val signedTx = addSigs(ourParams, theirParams, anchorOutput.amount.toLong, ourTx, ourSig, theirSig)
|
||||
checksig(ourParams, theirParams, anchorOutput, ourTx) match {
|
||||
checksig(ourParams, theirParams, anchorOutput, signedTx) match {
|
||||
case false =>
|
||||
them ! error(Some("Bad signature"))
|
||||
publish_ourcommit(ourCommit)
|
||||
goto(CLOSING) using DATA_CLOSING(ourParams, theirParams, shaChain, ourCommit, theirCommit, ourCommitPublished = Some(ourCommit.publishableTx))
|
||||
case true =>
|
||||
val ourNextRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, ourCommit.index + 1))
|
||||
val ourRevocationPreimage = ShaChain.shaChainFromSeed(ourParams.shaSeed, ourCommit.index)
|
||||
val ourRevocationHash = Crypto.sha256(ourRevocationPreimage)
|
||||
val ourNextRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, ourCommit.index + 2))
|
||||
them ! update_revocation(ourRevocationPreimage, ourNextRevocationHash)
|
||||
stay using d.copy(theirChanges = TheirChanges(Nil))
|
||||
val ourCommit1 = ourCommit.copy(index = ourCommit.index + 1, spec, publishableTx = signedTx)
|
||||
stay using d.copy(ourCommit = ourCommit1, theirChanges = theirChanges.copy(proposed = Nil, acked = theirChanges.acked ++ theirChanges.proposed))
|
||||
}
|
||||
|
||||
case Event(msg@update_revocation(revocationPreimage, nextRevocationHash), d@DATA_NORMAL(ourParams, theirParams, shaChain, _, ourCommit, theirCommit, ourChanges, theirChanges, _, _)) =>
|
||||
// we received a revocation because we sent a signature
|
||||
// => all our changes have been acked
|
||||
//TODO : check rev pre image is valid
|
||||
stay using d.copy(ourChanges = ourChanges.copy(signed = Nil, acked = ourChanges.acked ++ ourChanges.signed), theirNextRevocationHash = Some(nextRevocationHash))
|
||||
|
||||
/*case Event(CMD_CLOSE(scriptPubKeyOpt), d@DATA_NORMAL(ack_in, ack_out, ourParams, theirParams, shaChain, _, staged, commitment, nextCommitment)) =>
|
||||
val scriptPubKey: BinaryData = scriptPubKeyOpt.getOrElse(Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(ourFinalPubKey.data)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil))
|
||||
them ! close_clearing(scriptPubKey)
|
||||
|
@ -744,8 +739,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
|||
|
||||
case Event(CMD_GETINFO, _) =>
|
||||
sender ! RES_GETINFO(theirNodeId, stateData match {
|
||||
//TODO: case c: CurrentCommitment => c.commitment.anchorId
|
||||
case _ => "unknown"
|
||||
case c: CurrentCommitment => c.anchorId
|
||||
case _ => Hash.Zeroes
|
||||
}, stateName, stateData)
|
||||
stay
|
||||
|
||||
|
|
|
@ -16,103 +16,18 @@ case object OUT extends Direction
|
|||
|
||||
final case class Change2(direction: Direction, ack: Long, msg: GeneratedMessage)
|
||||
|
||||
case class Htlc2(id: Long, amountMsat: Int, rHash: sha256_hash, expiry: locktime, nextNodeIds: Seq[String] = Nil, val previousChannelId: Option[BinaryData])
|
||||
//case class Htlc2(id: Long, amountMsat: Int, rHash: sha256_hash, expiry: locktime, nextNodeIds: Seq[String] = Nil, val previousChannelId: Option[BinaryData])
|
||||
|
||||
case class Htlc(direction: Direction, id: Long, amountMsat: Int, rHash: sha256_hash, expiry: locktime, nextNodeIds: Seq[String] = Nil, val previousChannelId: Option[BinaryData])
|
||||
|
||||
// @formatter:on
|
||||
|
||||
case class ChannelOneSide(pay_msat: Long, fee_msat: Long, htlcs_received: Seq[Htlc2]) {
|
||||
case class ChannelOneSide(pay_msat: Long, fee_msat: Long) {
|
||||
val funds = pay_msat + fee_msat
|
||||
}
|
||||
|
||||
case class ChannelState(us: ChannelOneSide, them: ChannelOneSide, feeRate: Long) {
|
||||
/**
|
||||
* Because each party needs to be able to compute the other party's commitment tx
|
||||
*
|
||||
* @return the channel state as seen by the other party
|
||||
*/
|
||||
def reverse: ChannelState = this.copy(them = us, us = them)
|
||||
|
||||
def commit_changes(staged: List[Change2]): ChannelState = {
|
||||
staged.foldLeft(this) {
|
||||
case (state, Change2(direction, _, htlc: update_add_htlc)) => state.add_htlc(direction, Htlc2(htlc.id, htlc.amountMsat, htlc.rHash, htlc.expiry, Nil, None)) // TODO
|
||||
case (state, Change2(direction, _, fulfill: update_fulfill_htlc)) => state.fulfill_htlc(direction, fulfill.id, fulfill.r)
|
||||
case (state, Change2(direction, _, fail: update_fail_htlc)) => state.fail_htlc(direction, fail.id)
|
||||
}
|
||||
}
|
||||
|
||||
def add_htlc(direction: Direction, htlc: Htlc2): ChannelState =
|
||||
direction match {
|
||||
case IN => this.copy(them = them.copy(pay_msat = them.pay_msat - htlc.amountMsat), us = us.copy(htlcs_received = us.htlcs_received :+ htlc))
|
||||
case OUT => this.copy(them = them.copy(htlcs_received = them.htlcs_received :+ htlc), us = us.copy(pay_msat = us.pay_msat - htlc.amountMsat))
|
||||
}
|
||||
|
||||
def fulfill_htlc(direction: Direction, id: Long, r: sha256_hash): ChannelState =
|
||||
direction match {
|
||||
case IN =>
|
||||
them.htlcs_received.find(_.id == id) match {
|
||||
case Some(htlc) =>
|
||||
// we were the sender of this 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_received = them.htlcs_received.filterNot(_ == htlc), fee_msat = new_them_fee))
|
||||
case None => throw new RuntimeException(s"could not find htlc direction=$direction id=$id")
|
||||
}
|
||||
case OUT =>
|
||||
us.htlcs_received.find(_.id == id) match {
|
||||
case Some(htlc) =>
|
||||
// we were the receiver of this 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_received = us.htlcs_received.filterNot(_ == htlc), fee_msat = new_us_fee),
|
||||
them = them.copy(pay_msat = new_them_amount, fee_msat = new_them_fee))
|
||||
case None => throw new RuntimeException(s"could not find htlc direction=$direction id=$id")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cause of failures include: timeout, routing failure
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
def fail_htlc(direction: Direction, id: Long): ChannelState =
|
||||
direction match {
|
||||
case IN =>
|
||||
them.htlcs_received.find(_.id == id) match {
|
||||
case Some(htlc) => this.copy(them = them.copy(htlcs_received = them.htlcs_received.filterNot(_ == htlc)), us = us.copy(pay_msat = us.pay_msat + htlc.amountMsat))
|
||||
case None => throw new RuntimeException(s"could not find htlc direction=$direction id=$id")
|
||||
}
|
||||
case OUT =>
|
||||
us.htlcs_received.find(_.id == id) match {
|
||||
case Some(htlc) => this.copy(them = them.copy(pay_msat = them.pay_msat + htlc.amountMsat), us = us.copy(htlcs_received = us.htlcs_received.filterNot(_ == htlc)))
|
||||
case None => throw new RuntimeException(s"could not find htlc direction=$direction id=$id")
|
||||
}
|
||||
}
|
||||
|
||||
def adjust_fees(fee: Long, is_funder: Boolean): ChannelState = {
|
||||
if (is_funder) {
|
||||
val (funder, nonfunder) = ChannelState.adjust_fees(this.us, this.them, fee)
|
||||
this.copy(us = funder, them = nonfunder)
|
||||
} else {
|
||||
val (funder, nonfunder) = ChannelState.adjust_fees(this.them, this.us, fee)
|
||||
this.copy(us = nonfunder, them = funder)
|
||||
}
|
||||
}
|
||||
|
||||
def prettyString(): String = s"pay_us=${us.pay_msat} htlcs_us=${us.htlcs_received.map(_.amountMsat).sum} pay_them=${them.pay_msat} htlcs_them=${them.htlcs_received.map(_.amountMsat).sum} total=${us.pay_msat + us.htlcs_received.map(_.amountMsat).sum + them.pay_msat + them.htlcs_received.map(_.amountMsat).sum}"
|
||||
def reverse = this.copy(us = them, them = us)
|
||||
}
|
||||
|
||||
object ChannelState {
|
||||
|
@ -142,8 +57,8 @@ object ChannelState {
|
|||
val fee = computeFee(feeRate, 0)
|
||||
val pay_msat = (anchorAmount - fee) * 1000
|
||||
val fee_msat = fee * 1000
|
||||
val us = ChannelOneSide(pay_msat, fee_msat, Nil)
|
||||
val them = ChannelOneSide(0, 0, Nil)
|
||||
val us = ChannelOneSide(pay_msat, fee_msat)
|
||||
val them = ChannelOneSide(0, 0)
|
||||
ChannelState(us, them, feeRate)
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,9 @@ final case class OurChannelParams(delay: locktime, commitPrivKey: BinaryData, fi
|
|||
}
|
||||
final case class TheirChannelParams(delay: locktime, commitPubKey: BinaryData, finalPubKey: BinaryData, minDepth: Option[Int], initialFeeRate: Long)
|
||||
|
||||
final case class CommitmentSpec(htlcs: Set[Htlc], fee: Long, amount_us: Long, amount_them: Long)
|
||||
final case class CommitmentSpec(htlcs: Set[Htlc], feeRate: Long, initial_amount_us_msat : Long, initial_amount_them_msat: Long, amount_us_msat: Long, amount_them_msat: Long) {
|
||||
val totalFunds = amount_us_msat + amount_them_msat + htlcs.toSeq.map(_.amountMsat).sum
|
||||
}
|
||||
|
||||
trait CurrentCommitment {
|
||||
def ourParams: OurChannelParams
|
||||
|
@ -145,8 +147,9 @@ object TypeDefs {
|
|||
type Change = GeneratedMessage
|
||||
}
|
||||
import TypeDefs._
|
||||
case class OurChanges(proposed: List[Change], signed: List[Change])
|
||||
case class TheirChanges(proposed: List[Change])
|
||||
case class OurChanges(proposed: List[Change], signed: List[Change], acked: List[Change])
|
||||
case class TheirChanges(proposed: List[Change], acked: List[Change])
|
||||
case class Changes(ourChanges: OurChanges, theirChanges: TheirChanges)
|
||||
case class OurCommit(index: Long, spec: CommitmentSpec, publishableTx: Transaction)
|
||||
case class TheirCommit(index: Long, spec: CommitmentSpec, theirRevocationHash: sha256_hash)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import Scripts._
|
|||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.channel.TypeDefs.Change
|
||||
import lightning.{sha256_hash, signature, update_add_htlc, update_fulfill_htlc}
|
||||
import lightning._
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
|
@ -13,33 +13,61 @@ import scala.util.Try
|
|||
*/
|
||||
object Helpers {
|
||||
|
||||
def reduce(initialSpec: CommitmentSpec, in: List[Change], out: List[Change]): CommitmentSpec = ???
|
||||
def removeHtlc(changes: List[Change], id: Long): List[Change] = changes.filterNot(_ match {
|
||||
case u: update_add_htlc if u.id == id => true
|
||||
case _ => false
|
||||
})
|
||||
|
||||
/*{
|
||||
|
||||
(in ++ out).sortBy().foldLeft(initialSpec.copy(htlcs = initialSpec.htlcs) {
|
||||
case (spec, f: update_add_htlc) => null
|
||||
case (spec, f: update_fulfill_htlc) => null
|
||||
case (spec, f: update_fail_htlc) => null
|
||||
case (spec, f: update_fee_htlc) => null
|
||||
}
|
||||
null
|
||||
|
||||
in.foldLeft(initialSpec.copy(htlcs = initialSpec.htlcs) {
|
||||
case (spec, f: update_fulfill_htlc) =>
|
||||
def addHtlc(spec: CommitmentSpec, direction: Direction, update: update_add_htlc): CommitmentSpec = {
|
||||
val htlc = Htlc(direction, update.id, update.amountMsat, update.rHash, update.expiry, previousChannelId = None)
|
||||
direction match {
|
||||
case OUT => spec.copy(amount_us_msat = spec.amount_us_msat - htlc.amountMsat, htlcs = spec.htlcs + htlc)
|
||||
case IN => spec.copy(amount_them_msat = spec.amount_them_msat - htlc.amountMsat, htlcs = spec.htlcs + htlc)
|
||||
}
|
||||
}
|
||||
|
||||
val new_htlcs = in.collect { case u: update_add_htlc => Htlc(IN, u.id, u.amountMsat, u.rHash, u.expiry, Nil, None) } ++
|
||||
out.collect { case u: update_add_htlc => Htlc(OUT, u.id, u.amountMsat, u.rHash, u.expiry, Nil, None) }
|
||||
// OUT means we are sending an update_fulfill_htlc message which means that we are fulfilling an HTLC that they sent
|
||||
def fulfillHtlc(spec: CommitmentSpec, direction: Direction, update: update_fulfill_htlc): CommitmentSpec = {
|
||||
spec.htlcs.find(htlc => htlc.id == update.id && htlc.rHash == bin2sha256(Crypto.sha256(update.r))) match {
|
||||
case Some(htlc) => direction match {
|
||||
case OUT => spec.copy(amount_us_msat = spec.amount_us_msat + htlc.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
case IN => spec.copy(amount_them_msat = spec.amount_them_msat + htlc.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OUT means we are sending an update_fail_htlc message which means that we are failing an HTLC that they sent
|
||||
def failHtlc(spec: CommitmentSpec, direction: Direction, update: update_fail_htlc): CommitmentSpec = {
|
||||
spec.htlcs.find(_.id == update.id) match {
|
||||
case Some(htlc) => direction match {
|
||||
case OUT => spec.copy(amount_them_msat = spec.amount_them_msat + htlc.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
case IN => spec.copy(amount_us_msat = spec.amount_us_msat + htlc.amountMsat, htlcs = spec.htlcs - htlc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in.foldLeft(initialSpec.copy(htlcs = initialSpec.htlcs ++ new_htlcs)) {
|
||||
case (spec, f: update_fulfill_htlc) =>
|
||||
val htlc = spec.htlcs.find(h => h.direction == OUT && h.id == f.id).getOrElse(???)
|
||||
spec.copy(htlcs = spec.htlcs - htlc, amount_them = spec.amount_them + )
|
||||
}*/
|
||||
// .foldLeft(initialSpec.htlcs) { case (htlcs, add:) => //
|
||||
|
||||
def reduce(ourCommitSpec: CommitmentSpec, ourChanges: List[Change], theirChanges: List[Change]): CommitmentSpec = {
|
||||
val spec = ourCommitSpec.copy(htlcs = Set(), amount_us_msat = ourCommitSpec.initial_amount_us_msat, amount_them_msat = ourCommitSpec.initial_amount_them_msat)
|
||||
val spec1 = ourChanges.foldLeft(spec) {
|
||||
case (spec, u: update_add_htlc) => addHtlc(spec, OUT, u)
|
||||
case (spec, _) => spec
|
||||
}
|
||||
val spec2 = theirChanges.foldLeft(spec1) {
|
||||
case (spec, u: update_add_htlc) => addHtlc(spec, IN, u)
|
||||
case (spec, _) => spec
|
||||
}
|
||||
val spec3 = ourChanges.foldLeft(spec2) {
|
||||
case (spec, u: update_fulfill_htlc) => fulfillHtlc(spec, OUT, u)
|
||||
case (spec, u: update_fail_htlc) => failHtlc(spec, OUT, u)
|
||||
case (spec, _) => spec
|
||||
}
|
||||
val spec4 = theirChanges.foldLeft(spec3) {
|
||||
case (spec, u: update_fulfill_htlc) => fulfillHtlc(spec, IN, u)
|
||||
case (spec, u: update_fail_htlc) => failHtlc(spec, IN, u)
|
||||
case (spec, _) => spec
|
||||
}
|
||||
spec4
|
||||
}
|
||||
|
||||
def makeOurTx(ourParams: OurChannelParams, theirParams: TheirChannelParams, inputs: Seq[TxIn], ourRevocationHash: sha256_hash, spec: CommitmentSpec): Transaction =
|
||||
makeCommitTx(inputs, ourParams.finalPubKey, theirParams.finalPubKey, ourParams.delay, ourRevocationHash, spec)
|
||||
|
@ -47,10 +75,10 @@ object Helpers {
|
|||
def makeTheirTx(ourParams: OurChannelParams, theirParams: TheirChannelParams, inputs: Seq[TxIn], theirRevocationHash: sha256_hash, spec: CommitmentSpec): Transaction =
|
||||
makeCommitTx(inputs, theirParams.finalPubKey, ourParams.finalPubKey, theirParams.delay, theirRevocationHash, spec)
|
||||
|
||||
def sign(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorAmount: Long, tx: Transaction): signature =
|
||||
def sign(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorAmount: Long, tx: Transaction): signature =
|
||||
bin2signature(Transaction.signInput(tx, 0, multiSig2of2(ourParams.commitPubKey, theirParams.commitPubKey), SIGHASH_ALL, anchorAmount, 1, ourParams.commitPrivKey))
|
||||
|
||||
def addSigs(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorAmount: Long, tx: Transaction, ourSig: signature, theirSig: signature): Transaction = {
|
||||
def addSigs(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorAmount: Long, tx: Transaction, ourSig: signature, theirSig: signature): Transaction = {
|
||||
// TODO : Transaction.sign(...) should handle multisig
|
||||
val ourSig = Transaction.signInput(tx, 0, multiSig2of2(ourParams.commitPubKey, theirParams.commitPubKey), SIGHASH_ALL, anchorAmount, 1, ourParams.commitPrivKey)
|
||||
val witness = witness2of2(theirSig, ourSig, theirParams.commitPubKey, ourParams.commitPubKey)
|
||||
|
|
|
@ -116,6 +116,7 @@ object Scripts {
|
|||
|
||||
def scriptPubKeyHtlcSend(ourkey: BinaryData, theirkey: BinaryData, abstimeout: Long, reltimeout: Long, rhash: BinaryData, commit_revoke: BinaryData): Seq[ScriptElt] = {
|
||||
// @formatter:off
|
||||
OP_SIZE :: OP_PUSHDATA(Script.encodeNumber(32)) :: OP_EQUALVERIFY ::
|
||||
OP_HASH160 :: OP_DUP ::
|
||||
OP_PUSHDATA(ripemd160(rhash)) :: OP_EQUAL ::
|
||||
OP_SWAP :: OP_PUSHDATA(ripemd160(commit_revoke)) :: OP_EQUAL :: OP_ADD ::
|
||||
|
@ -130,6 +131,7 @@ object Scripts {
|
|||
|
||||
def scriptPubKeyHtlcReceive(ourkey: BinaryData, theirkey: BinaryData, abstimeout: Long, reltimeout: Long, rhash: BinaryData, commit_revoke: BinaryData): Seq[ScriptElt] = {
|
||||
// @formatter:off
|
||||
OP_SIZE :: OP_PUSHDATA(Script.encodeNumber(32)) :: OP_EQUALVERIFY ::
|
||||
OP_HASH160 :: OP_DUP ::
|
||||
OP_PUSHDATA(ripemd160(rhash)) :: OP_EQUAL ::
|
||||
OP_IF ::
|
||||
|
@ -145,42 +147,49 @@ object Scripts {
|
|||
// @formatter:on
|
||||
}
|
||||
|
||||
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)
|
||||
def makeCommitTx(ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, anchorTxId: BinaryData, anchorOutputIndex: Int, revocationHash: BinaryData, spec: CommitmentSpec): Transaction =
|
||||
makeCommitTx(inputs = TxIn(OutPoint(anchorTxId, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourFinalKey, theirFinalKey, theirDelay, revocationHash, spec)
|
||||
|
||||
// this way it is easy to reuse the inputTx of an existing commitmentTx
|
||||
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 outputs = Seq(
|
||||
// TODO : is that the correct way to handle sub-satoshi balances ?
|
||||
TxOut(amount = Satoshi(channelState.us.pay_msat / 1000), publicKeyScript = pay2wsh(redeemScript)),
|
||||
TxOut(amount = Satoshi(channelState.them.pay_msat / 1000), publicKeyScript = pay2wpkh(theirFinalKey))
|
||||
).filterNot(_.amount.toLong < 546) // do not add dust
|
||||
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = inputs,
|
||||
txOut = outputs,
|
||||
lockTime = 0)
|
||||
|
||||
val sendOuts = channelState.them.htlcs_received.map(htlc =>
|
||||
TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
)
|
||||
val receiveOuts = channelState.us.htlcs_received.map(htlc =>
|
||||
TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
)
|
||||
val tx1 = tx.copy(txOut = tx.txOut ++ sendOuts ++ receiveOuts)
|
||||
permuteOutputs(tx1)
|
||||
}
|
||||
// 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 outputs = Seq(
|
||||
// // TODO : is that the correct way to handle sub-satoshi balances ?
|
||||
// TxOut(amount = Satoshi(channelState.us.pay_msat / 1000), publicKeyScript = pay2wsh(redeemScript)),
|
||||
// TxOut(amount = Satoshi(channelState.them.pay_msat / 1000), publicKeyScript = pay2wpkh(theirFinalKey))
|
||||
// ).filterNot(_.amount.toLong < 546) // do not add dust
|
||||
//
|
||||
// val tx = Transaction(
|
||||
// version = 2,
|
||||
// txIn = inputs,
|
||||
// txOut = outputs,
|
||||
// lockTime = 0)
|
||||
//
|
||||
// val sendOuts = channelState.them.htlcs_received.map(htlc =>
|
||||
// TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
// )
|
||||
// val receiveOuts = channelState.us.htlcs_received.map(htlc =>
|
||||
// TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
// )
|
||||
// val tx1 = tx.copy(txOut = tx.txOut ++ sendOuts ++ receiveOuts)
|
||||
// permuteOutputs(tx1)
|
||||
// }
|
||||
|
||||
def makeCommitTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, revocationHash: BinaryData, commitmentSpec: CommitmentSpec): Transaction = {
|
||||
val redeemScript = redeemSecretOrDelay(ourFinalKey, locktime2long_csv(theirDelay), theirFinalKey, revocationHash: BinaryData)
|
||||
val htlcs = commitmentSpec.htlcs.filter(_.amountMsat >= 546000)
|
||||
val fee_msat = ChannelState.computeFee(commitmentSpec.feeRate, htlcs.size) * 1000
|
||||
val (amount_us_msat: Long, amount_them_msat: Long) = (commitmentSpec.amount_us_msat, commitmentSpec.amount_them_msat) match {
|
||||
case (us, them) if us >= fee_msat/2 && them >= fee_msat / 2 => (us - fee_msat / 2, them - fee_msat / 2)
|
||||
case (us, them) if us < fee_msat/2 => (0L, Math.max(0L, them - fee_msat + us))
|
||||
case (us, them) if them < fee_msat/2 => (Math.max(us - fee_msat + them, 0L), 0L)
|
||||
}
|
||||
|
||||
val outputs = Seq(
|
||||
// TODO : is that the correct way to handle sub-satoshi balances ?
|
||||
TxOut(amount = Satoshi(commitmentSpec.amount_us / 1000), publicKeyScript = pay2wsh(redeemScript)),
|
||||
TxOut(amount = Satoshi(commitmentSpec.amount_them / 1000), publicKeyScript = pay2wpkh(theirFinalKey))
|
||||
TxOut(amount = Satoshi(amount_us_msat / 1000), publicKeyScript = pay2wsh(redeemScript)),
|
||||
TxOut(amount = Satoshi(amount_them_msat / 1000), publicKeyScript = pay2wpkh(theirFinalKey))
|
||||
).filterNot(_.amount.toLong < 546) // do not add dust
|
||||
|
||||
val tx = Transaction(
|
||||
|
@ -189,10 +198,10 @@ object Scripts {
|
|||
txOut = outputs,
|
||||
lockTime = 0)
|
||||
|
||||
val sendOuts = commitmentSpec.htlcs.filter(_.direction == OUT).map(htlc =>
|
||||
val sendOuts = htlcs.filter(_.direction == OUT).map(htlc =>
|
||||
TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
)
|
||||
val receiveOuts = commitmentSpec.htlcs.filter(_.direction == IN).map(htlc =>
|
||||
val receiveOuts = htlcs.filter(_.direction == IN).map(htlc =>
|
||||
TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
)
|
||||
val tx1 = tx.copy(txOut = tx.txOut ++ sendOuts ++ receiveOuts)
|
||||
|
@ -209,18 +218,18 @@ object Scripts {
|
|||
* @param channelState channel state
|
||||
* @return an unsigned "final" tx
|
||||
*/
|
||||
def makeFinalTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, channelState: ChannelState): Transaction = {
|
||||
assert(channelState.them.htlcs_received.isEmpty && channelState.us.htlcs_received.isEmpty, s"cannot close a channel with pending htlcs (see rusty's state_types.h line 103)")
|
||||
|
||||
permuteOutputs(Transaction(
|
||||
version = 2,
|
||||
txIn = inputs,
|
||||
txOut = Seq(
|
||||
TxOut(amount = Satoshi(channelState.them.pay_msat / 1000), publicKeyScript = pay2wpkh(theirFinalKey)),
|
||||
TxOut(amount = Satoshi(channelState.us.pay_msat / 1000), publicKeyScript = pay2wpkh(ourFinalKey))
|
||||
),
|
||||
lockTime = 0))
|
||||
}
|
||||
// def makeFinalTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, channelState: ChannelState): Transaction = {
|
||||
// assert(channelState.them.htlcs_received.isEmpty && channelState.us.htlcs_received.isEmpty, s"cannot close a channel with pending htlcs (see rusty's state_types.h line 103)")
|
||||
//
|
||||
// permuteOutputs(Transaction(
|
||||
// version = 2,
|
||||
// txIn = inputs,
|
||||
// txOut = Seq(
|
||||
// TxOut(amount = Satoshi(channelState.them.pay_msat / 1000), publicKeyScript = pay2wpkh(theirFinalKey)),
|
||||
// TxOut(amount = Satoshi(channelState.us.pay_msat / 1000), publicKeyScript = pay2wpkh(ourFinalKey))
|
||||
// ),
|
||||
// lockTime = 0))
|
||||
// }
|
||||
|
||||
def isFunder(o: open_channel): Boolean = o.anch == open_channel.anchor_offer.WILL_CREATE_ANCHOR
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package fr.acinq.eclair.crypto
|
||||
|
||||
import akka.util.ByteString
|
||||
import fr.acinq.bitcoin.{BinaryData, Protocol}
|
||||
import fr.acinq.eclair.crypto.LightningCrypto.AeadChacha20Poly1305
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
/**
|
||||
* message format used by lightning:
|
||||
* header: 20 bytes
|
||||
* - 4 bytes: body length
|
||||
* - 16 bytes: AEAD tag
|
||||
* body: header.length + 16 bytes
|
||||
* - length bytes: encrypted plaintext
|
||||
* - 16 bytes: AEAD tag
|
||||
*
|
||||
* header and body are encrypted with the same key, with a nonce that is incremented each time:
|
||||
* header = encrypt(plaintext.length, key, nonce++)
|
||||
* body = encrypt(plaintext, key, nonce++)
|
||||
*/
|
||||
|
||||
case class Encryptor(key: BinaryData, nonce: Long)
|
||||
|
||||
object Encryptor {
|
||||
def encrypt(encryptor: Encryptor, data: BinaryData) : (Encryptor, BinaryData) = {
|
||||
val header = Protocol.writeUInt32(data.length)
|
||||
val (ciphertext1, mac1) = AeadChacha20Poly1305.encrypt(encryptor.key, Protocol.writeUInt64(encryptor.nonce), header, Array.emptyByteArray)
|
||||
val (ciphertext2, mac2) = AeadChacha20Poly1305.encrypt(encryptor.key, Protocol.writeUInt64(encryptor.nonce + 1), data, Array.emptyByteArray)
|
||||
(encryptor.copy(nonce = encryptor.nonce + 2), ciphertext1 ++ mac1 ++ ciphertext2 ++ mac2)
|
||||
}
|
||||
}
|
||||
|
||||
case class Decryptor(key: BinaryData, nonce: Long, buffer: ByteString = ByteString.empty, header: Option[Decryptor.Header] = None, bodies: Vector[BinaryData] = Vector.empty[BinaryData])
|
||||
|
||||
object Decryptor {
|
||||
|
||||
case class Header(length: Int)
|
||||
|
||||
@tailrec
|
||||
def add(state: Decryptor, data: ByteString): Decryptor = {
|
||||
if (data.isEmpty) state
|
||||
else state match {
|
||||
case Decryptor(key, nonce, buffer, None, _) =>
|
||||
val buffer1 = buffer ++ data
|
||||
if (buffer1.length >= 20) {
|
||||
val plaintext = AeadChacha20Poly1305.decrypt(key, Protocol.writeUInt64(nonce), buffer1.take(4), Array.emptyByteArray, buffer1.drop(4).take(16))
|
||||
val length = Protocol.uint32(plaintext.take(4)).toInt
|
||||
val state1 = state.copy(header = Some(Header(length)), nonce = nonce + 1, buffer = ByteString.empty)
|
||||
add(state1, buffer1.drop(20))
|
||||
}
|
||||
else state.copy(buffer = buffer1)
|
||||
case Decryptor(key, nonce, buffer, Some(header), bodies) =>
|
||||
val buffer1 = buffer ++ data
|
||||
if (buffer1.length >= (header.length + 16)) {
|
||||
val plaintext = AeadChacha20Poly1305.decrypt(key, Protocol.writeUInt64(nonce), buffer1.take(header.length), Array.emptyByteArray, buffer1.drop(header.length).take(16))
|
||||
val state1 = state.copy(nonce = nonce + 1, header = None, bodies = bodies :+ plaintext, buffer = ByteString.empty)
|
||||
add(state1, buffer1.drop(header.length + 16))
|
||||
} else state.copy(buffer = buffer1)
|
||||
}
|
||||
}
|
||||
|
||||
def add(state: Decryptor, data: BinaryData): Decryptor = add(state, ByteString.fromArray(data))
|
||||
}
|
|
@ -3,12 +3,13 @@ package fr.acinq.eclair.io
|
|||
import javax.crypto.Cipher
|
||||
|
||||
import akka.actor._
|
||||
import akka.io.Tcp.{Register, Received, Write}
|
||||
import akka.io.Tcp.{Received, Register, Write}
|
||||
import akka.util.ByteString
|
||||
import com.trueaccord.scalapb.GeneratedMessage
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.crypto.{Decryptor, Encryptor}
|
||||
import fr.acinq.eclair.crypto.LightningCrypto._
|
||||
import fr.acinq.eclair.io.AuthHandler.Secrets
|
||||
import lightning._
|
||||
|
@ -37,56 +38,6 @@ case object IO_NORMAL extends State
|
|||
|
||||
// @formatter:on
|
||||
|
||||
/**
|
||||
* message format used by lightningd:
|
||||
* header: 20 bytes
|
||||
* - 4 bytes: body length
|
||||
* - 16 bytes: AEAD tag
|
||||
* body: header.length + 16 bytes
|
||||
* - length bytes: encrypted plaintext
|
||||
* - 16 bytes: AEAD tag
|
||||
*
|
||||
* header and body are encrypted with the same key, with a nonce that is incremented each time:
|
||||
* header = encrypt(plaintext.length, key, nonce++)
|
||||
* body = encrypt(plaintext, key, nonce++)
|
||||
*/
|
||||
|
||||
|
||||
object Decryptor {
|
||||
case class Header(length: Int)
|
||||
|
||||
def add(state: Decryptor, data: ByteString) : Decryptor = state match {
|
||||
case Decryptor(key, nonce, buffer, None, None) =>
|
||||
val buffer1 = buffer ++ data
|
||||
if (buffer1.length >= 20) {
|
||||
val plaintext = AeadChacha20Poly1305.decrypt(key, Protocol.writeUInt64(nonce), buffer1.take(4), Array.emptyByteArray, buffer1.drop(4).take(16))
|
||||
val length = Protocol.uint32(plaintext.take(4)).toInt
|
||||
val state1 = state.copy(header = Some(Header(length)), nonce = nonce + 1, buffer = ByteString.empty)
|
||||
add(state1, buffer1.drop(20))
|
||||
}
|
||||
else state.copy(buffer = buffer1)
|
||||
case Decryptor(key, nonce, buffer, Some(header), None) =>
|
||||
val buffer1 = buffer ++ data
|
||||
if (buffer1.length >= header.length) {
|
||||
val plaintext = AeadChacha20Poly1305.decrypt(key, Protocol.writeUInt64(nonce), buffer1.take(header.length), Array.emptyByteArray, buffer1.drop(header.length).take(16))
|
||||
state.copy(nonce = nonce + 1, body = Some(plaintext), buffer = ByteString.empty)
|
||||
} else state.copy(buffer = buffer1)
|
||||
}
|
||||
}
|
||||
|
||||
case class Decryptor(key: BinaryData, nonce: Long, buffer: ByteString = ByteString.empty, header: Option[Decryptor.Header] = None, body: Option[BinaryData] = None)
|
||||
|
||||
object Encryptor {
|
||||
def encrypt(encryptor: Encryptor, data: BinaryData) : (Encryptor, BinaryData) = {
|
||||
val header = Protocol.writeUInt32(data.length)
|
||||
val (ciphertext1, mac1) = AeadChacha20Poly1305.encrypt(encryptor.key, Protocol.writeUInt64(encryptor.nonce), header, Array.emptyByteArray)
|
||||
val (ciphertext2, mac2) = AeadChacha20Poly1305.encrypt(encryptor.key, Protocol.writeUInt64(encryptor.nonce + 1), data, Array.emptyByteArray)
|
||||
(encryptor.copy(nonce = encryptor.nonce + 2), ciphertext1 ++ mac1 ++ ciphertext2 ++ mac2)
|
||||
}
|
||||
}
|
||||
|
||||
case class Encryptor(key: BinaryData, nonce: Long)
|
||||
|
||||
class AuthHandler(them: ActorRef, blockchain: ActorRef, our_params: OurChannelParams) extends LoggingFSM[State, Data] with Stash {
|
||||
|
||||
import AuthHandler._
|
||||
|
@ -145,7 +96,7 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, our_params: OurChannelPa
|
|||
case Event(Received(chunk), s@SessionData(theirpub, decryptor, encryptor)) =>
|
||||
log.debug(s"received chunk=${BinaryData(chunk)}")
|
||||
val decryptor1 = Decryptor.add(decryptor, chunk)
|
||||
decryptor1.body match {
|
||||
decryptor1.bodies.headOption match {
|
||||
case None => stay using s.copy(decryptor = decryptor1)
|
||||
case Some(plaintext) =>
|
||||
val pkt(Auth(auth)) = pkt.parseFrom(plaintext)
|
||||
|
@ -156,7 +107,7 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, our_params: OurChannelPa
|
|||
context.stop(self)
|
||||
}
|
||||
val channel = context.actorOf(Channel.props(self, blockchain, our_params, their_nodeid.toString()), name = "channel")
|
||||
goto(IO_NORMAL) using Normal(channel, s.copy(decryptor = decryptor1.copy(header = None, body = None)))
|
||||
goto(IO_NORMAL) using Normal(channel, s.copy(decryptor = decryptor1.copy(header = None, bodies = decryptor1.bodies.tail)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,13 +115,11 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, our_params: OurChannelPa
|
|||
case Event(Received(chunk), n@Normal(channel, s@SessionData(theirpub, decryptor, encryptor))) =>
|
||||
log.debug(s"received chunk=${BinaryData(chunk)}")
|
||||
val decryptor1 = Decryptor.add(decryptor, chunk)
|
||||
decryptor1.body match {
|
||||
case None => stay using Normal(channel, s.copy(decryptor = decryptor1))
|
||||
case Some(plaintext) =>
|
||||
val packet = pkt.parseFrom(plaintext)
|
||||
self ! packet
|
||||
stay using Normal(channel, s.copy(decryptor = decryptor1.copy(header = None, body = None)))
|
||||
}
|
||||
decryptor1.bodies.map(plaintext => {
|
||||
val packet = pkt.parseFrom(plaintext)
|
||||
self ! packet
|
||||
})
|
||||
stay using Normal(channel, s.copy(decryptor = decryptor1.copy(header = None, bodies = Vector.empty[BinaryData])))
|
||||
|
||||
case Event(packet: pkt, n@Normal(channel, s@SessionData(theirpub, decryptor, encryptor))) =>
|
||||
log.debug(s"receiving $packet")
|
||||
|
|
15
eclair-demo/src/test/resources/scenarii/01-offer1.script
Normal file
15
eclair-demo/src/test/resources/scenarii/01-offer1.script
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Simple test that we can commit an HTLC
|
||||
# Initial state: A=1000000, B=0, both fee rates=10000
|
||||
A:offer 1000000,9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
|
||||
B:recvoffer
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
B:commit
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
checksync
|
||||
echo ***A***
|
||||
A:dump
|
||||
echo ***B***
|
||||
B:dump
|
|
@ -0,0 +1,30 @@
|
|||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs: (1,1000000)
|
||||
Received htlcs:
|
||||
Balance us: 999000000
|
||||
Balance them: 0
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs:
|
||||
Received htlcs: (1,1000000)
|
||||
Balance us: 0
|
||||
Balance them: 999000000
|
||||
Fee rate: 10000
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs:
|
||||
Received htlcs: (1,1000000)
|
||||
Balance us: 0
|
||||
Balance them: 999000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs: (1,1000000)
|
||||
Received htlcs:
|
||||
Balance us: 999000000
|
||||
Balance them: 0
|
||||
Fee rate: 10000
|
18
eclair-demo/src/test/resources/scenarii/02-offer2.script
Normal file
18
eclair-demo/src/test/resources/scenarii/02-offer2.script
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Simple test that we can commit two HTLCs
|
||||
# Initial state: A=1000000, B=0, both fee rates=10000
|
||||
A:offer 1000000,7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c
|
||||
A:offer 2000000,6016bcc377c93692f2fe19fbad47eee6fb8f4cc98c56e935db5edb69806d84f6
|
||||
A:commit
|
||||
B:recvoffer
|
||||
B:recvoffer
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
B:commit
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
checksync
|
||||
echo ***A***
|
||||
A:dump
|
||||
echo ***B***
|
||||
B:dump
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs: (1,1000000) (2,2000000)
|
||||
Received htlcs:
|
||||
Balance us: 997000000
|
||||
Balance them: 0
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs:
|
||||
Received htlcs: (1,1000000) (2,2000000)
|
||||
Balance us: 0
|
||||
Balance them: 997000000
|
||||
Fee rate: 10000
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs:
|
||||
Received htlcs: (1,1000000) (2,2000000)
|
||||
Balance us: 0
|
||||
Balance them: 997000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs: (1,1000000) (2,2000000)
|
||||
Received htlcs:
|
||||
Balance us: 997000000
|
||||
Balance them: 0
|
||||
Fee rate: 10000
|
24
eclair-demo/src/test/resources/scenarii/03-fulfill1.script
Normal file
24
eclair-demo/src/test/resources/scenarii/03-fulfill1.script
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Simple test that we can fulfill (remove) an HTLC after fully settled.
|
||||
A:offer 1000000,b8928207364d445daa42f4ba8be0ef661b8d7190206c01f6ee8281566b3dec0a
|
||||
B:recvoffer
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
B:commit
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
|
||||
B:fulfill 1,60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752
|
||||
B:commit
|
||||
A:recvremove
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
|
||||
checksync
|
||||
echo ***A***
|
||||
A:dump
|
||||
echo ***B***
|
||||
B:dump
|
|
@ -0,0 +1,30 @@
|
|||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 999000000
|
||||
Balance them: 1000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 1000000
|
||||
Balance them: 999000000
|
||||
Fee rate: 10000
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 1000000
|
||||
Balance them: 999000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 999000000
|
||||
Balance them: 1000000
|
||||
Fee rate: 10000
|
|
@ -0,0 +1,22 @@
|
|||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
SIGNED
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
SIGNED
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
SIGNED
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
SIGNED
|
|
@ -0,0 +1,22 @@
|
|||
# Test A can commit twice; queueing two commits onto B's queue.
|
||||
# Initial state: A=1000000, B=0, both fee rates=10000
|
||||
A:offer 1000000,7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c
|
||||
B:recvoffer
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
|
||||
A:offer 2000000,6016bcc377c93692f2fe19fbad47eee6fb8f4cc98c56e935db5edb69806d84f6
|
||||
B:recvoffer
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
|
||||
B:commit
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
checksync
|
||||
echo ***A***
|
||||
A:dump
|
||||
echo ***B***
|
||||
B:dump
|
|
@ -0,0 +1,30 @@
|
|||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs: (1,1000000) (2,2000000)
|
||||
Received htlcs:
|
||||
Balance us: 997000000
|
||||
Balance them: 0
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs: (1,1000000) (2,2000000)
|
||||
Balance us: 0
|
||||
Balance them: 997000000
|
||||
Fee rate: 10000
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs: (1,1000000) (2,2000000)
|
||||
Balance us: 0
|
||||
Balance them: 997000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs: (1,1000000) (2,2000000)
|
||||
Received htlcs:
|
||||
Balance us: 997000000
|
||||
Balance them: 0
|
||||
Fee rate: 10000
|
|
@ -0,0 +1,20 @@
|
|||
# Test committing before receiving previous revocation.
|
||||
A:nocommitwait
|
||||
A:offer 1
|
||||
A:commit
|
||||
A:offer 3
|
||||
A:commit
|
||||
B:recvoffer
|
||||
B:recvcommit
|
||||
B:recvoffer
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
A:recvrevoke
|
||||
B:commit
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
checksync
|
||||
echo ***A***
|
||||
A:dump
|
||||
echo ***B***
|
||||
B:dump
|
|
@ -0,0 +1,22 @@
|
|||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs: 1 3
|
||||
Received htlcs:
|
||||
SIGNED
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs: 1 3
|
||||
SIGNED
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs: 1 3
|
||||
SIGNED
|
||||
REMOTE COMMITS:
|
||||
Commit 1:
|
||||
Offered htlcs: 1 3
|
||||
Received htlcs:
|
||||
SIGNED
|
|
@ -0,0 +1,47 @@
|
|||
# Offers which cross over still get resolved.
|
||||
# Initial state: A=1000000, B=0, both fee rates=10000
|
||||
A:offer 500000000,b8928207364d445daa42f4ba8be0ef661b8d7190206c01f6ee8281566b3dec0a
|
||||
B:recvoffer
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
B:commit
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
|
||||
B:fulfill 1,60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752
|
||||
B:commit
|
||||
A:recvremove
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
|
||||
# Intermediate state: A=500000, B=500000, both fee rates=10000
|
||||
echo ***A (intermediate)***
|
||||
A:dump
|
||||
echo ***B (intermediate)***
|
||||
B:dump
|
||||
|
||||
A:offer 1000000,7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c
|
||||
A:commit
|
||||
B:offer 2000000,6016bcc377c93692f2fe19fbad47eee6fb8f4cc98c56e935db5edb69806d84f6
|
||||
B:recvoffer
|
||||
B:recvcommit
|
||||
B:commit
|
||||
|
||||
A:recvoffer
|
||||
A:recvrevoke
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
|
||||
checksync
|
||||
echo ***A***
|
||||
A:dump
|
||||
echo ***B***
|
||||
B:dump
|
|
@ -0,0 +1,60 @@
|
|||
***A (intermediate)***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 500000000
|
||||
Balance them: 500000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 500000000
|
||||
Balance them: 500000000
|
||||
Fee rate: 10000
|
||||
***B (intermediate)***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 500000000
|
||||
Balance them: 500000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 500000000
|
||||
Balance them: 500000000
|
||||
Fee rate: 10000
|
||||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 3:
|
||||
Offered htlcs: (2,1000000)
|
||||
Received htlcs: (1,2000000)
|
||||
Balance us: 499000000
|
||||
Balance them: 498000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 4:
|
||||
Offered htlcs: (1,2000000)
|
||||
Received htlcs: (2,1000000)
|
||||
Balance us: 498000000
|
||||
Balance them: 499000000
|
||||
Fee rate: 10000
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 4:
|
||||
Offered htlcs: (1,2000000)
|
||||
Received htlcs: (2,1000000)
|
||||
Balance us: 498000000
|
||||
Balance them: 499000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 3:
|
||||
Offered htlcs: (2,1000000)
|
||||
Received htlcs: (1,2000000)
|
||||
Balance us: 499000000
|
||||
Balance them: 498000000
|
||||
Fee rate: 10000
|
|
@ -0,0 +1,52 @@
|
|||
# Commits which cross over still get resolved.
|
||||
# Initial state: A=1000000, B=0, both fee rates=10000
|
||||
A:offer 500000000,b8928207364d445daa42f4ba8be0ef661b8d7190206c01f6ee8281566b3dec0a
|
||||
B:recvoffer
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
B:commit
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
|
||||
B:fulfill 1,60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752
|
||||
B:commit
|
||||
A:recvremove
|
||||
A:recvcommit
|
||||
B:recvrevoke
|
||||
A:commit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
|
||||
# Intermediate state: A=500000, B=500000, both fee rates=10000
|
||||
echo ***A (intermediate)***
|
||||
A:dump
|
||||
echo ***B (intermediate)***
|
||||
B:dump
|
||||
|
||||
A:offer 1000000,7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c
|
||||
B:offer 2000000,6016bcc377c93692f2fe19fbad47eee6fb8f4cc98c56e935db5edb69806d84f6
|
||||
A:commit
|
||||
B:commit
|
||||
|
||||
A:recvoffer
|
||||
B:recvoffer
|
||||
A:recvcommit
|
||||
B:recvcommit
|
||||
|
||||
A:recvrevoke
|
||||
B:recvrevoke
|
||||
|
||||
# They've got to come into sync eventually!
|
||||
A:commit
|
||||
B:commit
|
||||
A:recvcommit
|
||||
B:recvcommit
|
||||
A:recvrevoke
|
||||
B:recvrevoke
|
||||
|
||||
checksync
|
||||
echo ***A***
|
||||
A:dump
|
||||
echo ***B***
|
||||
B:dump
|
|
@ -0,0 +1,60 @@
|
|||
***A (intermediate)***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 500000000
|
||||
Balance them: 500000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 500000000
|
||||
Balance them: 500000000
|
||||
Fee rate: 10000
|
||||
***B (intermediate)***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 500000000
|
||||
Balance them: 500000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs:
|
||||
Received htlcs:
|
||||
Balance us: 500000000
|
||||
Balance them: 500000000
|
||||
Fee rate: 10000
|
||||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 4:
|
||||
Offered htlcs: (2,1000000)
|
||||
Received htlcs: (1,2000000)
|
||||
Balance us: 499000000
|
||||
Balance them: 498000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 4:
|
||||
Offered htlcs: (1,2000000)
|
||||
Received htlcs: (2,1000000)
|
||||
Balance us: 498000000
|
||||
Balance them: 499000000
|
||||
Fee rate: 10000
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 4:
|
||||
Offered htlcs: (1,2000000)
|
||||
Received htlcs: (2,1000000)
|
||||
Balance us: 498000000
|
||||
Balance them: 499000000
|
||||
Fee rate: 10000
|
||||
REMOTE COMMITS:
|
||||
Commit 4:
|
||||
Offered htlcs: (2,1000000)
|
||||
Received htlcs: (1,2000000)
|
||||
Balance us: 499000000
|
||||
Balance them: 498000000
|
||||
Fee rate: 10000
|
|
@ -0,0 +1,22 @@
|
|||
***A***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs: 1
|
||||
Received htlcs: 2
|
||||
SIGNED
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs: 2
|
||||
Received htlcs: 1
|
||||
SIGNED
|
||||
***B***
|
||||
LOCAL COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs: 2
|
||||
Received htlcs: 1
|
||||
SIGNED
|
||||
REMOTE COMMITS:
|
||||
Commit 2:
|
||||
Offered htlcs: 1
|
||||
Received htlcs: 2
|
||||
SIGNED
|
|
@ -14,7 +14,7 @@ class ClaimReceivedHtlcSpec extends FunSuite {
|
|||
val (_, finalKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA")
|
||||
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
|
||||
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
|
||||
val R = "this is Alice's R".getBytes("UTF-8")
|
||||
val R = Crypto.sha256("this is Alice's R".getBytes("UTF-8"))
|
||||
val Rhash = Crypto.sha256(R)
|
||||
val H = Crypto.hash160(R)
|
||||
val revokeCommit = "Alice foo".getBytes("UTF-8")
|
||||
|
@ -26,7 +26,7 @@ class ClaimReceivedHtlcSpec extends FunSuite {
|
|||
val (_, finalKey) = Base58Check.decode("cQLk5fMydgVwJjygt9ta8GcUU4GXLumNiXJCQviibs2LE5vyMXey")
|
||||
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
|
||||
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
|
||||
val R: BinaryData = "this is Bob's R".getBytes("UTF-8")
|
||||
val R: BinaryData = Crypto.sha256("this is Bob's R".getBytes("UTF-8"))
|
||||
val Rhash: BinaryData = Crypto.sha256(R)
|
||||
val H: BinaryData = Crypto.hash160(R)
|
||||
val revokeCommit: BinaryData = Crypto.sha256("Alice revocation R".getBytes("UTF-8"))
|
||||
|
|
|
@ -2,7 +2,7 @@ package fr.acinq.eclair
|
|||
|
||||
import fr.acinq.bitcoin.Crypto._
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.channel.ChannelState
|
||||
import fr.acinq.eclair.channel.{ChannelState, CommitmentSpec}
|
||||
import fr.acinq.eclair.channel.Scripts._
|
||||
import lightning._
|
||||
import lightning.locktime.Locktime.Blocks
|
||||
|
@ -81,24 +81,18 @@ class ProtocolSpec extends FunSuite {
|
|||
initialFeeRate = 1)
|
||||
|
||||
// we assume that Alice knows Bob's H
|
||||
val openAnchor = open_anchor(anchor.hash, anchorOutputIndex, 1000*1000, signature.defaultInstance) // commit sig will be computed later
|
||||
val channelState = ChannelState.initialFunding(1000, ours.initialFeeRate) // initialFunding(ours, theirs, openAnchor, fee = 0)
|
||||
val tx = makeCommitTx(ours.finalKey, theirs.finalKey, theirs.delay, openAnchor.txid, openAnchor.outputIndex, Bob.H, channelState)
|
||||
val openAnchor = open_anchor(anchor.hash, anchorOutputIndex, 1000*1000)
|
||||
val spec = CommitmentSpec(Set(), ours.initialFeeRate, 1000 *1000, 0, 1000 *1000, 0)
|
||||
val tx = makeCommitTx(ours.finalKey, theirs.finalKey, theirs.delay, openAnchor.txid, openAnchor.outputIndex, Bob.H, spec)
|
||||
val redeemScript = multiSig2of2(Alice.commitPubKey, Bob.commitPubKey)
|
||||
val sigA: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount.toLong, 1, Alice.commitKey)
|
||||
//val sigA: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, Alice.commitKey)
|
||||
val openAnchor1 = openAnchor.copy(commitSig = sigA)
|
||||
|
||||
// now Bob receives open anchor and wants to check that Alice's commit sig is valid
|
||||
// Bob can sign too and check that he can spend the anchox tx
|
||||
// now Bob receives open anchor, creates Alice's commit tx and sends backs its signature.
|
||||
// this first commit tx sends all the funds to Alice and nothing to Bob
|
||||
val sigB: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount.toLong, 1, Bob.commitKey)
|
||||
//val sigB = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, Bob.commitKey)
|
||||
val witness = witness2of2(openAnchor1.commitSig, sigB, Alice.commitPubKey, Bob.commitPubKey)
|
||||
val witness = witness2of2(sigA, sigB, Alice.commitPubKey, Bob.commitPubKey)
|
||||
val commitTx = tx.copy(witness = Seq(witness))
|
||||
Transaction.correctlySpends(commitTx, Seq(anchor), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
// or Bob can just check that Alice's sig is valid
|
||||
val hash = Transaction.hashForSigning(commitTx, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount.toLong, signatureVersion = 1)
|
||||
assert(Crypto.verifySignature(hash, Crypto.decodeSignature(sigA.dropRight(1)), Alice.commitPubKey))
|
||||
|
||||
// how do we spend our commit tx ?
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import fr.acinq.bitcoin.Crypto
|
||||
import fr.acinq.eclair._
|
||||
import lightning._
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class HelpersSpec extends FunSuite {
|
||||
test("add, fulfill and fail htlcs") {
|
||||
val spec = CommitmentSpec(Set(), 1000, 2000 * 1000, 0, 2000 * 1000, 0)
|
||||
val R1: sha256_hash = Crypto.sha256("foo".getBytes())
|
||||
val H1: sha256_hash = Crypto.sha256(R1)
|
||||
val R2: sha256_hash = Crypto.sha256("bar".getBytes())
|
||||
val H2: sha256_hash = Crypto.sha256(R2)
|
||||
|
||||
val ours1 = update_add_htlc(1, 1000, H1, locktime.defaultInstance, routing.defaultInstance)
|
||||
val spec1 = Helpers.reduce(spec, ours1 :: Nil, Nil)
|
||||
assert(spec1.htlcs.size == 1 && spec1.htlcs.head.id == 1 && spec1.htlcs.head.rHash == H1)
|
||||
assert(spec1.amount_us_msat == spec.amount_us_msat - ours1.amountMsat)
|
||||
assert(spec1.amount_them_msat == spec.amount_them_msat)
|
||||
assert(spec1.totalFunds == spec.totalFunds)
|
||||
|
||||
val theirs1 = update_fulfill_htlc(ours1.id, R1)
|
||||
val spec2 = Helpers.reduce(spec1, ours1 :: Nil, theirs1 :: Nil)
|
||||
assert(spec2.htlcs.isEmpty && spec2.amount_them_msat == 1000 && spec2.totalFunds == spec.totalFunds)
|
||||
|
||||
val theirs2 = update_add_htlc(2, 1000, H2, locktime.defaultInstance, routing.defaultInstance)
|
||||
val spec3 = Helpers.reduce(spec2, ours1 :: Nil, theirs1 :: theirs2 :: Nil)
|
||||
assert(spec3.htlcs.size == 1)
|
||||
assert(spec3.amount_us_msat == spec2.amount_us_msat)
|
||||
assert(spec3.amount_them_msat == spec2.amount_them_msat - theirs2.amountMsat)
|
||||
assert(spec3.totalFunds == spec.totalFunds)
|
||||
|
||||
val ours2 = update_fail_htlc(theirs2.id, fail_reason.defaultInstance)
|
||||
val spec4 = Helpers.reduce(spec3, ours1 :: ours2 :: Nil, theirs1 :: theirs2 :: Nil)
|
||||
assert(spec4 == spec2)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import java.io.{File, FileInputStream}
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.testkit.{TestActorRef, TestFSMRef, TestKit}
|
||||
import fr.acinq.eclair.blockchain.PollingWatcher
|
||||
import fr.acinq.eclair.channel.TestConstants.{Alice, Bob}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
import org.scalatest.{BeforeAndAfterAll, Ignore, Matchers, fixture}
|
||||
|
||||
import scala.io.Source
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Created by PM on 30/05/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class NewRustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fixture.FunSuiteLike with BeforeAndAfterAll {
|
||||
|
||||
type FixtureParam = Tuple2[List[String], List[String]]
|
||||
|
||||
override def withFixture(test: OneArgTest) = {
|
||||
val pipe: TestActorRef[SynchronizationPipe] = TestActorRef[SynchronizationPipe]
|
||||
val blockchainA = TestActorRef(new PollingWatcher(new TestBitcoinClient()))
|
||||
val blockchainB = TestActorRef(new PollingWatcher(new TestBitcoinClient()))
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainA, Alice.channelParams, "B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainB, Bob.channelParams, "A"))
|
||||
pipe !(alice, bob)
|
||||
within(30 seconds) {
|
||||
awaitCond(alice.stateName == NORMAL)
|
||||
awaitCond(bob.stateName == NORMAL)
|
||||
}
|
||||
pipe ! new File(getClass.getResource(s"/scenarii/${test.name}.script").getFile)
|
||||
awaitCond(pipe.underlying.isTerminated, 30 seconds)
|
||||
val ref = Source.fromFile(getClass.getResource(s"/scenarii/${test.name}.script.expected").getFile).getLines().filterNot(_.startsWith("#")).toList
|
||||
val res = Source.fromFile(new File("result.txt")).getLines().filterNot(_.startsWith("#")).toList
|
||||
test((ref, res))
|
||||
}
|
||||
|
||||
override def afterAll {
|
||||
TestKit.shutdownActorSystem(system)
|
||||
}
|
||||
|
||||
test("01-offer1") { case (ref, res) => assert(ref === res)}
|
||||
test("02-offer2") { case (ref, res) => assert(ref === res)}
|
||||
test("03-fulfill1") { case (ref, res) => assert(ref === res)}
|
||||
test("04-two-commits-onedir") { case (ref, res) => assert(ref === res)}
|
||||
// test("05-two-commits-in-flight") { case (ref, res) => assert(ref === res)} DOES NOT PASS : cannot send two commit in a row (without having first revocation)
|
||||
test("10-offers-crossover") { case (ref, res) => assert(ref === res)}
|
||||
test("11-commits-crossover") { case (ref, res) => assert(ref === res)}
|
||||
/*test("13-fee") { case (ref, res) => assert(ref === res)}
|
||||
test("14-fee-twice") { case (ref, res) => assert(ref === res)}
|
||||
test("15-fee-twice-back-to-back") { case (ref, res) => assert(ref === res)}*/
|
||||
|
||||
}
|
|
@ -4,18 +4,21 @@ import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
|
|||
import akka.testkit.TestProbe
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||
import fr.acinq.eclair._
|
||||
import lightning.{locktime, update_add_htlc, update_fulfill_htlc}
|
||||
import lightning.{locktime, update_add_htlc}
|
||||
import lightning.locktime.Locktime.Blocks
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.Ignore
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Created by PM on 26/04/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class NominalChannelSpec extends BaseChannelTestClass {
|
||||
|
||||
test("open channel and reach normal state") { case (alice, bob, pipe) =>
|
||||
|
||||
val monitorA = TestProbe()
|
||||
alice ! SubscribeTransitionCallBack(monitorA.ref)
|
||||
val CurrentState(_, OPEN_WAIT_FOR_OPEN_WITHANCHOR) = monitorA.expectMsgClass(classOf[CurrentState[_]])
|
||||
|
@ -34,7 +37,7 @@ class NominalChannelSpec extends BaseChannelTestClass {
|
|||
val Transition(_, OPEN_WAIT_FOR_COMMIT_SIG, OPEN_WAITING_OURANCHOR) = monitorA.expectMsgClass(classOf[Transition[_]])
|
||||
val Transition(_, OPEN_WAIT_FOR_ANCHOR, OPEN_WAITING_THEIRANCHOR) = monitorB.expectMsgClass(classOf[Transition[_]])
|
||||
|
||||
val Transition(_, OPEN_WAITING_OURANCHOR, OPEN_WAIT_FOR_COMPLETE_OURANCHOR) = monitorA.expectMsgClass(classOf[Transition[_]])
|
||||
val Transition(_, OPEN_WAITING_OURANCHOR, OPEN_WAIT_FOR_COMPLETE_OURANCHOR) = monitorA.expectMsgClass(5 seconds, classOf[Transition[_]])
|
||||
val Transition(_, OPEN_WAITING_THEIRANCHOR, OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR) = monitorB.expectMsgClass(classOf[Transition[_]])
|
||||
|
||||
val Transition(_, OPEN_WAIT_FOR_COMPLETE_OURANCHOR, NORMAL) = monitorA.expectMsgClass(classOf[Transition[_]])
|
||||
|
@ -54,57 +57,67 @@ class NominalChannelSpec extends BaseChannelTestClass {
|
|||
val H = Crypto.sha256(R)
|
||||
|
||||
alice ! CMD_ADD_HTLC(60000000, H, locktime(Blocks(4)))
|
||||
Thread.sleep(100)
|
||||
|
||||
alice.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, List(Change2(OUT, _, update_add_htlc(_, _, h, _, _))), _, _, _) if h == bin2sha256(H) => {}
|
||||
case d: DATA_NORMAL =>
|
||||
val List(update_add_htlc(_, _, h, _, _)) = d.ourChanges.proposed
|
||||
assert(h == bin2sha256(H))
|
||||
}
|
||||
bob.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, List(Change2(IN, _, update_add_htlc(_, _, h, _, _))), _, _, _) if h == bin2sha256(H) => {}
|
||||
case d: DATA_NORMAL =>
|
||||
val List(update_add_htlc(_, _, h, _, _)) = d.theirChanges.proposed
|
||||
assert(h == bin2sha256(H))
|
||||
}
|
||||
|
||||
alice ! CMD_SIGN
|
||||
Thread.sleep(100)
|
||||
|
||||
/*alice.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, Nil, Commitment(1, _, ChannelState(ChannelOneSide(_, _, Nil), ChannelOneSide(_, _, List(Htlc(1, _, _, _, _, _)))), _), _) => {}
|
||||
alice.stateData match {
|
||||
case d: DATA_NORMAL =>
|
||||
val htlc = d.theirCommit.spec.htlcs.head
|
||||
assert(htlc.rHash == bin2sha256(H))
|
||||
}
|
||||
bob.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, Nil, Commitment(1, _, ChannelState(ChannelOneSide(_, _, List(Htlc(1, _, _, _, _, _))), ChannelOneSide(_, _, Nil)), _), _) => {}
|
||||
}*/
|
||||
case d: DATA_NORMAL =>
|
||||
val htlc = d.ourCommit.spec.htlcs.head
|
||||
assert(htlc.rHash == bin2sha256(H))
|
||||
}
|
||||
|
||||
bob ! CMD_FULFILL_HTLC(1, R)
|
||||
|
||||
/*alice.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, List(Change2(IN, _, update_fulfill_htlc(1, r))), _, _) if r == bin2sha256(R) => {}
|
||||
}
|
||||
bob.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, List(Change2(OUT, _, update_fulfill_htlc(1, r))), _, _) if r == bin2sha256(R) => {}
|
||||
}*/
|
||||
|
||||
bob ! CMD_SIGN
|
||||
alice ! CMD_SIGN
|
||||
|
||||
/*alice.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, Nil, Commitment(2, _, ChannelState(ChannelOneSide(_, _, Nil), ChannelOneSide(_, _, Nil)), _), _) => {}
|
||||
Thread.sleep(200)
|
||||
|
||||
alice.stateData match {
|
||||
case d: DATA_NORMAL =>
|
||||
assert(d.ourCommit.spec.htlcs.isEmpty)
|
||||
assert(d.ourCommit.spec.amount_us_msat == d.ourCommit.spec.initial_amount_us_msat - 60000000)
|
||||
assert(d.ourCommit.spec.amount_them_msat == d.ourCommit.spec.initial_amount_them_msat + 60000000)
|
||||
}
|
||||
bob.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, Nil, Commitment(2, _, ChannelState(ChannelOneSide(_, _, Nil), ChannelOneSide(_, _, Nil)), _), _) => {}
|
||||
}*/
|
||||
|
||||
case d: DATA_NORMAL =>
|
||||
assert(d.ourCommit.spec.htlcs.isEmpty)
|
||||
assert(d.ourCommit.spec.amount_us_msat == d.ourCommit.spec.initial_amount_us_msat + 60000000)
|
||||
assert(d.ourCommit.spec.amount_them_msat == d.ourCommit.spec.initial_amount_them_msat - 60000000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("close channel starting with no HTLC") { case (alice, bob, pipe) =>
|
||||
pipe !(alice, bob) // this starts the communication between alice and bob
|
||||
|
||||
within(30 seconds) {
|
||||
|
||||
awaitCond(alice.stateName == NORMAL)
|
||||
awaitCond(bob.stateName == NORMAL)
|
||||
|
||||
alice ! CMD_CLOSE(None)
|
||||
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
awaitCond(bob.stateName == CLOSING)
|
||||
}
|
||||
// pipe !(alice, bob) // this starts the communication between alice and bob
|
||||
//
|
||||
// within(30 seconds) {
|
||||
//
|
||||
// awaitCond(alice.stateName == NORMAL)
|
||||
// awaitCond(bob.stateName == NORMAL)
|
||||
//
|
||||
// alice ! CMD_CLOSE(None)
|
||||
//
|
||||
// awaitCond(alice.stateName == CLOSING)
|
||||
// awaitCond(bob.stateName == CLOSING)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import java.io.{BufferedWriter, File, FileWriter}
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Stash}
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.eclair._
|
||||
import lightning.locktime
|
||||
import lightning.locktime.Locktime.Blocks
|
||||
|
||||
/**
|
||||
* Created by PM on 30/05/2016.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Handles a bi-directional path between 2 actors
|
||||
used to avoid the chicken-and-egg problem of:
|
||||
a = new Channel(b)
|
||||
b = new Channel(a)
|
||||
This pipe executes scripted tests and allows for
|
||||
fine grained control on the order of messages
|
||||
*/
|
||||
class SynchronizationPipe() extends Actor with ActorLogging with Stash {
|
||||
|
||||
val offer = "(.):offer ([0-9]+),([0-9a-f]+)".r
|
||||
val fulfill = "(.):fulfill ([0-9]+),([0-9a-f]+)".r
|
||||
val commit = "(.):commit".r
|
||||
val feechange = "(.):feechange".r
|
||||
val recv = "(.):recv.*".r
|
||||
val nocommitwait = "(.):nocommitwait.*".r
|
||||
val echo = "echo (.*)".r
|
||||
val dump = "(.):dump".r
|
||||
|
||||
val fout = new BufferedWriter(new FileWriter("result.txt"))
|
||||
|
||||
def exec(script: List[String], a: ActorRef, b: ActorRef): Unit = {
|
||||
def resolve(x: String) = if (x == "A") a else b
|
||||
script match {
|
||||
case offer(x, amount, rhash) :: rest =>
|
||||
resolve(x) ! CMD_ADD_HTLC(amount.toInt, BinaryData(rhash), locktime(Blocks(4)), Seq())
|
||||
exec(rest, a, b)
|
||||
case fulfill(x, id, r) :: rest =>
|
||||
resolve(x) ! CMD_FULFILL_HTLC(id.toInt, BinaryData(r))
|
||||
exec(rest, a, b)
|
||||
case commit(x) :: rest =>
|
||||
resolve(x) ! CMD_SIGN
|
||||
exec(rest, a, b)
|
||||
/*case feechange(x) :: rest =>
|
||||
resolve(x) ! CmdFeeChange()
|
||||
exec(rest, a, b)*/
|
||||
case recv(x) :: rest =>
|
||||
context.become(wait(a, b, script))
|
||||
case nocommitwait(x) :: rest =>
|
||||
log.warning("ignoring nocommitwait")
|
||||
exec(rest, a, b)
|
||||
case "checksync" :: rest =>
|
||||
log.warning("ignoring checksync")
|
||||
exec(rest, a, b)
|
||||
case echo(s) :: rest =>
|
||||
fout.write(s)
|
||||
fout.newLine()
|
||||
exec(rest, a, b)
|
||||
case dump(x) :: rest =>
|
||||
resolve(x) ! CMD_GETSTATEDATA
|
||||
context.become(wait(a, b, script))
|
||||
case "" :: rest =>
|
||||
exec(rest, a, b)
|
||||
case List() | Nil =>
|
||||
log.info(s"done")
|
||||
fout.close()
|
||||
context stop self
|
||||
}
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case (a: ActorRef, b: ActorRef) =>
|
||||
unstashAll()
|
||||
context become passthrough(a, b)
|
||||
case msg => stash()
|
||||
}
|
||||
|
||||
def passthrough(a: ActorRef, b: ActorRef): Receive = {
|
||||
case file: File =>
|
||||
import scala.io.Source
|
||||
val script = Source.fromFile(file).getLines().filterNot(_.startsWith("#")).toList
|
||||
exec(script, a, b)
|
||||
case msg if sender() == a => b forward msg
|
||||
case msg if sender() == b => a forward msg
|
||||
case msg => log.error("" + msg)
|
||||
}
|
||||
|
||||
def wait(a: ActorRef, b: ActorRef, script: List[String]): Receive = {
|
||||
case msg if sender() == a && script.head.startsWith("B:recv") =>
|
||||
b forward msg
|
||||
unstashAll()
|
||||
exec(script.drop(1), a, b)
|
||||
case msg if sender() == b && script.head.startsWith("A:recv") =>
|
||||
a forward msg
|
||||
unstashAll()
|
||||
exec(script.drop(1), a, b)
|
||||
case d: DATA_NORMAL if script.head.endsWith(":dump") =>
|
||||
def rtrim(s: String) = s.replaceAll("\\s+$", "")
|
||||
val l = List(
|
||||
"LOCAL COMMITS:",
|
||||
s" Commit ${d.ourCommit.index}:",
|
||||
s" Offered htlcs: ${d.ourCommit.spec.htlcs.filter(_.direction == OUT).map(h => (h.id, h.amountMsat)).mkString(" ")}",
|
||||
s" Received htlcs: ${d.ourCommit.spec.htlcs.filter(_.direction == IN).map(h => (h.id, h.amountMsat)).mkString(" ")}",
|
||||
s" Balance us: ${d.ourCommit.spec.amount_us_msat}",
|
||||
s" Balance them: ${d.ourCommit.spec.amount_them_msat}",
|
||||
s" Fee rate: ${d.ourCommit.spec.feeRate}",
|
||||
"REMOTE COMMITS:",
|
||||
s" Commit ${d.theirCommit.index}:",
|
||||
s" Offered htlcs: ${d.theirCommit.spec.htlcs.filter(_.direction == OUT).map(h => (h.id, h.amountMsat)).mkString(" ")}",
|
||||
s" Received htlcs: ${d.theirCommit.spec.htlcs.filter(_.direction == IN).map(h => (h.id, h.amountMsat)).mkString(" ")}",
|
||||
s" Balance us: ${d.theirCommit.spec.amount_us_msat}",
|
||||
s" Balance them: ${d.theirCommit.spec.amount_them_msat}",
|
||||
s" Fee rate: ${d.theirCommit.spec.feeRate}")
|
||||
.foreach(s => {
|
||||
fout.write(rtrim(s))
|
||||
fout.newLine()
|
||||
})
|
||||
unstashAll()
|
||||
exec(script.drop(1), a, b)
|
||||
case other =>
|
||||
stash()
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ import scala.concurrent.{ExecutionContext, Future}
|
|||
*/
|
||||
class TestBitcoinClient extends ExtendedBitcoinClient(new BitcoinJsonRPCClient("", "", "", 0)) {
|
||||
|
||||
client.client.close()
|
||||
|
||||
override def makeAnchorTx(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Long)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
|
||||
val anchorTx = Transaction(version = 1,
|
||||
txIn = Seq.empty[TxIn],
|
||||
|
@ -21,7 +23,6 @@ class TestBitcoinClient extends ExtendedBitcoinClient(new BitcoinJsonRPCClient("
|
|||
|
||||
override def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[String] = Future.successful(tx.txid.toString())
|
||||
|
||||
|
||||
override def getTxConfirmations(txId: String)(implicit ec: ExecutionContext): Future[Option[Int]] = Future.successful(Some(10))
|
||||
|
||||
override def isUnspent(txId: String, outputIndex: Int)(implicit ec: ExecutionContext): Future[Boolean] = ???
|
||||
|
|
|
@ -9,20 +9,20 @@ import lightning.locktime.Locktime.Blocks
|
|||
* Created by PM on 26/04/2016.
|
||||
*/
|
||||
object TestConstants {
|
||||
val anchorAmount = 100100000L
|
||||
val anchorAmount = 1000000L
|
||||
|
||||
// Alice is funder, Bob is not
|
||||
|
||||
object Alice {
|
||||
val (Base58.Prefix.SecretKeyTestnet, commitPrivKey) = Base58Check.decode("cQPmcNr6pwBQPyGfab3SksE9nTCtx9ism9T4dkS9dETNU2KKtJHk")
|
||||
val (Base58.Prefix.SecretKeyTestnet, finalPrivKey) = Base58Check.decode("cUrAtLtV7GGddqdkhUxnbZVDWGJBTducpPoon3eKp9Vnr1zxs6BG")
|
||||
val channelParams = OurChannelParams(locktime(Blocks(10)), commitPrivKey, finalPrivKey, 1, 100000, "alice-seed".getBytes(), Some(anchorAmount))
|
||||
val channelParams = OurChannelParams(locktime(Blocks(10)), commitPrivKey, finalPrivKey, 1, 10000, "alice-seed".getBytes(), Some(anchorAmount))
|
||||
}
|
||||
|
||||
object Bob {
|
||||
val (Base58.Prefix.SecretKeyTestnet, commitPrivKey) = Base58Check.decode("cSUwLtdZ2tht9ZmHhdQue48pfe7tY2GT2TGWJDtjoZgo6FHrubGk")
|
||||
val (Base58.Prefix.SecretKeyTestnet, finalPrivKey) = Base58Check.decode("cPR7ZgXpUaDPA3GwGceMDS5pfnSm955yvks3yELf3wMJwegsdGTg")
|
||||
val channelParams = OurChannelParams(locktime(Blocks(10)), commitPrivKey, finalPrivKey, 2, 100000, "bob-seed".getBytes(), None)
|
||||
val channelParams = OurChannelParams(locktime(Blocks(10)), commitPrivKey, finalPrivKey, 2, 10000, "bob-seed".getBytes(), None)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
package fr.acinq.eclair.crypto
|
||||
|
||||
import java.util.concurrent.{CountDownLatch, TimeUnit}
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props}
|
||||
import akka.util.ByteString
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Hash}
|
||||
import fr.acinq.eclair.channel.Pipe
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
object EncryptorSpec {
|
||||
val random = new scala.util.Random()
|
||||
|
||||
class Ping(pong: ActorRef, sendingKey: BinaryData, receivinKey: BinaryData, latch: CountDownLatch) extends Actor with ActorLogging {
|
||||
context.system.scheduler.schedule(50 + random.nextInt(50) milliseconds, 25 milliseconds, self, 'send)
|
||||
|
||||
def receive = running(Encryptor(sendingKey, 0), Decryptor(receivinKey, 0))
|
||||
|
||||
def running(encryptor: Encryptor, decryptor: Decryptor) : Receive = {
|
||||
case chunk: BinaryData =>
|
||||
val decryptor1 = Decryptor.add(decryptor, ByteString.fromArray(chunk))
|
||||
decryptor1.bodies.map(_ => latch.countDown())
|
||||
context become running(encryptor, decryptor1.copy(bodies = Vector.empty[BinaryData]))
|
||||
case 'send =>
|
||||
val message: BinaryData = s"it is now ${System.currentTimeMillis()}".getBytes("UTF-8")
|
||||
val (encryptor1, ciphertext) = Encryptor.encrypt(encryptor, message)
|
||||
pong ! ciphertext
|
||||
context become running(encryptor1, decryptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class EncryptorSpec extends FunSuite {
|
||||
import EncryptorSpec._
|
||||
|
||||
test("encryption/description tests") {
|
||||
val key: BinaryData = Crypto.sha256(Hash.Zeroes)
|
||||
var enc = Encryptor(key, 0)
|
||||
var dec = Decryptor(key, 0)
|
||||
|
||||
def run(e: Encryptor, d: Decryptor): (Encryptor, Decryptor) = {
|
||||
val plaintext = new Array[Byte](30)
|
||||
random.nextBytes(plaintext)
|
||||
val (e1, c) = Encryptor.encrypt(e, plaintext)
|
||||
val d1 = Decryptor.add(d, ByteString.fromArray(c))
|
||||
assert(java.util.Arrays.equals(plaintext, d1.bodies.head))
|
||||
(e1, d1.copy(bodies = Vector()))
|
||||
}
|
||||
|
||||
for (i <- 0 until 10) {
|
||||
val (e, d) = run(enc, dec)
|
||||
enc = e
|
||||
dec = d
|
||||
}
|
||||
}
|
||||
|
||||
test("decryption of several messages in multiples chunk") {
|
||||
val key: BinaryData = Crypto.sha256(Hash.Zeroes)
|
||||
val enc = Encryptor(key, 0)
|
||||
var dec = Decryptor(key, 0)
|
||||
|
||||
val plaintext1: BinaryData = new Array[Byte](200)
|
||||
random.nextBytes(plaintext1)
|
||||
val plaintext2: BinaryData = new Array[Byte](300)
|
||||
random.nextBytes(plaintext2)
|
||||
|
||||
val (enc1, ciphertext1) = Encryptor.encrypt(enc, plaintext1)
|
||||
val (enc2, ciphertext2) = Encryptor.encrypt(enc1, plaintext2)
|
||||
|
||||
val chunks = (ciphertext1 ++ ciphertext2).grouped(35).toList
|
||||
chunks.map(chunk => dec = Decryptor.add(dec, chunk))
|
||||
|
||||
assert(dec.header == None && dec.bodies == Vector(plaintext1, plaintext2))
|
||||
}
|
||||
|
||||
test("decryption of several messages in a single chunk") {
|
||||
val key: BinaryData = Crypto.sha256(Hash.Zeroes)
|
||||
val random = new scala.util.Random()
|
||||
val enc = Encryptor(key, 0)
|
||||
val dec = Decryptor(key, 0)
|
||||
|
||||
val plaintext1: BinaryData = new Array[Byte](200)
|
||||
random.nextBytes(plaintext1)
|
||||
val plaintext2: BinaryData = new Array[Byte](300)
|
||||
random.nextBytes(plaintext2)
|
||||
|
||||
val (enc1, ciphertext1) = Encryptor.encrypt(enc, plaintext1)
|
||||
val (enc2, ciphertext2) = Encryptor.encrypt(enc1, plaintext2)
|
||||
|
||||
val dec1 = Decryptor.add(dec, ciphertext1 ++ ciphertext2)
|
||||
|
||||
assert(dec1.header == None && dec1.bodies == Vector(plaintext1, plaintext2))
|
||||
}
|
||||
|
||||
test("concurrency tests") {
|
||||
val system = ActorSystem("mySystem")
|
||||
|
||||
val k1: BinaryData = Crypto.sha256(Hash.One)
|
||||
val k0: BinaryData = Crypto.sha256(Hash.Zeroes)
|
||||
val pipe = system.actorOf(Props[Pipe], "pipe")
|
||||
val latch = new CountDownLatch(100)
|
||||
val a = system.actorOf(Props(classOf[Ping], pipe, k0, k1, latch), "a")
|
||||
Thread.sleep(7)
|
||||
val b = system.actorOf(Props(classOf[Ping], pipe, k1, k0, latch), "b")
|
||||
pipe ! (a, b)
|
||||
|
||||
latch.await(5, TimeUnit.SECONDS)
|
||||
system.shutdown()
|
||||
}
|
||||
}
|
|
@ -120,9 +120,6 @@ message open_anchor {
|
|||
required uint32 output_index = 2;
|
||||
// Amount of anchor output.
|
||||
required uint64 amount = 3;
|
||||
|
||||
// Signature for your initial commitment tx.
|
||||
required signature commit_sig = 4;
|
||||
}
|
||||
|
||||
// Reply: signature for your initial commitment tx
|
||||
|
@ -176,7 +173,7 @@ message update_fail_htlc {
|
|||
required fail_reason reason = 2;
|
||||
}
|
||||
|
||||
// Commit all the staged HTLCs.
|
||||
// Commit all the staged changes.
|
||||
message update_commit {
|
||||
// Signature for your new commitment tx.
|
||||
required signature sig = 1;
|
||||
|
|
6
pom.xml
6
pom.xml
|
@ -41,9 +41,9 @@
|
|||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<scala.version>2.11.7</scala.version>
|
||||
<scala.version>2.11.8</scala.version>
|
||||
<scala.version.short>2.11</scala.version.short>
|
||||
<akka.version>2.4.4</akka.version>
|
||||
<akka.version>2.4.6</akka.version>
|
||||
<bitcoinlib.version>0.9.6-RC1</bitcoinlib.version>
|
||||
<acinqtools.version>1.2</acinqtools.version>
|
||||
</properties>
|
||||
|
@ -199,7 +199,7 @@
|
|||
<dependency>
|
||||
<groupId>org.scalatest</groupId>
|
||||
<artifactId>scalatest_${scala.version.short}</artifactId>
|
||||
<version>2.2.5</version>
|
||||
<version>2.2.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
Loading…
Add table
Reference in a new issue