1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 14:22:39 +01:00

fixed state calculation, added logs, fixed closing sequence

This commit is contained in:
pm47 2015-08-27 22:50:03 +02:00
parent 0dee6320a7
commit a7ef498289
3 changed files with 108 additions and 37 deletions

View file

@ -14,7 +14,7 @@ object Boot extends App {
val system = ActorSystem()
val anchorInput = AnchorInput(100000000L, OutPoint("bff676222800bf24bbf32f5a0fc83c4ddd5782f6ba23b4b352b3a6ddf0fe0b95", 0), SignData("76a914763518984abc129ab5825b8c14b6f4c8fa16f9c988ac", Base58Check.decode("cRqkWfx32NcfJjkutHqLrfbuY8HhTeCNLa7NLBpWy4bpk7bEYQYg")._2))
val anchorInput = AnchorInput(1000L, OutPoint("bff676222800bf24bbf32f5a0fc83c4ddd5782f6ba23b4b352b3a6ddf0fe0b95", 0), SignData("76a914763518984abc129ab5825b8c14b6f4c8fa16f9c988ac", Base58Check.decode("cRqkWfx32NcfJjkutHqLrfbuY8HhTeCNLa7NLBpWy4bpk7bEYQYg")._2))
val alice = system.actorOf(Props(new Node(Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), Hex.decode("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), Some(anchorInput))), name = "alice")
val bob = system.actorOf(Props(new Node(Hex.decode("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), Hex.decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"), None)), name = "bob")
@ -35,14 +35,19 @@ object Boot extends App {
val r = sha256_hash(7, 7, 7, 7)
val rHash = Crypto.sha256(r)
alice ! CMD_SEND_HTLC_UPDATE(100000, rHash, locktime(Blocks(4)))
alice ! CMD_SEND_HTLC_UPDATE(100, rHash, locktime(Blocks(4)))
Thread.sleep(1000)
bob ! CMD_SEND_HTLC_COMPLETE(r)
Thread.sleep(1000)
alice ! CMD_SEND_UPDATE
bob ! CMD_SEND_UPDATE(200)
Thread.sleep(1000)
alice ! CMD_CLOSE(0)
Thread.sleep(1000)
alice ! BITCOIN_CLOSE_DONE
bob ! BITCOIN_CLOSE_DONE
}

View file

@ -45,10 +45,10 @@ case object CLOSED extends State
case object INPUT_NONE
sealed trait BlockchainEvent
final case class TxConfirmed(blockId: sha256_hash, confirmations: Int) extends BlockchainEvent
final case class BITCOIN_CLOSE_DONE()
case object BITCOIN_CLOSE_DONE
sealed trait Command
case object CMD_SEND_UPDATE extends Command
final case class CMD_SEND_UPDATE(delta: Long) extends Command
final case class CMD_SEND_HTLC_UPDATE(amount: Int, rHash: sha256_hash, expiry: locktime) extends Command
final case class CMD_SEND_HTLC_COMPLETE(r: sha256_hash) extends Command
final case class CMD_CLOSE(fee: Long) extends Command
@ -212,16 +212,17 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
}
when(NORMAL) {
case Event(CMD_SEND_UPDATE, DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val (newState, delta) = previousState.fold
case Event(CMD_SEND_UPDATE(delta), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val newState = previousState.update(delta)
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
them ! update(ourRevocationHash, delta)
goto(WAIT_FOR_UPDATE_ACCEPT) using DATA_WAIT_FOR_UPDATE_ACCEPT(ourParams, theirParams, p, UpdateProposal(newState, ourRevocationHashPreimage))
case Event(update(theirRevocationHash, theirDelta), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val (newState, delta) = previousState.fold
assert(delta == -theirDelta, s"delta mismatch, ours=$delta theirs=$theirDelta")
// ourDelta = -theirDelta
// TODO : we should also make sure that funds are sufficient
val newState = previousState.update(-theirDelta)
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// we build our side of the new commitment tx
@ -236,19 +237,17 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
val htlc = update_add_htlc(ourRevocationHash, amount, rHash, expiry)
val newState = previousState.copy(them = previousState.them.copy(htlcs = previousState.them.htlcs :+ htlc))
val newState = previousState.htlc_send(htlc)
them ! htlc
goto(WAIT_FOR_HTLC_ACCEPT) using DATA_WAIT_FOR_HTLC_ACCEPT(ourParams, theirParams, p, UpdateProposal(newState, ourRevocationHashPreimage))
case Event(m@update_add_htlc(theirRevocationHash, amount, rHash, expiry), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
case Event(htlc@update_add_htlc(theirRevocationHash, amount, rHash, expiry), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
// TODO : we should check that we can reach the next node (which can be the final payee) using routing info that will be provided in the msg
// them ! update_decline_htlc(CannotRoute)
// TODO : we should also make sure that funds are sufficient
// them ! update_decline_htlc(InsufficientFunds)
// if one of the above is false, then reply with update_decline_htlc
// the receiver of this message will have its balance increased : it is the receiver of the htlc
// TODO : can the amount be negative ?
val newState = previousState.copy(us = previousState.us.copy(htlcs = previousState.us.htlcs :+ m))
val newState = previousState.htlc_receive(htlc)
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// we build our side of the new commitment tx
@ -262,8 +261,8 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
goto(WAIT_FOR_UPDATE_SIG) using DATA_WAIT_FOR_UPDATE_SIG(ourParams, theirParams, p, CommitmentTx(ourCommitTx, newState, ourRevocationHashPreimage, theirRevocationHash))
case Event(CMD_SEND_HTLC_COMPLETE(r), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val htlc = previousState.us.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).getOrElse(throw new RuntimeException(s"could not find corresponding htlc (r=$r)"))
val newState = previousState.copy(them = previousState.them.copy(htlcs = previousState.them.htlcs.filterNot(_ == htlc)))
// we paid upstream in exchange for r, now lets gets paid
val newState = previousState.htlc_complete(r)
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// Complete your HTLC: I have the R value, pay me!
@ -271,10 +270,8 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
goto(WAIT_FOR_HTLC_ACCEPT) using DATA_WAIT_FOR_HTLC_ACCEPT(ourParams, theirParams, p, UpdateProposal(newState, ourRevocationHashPreimage))
case Event(update_complete_htlc(theirRevocationHash, r), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
// they are requesting that we pay them in the channel
val htlc = previousState.them.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).getOrElse(throw new RuntimeException(s"could not find corresponding htlc (r=$r)"))
// TODO : following is probably wrong
val newState = previousState.copy(them = previousState.them.copy(pay = previousState.them.pay + htlc.amount), us = previousState.us.copy(pay = previousState.us.pay, htlcs = previousState.us.htlcs.filterNot(_ == htlc)))
// they are requesting that we pay them in the channel in exchange for r
val newState = previousState.htlc_complete(r)
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// we build our side of the new commitment tx
@ -292,13 +289,22 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
goto(WAIT_FOR_CLOSE_COMPLETE)
case Event(close_channel(theirSig, closeFee), DATA_NORMAL(ourParams, theirParams, CommitmentTx(commitmentTx, state, _, _))) =>
val finalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state)
val ourSigForThem = bin2signature(Transaction.signInput(finalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
// TODO : make sure the final tx is now spendable (should we spend it right now ?)
// the only difference between their final tx and ours is the order of the outputs, because state is symmetric
val theirFinalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirFinalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
val ourFinalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state)
val ourSig = bin2signature(Transaction.signInput(ourFinalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
val signedFinaltx = ourFinalTx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
log.debug(s"final tx : ${Hex.toHexString(Transaction.write(signedFinaltx))}")
// ok now we can broadcast the final tx if we want
them ! close_channel_complete(ourSigForThem)
goto(WAIT_FOR_CLOSE_ACK)
}
onTransition {
case _ -> NORMAL => log.debug(s"my state is now ${nextStateData.asInstanceOf[DATA_NORMAL].commitmentTx.state.prettyString()}")
}
when(WAIT_FOR_UPDATE_ACCEPT) {
case Event(update_accept(theirSig, theirRevocationHash), DATA_WAIT_FOR_UPDATE_ACCEPT(ourParams, theirParams, previous, UpdateProposal(newState, ourRevocationHashPreimage))) =>
// counterparty replied with the signature for the new commitment tx
@ -347,12 +353,22 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
when(WAIT_FOR_CLOSE_COMPLETE) {
case Event(close_channel(theirSig, closeFee), DATA_NORMAL(ourParams, theirParams, CommitmentTx(commitmentTx, state, _, _))) =>
val finalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state)
val ourSigForThem = bin2signature(Transaction.signInput(finalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
//the only difference between their final tx and ours is the order of the outputs, because state is symmetric
val theirFinalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirFinalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
val ourFinalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state)
val ourSig = bin2signature(Transaction.signInput(ourFinalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
val signedFinaltx = ourFinalTx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
log.debug(s"final tx : ${Hex.toHexString(Transaction.write(signedFinaltx))}")
// ok now we can broadcast the final tx if we want
them ! close_channel_ack()
goto(CLOSE_WAIT_CLOSE)
case Event(close_channel_complete(theirSig), _) =>
case Event(close_channel_complete(theirSig), DATA_NORMAL(ourParams, theirParams, CommitmentTx(commitmentTx, state, _, _))) =>
val finalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state)
val ourSig = Transaction.signInput(finalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey))
val signedFinaltx = finalTx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
log.debug(s"final tx : ${Hex.toHexString(Transaction.write(signedFinaltx))}")
// ok now we can broadcast the final tx if we want
them ! close_channel_ack()
goto(CLOSE_WAIT_CLOSE)
@ -368,4 +384,8 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
goto(CLOSED)
}
when(CLOSED) {
case _ => stay
}
}

View file

@ -14,9 +14,28 @@ import scala.annotation.tailrec
package lightning {
/*
BOB POV !!
ChannelState(ALICE: ChannelOneSide(pay = 500, fee: Long, htlcs: Seq()), BOB: ChannelOneSide(pay = 500, fee: Long, htlcs = Seq()))
bob received update_add_htlc(100)
OPTION 1 ChannelState(ALICE: ChannelOneSide(pay = 500, fee: Long, htlcs: Seq(update_add_htlc(-100))), BOB: ChannelOneSide(pay = 500, fee: Long, htlcs = Seq(update_add_htlc(100))))
OPTION 2 ChannelState(ALICE: ChannelOneSide(pay = 400, fee: Long, htlcs: Seq()), BOB: ChannelOneSide(pay = 500, fee: Long, htlcs = Seq(update_add_htlc(100))))
bob received r from CAROL and sends a update_complete_htlc to ALICE
ChannelState(ALICE: ChannelOneSide(pay = 400, fee: Long, htlcs: Seq()), BOB: ChannelOneSide(pay = 600, fee: Long, htlcs = Seq()))
*/
case class ChannelOneSide(pay: Long, fee: Long, htlcs: Seq[update_add_htlc])
case class ChannelState(them: ChannelOneSide, us: ChannelOneSide) {
case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
/**
* 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
@ -24,14 +43,41 @@ case class ChannelState(them: ChannelOneSide, us: ChannelOneSide) {
def reverse: ChannelState = this.copy(them = us, us = them)
/**
* Remove all pending htlc and compute a delta as seen by us
* @return the new channel state and the delta
* Update the channel
* @param delta as seen by us, if delta > 0 we increase our balance
* @return the update channel state
*/
def fold: (ChannelState, Long) = {
val delta = us.htlcs.map(_.amount).sum - them.htlcs.map(_.amount).sum
val newState = this.copy(them = them.copy(pay = them.pay - delta, htlcs = Seq()), us = us.copy(pay = us.pay + delta, htlcs = Seq()))
(newState, delta)
def update(delta: Long): ChannelState = this.copy(them = them.copy(pay = them.pay - delta), us = us.copy(pay = us.pay + delta))
/**
* Update our state when we send an htlc
* @param htlc
* @return
*/
def htlc_receive(htlc: update_add_htlc): ChannelState = this.copy(them = them.copy(pay = them.pay - htlc.amount), us = us.copy(htlcs = us.htlcs :+ htlc))
/**
* Update our state when we receive an htlc
* @param htlc
* @return
*/
def htlc_send(htlc: update_add_htlc): ChannelState = this.copy(them = them.copy(htlcs = them.htlcs :+ htlc), us = us.copy(pay = us.pay - htlc.amount))
def htlc_complete(r: sha256_hash): ChannelState = {
if (us.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).isDefined) {
// TODO not optimized
val htlc = us.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).get
// we were the receiver of this htlc
this.copy(us = us.copy(pay = us.pay + htlc.amount, htlcs = us.htlcs.filterNot(_ == htlc)))
} else if (them.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).isDefined) {
// TODO not optimized
val htlc = them.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).get
// we were the sender of this htlc
this.copy(them = them.copy(pay = them.pay + htlc.amount, htlcs = them.htlcs.filterNot(_ == htlc)))
} else throw new RuntimeException(s"could not find corresponding htlc (r=$r)")
}
def prettyString(): String = s"pay_us=${us.pay} htlcs_us=${us.htlcs.map(_.amount).sum} pay_them=${them.pay} htlcs_them=${them.htlcs.map(_.amount).sum} total=${us.pay + us.htlcs.map(_.amount).sum + them.pay + them.htlcs.map(_.amount).sum}"
}
}
@ -89,7 +135,7 @@ package object lightning {
implicit def array2signature(in: Array[Byte]): signature = bin2signature(in)
implicit def signature2bin(in: signature) : BinaryData = {
implicit def signature2bin(in: signature): BinaryData = {
val rbos = new ByteArrayOutputStream()
Protocol.writeUInt64(in.r1, rbos)
Protocol.writeUInt64(in.r2, rbos)
@ -110,14 +156,14 @@ package object lightning {
case locktime(Seconds(seconds)) => seconds
}
def isLess(a: Seq[Byte], b: Seq[Byte]) : Boolean = {
def isLess(a: Seq[Byte], b: Seq[Byte]): Boolean = {
val a1 = a.dropWhile(_ == 0)
val b1 = b.dropWhile(_ == 0)
if (a1.length != b1.length)
a1.length <= b1.length
else {
@tailrec
def isLess0(x: List[Byte], y: List[Byte]) : Boolean = (x, y) match {
def isLess0(x: List[Byte], y: List[Byte]): Boolean = (x, y) match {
case (Nil, Nil) => false
case (hx :: tx, hy :: ty) if (hx == hy) => isLess0(tx, ty)
case (hx :: _, hy :: _) => ((hx & 0xff) < (hy & 0xff))
@ -126,7 +172,7 @@ package object lightning {
}
}
def multiSig2of2(pubkey1: BinaryData, pubkey2: BinaryData) : BinaryData = if (isLess(pubkey1, pubkey2))
def multiSig2of2(pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = if (isLess(pubkey1, pubkey2))
BinaryData(Script.createMultiSigMofN(2, Seq(pubkey1, pubkey2)))
else
BinaryData(Script.createMultiSigMofN(2, Seq(pubkey2, pubkey1)))