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

com.wavesplatform.network.PeerDatabaseImpl.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.network

import com.google.common.base.Ticker
import com.google.common.cache.CacheBuilder
import com.google.common.collect.EvictingQueue
import com.wavesplatform.settings.NetworkSettings
import com.wavesplatform.utils.{JsonFileStorage, ScorexLogging}
import io.netty.channel.Channel
import io.netty.channel.socket.nio.NioSocketChannel

import java.net.{InetAddress, InetSocketAddress, URI}
import java.util.concurrent.TimeUnit
import scala.annotation.tailrec
import scala.collection.*
import scala.collection.immutable.Seq
import scala.concurrent.duration.FiniteDuration
import scala.jdk.CollectionConverters.*
import scala.util.Random
import scala.util.control.NonFatal

class PeerDatabaseImpl(settings: NetworkSettings, ticker: Ticker = Ticker.systemTicker()) extends PeerDatabase with AutoCloseable with ScorexLogging {
  private def cache[T <: AnyRef](timeout: FiniteDuration) =
    CacheBuilder
      .newBuilder()
      .ticker(ticker)
      .expireAfterWrite(timeout.toMillis, TimeUnit.MILLISECONDS)
      .build[T, java.lang.Long]()

  private type PeersPersistenceType = Set[String]
  private val peersPersistence = cache[InetSocketAddress](settings.peersDataResidenceTime)
  private val blacklist        = cache[InetAddress](settings.blackListResidenceTime)
  private val suspension       = cache[InetAddress](settings.suspensionResidenceTime)
  private val reasons          = mutable.Map.empty[InetAddress, String]
  private val unverifiedPeers  = EvictingQueue.create[InetSocketAddress](settings.maxUnverifiedPeers)

  private val IPAndPort = """(\d+)\.(\d+)\.(\d+)\.(\d+):(\d+)""".r

  for (f <- settings.file if f.exists && f.isFile && f.length > 0) try {
    JsonFileStorage.load[PeersPersistenceType](f.getCanonicalPath).map {
      case IPAndPort(a, b, c, d, port) =>
        addCandidate(new InetSocketAddress(InetAddress.getByAddress(Array(a, b, c, d).map(_.toInt.toByte)), port.toInt))
      case _ =>
    }

    log.info(s"Loaded ${unverifiedPeers.size} known peer(s) from ${f.getName}")
  } catch {
    case NonFatal(e) => log.info("Legacy or corrupted peers.dat, ignoring, starting all over from known-peers...", e)
  }

  override def addCandidate(socketAddress: InetSocketAddress): Boolean = unverifiedPeers.synchronized {
    val r = !socketAddress.getAddress.isAnyLocalAddress &&
      !(socketAddress.getAddress.isLoopbackAddress && settings.bindAddress.exists(_.getPort == socketAddress.getPort)) &&
      Option(peersPersistence.getIfPresent(socketAddress)).isEmpty &&
      !unverifiedPeers.contains(socketAddress)
    if (r) unverifiedPeers.add(socketAddress)
    r
  }

  private def doTouch(socketAddress: InetSocketAddress, timestamp: Long): Unit = unverifiedPeers.synchronized {
    unverifiedPeers.removeIf(_ == socketAddress)
    peersPersistence.put(socketAddress, Option(peersPersistence.getIfPresent(socketAddress)).fold(timestamp)(_.toLong.max(timestamp)))
  }

  override def touch(socketAddress: InetSocketAddress): Unit = doTouch(socketAddress, System.currentTimeMillis())

  override def blacklist(inetAddress: InetAddress, reason: String): Unit =
    if (settings.enableBlacklisting) {
      unverifiedPeers.synchronized {
        unverifiedPeers.removeIf(_.getAddress == inetAddress)
        blacklist.put(inetAddress, ticker.read())
        reasons.put(inetAddress, reason)
      }
    }

  override def suspend(socketAddress: InetSocketAddress): Unit = getAddress(socketAddress).foreach { address =>
    unverifiedPeers.synchronized {
      log.trace(s"Suspending $socketAddress")
      unverifiedPeers.removeIf(_ == socketAddress)
      suspension.put(address, System.currentTimeMillis())
    }
  }

  override def knownPeers: immutable.Map[InetSocketAddress, Long] = {
    peersPersistence.cleanUp() // run all deferred actions (expiration/listeners/etc)
    peersPersistence
      .asMap()
      .asScala
      .collect {
        case (addr, ts) if !(settings.enableBlacklisting && isBlacklisted(addr.getAddress)) => addr -> ts.toLong
      }
      .toMap
  }

  def isBlacklisted(address: InetAddress): Boolean = blacklist.asMap().containsKey(address)
  def isSuspended(address: InetAddress): Boolean   = suspension.asMap().containsKey(address)

  override def detailedBlacklist: immutable.Map[InetAddress, (Long, String)] =
    blacklist.asMap().asScala.view.mapValues(_.toLong).map { case (h, t) => h -> ((t, Option(reasons(h)).getOrElse(""))) }.toMap

  override def detailedSuspended: immutable.Map[InetAddress, Long] = suspension.asMap().asScala.view.mapValues(_.toLong).toMap

  private def resolvePeerAddress(addr: String): Seq[InetSocketAddress] = {
    val uri = new URI(s"node://$addr")
    require(uri.getPort > 0, s"invalid port ${uri.getPort}")
    InetAddress
      .getAllByName(uri.getHost)
      .view
      .map { ia =>
        new InetSocketAddress(ia, uri.getPort)
      }
      .toSeq
  }

  override def nextCandidate(excluded: immutable.Set[InetSocketAddress]): Option[InetSocketAddress] = unverifiedPeers.synchronized {
    def excludeAddress(isa: InetSocketAddress): Boolean = {
      excluded(isa) || isBlacklisted(isa.getAddress) || isSuspended(isa.getAddress)
    }

    @tailrec
    def nextUnverified(): Option[InetSocketAddress] = {
      unverifiedPeers.poll() match {
        case null => None
        case nonNull =>
          if (!excludeAddress(nonNull)) Some(nonNull) else nextUnverified()
      }
    }

    settings.knownPeers
      .flatMap(p => resolvePeerAddress(p))
      .filterNot(excludeAddress)
      .headOption
      .orElse(nextUnverified())
      .orElse(Random.shuffle(knownPeers.keySet.filterNot(excludeAddress)).headOption)
  }

  def clearBlacklist(): Unit = {
    blacklist.invalidateAll()
    reasons.clear()
  }

  override def close(): Unit = settings.file.foreach { f =>
    val rawPeers = knownPeers.keySet.map(address => s"${address.getAddress.getHostAddress}:${address.getPort}")

    log.info(s"Saving ${rawPeers.size} known peer(s) to ${f.getName}")

    JsonFileStorage.save[PeersPersistenceType](rawPeers, f.getCanonicalPath)
  }

  override def blacklistAndClose(channel: Channel, reason: String): Unit = getRemoteAddress(channel).foreach { x =>
    log.debug(s"Blacklisting ${id(channel)}: $reason")
    blacklist(x.getAddress, reason)
    channel.close()
  }

  private def getAddress(socketAddress: InetSocketAddress): Option[InetAddress] = {
    val r = Option(socketAddress.getAddress)
    if (r.isEmpty) log.debug(s"Can't obtain an address from $socketAddress")
    r
  }

  private def getRemoteAddress(channel: Channel): Option[InetSocketAddress] = channel match {
    case x: NioSocketChannel => Option(x.remoteAddress())
    case x =>
      log.debug(s"Doesn't know how to get a remoteAddress from $x")
      None
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy