All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.acinq.eclair.gui.GUIUpdater.scala Maven / Gradle / Ivy

There is a newer version: 0.3.3
Show newest version
/*
 * Copyright 2019 ACINQ SAS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.acinq.eclair.gui

import java.time.LocalDateTime

import akka.actor.{Actor, ActorLogging, ActorRef, Terminated}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin._
import fr.acinq.eclair.CoinUtils
import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor.{ZMQConnected, ZMQDisconnected}
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{ElectrumDisconnected, ElectrumReady}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.gui.controllers._
import fr.acinq.eclair.payment._
import fr.acinq.eclair.router.{NORMAL => _, _}
import javafx.application.Platform
import javafx.fxml.FXMLLoader
import javafx.scene.layout.VBox

import scala.collection.JavaConversions._

/**
 * Created by PM on 16/08/2016.
 */

class GUIUpdater(mainController: MainController) extends Actor with ActorLogging {

  val STATE_MUTUAL_CLOSE = Set(WAIT_FOR_INIT_INTERNAL, WAIT_FOR_OPEN_CHANNEL, WAIT_FOR_ACCEPT_CHANNEL, WAIT_FOR_FUNDING_INTERNAL, WAIT_FOR_FUNDING_CREATED, WAIT_FOR_FUNDING_SIGNED, NORMAL)
  val STATE_FORCE_CLOSE = Set(WAIT_FOR_FUNDING_CONFIRMED, WAIT_FOR_FUNDING_LOCKED, NORMAL, SHUTDOWN, NEGOTIATING, OFFLINE, SYNCING)

  /**
   * Needed to stop JavaFX complaining about updates from non GUI thread
   */
  private def runInGuiThread(f: () => Unit): Unit = {
    Platform.runLater(new Runnable() {
      @Override def run(): Unit = f()
    })
  }

  def receive: Receive = main(Map())

  def createChannelPanel(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, channelId: ByteVector32): (ChannelPaneController, VBox) = {
    log.info(s"new channel: $channel")
    val loader = new FXMLLoader(getClass.getResource("/gui/main/channelPane.fxml"))
    val channelPaneController = new ChannelPaneController(channel, remoteNodeId.toString())
    loader.setController(channelPaneController)
    val root = loader.load[VBox]
    channelPaneController.channelId.setText(channelId.toHex)
    channelPaneController.funder.setText(if (isFunder) "Yes" else "No")

    // set the node alias if the node has already been announced
    if (mainController.networkNodesMap.containsKey(remoteNodeId)) {
      val na = mainController.networkNodesMap.get(remoteNodeId)
      channelPaneController.updateRemoteNodeAlias(na.alias)
    }

    (channelPaneController, root)
  }

