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:
parent
0dee6320a7
commit
a7ef498289
3 changed files with 108 additions and 37 deletions
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
|
|
Loading…
Add table
Reference in a new issue