1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-15 04:11:33 +01:00

Merge branch 'master' into android

This commit is contained in:
sstone 2019-10-31 17:23:05 +01:00
commit ba2321688a
35 changed files with 373 additions and 235 deletions

View file

@ -32,12 +32,12 @@ object Features {
val CHANNEL_RANGE_QUERIES_BIT_MANDATORY = 6 val CHANNEL_RANGE_QUERIES_BIT_MANDATORY = 6
val CHANNEL_RANGE_QUERIES_BIT_OPTIONAL = 7 val CHANNEL_RANGE_QUERIES_BIT_OPTIONAL = 7
val VARIABLE_LENGTH_ONION_MANDATORY = 8
val VARIABLE_LENGTH_ONION_OPTIONAL = 9
val CHANNEL_RANGE_QUERIES_EX_BIT_MANDATORY = 10 val CHANNEL_RANGE_QUERIES_EX_BIT_MANDATORY = 10
val CHANNEL_RANGE_QUERIES_EX_BIT_OPTIONAL = 11 val CHANNEL_RANGE_QUERIES_EX_BIT_OPTIONAL = 11
val VARIABLE_LENGTH_ONION_MANDATORY = 8
val VARIABLE_LENGTH_ONION_OPTIONAL = 9
// Note that BitVector indexes from left to right whereas the specification indexes from right to left. // Note that BitVector indexes from left to right whereas the specification indexes from right to left.
// This is why we have to reverse the bits to check if a feature is set. // This is why we have to reverse the bits to check if a feature is set.

View file

@ -57,8 +57,8 @@ object JsonSerializers {
implicit val remoteCommitsReadWriter: ReadWriter[RemoteCommit] = macroRW implicit val remoteCommitsReadWriter: ReadWriter[RemoteCommit] = macroRW
implicit val commitSgReadWriter: ReadWriter[CommitSig] = macroRW implicit val commitSgReadWriter: ReadWriter[CommitSig] = macroRW
implicit val waitingForRevocationReadWriter: ReadWriter[WaitingForRevocation] = macroRW implicit val waitingForRevocationReadWriter: ReadWriter[WaitingForRevocation] = macroRW
implicit val localOriginReadWriter: ReadWriter[fr.acinq.eclair.payment.Local] = macroRW implicit val localOriginReadWriter: ReadWriter[fr.acinq.eclair.payment.Origin.Local] = macroRW
implicit val relayedOriginReadWriter: ReadWriter[fr.acinq.eclair.payment.Relayed] = macroRW implicit val relayedOriginReadWriter: ReadWriter[fr.acinq.eclair.payment.Origin.Relayed] = macroRW
implicit val paymentOriginReadWriter: ReadWriter[Origin] = ReadWriter.merge(localOriginReadWriter, relayedOriginReadWriter) implicit val paymentOriginReadWriter: ReadWriter[Origin] = ReadWriter.merge(localOriginReadWriter, relayedOriginReadWriter)
implicit val remoteChangesReadWriter: ReadWriter[RemoteChanges] = macroRW implicit val remoteChangesReadWriter: ReadWriter[RemoteChanges] = macroRW

View file

@ -129,6 +129,15 @@ object NodeParams {
} }
def makeNodeParams(config: Config, keyManager: KeyManager, torAddress_opt: Option[NodeAddress], database: Databases, blockCount: AtomicLong, feeEstimator: FeeEstimator): NodeParams = { def makeNodeParams(config: Config, keyManager: KeyManager, torAddress_opt: Option[NodeAddress], database: Databases, blockCount: AtomicLong, feeEstimator: FeeEstimator): NodeParams = {
// check configuration for keys that have been renamed in v0.3.2
val deprecatedKeyPaths = Map(
"default-feerates" -> "on-chain-fees.default-feerates",
"max-feerate-mismatch" -> "on-chain-fees.max-feerate-mismatch",
"update-fee_min-diff-ratio" -> "on-chain-fees.update-fee-min-diff-ratio"
)
deprecatedKeyPaths.foreach {
case (old, new_) => require(!config.hasPath(old), s"configuration key '$old' has been replaced by '$new_'")
}
val chain = config.getString("chain") val chain = config.getString("chain")
val chainHash = makeChainHash(chain) val chainHash = makeChainHash(chain)

View file

@ -38,8 +38,8 @@ import scala.util.{Failure, Success, Try}
/** /**
* Created by PM on 20/08/2015. * Created by PM on 20/08/2015.
*/ */
object Channel { object Channel {
def props(nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: PublicKey, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, origin_opt: Option[ActorRef]) = Props(new Channel(nodeParams, wallet, remoteNodeId, blockchain, router, relayer, origin_opt)) def props(nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: PublicKey, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, origin_opt: Option[ActorRef]) = Props(new Channel(nodeParams, wallet, remoteNodeId, blockchain, router, relayer, origin_opt))
@ -1325,7 +1325,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
// for our outgoing payments, let's send events if we know that they will settle on chain // for our outgoing payments, let's send events if we know that they will settle on chain
Closing Closing
.onchainOutgoingHtlcs(d.commitments.localCommit, d.commitments.remoteCommit, d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit), tx) .onchainOutgoingHtlcs(d.commitments.localCommit, d.commitments.remoteCommit, d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit), tx)
.map(add => (add, d.commitments.originChannels.get(add.id).collect { case Local(id, _) => id })) // we resolve the payment id if this was a local payment .map(add => (add, d.commitments.originChannels.get(add.id).collect { case Origin.Local(id, _) => id })) // we resolve the payment id if this was a local payment
.collect { case (add, Some(id)) => context.system.eventStream.publish(PaymentSettlingOnChain(id, amount = add.amountMsat, add.paymentHash)) } .collect { case (add, Some(id)) => context.system.eventStream.publish(PaymentSettlingOnChain(id, amount = add.amountMsat, add.paymentHash)) }
// we update the channel data // we update the channel data
val d1 = d.copy(localCommitPublished = localCommitPublished1, remoteCommitPublished = remoteCommitPublished1, nextRemoteCommitPublished = nextRemoteCommitPublished1, futureRemoteCommitPublished = futureRemoteCommitPublished1, revokedCommitPublished = revokedCommitPublished1) val d1 = d.copy(localCommitPublished = localCommitPublished1, remoteCommitPublished = remoteCommitPublished1, nextRemoteCommitPublished = nextRemoteCommitPublished1, futureRemoteCommitPublished = futureRemoteCommitPublished1, revokedCommitPublished = revokedCommitPublished1)
@ -1721,8 +1721,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
*/ */
/** /**
* This function is used to return feedback to user at channel opening * This function is used to return feedback to user at channel opening
*/ */
def replyToUser(message: Either[Channel.ChannelError, String]): Unit = { def replyToUser(message: Either[Channel.ChannelError, String]): Unit = {
val m = message match { val m = message match {
case Left(LocalError(t)) => Status.Failure(t) case Left(LocalError(t)) => Status.Failure(t)
@ -1746,17 +1746,18 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} }
/** /**
* This is used to check for the commitment fees when the channel is not operational but we have something at stake * This is used to check for the commitment fees when the channel is not operational but we have something at stake
* @param c the new feerates *
* @param d the channel commtiments * @param c the new feerates
* @return * @param d the channel commtiments
*/ * @return
*/
def handleOfflineFeerate(c: CurrentFeerates, d: HasCommitments) = { def handleOfflineFeerate(c: CurrentFeerates, d: HasCommitments) = {
val networkFeeratePerKw = c.feeratesPerKw.feePerBlock(target = nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget) val networkFeeratePerKw = c.feeratesPerKw.feePerBlock(target = nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget)
val currentFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw val currentFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw
// if the fees are too high we risk to not be able to confirm our current commitment // if the fees are too high we risk to not be able to confirm our current commitment
if(networkFeeratePerKw > currentFeeratePerKw && Helpers.isFeeDiffTooHigh(currentFeeratePerKw, networkFeeratePerKw, nodeParams.onChainFeeConf.maxFeerateMismatch)){ if (networkFeeratePerKw > currentFeeratePerKw && Helpers.isFeeDiffTooHigh(currentFeeratePerKw, networkFeeratePerKw, nodeParams.onChainFeeConf.maxFeerateMismatch)) {
if(nodeParams.onChainFeeConf.closeOnOfflineMismatch) { if (nodeParams.onChainFeeConf.closeOnOfflineMismatch) {
log.warning(s"closing OFFLINE ${d.channelId} due to fee mismatch: currentFeeratePerKw=$currentFeeratePerKw networkFeeratePerKw=$networkFeeratePerKw") log.warning(s"closing OFFLINE ${d.channelId} due to fee mismatch: currentFeeratePerKw=$currentFeeratePerKw networkFeeratePerKw=$networkFeeratePerKw")
handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = currentFeeratePerKw, remoteFeeratePerKw = networkFeeratePerKw), d, Some(c)) handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = currentFeeratePerKw, remoteFeeratePerKw = networkFeeratePerKw), d, Some(c))
} else { } else {
@ -1783,9 +1784,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} }
/** /**
* When we are funder, we use this function to detect when our funding tx has been double-spent (by another transaction * When we are funder, we use this function to detect when our funding tx has been double-spent (by another transaction
* that we made for some reason). If the funding tx has been double spent we can forget about the channel. * that we made for some reason). If the funding tx has been double spent we can forget about the channel.
*/ */
def checkDoubleSpent(fundingTx: Transaction): Unit = { def checkDoubleSpent(fundingTx: Transaction): Unit = {
log.debug(s"checking status of funding tx txid=${fundingTx.txid}") log.debug(s"checking status of funding tx txid=${fundingTx.txid}")
wallet.doubleSpent(fundingTx).onComplete { wallet.doubleSpent(fundingTx).onComplete {
@ -1981,8 +1982,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} }
/** /**
* This helper method will publish txes only if they haven't yet reached minDepth * This helper method will publish txes only if they haven't yet reached minDepth
*/ */
def publishIfNeeded(txes: Iterable[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32]): Unit = { def publishIfNeeded(txes: Iterable[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32]): Unit = {
val (skip, process) = txes.partition(Closing.inputsAlreadySpent(_, irrevocablySpent)) val (skip, process) = txes.partition(Closing.inputsAlreadySpent(_, irrevocablySpent))
process.foreach { tx => process.foreach { tx =>
@ -1993,8 +1994,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} }
/** /**
* This helper method will watch txes only if they haven't yet reached minDepth * This helper method will watch txes only if they haven't yet reached minDepth
*/ */
def watchConfirmedIfNeeded(txes: Iterable[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32]): Unit = { def watchConfirmedIfNeeded(txes: Iterable[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32]): Unit = {
val (skip, process) = txes.partition(Closing.inputsAlreadySpent(_, irrevocablySpent)) val (skip, process) = txes.partition(Closing.inputsAlreadySpent(_, irrevocablySpent))
process.foreach(tx => blockchain ! WatchConfirmed(self, tx, nodeParams.minDepthBlocks, BITCOIN_TX_CONFIRMED(tx))) process.foreach(tx => blockchain ! WatchConfirmed(self, tx, nodeParams.minDepthBlocks, BITCOIN_TX_CONFIRMED(tx)))
@ -2002,8 +2003,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} }
/** /**
* This helper method will watch txes only if the utxo they spend hasn't already been irrevocably spent * This helper method will watch txes only if the utxo they spend hasn't already been irrevocably spent
*/ */
def watchSpentIfNeeded(parentTx: Transaction, txes: Iterable[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32]): Unit = { def watchSpentIfNeeded(parentTx: Transaction, txes: Iterable[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32]): Unit = {
val (skip, process) = txes.partition(Closing.inputsAlreadySpent(_, irrevocablySpent)) val (skip, process) = txes.partition(Closing.inputsAlreadySpent(_, irrevocablySpent))
process.foreach(tx => blockchain ! WatchSpent(self, parentTx, tx.txIn.head.outPoint.index.toInt, BITCOIN_OUTPUT_SPENT)) process.foreach(tx => blockchain ! WatchSpent(self, parentTx, tx.txIn.head.outPoint.index.toInt, BITCOIN_OUTPUT_SPENT))
@ -2227,8 +2228,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} }
/** /**
* This helper function runs the state's default event handlers, and react to exceptions by unilaterally closing the channel * This helper function runs the state's default event handlers, and react to exceptions by unilaterally closing the channel
*/ */
def handleExceptions(s: StateFunction): StateFunction = { def handleExceptions(s: StateFunction): StateFunction = {
case event if s.isDefinedAt(event) => case event if s.isDefinedAt(event) =>
try { try {
@ -2239,8 +2240,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} }
def origin(c: CMD_ADD_HTLC): Origin = c.upstream match { def origin(c: CMD_ADD_HTLC): Origin = c.upstream match {
case Left(id) => Local(id, Some(sender)) // we were the origin of the payment case Upstream.Local(id) => Origin.Local(id, Some(sender)) // we were the origin of the payment
case Right(u) => Relayed(u.channelId, u.id, u.amountMsat, c.amount) // this is a relayed payment case Upstream.Relayed(u) => Origin.Relayed(u.channelId, u.id, u.amountMsat, c.amount) // this is a relayed payment to an outgoing channel
} }
def feePaid(fee: Satoshi, tx: Transaction, desc: String, channelId: ByteVector32): Unit = { def feePaid(fee: Satoshi, tx: Transaction, desc: String, channelId: ByteVector32): Unit = {
@ -2276,9 +2277,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} }
/** /**
* This method allows performing actions during the transition, e.g. after a call to [[MyState.storing]]. This is * This method allows performing actions during the transition, e.g. after a call to [[MyState.storing]]. This is
* particularly useful to publish transactions only after we are sure that the state has been persisted. * particularly useful to publish transactions only after we are sure that the state has been persisted.
*/ */
def calling(f: => Unit): FSM.State[fr.acinq.eclair.channel.State, Data] = { def calling(f: => Unit): FSM.State[fr.acinq.eclair.channel.State, Data] = {
f f
state state

View file

@ -27,7 +27,6 @@ import fr.acinq.eclair.wire.{AcceptChannel, ChannelAnnouncement, ChannelReestabl
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, UInt64} import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, UInt64}
import scodec.bits.{BitVector, ByteVector} import scodec.bits.{BitVector, ByteVector}
/** /**
* Created by PM on 20/05/2016. * Created by PM on 20/05/2016.
*/ */
@ -105,8 +104,16 @@ case class BITCOIN_PARENT_TX_CONFIRMED(childTx: Transaction) extends BitcoinEven
"Y8888P" "Y88888P" 888 888 888 888 d88P 888 888 Y888 8888888P" "Y8888P" "Y8888P" "Y88888P" 888 888 888 888 d88P 888 888 Y888 8888888P" "Y8888P"
*/ */
sealed trait Upstream
object Upstream {
/** Our node is the origin of the payment. */
final case class Local(id: UUID) extends Upstream
/** Our node forwarded a single incoming HTLC to an outgoing channel. */
final case class Relayed(add: UpdateAddHtlc) extends Upstream
}
sealed trait Command sealed trait Command
final case class CMD_ADD_HTLC(amount: MilliSatoshi, paymentHash: ByteVector32, cltvExpiry: CltvExpiry, onion: OnionRoutingPacket, upstream: Either[UUID, UpdateAddHtlc], commit: Boolean = false, previousFailures: Seq[AddHtlcFailed] = Seq.empty) extends Command final case class CMD_ADD_HTLC(amount: MilliSatoshi, paymentHash: ByteVector32, cltvExpiry: CltvExpiry, onion: OnionRoutingPacket, upstream: Upstream, commit: Boolean = false, previousFailures: Seq[AddHtlcFailed] = Seq.empty) extends Command
final case class CMD_FULFILL_HTLC(id: Long, r: ByteVector32, commit: Boolean = false) extends Command final case class CMD_FULFILL_HTLC(id: Long, r: ByteVector32, commit: Boolean = false) extends Command
final case class CMD_FAIL_HTLC(id: Long, reason: Either[ByteVector, FailureMessage], commit: Boolean = false) extends Command final case class CMD_FAIL_HTLC(id: Long, reason: Either[ByteVector, FailureMessage], commit: Boolean = false) extends Command
final case class CMD_FAIL_MALFORMED_HTLC(id: Long, onionHash: ByteVector32, failureCode: Int, commit: Boolean = false) extends Command final case class CMD_FAIL_MALFORMED_HTLC(id: Long, onionHash: ByteVector32, failureCode: Int, commit: Boolean = false) extends Command

View file

@ -188,7 +188,8 @@ object Commitments {
} }
} }
val htlcValueInFlight = outgoingHtlcs.map(_.add.amountMsat).sum // NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since outgoingHtlcs is a Set).
val htlcValueInFlight = outgoingHtlcs.toSeq.map(_.add.amountMsat).sum
if (commitments1.remoteParams.maxHtlcValueInFlightMsat < htlcValueInFlight) { if (commitments1.remoteParams.maxHtlcValueInFlightMsat < htlcValueInFlight) {
// TODO: this should be a specific UPDATE error // TODO: this should be a specific UPDATE error
return Left(HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.remoteParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight)) return Left(HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.remoteParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight))
@ -229,7 +230,8 @@ object Commitments {
} }
} }
val htlcValueInFlight = incomingHtlcs.map(_.add.amountMsat).sum // NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since incomingHtlcs is a Set).
val htlcValueInFlight = incomingHtlcs.toSeq.map(_.add.amountMsat).sum
if (commitments1.localParams.maxHtlcValueInFlightMsat < htlcValueInFlight) { if (commitments1.localParams.maxHtlcValueInFlightMsat < htlcValueInFlight) {
throw HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.localParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight) throw HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.localParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight)
} }

View file

@ -46,7 +46,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging {
} }
def migration23(statement: Statement) = { def migration23(statement: Statement) = {
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channel_errors (channel_id BLOB NOT NULL, node_id BLOB NOT NULL, error_name STRING NOT NULL, error_message STRING NOT NULL, is_fatal INTEGER NOT NULL, timestamp INTEGER NOT NULL)") statement.executeUpdate("CREATE TABLE IF NOT EXISTS channel_errors (channel_id BLOB NOT NULL, node_id BLOB NOT NULL, error_name TEXT NOT NULL, error_message TEXT NOT NULL, is_fatal INTEGER NOT NULL, timestamp INTEGER NOT NULL)")
statement.executeUpdate("CREATE INDEX IF NOT EXISTS channel_errors_timestamp_idx ON channel_errors(timestamp)") statement.executeUpdate("CREATE INDEX IF NOT EXISTS channel_errors_timestamp_idx ON channel_errors(timestamp)")
} }
@ -66,8 +66,8 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging {
statement.executeUpdate("CREATE TABLE IF NOT EXISTS received (amount_msat INTEGER NOT NULL, payment_hash BLOB NOT NULL, from_channel_id BLOB NOT NULL, timestamp INTEGER NOT NULL)") statement.executeUpdate("CREATE TABLE IF NOT EXISTS received (amount_msat INTEGER NOT NULL, payment_hash BLOB NOT NULL, from_channel_id BLOB NOT NULL, timestamp INTEGER NOT NULL)")
statement.executeUpdate("CREATE TABLE IF NOT EXISTS relayed (amount_in_msat INTEGER NOT NULL, amount_out_msat INTEGER NOT NULL, payment_hash BLOB NOT NULL, from_channel_id BLOB NOT NULL, to_channel_id BLOB NOT NULL, timestamp INTEGER NOT NULL)") statement.executeUpdate("CREATE TABLE IF NOT EXISTS relayed (amount_in_msat INTEGER NOT NULL, amount_out_msat INTEGER NOT NULL, payment_hash BLOB NOT NULL, from_channel_id BLOB NOT NULL, to_channel_id BLOB NOT NULL, timestamp INTEGER NOT NULL)")
statement.executeUpdate("CREATE TABLE IF NOT EXISTS network_fees (channel_id BLOB NOT NULL, node_id BLOB NOT NULL, tx_id BLOB NOT NULL, fee_sat INTEGER NOT NULL, tx_type TEXT NOT NULL, timestamp INTEGER NOT NULL)") statement.executeUpdate("CREATE TABLE IF NOT EXISTS network_fees (channel_id BLOB NOT NULL, node_id BLOB NOT NULL, tx_id BLOB NOT NULL, fee_sat INTEGER NOT NULL, tx_type TEXT NOT NULL, timestamp INTEGER NOT NULL)")
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channel_events (channel_id BLOB NOT NULL, node_id BLOB NOT NULL, capacity_sat INTEGER NOT NULL, is_funder BOOLEAN NOT NULL, is_private BOOLEAN NOT NULL, event STRING NOT NULL, timestamp INTEGER NOT NULL)") statement.executeUpdate("CREATE TABLE IF NOT EXISTS channel_events (channel_id BLOB NOT NULL, node_id BLOB NOT NULL, capacity_sat INTEGER NOT NULL, is_funder BOOLEAN NOT NULL, is_private BOOLEAN NOT NULL, event TEXT NOT NULL, timestamp INTEGER NOT NULL)")
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channel_errors (channel_id BLOB NOT NULL, node_id BLOB NOT NULL, error_name STRING NOT NULL, error_message STRING NOT NULL, is_fatal INTEGER NOT NULL, timestamp INTEGER NOT NULL)") statement.executeUpdate("CREATE TABLE IF NOT EXISTS channel_errors (channel_id BLOB NOT NULL, node_id BLOB NOT NULL, error_name TEXT NOT NULL, error_message TEXT NOT NULL, is_fatal INTEGER NOT NULL, timestamp INTEGER NOT NULL)")
statement.executeUpdate("CREATE INDEX IF NOT EXISTS balance_updated_idx ON balance_updated(timestamp)") statement.executeUpdate("CREATE INDEX IF NOT EXISTS balance_updated_idx ON balance_updated(timestamp)")
statement.executeUpdate("CREATE INDEX IF NOT EXISTS sent_timestamp_idx ON sent(timestamp)") statement.executeUpdate("CREATE INDEX IF NOT EXISTS sent_timestamp_idx ON sent(timestamp)")

View file

@ -32,7 +32,6 @@ import scodec.bits.ByteVector
import scala.collection.immutable.SortedMap import scala.collection.immutable.SortedMap
class SqliteNetworkDb(sqlite: Connection, chainHash: ByteVector32) extends NetworkDb with Logging { class SqliteNetworkDb(sqlite: Connection, chainHash: ByteVector32) extends NetworkDb with Logging {
import SqliteUtils._ import SqliteUtils._
import SqliteUtils.ExtendedResultSet._ import SqliteUtils.ExtendedResultSet._

View file

@ -534,17 +534,17 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: A
onTransition { onTransition {
case _ -> CONNECTED => case _ -> CONNECTED =>
//Metrics.connectedPeers.increment() Metrics.connectedPeers.increment()
context.system.eventStream.publish(PeerConnected(self, remoteNodeId)) context.system.eventStream.publish(PeerConnected(self, remoteNodeId))
case CONNECTED -> DISCONNECTED => case CONNECTED -> DISCONNECTED =>
//Metrics.connectedPeers.decrement() Metrics.connectedPeers.decrement()
context.system.eventStream.publish(PeerDisconnected(self, remoteNodeId)) context.system.eventStream.publish(PeerDisconnected(self, remoteNodeId))
} }
onTermination { onTermination {
case StopEvent(_, CONNECTED, d: ConnectedData) => case StopEvent(_, CONNECTED, d: ConnectedData) =>
// the transition handler won't be fired if we go directly from CONNECTED to closed // the transition handler won't be fired if we go directly from CONNECTED to closed
//Metrics.connectedPeers.decrement() Metrics.connectedPeers.decrement()
context.system.eventStream.publish(PeerDisconnected(self, remoteNodeId)) context.system.eventStream.publish(PeerDisconnected(self, remoteNodeId))
} }
@ -654,9 +654,9 @@ object Peer {
// @formatter:on // @formatter:on
object Metrics { object Metrics {
// val peers = Kamon.rangeSampler("peers.count").withoutTags() val peers = Kamon.rangeSampler("peers.count").withoutTags()
// val connectedPeers = Kamon.rangeSampler("peers.connected.count").withoutTags() val connectedPeers = Kamon.rangeSampler("peers.connected.count").withoutTags()
// val channels = Kamon.rangeSampler("channels.count").withoutTags() val channels = Kamon.rangeSampler("channels.count").withoutTags()
} }
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingAmount: Satoshi): LocalParams = { def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingAmount: Satoshi): LocalParams = {

View file

@ -27,18 +27,17 @@ import fr.acinq.eclair.channel.Helpers.Closing
import fr.acinq.eclair.channel.{HasCommitments, _} import fr.acinq.eclair.channel.{HasCommitments, _}
import fr.acinq.eclair.db.PendingRelayDb import fr.acinq.eclair.db.PendingRelayDb
import fr.acinq.eclair.payment.Relayer.RelayPayload import fr.acinq.eclair.payment.Relayer.RelayPayload
import fr.acinq.eclair.payment.{Relayed, Relayer} import fr.acinq.eclair.payment.{Origin, Relayer}
import fr.acinq.eclair.router.Rebroadcast
import fr.acinq.eclair.transactions.{IN, OUT} import fr.acinq.eclair.transactions.{IN, OUT}
import fr.acinq.eclair.wire.{TemporaryNodeFailure, UpdateAddHtlc} import fr.acinq.eclair.wire.{TemporaryNodeFailure, UpdateAddHtlc}
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.util.Success
/** /**
* Ties network connections to peers. * Ties network connections to peers.
* Created by PM on 14/02/2017. * Created by PM on 14/02/2017.
*/ */
class Switchboard(nodeParams: NodeParams, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet) extends Actor with ActorLogging { class Switchboard(nodeParams: NodeParams, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet) extends Actor with ActorLogging {
import Switchboard._ import Switchboard._
@ -93,7 +92,7 @@ class Switchboard(nodeParams: NodeParams, authenticator: ActorRef, watcher: Acto
case d: Peer.Disconnect => case d: Peer.Disconnect =>
getPeer(d.nodeId) match { getPeer(d.nodeId) match {
case Some(peer) => peer forward d case Some(peer) => peer forward d
case None => sender ! Status.Failure(new RuntimeException("peer not found")) case None => sender ! Status.Failure(new RuntimeException("peer not found"))
} }
case o: Peer.OpenChannel => case o: Peer.OpenChannel =>
@ -112,25 +111,18 @@ class Switchboard(nodeParams: NodeParams, authenticator: ActorRef, watcher: Acto
} }
/** /**
* Retrieves a peer based on its public key. * Retrieves a peer based on its public key.
* *
* NB: Internally akka uses a TreeMap to store the binding, so this lookup is O(log(N)) where N is the number of * NB: Internally akka uses a TreeMap to store the binding, so this lookup is O(log(N)) where N is the number of
* peers. We could make it O(1) by using our own HashMap, but it creates other problems when we need to remove an * peers. We could make it O(1) by using our own HashMap, but it creates other problems when we need to remove an
* existing peer. This seems like a reasonable trade-off because we only make this call once per connection, and N * existing peer. This seems like a reasonable trade-off because we only make this call once per connection, and N
* should never be very big anyway. * should never be very big anyway.
* */
* @param remoteNodeId
* @return
*/
def getPeer(remoteNodeId: PublicKey): Option[ActorRef] = context.child(peerActorName(remoteNodeId)) def getPeer(remoteNodeId: PublicKey): Option[ActorRef] = context.child(peerActorName(remoteNodeId))
/** /**
* * @param previousKnownAddress only to be set if we know for sure that this ip worked in the past
* @param remoteNodeId */
* @param previousKnownAddress only to be set if we know for sure that this ip worked in the past
* @param offlineChannels
* @return
*/
def createOrGetPeer(remoteNodeId: PublicKey, previousKnownAddress: Option[InetSocketAddress], offlineChannels: Set[HasCommitments]) = { def createOrGetPeer(remoteNodeId: PublicKey, previousKnownAddress: Option[InetSocketAddress], offlineChannels: Set[HasCommitments]) = {
getPeer(remoteNodeId) match { getPeer(remoteNodeId) match {
case Some(peer) => peer case Some(peer) => peer
@ -155,14 +147,14 @@ object Switchboard extends Logging {
def peerActorName(remoteNodeId: PublicKey): String = s"peer-$remoteNodeId" def peerActorName(remoteNodeId: PublicKey): String = s"peer-$remoteNodeId"
/** /**
* If we have stopped eclair while it was forwarding HTLCs, it is possible that we are in a state were an incoming HTLC * If we have stopped eclair while it was forwarding HTLCs, it is possible that we are in a state were an incoming HTLC
* was committed by both sides, but we didn't have time to send and/or sign the corresponding HTLC to the downstream node. * was committed by both sides, but we didn't have time to send and/or sign the corresponding HTLC to the downstream node.
* *
* In that case, if we do nothing, the incoming HTLC will eventually expire and we won't lose money, but the channel will * In that case, if we do nothing, the incoming HTLC will eventually expire and we won't lose money, but the channel will
* get closed, which is a major inconvenience. * get closed, which is a major inconvenience.
* *
* This check will detect this and will allow us to fast-fail HTLCs and thus preserve channels. * This check will detect this and will allow us to fast-fail HTLCs and thus preserve channels.
*/ */
def checkBrokenHtlcsLink(channels: Seq[HasCommitments], privateKey: PrivateKey, features: ByteVector): Seq[UpdateAddHtlc] = { def checkBrokenHtlcsLink(channels: Seq[HasCommitments], privateKey: PrivateKey, features: ByteVector): Seq[UpdateAddHtlc] = {
// We are interested in incoming HTLCs, that have been *cross-signed* (otherwise they wouldn't have been relayed). // We are interested in incoming HTLCs, that have been *cross-signed* (otherwise they wouldn't have been relayed).
@ -178,7 +170,7 @@ object Switchboard extends Logging {
// Here we do it differently because we need the origin information. // Here we do it differently because we need the origin information.
val relayed_out = channels val relayed_out = channels
.flatMap(_.commitments.originChannels.values) .flatMap(_.commitments.originChannels.values)
.collect { case r: Relayed => r } .collect { case r: Origin.Relayed => r }
.toSet .toSet
val htlcs_broken = htlcs_in.filterNot(htlc_in => relayed_out.exists(r => r.originChannelId == htlc_in.channelId && r.originHtlcId == htlc_in.id)) val htlcs_broken = htlcs_in.filterNot(htlc_in => relayed_out.exists(r => r.originChannelId == htlc_in.channelId && r.originHtlcId == htlc_in.id))
@ -189,17 +181,17 @@ object Switchboard extends Logging {
} }
/** /**
* We store [[CMD_FULFILL_HTLC]]/[[CMD_FAIL_HTLC]]/[[CMD_FAIL_MALFORMED_HTLC]] * We store [[CMD_FULFILL_HTLC]]/[[CMD_FAIL_HTLC]]/[[CMD_FAIL_MALFORMED_HTLC]]
* in a database (see [[fr.acinq.eclair.payment.CommandBuffer]]) because we * in a database (see [[fr.acinq.eclair.payment.CommandBuffer]]) because we
* don't want to lose preimages, or to forget to fail incoming htlcs, which * don't want to lose preimages, or to forget to fail incoming htlcs, which
* would lead to unwanted channel closings. * would lead to unwanted channel closings.
* *
* Because of the way our watcher works, in a scenario where a downstream * Because of the way our watcher works, in a scenario where a downstream
* channel has gone to the blockchain, it may send several times the same * channel has gone to the blockchain, it may send several times the same
* command, and the upstream channel may have disappeared in the meantime. * command, and the upstream channel may have disappeared in the meantime.
* *
* That's why we need to periodically clean up the pending relay db. * That's why we need to periodically clean up the pending relay db.
*/ */
def cleanupRelayDb(channels: Seq[HasCommitments], relayDb: PendingRelayDb): Int = { def cleanupRelayDb(channels: Seq[HasCommitments], relayDb: PendingRelayDb): Int = {
// We are interested in incoming HTLCs, that have been *cross-signed* (otherwise they wouldn't have been relayed). // We are interested in incoming HTLCs, that have been *cross-signed* (otherwise they wouldn't have been relayed).
@ -239,14 +231,14 @@ class HtlcReaper extends Actor with ActorLogging {
val acked = htlcs val acked = htlcs
.filter(_.channelId == data.channelId) // only consider htlcs related to this channel .filter(_.channelId == data.channelId) // only consider htlcs related to this channel
.filter { .filter {
case htlc if Commitments.getHtlcCrossSigned(data.commitments, IN, htlc.id).isDefined => case htlc if Commitments.getHtlcCrossSigned(data.commitments, IN, htlc.id).isDefined =>
// this htlc is cross signed in the current commitment, we can fail it // this htlc is cross signed in the current commitment, we can fail it
log.info(s"failing broken htlc=$htlc") log.info(s"failing broken htlc=$htlc")
channel ! CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure), commit = true) channel ! CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure), commit = true)
false // the channel may very well be disconnected before we sign (=ack) the fail, so we keep it for now false // the channel may very well be disconnected before we sign (=ack) the fail, so we keep it for now
case _ => case _ =>
true // the htlc has already been failed, we can forget about it now true // the htlc has already been failed, we can forget about it now
} }
acked.foreach(htlc => log.info(s"forgetting htlc id=${htlc.id} channelId=${htlc.channelId}")) acked.foreach(htlc => log.info(s"forgetting htlc id=${htlc.id} channelId=${htlc.channelId}"))
context become main(htlcs diff acked) context become main(htlcs diff acked)
} }

View file

@ -23,7 +23,7 @@ import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair._ import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.WatchEventSpentBasic import fr.acinq.eclair.blockchain.WatchEventSpentBasic
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT, CMD_ADD_HTLC, Register} import fr.acinq.eclair.channel.{BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT, CMD_ADD_HTLC, Register, Upstream}
import fr.acinq.eclair.crypto.{Sphinx, TransportHandler} import fr.acinq.eclair.crypto.{Sphinx, TransportHandler}
import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus, PaymentsDb} import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus, PaymentsDb}
import fr.acinq.eclair.payment.PaymentInitiator.SendPaymentRequest import fr.acinq.eclair.payment.PaymentInitiator.SendPaymentRequest
@ -140,8 +140,17 @@ class PaymentLifecycle(nodeParams: NodeParams, progressHandler: PaymentProgressH
} }
// in any case, we forward the update to the router // in any case, we forward the update to the router
router ! failureMessage.update router ! failureMessage.update
// we also update assisted routes, because they take precedence over the router's routing table
val assistedRoutes1 = c.assistedRoutes.map(_.map {
case extraHop: ExtraHop if extraHop.shortChannelId == failureMessage.update.shortChannelId => extraHop.copy(
cltvExpiryDelta = failureMessage.update.cltvExpiryDelta,
feeBase = failureMessage.update.feeBaseMsat,
feeProportionalMillionths = failureMessage.update.feeProportionalMillionths
)
case extraHop => extraHop
})
// let's try again, router will have updated its state // let's try again, router will have updated its state
router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, c.assistedRoutes, ignoreNodes, ignoreChannels, c.routeParams) router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, assistedRoutes1, ignoreNodes, ignoreChannels, c.routeParams)
} else { } else {
// this node is fishy, it gave us a bad sig!! let's filter it out // this node is fishy, it gave us a bad sig!! let's filter it out
log.warning(s"got bad signature from node=$nodeId update=${failureMessage.update}") log.warning(s"got bad signature from node=$nodeId update=${failureMessage.update}")
@ -300,7 +309,7 @@ object PaymentLifecycle {
val nodes = hops.map(_.nextNodeId) val nodes = hops.map(_.nextNodeId)
// BOLT 2 requires that associatedData == paymentHash // BOLT 2 requires that associatedData == paymentHash
val onion = buildOnion(nodes, payloads, paymentHash) val onion = buildOnion(nodes, payloads, paymentHash)
CMD_ADD_HTLC(firstAmount, paymentHash, firstExpiry, onion.packet, upstream = Left(id), commit = true) -> onion.sharedSecrets CMD_ADD_HTLC(firstAmount, paymentHash, firstExpiry, onion.packet, Upstream.Local(id), commit = true) -> onion.sharedSecrets
} }
/** /**

View file

@ -24,6 +24,7 @@ import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.payment.Origin.{Relayed, Local}
import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.wire._ import fr.acinq.eclair.wire._
import fr.acinq.eclair.{CltvExpiryDelta, Features, LongToBtcAmount, MilliSatoshi, NodeParams, ShortChannelId, UInt64, nodeFee} import fr.acinq.eclair.{CltvExpiryDelta, Features, LongToBtcAmount, MilliSatoshi, NodeParams, ShortChannelId, UInt64, nodeFee}
@ -35,8 +36,12 @@ import scala.collection.mutable
// @formatter:off // @formatter:off
sealed trait Origin sealed trait Origin
case class Local(id: UUID, sender: Option[ActorRef]) extends Origin // we don't persist reference to local actors object Origin {
case class Relayed(originChannelId: ByteVector32, originHtlcId: Long, amountIn: MilliSatoshi, amountOut: MilliSatoshi) extends Origin /** Our node is the origin of the payment. */
case class Local(id: UUID, sender: Option[ActorRef]) extends Origin // we don't persist reference to local actors
/** Our node forwarded a single incoming HTLC to an outgoing channel. */
case class Relayed(originChannelId: ByteVector32, originHtlcId: Long, amountIn: MilliSatoshi, amountOut: MilliSatoshi) extends Origin
}
sealed trait ForwardMessage sealed trait ForwardMessage
case class ForwardAdd(add: UpdateAddHtlc, previousFailures: Seq[AddHtlcFailed] = Seq.empty) extends ForwardMessage case class ForwardAdd(add: UpdateAddHtlc, previousFailures: Seq[AddHtlcFailed] = Seq.empty) extends ForwardMessage
@ -125,7 +130,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
commandBuffer ! CommandBuffer.CommandSend(add.channelId, add.id, cmdFail) commandBuffer ! CommandBuffer.CommandSend(add.channelId, add.id, cmdFail)
} }
case Status.Failure(Register.ForwardShortIdFailure(Register.ForwardShortId(shortChannelId, CMD_ADD_HTLC(_, _, _, _, Right(add), _, _)))) => case Status.Failure(Register.ForwardShortIdFailure(Register.ForwardShortId(shortChannelId, CMD_ADD_HTLC(_, _, _, _, Upstream.Relayed(add), _, _)))) =>
log.warning(s"couldn't resolve downstream channel $shortChannelId, failing htlc #${add.id}") log.warning(s"couldn't resolve downstream channel $shortChannelId, failing htlc #${add.id}")
val cmdFail = CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true) val cmdFail = CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true)
commandBuffer ! CommandBuffer.CommandSend(add.channelId, add.id, cmdFail) commandBuffer ! CommandBuffer.CommandSend(add.channelId, add.id, cmdFail)
@ -143,11 +148,10 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
sender ! Status.Failure(addFailed) sender ! Status.Failure(addFailed)
case Relayed(originChannelId, originHtlcId, _, _) => case Relayed(originChannelId, originHtlcId, _, _) =>
addFailed.originalCommand match { addFailed.originalCommand match {
case Some(cmd) => case Some(CMD_ADD_HTLC(_, _, _, _, Upstream.Relayed(add), _, previousFailures)) =>
log.info(s"retrying htlc #$originHtlcId paymentHash=$paymentHash from channelId=$originChannelId") log.info(s"retrying htlc #$originHtlcId paymentHash=$paymentHash from channelId=$originChannelId")
// NB: cmd.upstream.right is defined since this is a relayed payment self ! ForwardAdd(add, previousFailures :+ addFailed)
self ! ForwardAdd(cmd.upstream.right.get, cmd.previousFailures :+ addFailed) case _ =>
case None =>
val failure = translateError(addFailed) val failure = translateError(addFailed)
val cmdFail = CMD_FAIL_HTLC(originHtlcId, Right(failure), commit = true) val cmdFail = CMD_FAIL_HTLC(originHtlcId, Right(failure), commit = true)
log.info(s"rejecting htlc #$originHtlcId paymentHash=$paymentHash from channelId=$originChannelId reason=${cmdFail.reason}") log.info(s"rejecting htlc #$originHtlcId paymentHash=$paymentHash from channelId=$originChannelId reason=${cmdFail.reason}")
@ -377,7 +381,7 @@ object Relayer extends Logging {
case Some(channelUpdate) if relayPayload.relayFeeMsat < nodeFee(channelUpdate.feeBaseMsat, channelUpdate.feeProportionalMillionths, payload.amountToForward) => case Some(channelUpdate) if relayPayload.relayFeeMsat < nodeFee(channelUpdate.feeBaseMsat, channelUpdate.feeProportionalMillionths, payload.amountToForward) =>
RelayFailure(CMD_FAIL_HTLC(add.id, Right(FeeInsufficient(add.amountMsat, channelUpdate)), commit = true)) RelayFailure(CMD_FAIL_HTLC(add.id, Right(FeeInsufficient(add.amountMsat, channelUpdate)), commit = true))
case Some(channelUpdate) => case Some(channelUpdate) =>
RelaySuccess(channelUpdate.shortChannelId, CMD_ADD_HTLC(payload.amountToForward, add.paymentHash, payload.outgoingCltv, nextPacket, upstream = Right(add), commit = true, previousFailures = previousFailures)) RelaySuccess(channelUpdate.shortChannelId, CMD_ADD_HTLC(payload.amountToForward, add.paymentHash, payload.outgoingCltv, nextPacket, Upstream.Relayed(add), commit = true, previousFailures = previousFailures))
} }
} }