  def main(m: Map[ActorRef, ChannelPaneController]): Receive = {

    case ChannelCreated(channel, peer, remoteNodeId, isFunder, temporaryChannelId, _, _) =>
      context.watch(channel)
      val (channelPaneController, root) = createChannelPanel(channel, peer, remoteNodeId, isFunder, temporaryChannelId)
      runInGuiThread(() => mainController.channelBox.getChildren.addAll(root))
      context.become(main(m + (channel -> channelPaneController)))

    case ChannelRestored(channel, peer, remoteNodeId, isFunder, channelId, currentData) =>
      context.watch(channel)
      val (channelPaneController, root) = createChannelPanel(channel, peer, remoteNodeId, isFunder, channelId)
      channelPaneController.updateBalance(currentData.commitments)
      val m1 = m + (channel -> channelPaneController)
      val totalBalance = m1.values.map(_.getBalance).sum
      runInGuiThread(() => {
        channelPaneController.refreshBalance()
        mainController.refreshTotalBalance(totalBalance)
        channelPaneController.txId.setText(currentData.commitments.commitInput.outPoint.txid.toHex)
        mainController.channelBox.getChildren.addAll(root)
      })
      context.become(main(m1))

    case ShortChannelIdAssigned(channel, _, shortChannelId) if m.contains(channel) =>
      val channelPaneController = m(channel)
      runInGuiThread(() => channelPaneController.shortChannelId.setText(shortChannelId.toString))

    case ChannelIdAssigned(channel, _, _, channelId) if m.contains(channel) =>
      val channelPaneController = m(channel)
      runInGuiThread(() => channelPaneController.channelId.setText(channelId.toHex))

    case ChannelStateChanged(channel, _, _, _, currentState, currentData) if m.contains(channel) =>
      val channelPaneController = m(channel)
      runInGuiThread { () =>
        (currentState, currentData) match {
          case (WAIT_FOR_FUNDING_CONFIRMED, d: HasCommitments) => channelPaneController.txId.setText(d.commitments.commitInput.outPoint.txid.toHex)
          case _ =>
        }
        channelPaneController.close.setVisible(STATE_MUTUAL_CLOSE.contains(currentState))
        channelPaneController.forceclose.setVisible(STATE_FORCE_CLOSE.contains(currentState))
        channelPaneController.state.setText(currentState.toString)
      }

    case ChannelSignatureReceived(channel, commitments) if m.contains(channel) =>
      val channelPaneController = m(channel)
      channelPaneController.updateBalance(commitments)
      val totalBalance = m.values.map(_.getBalance).sum
      runInGuiThread(() => {
        channelPaneController.refreshBalance()
        mainController.refreshTotalBalance(totalBalance)
      })

    case Terminated(actor) if m.contains(actor) =>
      val channelPaneController = m(actor)
      log.debug(s"channel=${channelPaneController.channelId.getText} to be removed from gui")
      runInGuiThread(() => mainController.channelBox.getChildren.remove(channelPaneController.root))
      val m1 = m - actor
      val totalBalance = m1.values.map(_.getBalance).sum
      runInGuiThread(() => {
        mainController.refreshTotalBalance(totalBalance)
      })
      context.become(main(m1))

    case NodesDiscovered(nodeAnnouncements) =>
      runInGuiThread { () =>
        nodeAnnouncements.foreach { nodeAnnouncement =>
          log.debug(s"peer node discovered with node id={}", nodeAnnouncement.nodeId)
          if (!mainController.networkNodesMap.containsKey(nodeAnnouncement.nodeId)) {
            mainController.networkNodesMap.put(nodeAnnouncement.nodeId, nodeAnnouncement)
            m.foreach(f => if (nodeAnnouncement.nodeId.toString.equals(f._2.peerNodeId)) {
              f._2.updateRemoteNodeAlias(nodeAnnouncement.alias)
            })
          }
        }
      }

    case NodeLost(nodeId) =>
      log.debug(s"peer node lost with node id=$nodeId")
      runInGuiThread { () =>
        mainController.networkNodesMap.remove(nodeId)
      }

    case NodeUpdated(nodeAnnouncement) =>
      log.debug(s"peer node with id=${nodeAnnouncement.nodeId} has been updated")
      runInGuiThread { () =>
        mainController.networkNodesMap.put(nodeAnnouncement.nodeId, nodeAnnouncement)
        m.foreach(f => if (nodeAnnouncement.nodeId.toString.equals(f._2.peerNodeId)) {
          f._2.updateRemoteNodeAlias(nodeAnnouncement.alias)
        })
      }

    case ChannelsDiscovered(channelsDiscovered) =>
      runInGuiThread { () =>
        channelsDiscovered.foreach { case SingleChannelDiscovered(channelAnnouncement, capacity) =>
          log.debug(s"peer channel discovered with channel id={}", channelAnnouncement.shortChannelId)
          if (!mainController.networkChannelsMap.containsKey(channelAnnouncement.shortChannelId)) {
            mainController.networkChannelsMap.put(channelAnnouncement.shortChannelId, ChannelInfo(channelAnnouncement, None, None, None, None, capacity, None, None))
          }
        }
      }

    case ChannelLost(shortChannelId) =>
      log.debug(s"peer channel lost with channel id=$shortChannelId")
      runInGuiThread { () =>
        mainController.networkChannelsMap.remove(shortChannelId)
      }

    case ChannelUpdatesReceived(channelUpdates) =>
      runInGuiThread { () =>
        channelUpdates.foreach { channelUpdate =>
          log.debug(s"peer channel with id={} has been updated - flags: {} fees: {} {}", channelUpdate.shortChannelId, channelUpdate.channelFlags, channelUpdate.feeBaseMsat, channelUpdate.feeProportionalMillionths)
          if (mainController.networkChannelsMap.containsKey(channelUpdate.shortChannelId)) {
            val c = mainController.networkChannelsMap.get(channelUpdate.shortChannelId)
            if (Announcements.isNode1(channelUpdate.channelFlags)) {
              c.isNode1Enabled = Some(Announcements.isEnabled(channelUpdate.channelFlags))
              c.feeBaseMsatNode1_opt = Some(channelUpdate.feeBaseMsat.toLong)
              c.feeProportionalMillionthsNode1_opt = Some(channelUpdate.feeProportionalMillionths)
            } else {
              c.isNode2Enabled = Some(Announcements.isEnabled(channelUpdate.channelFlags))
              c.feeBaseMsatNode2_opt = Some(channelUpdate.feeBaseMsat.toLong)
              c.feeProportionalMillionthsNode2_opt = Some(channelUpdate.feeProportionalMillionths)
            }
            mainController.networkChannelsMap.put(channelUpdate.shortChannelId, c)
          }
        }
      }

    case p: PaymentFailed =>
      val distilledFailures = PaymentFailure.transformForUser(p.failures)
      val message = s"${distilledFailures.size} attempts:\n${
        distilledFailures.map {
          case LocalFailure(t) => s"- (local) ${t.getMessage}"
          case RemoteFailure(_, e) => s"- (remote) ${e.failureMessage.message}"
          case _ => "- Unknown error"
        }.mkString("\n")
      }"
      mainController.handlers.notification("Payment Failed", message, NOTIFICATION_ERROR)