View file

@ -250,6 +250,9 @@ class Router(val nodeParams: NodeParams, watcher: ActorRef, initialized: Option[
case Event(GetRoutingState, d: Data) => case Event(GetRoutingState, d: Data) =>
stay // ignored on Android stay // ignored on Android
case Event(GetNetworkStats, d: Data) =>
stay // ignored on Android
case Event(WatchEventSpentBasic(BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT(shortChannelId)), d) if d.channels.contains(shortChannelId) => case Event(WatchEventSpentBasic(BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT(shortChannelId)), d) if d.channels.contains(shortChannelId) =>
val lostChannel = d.channels(shortChannelId).ann val lostChannel = d.channels(shortChannelId).ann
log.info("funding tx of channelId={} has been spent", shortChannelId) log.info("funding tx of channelId={} has been spent", shortChannelId)
@ -281,6 +284,10 @@ class Router(val nodeParams: NodeParams, watcher: ActorRef, initialized: Option[
log.info("re-computing network statistics") log.info("re-computing network statistics")
stay using d.copy(stats = NetworkStats(d.channels.values.toSeq)) stay using d.copy(stats = NetworkStats(d.channels.values.toSeq))
case Event(TickComputeNetworkStats, d) if d.channels.nonEmpty =>
log.info("re-computing network statistics")
stay using d.copy(stats = NetworkStats(d.channels.values.toSeq))
case Event(TickPruneStaleChannels, d) => case Event(TickPruneStaleChannels, d) =>
// first we select channels that we will prune // first we select channels that we will prune
val staleChannels = getStaleChannels(d.channels.values, nodeParams.currentBlockHeight) val staleChannels = getStaleChannels(d.channels.values, nodeParams.currentBlockHeight)
@ -451,6 +458,7 @@ class Router(val nodeParams: NodeParams, watcher: ActorRef, initialized: Option[
case Event(PeerRoutingMessage(transport, remoteNodeId, routingMessage@ReplyChannelRange(chainHash, _, _, _, shortChannelIds, _)), d) => case Event(PeerRoutingMessage(transport, remoteNodeId, routingMessage@ReplyChannelRange(chainHash, _, _, _, shortChannelIds, _)), d) =>
sender ! TransportHandler.ReadAck(routingMessage) sender ! TransportHandler.ReadAck(routingMessage)
Kamon.runWithContextEntry(remoteNodeIdKey, remoteNodeId.toString) { Kamon.runWithContextEntry(remoteNodeIdKey, remoteNodeId.toString) {
Kamon.runWithSpan(Kamon.spanBuilder("reply-channel-range").start(), finishSpan = true) { Kamon.runWithSpan(Kamon.spanBuilder("reply-channel-range").start(), finishSpan = true) {
@ -469,7 +477,7 @@ class Router(val nodeParams: NodeParams, watcher: ActorRef, initialized: Option[
val timestamps_opt = routingMessage.timestamps_opt.map(_.timestamps).getOrElse(List.empty[ReplyChannelRangeTlv.Timestamps]) val timestamps_opt = routingMessage.timestamps_opt.map(_.timestamps).getOrElse(List.empty[ReplyChannelRangeTlv.Timestamps])
val checksums_opt = routingMessage.checksums_opt.map(_.checksums).getOrElse(List.empty[ReplyChannelRangeTlv.Checksums]) val checksums_opt = routingMessage.checksums_opt.map(_.checksums).getOrElse(List.empty[ReplyChannelRangeTlv.Checksums])
val shortChannelIdAndFlags = { val shortChannelIdAndFlags = Kamon.runWithSpan(Kamon.spanBuilder("compute-flags").start(), finishSpan = true) {
loop(shortChannelIds.array, timestamps_opt, checksums_opt) loop(shortChannelIds.array, timestamps_opt, checksums_opt)
} }

View file

@ -20,8 +20,8 @@ import fr.acinq.eclair.MilliSatoshi
import fr.acinq.eclair.wire._ import fr.acinq.eclair.wire._
/** /**
* Created by PM on 07/12/2016. * Created by PM on 07/12/2016.
*/ */
// @formatter:off // @formatter:off
sealed trait Direction { def opposite: Direction } sealed trait Direction { def opposite: Direction }
@ -36,10 +36,10 @@ final case class CommitmentSpec(htlcs: Set[DirectedHtlc], feeratePerKw: Long, to
} }
object CommitmentSpec { object CommitmentSpec {
def removeHtlc(changes: List[UpdateMessage], id: Long): List[UpdateMessage] = changes.filterNot(_ match { def removeHtlc(changes: List[UpdateMessage], id: Long): List[UpdateMessage] = changes.filterNot {
case u: UpdateAddHtlc if u.id == id => true case u: UpdateAddHtlc => u.id == id
case _ => false case _ => false
}) }
def addHtlc(spec: CommitmentSpec, direction: Direction, update: UpdateAddHtlc): CommitmentSpec = { def addHtlc(spec: CommitmentSpec, direction: Direction, update: UpdateAddHtlc): CommitmentSpec = {
val htlc = DirectedHtlc(direction, update) val htlc = DirectedHtlc(direction, update)
@ -54,7 +54,7 @@ object CommitmentSpec {
spec.htlcs.find(htlc => htlc.direction != direction && htlc.add.id == htlcId) match { spec.htlcs.find(htlc => htlc.direction != direction && htlc.add.id == htlcId) match {
case Some(htlc) if direction == OUT => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) case Some(htlc) if direction == OUT => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case Some(htlc) if direction == IN => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) case Some(htlc) if direction == IN => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case None => throw new RuntimeException(s"cannot find htlc id=${htlcId}") case None => throw new RuntimeException(s"cannot find htlc id=$htlcId")
} }
} }
@ -63,7 +63,7 @@ object CommitmentSpec {
spec.htlcs.find(htlc => htlc.direction != direction && htlc.add.id == htlcId) match { spec.htlcs.find(htlc => htlc.direction != direction && htlc.add.id == htlcId) match {
case Some(htlc) if direction == OUT => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) case Some(htlc) if direction == OUT => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case Some(htlc) if direction == IN => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) case Some(htlc) if direction == IN => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case None => throw new RuntimeException(s"cannot find htlc id=${htlcId}") case None => throw new RuntimeException(s"cannot find htlc id=$htlcId")
} }
} }

View file

@ -23,7 +23,8 @@ import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut}
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.payment.{Local, Origin, Relayed} import fr.acinq.eclair.payment.Origin
import fr.acinq.eclair.payment.Origin.{Relayed, Local}
import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions._ import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.CommonCodecs._ import fr.acinq.eclair.wire.CommonCodecs._
@ -37,8 +38,8 @@ import scala.compat.Platform
import scala.concurrent.duration._ import scala.concurrent.duration._
/** /**
* Created by PM on 02/06/2017. * Created by PM on 02/06/2017.
*/ */
object ChannelCodecs extends Logging { object ChannelCodecs extends Logging {
val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => new KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => new KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath]
@ -53,8 +54,8 @@ object ChannelCodecs extends Logging {
val channelVersionCodec: Codec[ChannelVersion] = discriminatorWithDefault[ChannelVersion]( val channelVersionCodec: Codec[ChannelVersion] = discriminatorWithDefault[ChannelVersion](
discriminator = discriminated[ChannelVersion].by(byte) discriminator = discriminated[ChannelVersion].by(byte)
.typecase(0x01, bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion]) .typecase(0x01, bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion])
// NB: 0x02 and 0x03 are *reserved* for backward compatibility reasons // NB: 0x02 and 0x03 are *reserved* for backward compatibility reasons
, ,
fallback = provide(ChannelVersion.ZEROES) // README: DO NOT CHANGE THIS !! old channels don't have a channel version fallback = provide(ChannelVersion.ZEROES) // README: DO NOT CHANGE THIS !! old channels don't have a channel version
// field and don't support additional features which is why all bits are set to 0. // field and don't support additional features which is why all bits are set to 0.
) )
@ -213,7 +214,7 @@ object ChannelCodecs extends Logging {
) )
val commitmentsCodec: Codec[Commitments] = ( val commitmentsCodec: Codec[Commitments] = (
("channelVersion" | channelVersionCodec) :: ("channelVersion" | channelVersionCodec) ::
("localParams" | localParamsCodec) :: ("localParams" | localParamsCodec) ::
("remoteParams" | remoteParamsCodec) :: ("remoteParams" | remoteParamsCodec) ::
("channelFlags" | byte) :: ("channelFlags" | byte) ::
@ -324,7 +325,7 @@ object ChannelCodecs extends Logging {
// this is a decode-only codec compatible with versions 818199e and below, with placeholders for new fields // this is a decode-only codec compatible with versions 818199e and below, with placeholders for new fields
val DATA_CLOSING_COMPAT_06_Codec: Codec[DATA_CLOSING] = ( val DATA_CLOSING_COMPAT_06_Codec: Codec[DATA_CLOSING] = (
("commitments" | commitmentsCodec) :: ("commitments" | commitmentsCodec) ::
("fundingTx" | provide[Option[Transaction]](None)) :: ("fundingTx" | provide[Option[Transaction]](None)) ::
("waitingSince" | provide(Platform.currentTime.milliseconds.toSeconds)) :: ("waitingSince" | provide(Platform.currentTime.milliseconds.toSeconds)) ::
("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) ::
@ -336,7 +337,7 @@ object ChannelCodecs extends Logging {
("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING].decodeOnly ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING].decodeOnly
val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = ( val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = (
("commitments" | commitmentsCodec) :: ("commitments" | commitmentsCodec) ::
("fundingTx" | optional(bool, txCodec)) :: ("fundingTx" | optional(bool, txCodec)) ::
("waitingSince" | int64) :: ("waitingSince" | int64) ::
("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) ::
@ -353,16 +354,16 @@ object ChannelCodecs extends Logging {
/** /**
* Order matters!! * Order matters!!
* *
* We use the fact that the discriminated codec encodes using the first suitable codec it finds in the list to handle * We use the fact that the discriminated codec encodes using the first suitable codec it finds in the list to handle
* database migration. * database migration.
* *
* For example, a data encoded with type 01 will be decoded using [[DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec]] and * For example, a data encoded with type 01 will be decoded using [[DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec]] and
* encoded to a type 08 using [[DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec]]. * encoded to a type 08 using [[DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec]].
* *
* More info here: https://github.com/scodec/scodec/issues/122 * More info here: https://github.com/scodec/scodec/issues/122
*/ */
val stateDataCodec: Codec[HasCommitments] = ("version" | constant(0x00)) ~> discriminated[HasCommitments].by(uint16) val stateDataCodec: Codec[HasCommitments] = ("version" | constant(0x00)) ~> discriminated[HasCommitments].by(uint16)
.typecase(0x10, DATA_NORMAL_Codec) .typecase(0x10, DATA_NORMAL_Codec)
.typecase(0x09, DATA_CLOSING_Codec) .typecase(0x09, DATA_CLOSING_Codec)

View file

@ -20,6 +20,8 @@ object Kamon {
def increment() = this def increment() = this
def decrement() = this
def record(a: Long) = this def record(a: Long) = this
} }
@ -31,6 +33,8 @@ object Kamon {
def histogram(name: String) = Mock def histogram(name: String) = Mock
def rangeSampler(name: String) = Mock
def runWithContextEntry[T, K](key: Context.Key[K], value: K)(f: => T): T = f def runWithContextEntry[T, K](key: Context.Key[K], value: K)(f: => T): T = f
def runWithSpan[T](span: Any, finishSpan: Boolean)(f: => T): T = f def runWithSpan[T](span: Any, finishSpan: Boolean)(f: => T): T = f

View file

@ -18,15 +18,28 @@ package fr.acinq.eclair
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import com.typesafe.config.ConfigFactory import com.typesafe.config.{ConfigFactory, ConfigValue}
import fr.acinq.bitcoin.Block import fr.acinq.bitcoin.Block
import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.crypto.LocalKeyManager
import org.scalatest.FunSuite import org.scalatest.FunSuite
import scala.collection.JavaConversions._
import scala.util.Try import scala.util.Try
class StartupSpec extends FunSuite { class StartupSpec extends FunSuite {
test("check configuration") {
val blockCount = new AtomicLong(0)
val keyManager = new LocalKeyManager(seed = randomBytes32, chainHash = Block.TestnetGenesisBlock.hash)
val conf = ConfigFactory.load().getConfig("eclair")
assert(Try(NodeParams.makeNodeParams(conf, keyManager, None, TestConstants.inMemoryDb(), blockCount, new TestConstants.TestFeeEstimator)).isSuccess)
val conf1 = conf.withFallback(ConfigFactory.parseMap(Map("max-feerate-mismatch" -> 42)))
intercept[RuntimeException] {
NodeParams.makeNodeParams(conf1, keyManager, None, TestConstants.inMemoryDb(), blockCount, new TestConstants.TestFeeEstimator)
}
}
test("NodeParams should fail if the alias is illegal (over 32 bytes)") { test("NodeParams should fail if the alias is illegal (over 32 bytes)") {
val threeBytesUTFChar = '\u20AC' // val threeBytesUTFChar = '\u20AC' //

View file

@ -20,7 +20,7 @@ import java.util.UUID
import fr.acinq.eclair.channel.Commitments._ import fr.acinq.eclair.channel.Commitments._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.payment.Local import fr.acinq.eclair.payment.Origin.Local
import fr.acinq.eclair.wire.IncorrectOrUnknownPaymentDetails import fr.acinq.eclair.wire.IncorrectOrUnknownPaymentDetails
import fr.acinq.eclair.{TestkitBaseClass, _} import fr.acinq.eclair.{TestkitBaseClass, _}
import org.scalatest.Outcome import org.scalatest.Outcome

View file

@ -69,7 +69,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val sender = TestProbe() val sender = TestProbe()
val h = randomBytes32 val h = randomBytes32
val add = CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
sender.expectMsg("ok") sender.expectMsg("ok")
val htlc = alice2bob.expectMsgType[UpdateAddHtlc] val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
@ -78,7 +78,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
commitments = initialState.commitments.copy( commitments = initialState.commitments.copy(
localNextHtlcId = 1, localNextHtlcId = 1,
localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil),
originChannels = Map(0L -> Local(add.upstream.left.get, Some(sender.ref))) originChannels = Map(0L -> Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)))
))) )))
} }
@ -87,7 +87,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe() val sender = TestProbe()
val h = randomBytes32 val h = randomBytes32
for (i <- 0 until 10) { for (i <- 0 until 10) {
sender.send(alice, CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
val htlc = alice2bob.expectMsgType[UpdateAddHtlc] val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
assert(htlc.id == i && htlc.paymentHash == h) assert(htlc.id == i && htlc.paymentHash == h)
@ -100,7 +100,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe() val sender = TestProbe()
val h = randomBytes32 val h = randomBytes32
val originHtlc = UpdateAddHtlc(channelId = randomBytes32, id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = h, onionRoutingPacket = TestConstants.emptyOnionPacket) val originHtlc = UpdateAddHtlc(channelId = randomBytes32, id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = h, onionRoutingPacket = TestConstants.emptyOnionPacket)
val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - 10000.msat, h, originHtlc.cltvExpiry - CltvExpiryDelta(7), TestConstants.emptyOnionPacket, upstream = Right(originHtlc)) val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - 10000.msat, h, originHtlc.cltvExpiry - CltvExpiryDelta(7), TestConstants.emptyOnionPacket, Upstream.Relayed(originHtlc))
sender.send(alice, cmd) sender.send(alice, cmd)
sender.expectMsg("ok") sender.expectMsg("ok")
val htlc = alice2bob.expectMsgType[UpdateAddHtlc] val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
@ -109,7 +109,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
commitments = initialState.commitments.copy( commitments = initialState.commitments.copy(
localNextHtlcId = 1, localNextHtlcId = 1,
localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil),
originChannels = Map(0L -> Relayed(originHtlc.channelId, originHtlc.id, originHtlc.amountMsat, htlc.amountMsat)) originChannels = Map(0L -> Origin.Relayed(originHtlc.channelId, originHtlc.id, originHtlc.amountMsat, htlc.amountMsat))
))) )))
} }
@ -118,10 +118,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe() val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val expiryTooSmall = CltvExpiry(currentBlockHeight + 3) val expiryTooSmall = CltvExpiry(currentBlockHeight + 3)
val add = CMD_ADD_HTLC(500000000 msat, randomBytes32, expiryTooSmall, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(500000000 msat, randomBytes32, expiryTooSmall, TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = ExpiryTooSmall(channelId(alice), Channel.MIN_CLTV_EXPIRY_DELTA.toCltvExpiry(currentBlockHeight), expiryTooSmall, currentBlockHeight) val error = ExpiryTooSmall(channelId(alice), Channel.MIN_CLTV_EXPIRY_DELTA.toCltvExpiry(currentBlockHeight), expiryTooSmall, currentBlockHeight)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -130,10 +130,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe() val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val expiryTooBig = (Channel.MAX_CLTV_EXPIRY_DELTA + 1).toCltvExpiry(currentBlockHeight) val expiryTooBig = (Channel.MAX_CLTV_EXPIRY_DELTA + 1).toCltvExpiry(currentBlockHeight)
val add = CMD_ADD_HTLC(500000000 msat, randomBytes32, expiryTooBig, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(500000000 msat, randomBytes32, expiryTooBig, TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = ExpiryTooBig(channelId(alice), maximum = Channel.MAX_CLTV_EXPIRY_DELTA.toCltvExpiry(currentBlockHeight), actual = expiryTooBig, blockCount = currentBlockHeight) val error = ExpiryTooBig(channelId(alice), maximum = Channel.MAX_CLTV_EXPIRY_DELTA.toCltvExpiry(currentBlockHeight), actual = expiryTooBig, blockCount = currentBlockHeight)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -141,10 +141,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val add = CMD_ADD_HTLC(50 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(50 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = HtlcValueTooSmall(channelId(alice), 1000 msat, 50 msat) val error = HtlcValueTooSmall(channelId(alice), 1000 msat, 50 msat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -153,7 +153,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe() val sender = TestProbe()
// channel starts with all funds on alice's side, alice sends some funds to bob, but not enough to make it go above reserve // channel starts with all funds on alice's side, alice sends some funds to bob, but not enough to make it go above reserve
val h = randomBytes32 val h = randomBytes32
val add = CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
sender.expectMsg("ok") sender.expectMsg("ok")
} }
@ -162,10 +162,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val add = CMD_ADD_HTLC(MilliSatoshi(Int.MaxValue), randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(MilliSatoshi(Int.MaxValue), randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = InsufficientFunds(channelId(alice), amount = MilliSatoshi(Int.MaxValue), missing = 1376443 sat, reserve = 20000 sat, fees = 8960 sat) val error = InsufficientFunds(channelId(alice), amount = MilliSatoshi(Int.MaxValue), missing = 1376443 sat, reserve = 20000 sat, fees = 8960 sat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -173,11 +173,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
val add = CMD_ADD_HTLC(initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(bob, add) sender.send(bob, add)
val error = InsufficientFunds(channelId(alice), amount = add.amount, missing = 0 sat, reserve = 10000 sat, fees = 0 sat) val error = InsufficientFunds(channelId(alice), amount = add.amount, missing = 0 sat, reserve = 10000 sat, fees = 0 sat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -190,29 +190,29 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// actual test begins // actual test begins
// at this point alice has the minimal amount to sustain a channel (29000 sat ~= alice reserve + commit fee) // at this point alice has the minimal amount to sustain a channel (29000 sat ~= alice reserve + commit fee)
val add = CMD_ADD_HTLC(120000000 msat, randomBytes32, CltvExpiry(400144), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(120000000 msat, randomBytes32, CltvExpiry(400144), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(bob, add) sender.send(bob, add)
val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), add.amount, missing = 1680 sat, 10000 sat, 10680 sat) val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), add.amount, missing = 1680 sat, 10000 sat, 10680 sat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate), Some(add))))
} }
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs and 0 balance)") { f => test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs and 0 balance)") { f =>
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
sender.send(alice, CMD_ADD_HTLC(500000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(500000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
sender.send(alice, CMD_ADD_HTLC(200000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(200000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
sender.send(alice, CMD_ADD_HTLC(67600000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(67600000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
val add = CMD_ADD_HTLC(1000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(1000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = InsufficientFunds(channelId(alice), amount = 1000000 msat, missing = 1000 sat, reserve = 20000 sat, fees = 12400 sat) val error = InsufficientFunds(channelId(alice), amount = 1000000 msat, missing = 1000 sat, reserve = 20000 sat, fees = 12400 sat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -220,16 +220,16 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
sender.send(alice, CMD_ADD_HTLC(300000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(300000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
sender.send(alice, CMD_ADD_HTLC(300000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(300000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
val add = CMD_ADD_HTLC(500000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(500000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = InsufficientFunds(channelId(alice), amount = 500000000 msat, missing = 332400 sat, reserve = 20000 sat, fees = 12400 sat) val error = InsufficientFunds(channelId(alice), amount = 500000000 msat, missing = 332400 sat, reserve = 20000 sat, fees = 12400 sat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -237,10 +237,25 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
val add = CMD_ADD_HTLC(151000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(151000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(bob, add) sender.send(bob, add)
val error = HtlcValueTooHighInFlight(channelId(bob), maximum = 150000000, actual = 151000000 msat) val error = HtlcValueTooHighInFlight(channelId(bob), maximum = 150000000, actual = 151000000 msat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
bob2alice.expectNoMsg(200 millis)
}
test("recv CMD_ADD_HTLC (over max inflight htlc value with duplicate amounts)") { f =>
import f._
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
val add = CMD_ADD_HTLC(75500000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(bob, add)
sender.expectMsg("ok")
bob2alice.expectMsgType[UpdateAddHtlc]
val add1 = CMD_ADD_HTLC(75500000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(bob, add1)
val error = HtlcValueTooHighInFlight(channelId(bob), maximum = 150000000, actual = 151000000 msat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), add1.paymentHash, error, Origin.Local(add1.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add1))))
bob2alice.expectNoMsg(200 millis) bob2alice.expectNoMsg(200 millis)
} }
@ -250,14 +265,14 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
// Bob accepts a maximum of 30 htlcs // Bob accepts a maximum of 30 htlcs
for (i <- 0 until 30) { for (i <- 0 until 30) {
sender.send(alice, CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
} }
val add = CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = TooManyAcceptedHtlcs(channelId(alice), maximum = 30) val error = TooManyAcceptedHtlcs(channelId(alice), maximum = 30)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -265,7 +280,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add1) sender.send(alice, add1)
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
@ -273,10 +288,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[CommitSig] alice2bob.expectMsgType[CommitSig]
// this is over channel-capacity // this is over channel-capacity
val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add2) sender.send(alice, add2)
val error = InsufficientFunds(channelId(alice), add2.amount, 564013 sat, 20000 sat, 10680 sat) val error = InsufficientFunds(channelId(alice), add2.amount, 564013 sat, 20000 sat, 10680 sat)
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Local(add2.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add2)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Origin.Local(add2.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add2))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -290,10 +305,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined && alice.stateData.asInstanceOf[DATA_NORMAL].remoteShutdown.isEmpty) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined && alice.stateData.asInstanceOf[DATA_NORMAL].remoteShutdown.isEmpty)
// actual test starts here // actual test starts here
val add = CMD_ADD_HTLC(500000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(500000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = NoMoreHtlcsClosingInProgress(channelId(alice)) val error = NoMoreHtlcsClosingInProgress(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -302,14 +317,14 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe() val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
// let's make alice send an htlc // let's make alice send an htlc
val add1 = CMD_ADD_HTLC(500000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add1 = CMD_ADD_HTLC(500000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add1) sender.send(alice, add1)
sender.expectMsg("ok") sender.expectMsg("ok")
// at the same time bob initiates a closing // at the same time bob initiates a closing
sender.send(bob, CMD_CLOSE(None)) sender.send(bob, CMD_CLOSE(None))
sender.expectMsg("ok") sender.expectMsg("ok")
// this command will be received by alice right after having received the shutdown // this command will be received by alice right after having received the shutdown
val add2 = CMD_ADD_HTLC(100000000 msat, randomBytes32, CltvExpiry(300000), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add2 = CMD_ADD_HTLC(100000000 msat, randomBytes32, CltvExpiry(300000), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
// messages cross // messages cross
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob) alice2bob.forward(bob)
@ -317,7 +332,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
bob2alice.forward(alice) bob2alice.forward(alice)
sender.send(alice, add2) sender.send(alice, add2)
val error = NoMoreHtlcsClosingInProgress(channelId(alice)) val error = NoMoreHtlcsClosingInProgress(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Local(add2.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add2)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Origin.Local(add2.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), Some(initialState.channelUpdate), Some(add2))))
} }
test("recv UpdateAddHtlc") { f => test("recv UpdateAddHtlc") { f =>
@ -456,7 +471,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
test("recv CMD_SIGN (two identical htlcs in each direction)") { f => test("recv CMD_SIGN (two identical htlcs in each direction)") { f =>
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val add = CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
@ -503,19 +518,19 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
assert(a2b_2 > aliceMinOffer && a2b_2 > bobMinReceive) assert(a2b_2 > aliceMinOffer && a2b_2 > bobMinReceive)
assert(b2a_1 > aliceMinReceive && b2a_1 > bobMinOffer) assert(b2a_1 > aliceMinReceive && b2a_1 > bobMinOffer)
assert(b2a_2 < aliceMinReceive && b2a_2 > bobMinOffer) assert(b2a_2 < aliceMinReceive && b2a_2 > bobMinOffer)
sender.send(alice, CMD_ADD_HTLC(a2b_1.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(a2b_1.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob) alice2bob.forward(bob)
sender.send(alice, CMD_ADD_HTLC(a2b_2.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(a2b_2.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob) alice2bob.forward(bob)
sender.send(bob, CMD_ADD_HTLC(b2a_1.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(bob, CMD_ADD_HTLC(b2a_1.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
bob2alice.expectMsgType[UpdateAddHtlc] bob2alice.expectMsgType[UpdateAddHtlc]
bob2alice.forward(alice) bob2alice.forward(alice)
sender.send(bob, CMD_ADD_HTLC(b2a_2.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(bob, CMD_ADD_HTLC(b2a_2.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
bob2alice.expectMsgType[UpdateAddHtlc] bob2alice.expectMsgType[UpdateAddHtlc]
bob2alice.forward(alice) bob2alice.forward(alice)
@ -535,7 +550,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
test("recv CMD_SIGN (htlcs with same pubkeyScript but different amounts)") { f => test("recv CMD_SIGN (htlcs with same pubkeyScript but different amounts)") { f =>
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val add = CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
val epsilons = List(3, 1, 5, 7, 6) // unordered on purpose val epsilons = List(3, 1, 5, 7, 6) // unordered on purpose
val htlcCount = epsilons.size val htlcCount = epsilons.size
for (i <- epsilons) { for (i <- epsilons) {
@ -733,12 +748,12 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val r = randomBytes32 val r = randomBytes32
val h = Crypto.sha256(r) val h = Crypto.sha256(r)
sender.send(alice, CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc] val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob) alice2bob.forward(bob)
sender.send(alice, CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
sender.expectMsg("ok") sender.expectMsg("ok")
val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc] val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob) alice2bob.forward(bob)
@ -1491,7 +1506,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
bob2blockchain.expectMsgType[WatchConfirmed] bob2blockchain.expectMsgType[WatchConfirmed]
} }
ignore("recv CMD_UPDATE_RELAY_FEE ") { f => test("recv CMD_UPDATE_RELAY_FEE ") { f =>
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val newFeeBaseMsat = TestConstants.Alice.nodeParams.feeBase * 2 val newFeeBaseMsat = TestConstants.Alice.nodeParams.feeBase * 2
@ -2077,7 +2092,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// alice = 800 000 // alice = 800 000
// bob = 200 000 // bob = 200 000
val add = CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(10000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
sender.expectMsg("ok") sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.expectMsgType[UpdateAddHtlc]
@ -2295,7 +2310,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
alice2bob.expectMsg(annSigsA) alice2bob.expectMsg(annSigsA)
} }
ignore("recv TickRefreshChannelUpdate", Tag("channels_public")) { f => test("recv BroadcastChannelUpdate", Tag("channels_public")) { f =>
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42, null)) sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42, null))

View file

@ -23,8 +23,8 @@ import akka.testkit.{TestActorRef, TestProbe}
import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{ByteVector32, ScriptFlags, Transaction} import fr.acinq.bitcoin.{ByteVector32, ScriptFlags, Transaction}
import fr.acinq.eclair.TestConstants.Alice import fr.acinq.eclair.TestConstants.Alice
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
import fr.acinq.eclair.channel.Channel.LocalError import fr.acinq.eclair.channel.Channel.LocalError
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.states.StateTestsHelperMethods
@ -71,7 +71,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
sender.send(alice, CMD_ADD_HTLC(1000000 msat, ByteVector32.Zeroes, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(1000000 msat, ByteVector32.Zeroes, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
val ab_add_0 = alice2bob.expectMsgType[UpdateAddHtlc] val ab_add_0 = alice2bob.expectMsgType[UpdateAddHtlc]
// add ->b // add ->b
alice2bob.forward(bob) alice2bob.forward(bob)
@ -152,7 +152,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
sender.send(alice, CMD_ADD_HTLC(1000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(1000000 msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
val ab_add_0 = alice2bob.expectMsgType[UpdateAddHtlc] val ab_add_0 = alice2bob.expectMsgType[UpdateAddHtlc]
// add ->b // add ->b
alice2bob.forward(bob, ab_add_0) alice2bob.forward(bob, ab_add_0)
@ -400,7 +400,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
channelUpdateListener.expectNoMsg(300 millis) channelUpdateListener.expectNoMsg(300 millis)
// we attempt to send a payment // we attempt to send a payment
sender.send(alice, CMD_ADD_HTLC(4200 msat, randomBytes32, CltvExpiry(123456), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.send(alice, CMD_ADD_HTLC(4200 msat, randomBytes32, CltvExpiry(123456), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID())))
val failure = sender.expectMsgType[Status.Failure] val failure = sender.expectMsgType[Status.Failure]
val AddHtlcFailed(_, _, ChannelUnavailable(_), _, _, _) = failure.cause val AddHtlcFailed(_, _, ChannelUnavailable(_), _, _, _) = failure.cause

View file

@ -104,10 +104,10 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
test("recv CMD_ADD_HTLC") { f => test("recv CMD_ADD_HTLC") { f =>
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
val add = CMD_ADD_HTLC(500000000 msat, r1, cltvExpiry = CltvExpiry(300000), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(500000000 msat, r1, cltvExpiry = CltvExpiry(300000), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = ChannelUnavailable(channelId(alice)) val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), None, Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), None, Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }

View file

@ -28,7 +28,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel.Helpers.Closing
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.payment.Local import fr.acinq.eclair.payment.Origin
import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown} import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown}
import fr.acinq.eclair.{CltvExpiry, LongToBtcAmount, TestConstants, TestkitBaseClass} import fr.acinq.eclair.{CltvExpiry, LongToBtcAmount, TestConstants, TestkitBaseClass}
import org.scalatest.{Outcome, Tag} import org.scalatest.{Outcome, Tag}
@ -85,10 +85,10 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
import f._ import f._
alice2bob.expectMsgType[ClosingSigned] alice2bob.expectMsgType[ClosingSigned]
val sender = TestProbe() val sender = TestProbe()
val add = CMD_ADD_HTLC(5000000000L msat, ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = CltvExpiry(300000), onion = TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(5000000000L msat, ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = CltvExpiry(300000), onion = TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = ChannelUnavailable(channelId(alice)) val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), None, Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), None, Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }

View file

@ -299,10 +299,10 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// actual test starts here // actual test starts here
val sender = TestProbe() val sender = TestProbe()
val add = CMD_ADD_HTLC(500000000 msat, ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = CltvExpiry(300000), onion = TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val add = CMD_ADD_HTLC(500000000 msat, ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = CltvExpiry(300000), onion = TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
sender.send(alice, add) sender.send(alice, add)
val error = ChannelUnavailable(channelId(alice)) val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), None, Some(add)))) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Origin.Local(add.upstream.asInstanceOf[Upstream.Local].id, Some(sender.ref)), None, Some(add))))
alice2bob.expectNoMsg(200 millis) alice2bob.expectNoMsg(200 millis)
} }
@ -418,7 +418,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
system.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed]) system.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed])
system.eventStream.subscribe(listener.ref, classOf[PaymentSettlingOnChain]) system.eventStream.subscribe(listener.ref, classOf[PaymentSettlingOnChain])
// alice sends an htlc to bob // alice sends an htlc to bob
val (ra1, htlca1) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) val (_, htlca1) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice)
// an error occurs and alice publishes her commit tx // an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
@ -452,7 +452,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
system.eventStream.subscribe(listener.ref, classOf[PaymentSettlingOnChain]) system.eventStream.subscribe(listener.ref, classOf[PaymentSettlingOnChain])
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
// alice sends an htlc // alice sends an htlc
val (r, htlc) = addHtlc(4200000 msat, alice, bob, alice2bob, bob2alice) val (_, htlc) = addHtlc(4200000 msat, alice, bob, alice2bob, bob2alice)
// and signs it (but bob doesn't sign it) // and signs it (but bob doesn't sign it)
sender.send(alice, CMD_SIGN) sender.send(alice, CMD_SIGN)
sender.expectMsg("ok") sender.expectMsg("ok")
@ -483,7 +483,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
system.eventStream.subscribe(listener.ref, classOf[PaymentSettlingOnChain]) system.eventStream.subscribe(listener.ref, classOf[PaymentSettlingOnChain])
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
// alice sends an htlc // alice sends an htlc
val (r, htlc) = addHtlc(4200000 msat, alice, bob, alice2bob, bob2alice) val (_, htlc) = addHtlc(4200000 msat, alice, bob, alice2bob, bob2alice)
// and signs it (but bob doesn't sign it) // and signs it (but bob doesn't sign it)
sender.send(alice, CMD_SIGN) sender.send(alice, CMD_SIGN)
sender.expectMsg("ok") sender.expectMsg("ok")

View file

@ -19,7 +19,7 @@ package fr.acinq.eclair.db
import java.sql.Connection import java.sql.Connection
import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{Block, Crypto} import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64, Crypto, Satoshi}
import fr.acinq.eclair.db.sqlite.SqliteNetworkDb import fr.acinq.eclair.db.sqlite.SqliteNetworkDb
import fr.acinq.eclair.db.sqlite.SqliteUtils._ import fr.acinq.eclair.db.sqlite.SqliteUtils._
import fr.acinq.eclair.router.{Announcements, PublicChannel} import fr.acinq.eclair.router.{Announcements, PublicChannel}
@ -28,7 +28,7 @@ import fr.acinq.eclair.{CltvExpiryDelta, LongToBtcAmount, ShortChannelId, TestCo
import org.scalatest.FunSuite import org.scalatest.FunSuite
import scodec.bits.HexStringSyntax import scodec.bits.HexStringSyntax
import scala.collection.SortedMap import scala.collection.{SortedMap, mutable}
class SqliteNetworkDbSpec extends FunSuite { class SqliteNetworkDbSpec extends FunSuite {
@ -104,6 +104,17 @@ class SqliteNetworkDbSpec extends FunSuite {
assert(node_4.addresses == List(Tor2("aaaqeayeaudaocaj", 42000))) assert(node_4.addresses == List(Tor2("aaaqeayeaudaocaj", 42000)))
} }
test("correctly handle txids that start with 0") {
val sqlite = TestConstants.sqliteInMemory()
val db = new SqliteNetworkDb(sqlite, Block.RegtestGenesisBlock.hash)
val sig = ByteVector64.Zeroes
val c = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(42), randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
val c_shrunk = shrink(c)
val txid = ByteVector32.fromValidHex("0001" * 16)
db.addChannel(c, txid, Satoshi(42))
assert(db.listChannels() === SortedMap(c.shortChannelId -> PublicChannel(c_shrunk, txid, Satoshi(42), None, None)))
}
def shrink(c: ChannelAnnouncement) = c.copy(bitcoinKey1 = null, bitcoinKey2 = null, bitcoinSignature1 = null, bitcoinSignature2 = null, nodeSignature1 = null, nodeSignature2 = null, chainHash = null, features = null) def shrink(c: ChannelAnnouncement) = c.copy(bitcoinKey1 = null, bitcoinKey2 = null, bitcoinSignature1 = null, bitcoinSignature2 = null, nodeSignature1 = null, nodeSignature2 = null, chainHash = null, features = null)
def shrink(c: ChannelUpdate) = c.copy(signature = null) def shrink(c: ChannelUpdate) = c.copy(signature = null)

View file

@ -56,8 +56,8 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
/** /**
* Created by PM on 15/03/2017. * Created by PM on 15/03/2017.
*/ */
@Ignore @Ignore
class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging { class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {

View file

@ -57,7 +57,7 @@ class SynchronizationPipe(latch: CountDownLatch) extends Actor with ActorLogging
script match { script match {
case offer(x, amount, rhash) :: rest => case offer(x, amount, rhash) :: rest =>
resolve(x) ! CMD_ADD_HTLC(MilliSatoshi(amount.toInt), ByteVector32.fromValidHex(rhash), CltvExpiry(144), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) resolve(x) ! CMD_ADD_HTLC(MilliSatoshi(amount.toInt), ByteVector32.fromValidHex(rhash), CltvExpiry(144), TestConstants.emptyOnionPacket, Upstream.Local(UUID.randomUUID()))
exec(rest, a, b) exec(rest, a, b)
case fulfill(x, id, r) :: rest => case fulfill(x, id, r) :: rest =>
resolve(x) ! CMD_FULFILL_HTLC(id.toInt, ByteVector32.fromValidHex(r)) resolve(x) ! CMD_FULFILL_HTLC(id.toInt, ByteVector32.fromValidHex(r))

View file

@ -18,7 +18,7 @@ package fr.acinq.eclair.payment
import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{Block, ByteVector32} import fr.acinq.bitcoin.{Block, ByteVector32}
import fr.acinq.eclair.channel.{CMD_ADD_HTLC, CMD_FAIL_HTLC} import fr.acinq.eclair.channel.{CMD_ADD_HTLC, CMD_FAIL_HTLC, Upstream}
import fr.acinq.eclair.payment.HtlcGenerationSpec.makeCommitments import fr.acinq.eclair.payment.HtlcGenerationSpec.makeCommitments
import fr.acinq.eclair.payment.Relayer.{OutgoingChannel, RelayFailure, RelayPayload, RelaySuccess} import fr.acinq.eclair.payment.Relayer.{OutgoingChannel, RelayFailure, RelayPayload, RelaySuccess}
import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.router.Announcements
@ -50,7 +50,7 @@ class ChannelSelectionSpec extends FunSuite {
val channelUpdate = dummyUpdate(ShortChannelId(12345), CltvExpiryDelta(10), 100 msat, 1000 msat, 100, 10000000 msat, true) val channelUpdate = dummyUpdate(ShortChannelId(12345), CltvExpiryDelta(10), 100 msat, 1000 msat, 100, 10000000 msat, true)
// nominal case // nominal case
assert(Relayer.relayOrFail(relayPayload, Some(channelUpdate)) === RelaySuccess(ShortChannelId(12345), CMD_ADD_HTLC(relayPayload.payload.amountToForward, relayPayload.add.paymentHash, relayPayload.payload.outgoingCltv, relayPayload.nextPacket, upstream = Right(relayPayload.add), commit = true))) assert(Relayer.relayOrFail(relayPayload, Some(channelUpdate)) === RelaySuccess(ShortChannelId(12345), CMD_ADD_HTLC(relayPayload.payload.amountToForward, relayPayload.add.paymentHash, relayPayload.payload.outgoingCltv, relayPayload.nextPacket, Upstream.Relayed(relayPayload.add), commit = true)))
// no channel_update // no channel_update
assert(Relayer.relayOrFail(relayPayload, channelUpdate_opt = None) === RelayFailure(CMD_FAIL_HTLC(relayPayload.add.id, Right(UnknownNextPeer), commit = true))) assert(Relayer.relayOrFail(relayPayload, channelUpdate_opt = None) === RelayFailure(CMD_FAIL_HTLC(relayPayload.add.id, Right(UnknownNextPeer), commit = true)))
// channel disabled // channel disabled
@ -67,7 +67,7 @@ class ChannelSelectionSpec extends FunSuite {
assert(Relayer.relayOrFail(relayPayload_insufficientfee, Some(channelUpdate)) === RelayFailure(CMD_FAIL_HTLC(relayPayload.add.id, Right(FeeInsufficient(relayPayload_insufficientfee.add.amountMsat, channelUpdate)), commit = true))) assert(Relayer.relayOrFail(relayPayload_insufficientfee, Some(channelUpdate)) === RelayFailure(CMD_FAIL_HTLC(relayPayload.add.id, Right(FeeInsufficient(relayPayload_insufficientfee.add.amountMsat, channelUpdate)), commit = true)))
// note that a generous fee is ok! // note that a generous fee is ok!
val relayPayload_highfee = relayPayload.copy(payload = onionPayload.copy(amountToForward = 900000 msat)) val relayPayload_highfee = relayPayload.copy(payload = onionPayload.copy(amountToForward = 900000 msat))
assert(Relayer.relayOrFail(relayPayload_highfee, Some(channelUpdate)) === RelaySuccess(ShortChannelId(12345), CMD_ADD_HTLC(relayPayload_highfee.payload.amountToForward, relayPayload_highfee.add.paymentHash, relayPayload_highfee.payload.outgoingCltv, relayPayload_highfee.nextPacket, upstream = Right(relayPayload.add), commit = true))) assert(Relayer.relayOrFail(relayPayload_highfee, Some(channelUpdate)) === RelaySuccess(ShortChannelId(12345), CMD_ADD_HTLC(relayPayload_highfee.payload.amountToForward, relayPayload_highfee.add.paymentHash, relayPayload_highfee.payload.outgoingCltv, relayPayload_highfee.nextPacket, Upstream.Relayed(relayPayload.add), commit = true)))
} }
test("channel selection") { test("channel selection") {

View file

@ -30,8 +30,10 @@ import fr.acinq.eclair.channel.{AddHtlcFailed, Channel, ChannelUnavailable}
import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus} import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus}
import fr.acinq.eclair.io.Peer.PeerRoutingMessage import fr.acinq.eclair.io.Peer.PeerRoutingMessage
import fr.acinq.eclair.payment.Origin.Local
import fr.acinq.eclair.payment.PaymentInitiator.SendPaymentRequest import fr.acinq.eclair.payment.PaymentInitiator.SendPaymentRequest
import fr.acinq.eclair.payment.PaymentLifecycle._ import fr.acinq.eclair.payment.PaymentLifecycle._
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
import fr.acinq.eclair.payment.PaymentSent.PartialPayment import fr.acinq.eclair.payment.PaymentSent.PartialPayment
import fr.acinq.eclair.router.Announcements.{makeChannelUpdate, makeNodeAnnouncement} import fr.acinq.eclair.router.Announcements.{makeChannelUpdate, makeNodeAnnouncement}
import fr.acinq.eclair.router._ import fr.acinq.eclair.router._
@ -358,6 +360,61 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
awaitCond(paymentDb.getOutgoingPayment(id).exists(_.status.isInstanceOf[OutgoingPaymentStatus.Failed])) awaitCond(paymentDb.getOutgoingPayment(id).exists(_.status.isInstanceOf[OutgoingPaymentStatus.Failed]))
} }
test("payment failed (Update in assisted route)") { fixture =>
import fixture._
val nodeParams = TestConstants.Alice.nodeParams.copy(keyManager = testKeyManager)
val paymentDb = nodeParams.db.payments
val relayer = TestProbe()
val routerForwarder = TestProbe()
val id = UUID.randomUUID()
val progressHandler = PaymentLifecycle.DefaultPaymentProgressHandler(id, defaultPaymentRequest, paymentDb)
val paymentFSM = TestFSMRef(new PaymentLifecycle(nodeParams, progressHandler, routerForwarder.ref, relayer.ref))
val monitor = TestProbe()
val sender = TestProbe()
paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
// we build an assisted route for channel bc and cd
val assistedRoutes = Seq(Seq(
ExtraHop(b, channelId_bc, channelUpdate_bc.feeBaseMsat, channelUpdate_bc.feeProportionalMillionths, channelUpdate_bc.cltvExpiryDelta),
ExtraHop(c, channelId_cd, channelUpdate_cd.feeBaseMsat, channelUpdate_cd.feeProportionalMillionths, channelUpdate_cd.cltvExpiryDelta)
))
val request = SendPayment(defaultPaymentHash, d, FinalLegacyPayload(defaultAmountMsat, defaultExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight)), maxAttempts = 5, assistedRoutes = assistedRoutes)
sender.send(paymentFSM, request)
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && paymentDb.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
val WaitingForRoute(_, _, Nil) = paymentFSM.stateData
routerForwarder.expectMsg(RouteRequest(nodeParams.nodeId, d, defaultAmountMsat, assistedRoutes = assistedRoutes, ignoreNodes = Set.empty, ignoreChannels = Set.empty))
routerForwarder.forward(router)
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
val WaitingForComplete(_, _, cmd1, Nil, sharedSecrets1, _, _, _) = paymentFSM.stateData
relayer.expectMsg(ForwardShortId(channelId_ab, cmd1))
// we change the cltv expiry
val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, CltvExpiryDelta(42), htlcMinimumMsat = channelUpdate_bc.htlcMinimumMsat, feeBaseMsat = channelUpdate_bc.feeBaseMsat, feeProportionalMillionths = channelUpdate_bc.feeProportionalMillionths, htlcMaximumMsat = channelUpdate_bc.htlcMaximumMsat.get)
val failure = IncorrectCltvExpiry(CltvExpiry(5), channelUpdate_bc_modified)
// and node replies with a failure containing a new channel update
sender.send(paymentFSM, UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets1.head._1, failure)))
// payment lifecycle forwards the embedded channelUpdate to the router
routerForwarder.expectMsg(channelUpdate_bc_modified)
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && paymentDb.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending)) // 1 failure but not final, the payment is still PENDING
val assistedRoutes1 = Seq(Seq(
ExtraHop(b, channelId_bc, channelUpdate_bc.feeBaseMsat, channelUpdate_bc.feeProportionalMillionths, channelUpdate_bc_modified.cltvExpiryDelta),
ExtraHop(c, channelId_cd, channelUpdate_cd.feeBaseMsat, channelUpdate_cd.feeProportionalMillionths, channelUpdate_cd.cltvExpiryDelta)
))
routerForwarder.expectMsg(RouteRequest(nodeParams.nodeId, d, defaultAmountMsat, assistedRoutes = assistedRoutes1, ignoreNodes = Set.empty, ignoreChannels = Set.empty))
routerForwarder.forward(router)
// router answers with a new route, taking into account the new update
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
val WaitingForComplete(_, _, cmd2, _, _, _, _, _) = paymentFSM.stateData
relayer.expectMsg(ForwardShortId(channelId_ab, cmd2))
assert(cmd2.cltvExpiry > cmd1.cltvExpiry)
}
def testPermanentFailure(fixture: FixtureParam, failure: FailureMessage): Unit = { def testPermanentFailure(fixture: FixtureParam, failure: FailureMessage): Unit = {
import fixture._ import fixture._
val nodeParams = TestConstants.Alice.nodeParams.copy(keyManager = testKeyManager) val nodeParams = TestConstants.Alice.nodeParams.copy(keyManager = testKeyManager)

View file

@ -24,6 +24,7 @@ import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.payment.Origin.Relayed
import fr.acinq.eclair.payment.PaymentLifecycle.{buildCommand, buildOnion} import fr.acinq.eclair.payment.PaymentLifecycle.{buildCommand, buildOnion}
import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.wire.Onion.{FinalLegacyPayload, FinalTlvPayload, PerHopPayload, RelayTlvPayload} import fr.acinq.eclair.wire.Onion.{FinalLegacyPayload, FinalTlvPayload, PerHopPayload, RelayTlvPayload}
@ -77,7 +78,7 @@ class RelayerSpec extends TestkitBaseClass {
assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId) assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId)
assert(fwd.message.amount === amount_bc) assert(fwd.message.amount === amount_bc)
assert(fwd.message.cltvExpiry === expiry_bc) assert(fwd.message.cltvExpiry === expiry_bc)
assert(fwd.message.upstream === Right(add_ab)) assert(fwd.message.upstream === Upstream.Relayed(add_ab))
sender.expectNoMsg(100 millis) sender.expectNoMsg(100 millis)
paymentHandler.expectNoMsg(100 millis) paymentHandler.expectNoMsg(100 millis)
@ -106,7 +107,7 @@ class RelayerSpec extends TestkitBaseClass {
assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId) assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId)
assert(fwd.message.amount === amount_bc) assert(fwd.message.amount === amount_bc)
assert(fwd.message.cltvExpiry === expiry_bc) assert(fwd.message.cltvExpiry === expiry_bc)
assert(fwd.message.upstream === Right(add_ab)) assert(fwd.message.upstream === Upstream.Relayed(add_ab))
sender.expectNoMsg(100 millis) sender.expectNoMsg(100 millis)
paymentHandler.expectNoMsg(100 millis) paymentHandler.expectNoMsg(100 millis)
@ -133,7 +134,7 @@ class RelayerSpec extends TestkitBaseClass {
// first try // first try
val fwd1 = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]] val fwd1 = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]]
assert(fwd1.shortChannelId === channelUpdate_bc_1.shortChannelId) assert(fwd1.shortChannelId === channelUpdate_bc_1.shortChannelId)
assert(fwd1.message.upstream === Right(add_ab)) assert(fwd1.message.upstream === Upstream.Relayed(add_ab))
// channel returns an error // channel returns an error
val origin = Relayed(channelId_ab, originHtlcId = 42, amountIn = 1100000 msat, amountOut = 1000000 msat) val origin = Relayed(channelId_ab, originHtlcId = 42, amountIn = 1100000 msat, amountOut = 1000000 msat)
@ -142,7 +143,7 @@ class RelayerSpec extends TestkitBaseClass {
// second try // second try
val fwd2 = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]] val fwd2 = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]]
assert(fwd2.shortChannelId === channelUpdate_bc.shortChannelId) assert(fwd2.shortChannelId === channelUpdate_bc.shortChannelId)
assert(fwd2.message.upstream === Right(add_ab)) assert(fwd2.message.upstream === Upstream.Relayed(add_ab))
// failure again // failure again
sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, HtlcValueTooHighInFlight(channelId_bc, UInt64(1000000000L), 1516977616L msat), origin, Some(channelUpdate_bc), originalCommand = Some(fwd2.message)))) sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, HtlcValueTooHighInFlight(channelId_bc, UInt64(1000000000L), 1516977616L msat), origin, Some(channelUpdate_bc), originalCommand = Some(fwd2.message))))
@ -206,7 +207,7 @@ class RelayerSpec extends TestkitBaseClass {
val fwd1 = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]] val fwd1 = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]]
assert(fwd1.shortChannelId === channelUpdate_bc.shortChannelId) assert(fwd1.shortChannelId === channelUpdate_bc.shortChannelId)
assert(fwd1.message.upstream === Right(add_ab)) assert(fwd1.message.upstream === Upstream.Relayed(add_ab))
sender.send(relayer, Status.Failure(Register.ForwardShortIdFailure(fwd1))) sender.send(relayer, Status.Failure(Register.ForwardShortIdFailure(fwd1)))
@ -232,7 +233,7 @@ class RelayerSpec extends TestkitBaseClass {
val fwd = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]] val fwd = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]]
assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId) assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId)
assert(fwd.message.upstream === Right(add_ab)) assert(fwd.message.upstream === Upstream.Relayed(add_ab))
sender.expectNoMsg(100 millis) sender.expectNoMsg(100 millis)
paymentHandler.expectNoMsg(100 millis) paymentHandler.expectNoMsg(100 millis)

View file

@ -37,6 +37,7 @@ import scala.collection.{SortedSet, immutable, mutable}
import scala.compat.Platform import scala.compat.Platform
import scala.concurrent.duration._ import scala.concurrent.duration._
//TODO: re-enable this using a modified version of the old test //TODO: re-enable this using a modified version of the old test
// as is it won't work on because on Android router just ignore querier // as is it won't work on because on Android router just ignore querier
@Ignore @Ignore

View file

@ -27,7 +27,7 @@ import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64, Crypto, Deterministi
import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.{LocalKeyManager, ShaChain} import fr.acinq.eclair.crypto.{LocalKeyManager, ShaChain}
import fr.acinq.eclair.payment.{Local, Relayed} import fr.acinq.eclair.payment.Origin.{Relayed, Local}
import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo, TransactionWithInputInfo} import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo, TransactionWithInputInfo}
import fr.acinq.eclair.transactions._ import fr.acinq.eclair.transactions._
@ -46,8 +46,8 @@ import scala.io.Source
import scala.util.Random import scala.util.Random
/** /**
* Created by PM on 31/05/2016. * Created by PM on 31/05/2016.
*/ */
class ChannelCodecsSpec extends FunSuite { class ChannelCodecsSpec extends FunSuite {
@ -558,4 +558,5 @@ object ChannelCodecsSpec {
new ChannelVersionSerializer + new ChannelVersionSerializer +
new InputInfoSerializer new InputInfoSerializer
} }
} }

View file

@ -1,3 +1,7 @@
eclair {
enable-kamon = false
}
akka { akka {
loggers = ["akka.event.slf4j.Slf4jLogger"] loggers = ["akka.event.slf4j.Slf4jLogger"]

View file

@ -22,7 +22,6 @@ import java.util.UUID
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction}
import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, UInt64}
import fr.acinq.eclair.channel.{ChannelVersion, State} import fr.acinq.eclair.channel.{ChannelVersion, State}
import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.db.{IncomingPaymentStatus, OutgoingPaymentStatus} import fr.acinq.eclair.db.{IncomingPaymentStatus, OutgoingPaymentStatus}
@ -248,6 +247,8 @@ object JsonSupport extends Json4sJacksonSupport {
new UInt64Serializer + new UInt64Serializer +
new SatoshiSerializer + new SatoshiSerializer +
new MilliSatoshiSerializer + new MilliSatoshiSerializer +
new CltvExpirySerializer +
new CltvExpiryDeltaSerializer +
new ShortChannelIdSerializer + new ShortChannelIdSerializer +
new StateSerializer + new StateSerializer +
new ShaChainSerializer + new ShaChainSerializer +

View file

@ -25,7 +25,6 @@ import fr.acinq.eclair.api.JsonSupport.CustomTypeHints
import fr.acinq.eclair.payment.{PaymentRequest, PaymentSettlingOnChain} import fr.acinq.eclair.payment.{PaymentRequest, PaymentSettlingOnChain}
import fr.acinq.eclair.transactions.{IN, OUT} import fr.acinq.eclair.transactions.{IN, OUT}
import fr.acinq.eclair.wire.{NodeAddress, Tor2, Tor3} import fr.acinq.eclair.wire.{NodeAddress, Tor2, Tor3}
import org.json4s.jackson.Serialization
import org.scalatest.{FunSuite, Matchers} import org.scalatest.{FunSuite, Matchers}
import scodec.bits._ import scodec.bits._
@ -34,8 +33,6 @@ class JsonSerializersSpec extends FunSuite with Matchers {
test("deserialize Map[OutPoint, ByteVector]") { test("deserialize Map[OutPoint, ByteVector]") {
val output1 = OutPoint(ByteVector32(hex"11418a2d282a40461966e4f578e1fdf633ad15c1b7fb3e771d14361127233be1"), 0) val output1 = OutPoint(ByteVector32(hex"11418a2d282a40461966e4f578e1fdf633ad15c1b7fb3e771d14361127233be1"), 0)
val output2 = OutPoint(ByteVector32(hex"3d62bd4f71dc63798418e59efbc7532380c900b5e79db3a5521374b161dd0e33"), 1) val output2 = OutPoint(ByteVector32(hex"3d62bd4f71dc63798418e59efbc7532380c900b5e79db3a5521374b161dd0e33"), 1)
val map = Map( val map = Map(
output1 -> hex"dead", output1 -> hex"dead",
output2 -> hex"beef" output2 -> hex"beef"
@ -43,12 +40,12 @@ class JsonSerializersSpec extends FunSuite with Matchers {
// it won't work with the default key serializer // it won't work with the default key serializer
val error = intercept[org.json4s.MappingException] { val error = intercept[org.json4s.MappingException] {
Serialization.write(map)(org.json4s.DefaultFormats) JsonSupport.serialization.write(map)(org.json4s.DefaultFormats)
} }
assert(error.msg.contains("Do not know how to serialize key of type class fr.acinq.bitcoin.OutPoint.")) assert(error.msg.contains("Do not know how to serialize key of type class fr.acinq.bitcoin.OutPoint."))
// but it works with our custom key serializer // but it works with our custom key serializer
val json = Serialization.write(map)(org.json4s.DefaultFormats + new ByteVectorSerializer + new OutPointKeySerializer) val json = JsonSupport.serialization.write(map)(org.json4s.DefaultFormats + new ByteVectorSerializer + new OutPointKeySerializer)
assert(json === s"""{"${output1.txid}:0":"dead","${output2.txid}:1":"beef"}""") assert(json === s"""{"${output1.txid}:0":"dead","${output2.txid}:1":"beef"}""")
} }
@ -58,15 +55,15 @@ class JsonSerializersSpec extends FunSuite with Matchers {
val tor2 = Tor2("aaaqeayeaudaocaj", 7777) val tor2 = Tor2("aaaqeayeaudaocaj", 7777)
val tor3 = Tor3("aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc", 9999) val tor3 = Tor3("aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc", 9999)
Serialization.write(ipv4)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""10.0.0.1:8888"""" JsonSupport.serialization.write(ipv4)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""10.0.0.1:8888""""
Serialization.write(ipv6LocalHost)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""[0:0:0:0:0:0:0:1]:9735"""" JsonSupport.serialization.write(ipv6LocalHost)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""[0:0:0:0:0:0:0:1]:9735""""
Serialization.write(tor2)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocaj.onion:7777"""" JsonSupport.serialization.write(tor2)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocaj.onion:7777""""
Serialization.write(tor3)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc.onion:9999"""" JsonSupport.serialization.write(tor3)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc.onion:9999""""
} }
test("Direction serialization") { test("Direction serialization") {
Serialization.write(IN)(org.json4s.DefaultFormats + new DirectionSerializer) shouldBe s""""IN"""" JsonSupport.serialization.write(IN)(org.json4s.DefaultFormats + new DirectionSerializer) shouldBe s""""IN""""
Serialization.write(OUT)(org.json4s.DefaultFormats + new DirectionSerializer) shouldBe s""""OUT"""" JsonSupport.serialization.write(OUT)(org.json4s.DefaultFormats + new DirectionSerializer) shouldBe s""""OUT""""
} }
test("Payment Request") { test("Payment Request") {
@ -78,7 +75,7 @@ class JsonSerializersSpec extends FunSuite with Matchers {
test("type hints") { test("type hints") {
implicit val formats = JsonSupport.json4sJacksonFormats.withTypeHintFieldName("type") + CustomTypeHints(Map(classOf[PaymentSettlingOnChain] -> "payment-settling-onchain")) + new MilliSatoshiSerializer implicit val formats = JsonSupport.json4sJacksonFormats.withTypeHintFieldName("type") + CustomTypeHints(Map(classOf[PaymentSettlingOnChain] -> "payment-settling-onchain")) + new MilliSatoshiSerializer
val e1 = PaymentSettlingOnChain(UUID.randomUUID, 42 msat, randomBytes32) val e1 = PaymentSettlingOnChain(UUID.randomUUID, 42 msat, randomBytes32)
assert(Serialization.writePretty(e1).contains("\"type\" : \"payment-settling-onchain\"")) assert(JsonSupport.serialization.writePretty(e1).contains("\"type\" : \"payment-settling-onchain\""))
} }
test("transaction serializer") { test("transaction serializer") {

View file

@ -64,8 +64,8 @@
<maven.compiler.target>1.7</maven.compiler.target> <maven.compiler.target>1.7</maven.compiler.target>
<scala.version>2.11.12</scala.version> <scala.version>2.11.12</scala.version>
<scala.version.short>2.11</scala.version.short> <scala.version.short>2.11</scala.version.short>
<akka.version>2.3.14</akka.version> <akka.version>2.3.14</akka.version>
<akka.http.version>10.0.11</akka.http.version> <akka.http.version>10.0.11</akka.http.version>
<sttp.version>1.3.9</sttp.version> <sttp.version>1.3.9</sttp.version>
<bitcoinlib.version>0.15</bitcoinlib.version> <bitcoinlib.version>0.15</bitcoinlib.version>
<guava.version>24.0-android</guava.version> <guava.version>24.0-android</guava.version>
@ -221,7 +221,8 @@
<systemProperties> <systemProperties>
<buildDirectory>${project.build.directory}</buildDirectory> <buildDirectory>${project.build.directory}</buildDirectory>
</systemProperties> </systemProperties>
<argLine>-Xmx1024m -Dfile.encoding=UTF-8</argLine> <argLine>-Xmx1024m</argLine>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>