    case p: PaymentSent =>
      log.debug(s"payment sent with h=${p.paymentHash}, amount=${p.amount}, fees=${p.feesPaid}")
      val message = CoinUtils.formatAmountInUnit(p.amount + p.feesPaid, FxApp.getUnit, withUnit = true)
      mainController.handlers.notification("Payment Sent", message, NOTIFICATION_SUCCESS)
      runInGuiThread(() => mainController.paymentSentList.prepend(PaymentSentRecord(p, LocalDateTime.now())))

    case p: PaymentReceived =>
      log.debug(s"payment received with h=${p.paymentHash}, amount=${p.amount}")
      runInGuiThread(() => mainController.paymentReceivedList.prepend(PaymentReceivedRecord(p, LocalDateTime.now())))

    case p: PaymentRelayed =>
      log.debug(s"payment relayed with h=${p.paymentHash}, amount=${p.amountIn}, feesEarned=${p.amountOut}")
      runInGuiThread(() => mainController.paymentRelayedList.prepend(PaymentRelayedRecord(p, LocalDateTime.now())))

    case ZMQConnected =>
      log.debug("ZMQ connection UP")
      runInGuiThread(() => mainController.hideBlockerModal)

    case ZMQDisconnected =>
      log.debug("ZMQ connection DOWN")
      runInGuiThread(() => mainController.showBlockerModal("Groestlcoin Core"))

    case _: ElectrumReady =>
      log.debug("Electrum connection UP")
      runInGuiThread(() => mainController.hideBlockerModal)

    case ElectrumDisconnected =>
      log.debug("Electrum connection DOWN")
      runInGuiThread(() => mainController.showBlockerModal("Electrum"))
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy