diff --git a/it/src/main/scala/encry/it/api/HttpApi.scala b/it/src/main/scala/encry/it/api/HttpApi.scala index 4eb3ebe547..52b2553876 100644 --- a/it/src/main/scala/encry/it/api/HttpApi.scala +++ b/it/src/main/scala/encry/it/api/HttpApi.scala @@ -90,6 +90,15 @@ trait HttpApi { // scalastyle:ignore ) } + def bestFullHeaderId: Future[String] = get("/info") flatMap { r => + val response = jsonAnswerAs[Json](r.getResponseBody) + val eitherId = response.hcursor.downField("bestFullHeaderId").as[Option[String]] + eitherId.fold[Future[String]]( + e => Future.failed(new Exception(s"Error getting `bestFullHeaderId` from /info response: $e\n$response", e)), + maybeId => Future.successful(maybeId.getOrElse("")) + ) + } + def balances: Future[Map[String, Long]] = get("/wallet/info") flatMap { r => val response = jsonAnswerAs[Json](r.getResponseBody) val eitherBalance = response.hcursor.downField("balances").as[Map[String, String]] @@ -148,13 +157,23 @@ trait HttpApi { // scalastyle:ignore waitFor[Int](_.headersHeight, h => h >= expectedHeight, retryingInterval) } + def waitForBestFullHeaderId(expectedId: String, retryingInterval: FiniteDuration = 1.minute): Future[String] = { + waitFor[String](_.bestFullHeaderId, id => id == expectedId, retryingInterval) + } + def waitFor[A](f: this.type => Future[A], cond: A => Boolean, retryInterval: FiniteDuration): Future[A] = { timer.retryUntil(f(this), cond, retryInterval) } def connect(addressAndPort: String): Future[Unit] = post("/peers/connect", addressAndPort).map(_ => ()) + def shutdown: Future[Unit] = post("/node/shutdown", "").map(_ => ()) + def postJson[A: Encoder](path: String, body: A): Future[Response] = post(path, body.asJson.toString()) + def error(): Unit = { + + } + } \ No newline at end of file diff --git a/it/src/main/scala/encry/it/configs/Configs.scala b/it/src/main/scala/encry/it/configs/Configs.scala index cd06c72f23..a7c940976d 100644 --- a/it/src/main/scala/encry/it/configs/Configs.scala +++ b/it/src/main/scala/encry/it/configs/Configs.scala @@ -22,6 +22,12 @@ object Configs { """.stripMargin ) + def connectOnlyWithKnownPeers(connectOnlyWithKnownPeersEnable: Boolean): Config = ConfigFactory.parseString( + s""" + |encry.network.connectOnlyWithKnownPeers=$connectOnlyWithKnownPeersEnable + """.stripMargin + ) + def miningDelay(miningDelay: Int): Config = ConfigFactory.parseString( s""" |encry.node.miningDelay=${miningDelay}s @@ -44,4 +50,23 @@ object Configs { |encry.wallet.seed="$key" """.stripMargin ) + + def networkAddress(address: String): Config = ConfigFactory.parseString( + s""" + |encry.network.bindAddress = "$address" + """.stripMargin + ) + + def apiAddress(address: String): Config = ConfigFactory.parseString( + s""" + |encry.restApi.bindAddress = "$address" + """.stripMargin + ) + + def constantsClass(name: String): Config = ConfigFactory.parseString( + s""" + |encry.node.constantsClass="$name" + """.stripMargin + ) + } diff --git a/it/src/main/scala/encry/it/docker/Docker.scala b/it/src/main/scala/encry/it/docker/Docker.scala index fe5f580302..1012fe7688 100644 --- a/it/src/main/scala/encry/it/docker/Docker.scala +++ b/it/src/main/scala/encry/it/docker/Docker.scala @@ -29,7 +29,7 @@ import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import scala.concurrent.{Await, Future, blocking} -import scala.util.Random +import scala.util.{Random, Try} import scala.util.control.NonFatal case class Docker(suiteConfig: Config = empty, @@ -210,13 +210,13 @@ case class Docker(suiteConfig: Config = empty, .build() } - def startNodeInternal(nodeConfig: Config): Node = + def startNodeInternal(nodeConfig: Config, specialVolumeOpt: Option[(String, String)] = None): Node = try { val settings = EncryAppSettings.fromConfig(nodeConfig.withFallback(configTemplate)) val nodeNumber = settings.network.nodeName.map(_.replace("node", "").toInt).getOrElse(0) val ip = ipForNode(nodeNumber) - val containerConfig = buildPeerContainerConfig(nodeConfig, EncryAppSettings.fromConfig(nodeConfig), ip) + val containerConfig = buildPeerContainerConfig(nodeConfig, EncryAppSettings.fromConfig(nodeConfig), ip, specialVolumeOpt) val containerId = { val containerName = networkName + "-" + settings.network.nodeName.getOrElse("NodeWithoutName") + "-" + uuidShort @@ -233,8 +233,8 @@ case class Docker(suiteConfig: Config = empty, client.startContainer(containerId) val containerInfo = client.inspectContainer(containerId) val ports = containerInfo.networkSettings().ports() - val hostPort = extractHostPort(ports, 9001) - val hostRestApiPort = extractHostPort(ports, 9051) //get port from settings + val hostPort = extractHostPort(ports, extractNetworkPortFromConfig(nodeConfig).getOrElse(9001)) + val hostRestApiPort = extractHostPort(ports, extractApiPortFromConfig(nodeConfig).getOrElse(9051)) val node = new Node(nodeConfig, hostRestApiPort, containerId, attachedNetwork.ipAddress(), hostPort, http) nodes.add(node) logger.debug(s"Started $containerId -> ${node.name}") @@ -246,10 +246,19 @@ case class Docker(suiteConfig: Config = empty, throw e } + def stopNode(node: Node, secondsToWaitBeforeKilling: Int = 0) { + client.stopContainer(node.containerId, secondsToWaitBeforeKilling) + } def extractHostPort(portBindingMap: JMap[String, JList[PortBinding]], containerPort: Int): Int = portBindingMap.get(s"$containerPort/tcp").get(0).hostPort().toInt + def extractNetworkPortFromConfig(config: Config): Option[Int] = + Try(config.getString("encry.network.bindAddress").split(":")(1).toInt).toOption + + def extractApiPortFromConfig(config: Config): Option[Int] = + Try(config.getString("encry.restApi.bindAddress").split(":")(1).toInt).toOption + private def saveNodeLogs(): Unit = { val logDir = Paths.get(System.getProperty("user.dir"), "target", "logs") Files.createDirectories(logDir) diff --git a/it/src/main/scala/encry/it/util/WaitUtils.scala b/it/src/main/scala/encry/it/util/WaitUtils.scala new file mode 100644 index 0000000000..6f00d0d2a2 --- /dev/null +++ b/it/src/main/scala/encry/it/util/WaitUtils.scala @@ -0,0 +1,22 @@ +package encry.it.util + +import scala.annotation.tailrec +import scala.concurrent.duration.Duration + +object WaitUtils { + + def waitForEqualsId(id1Func: => String, id2Func: => String)(implicit duration: Duration): (String, String) = { + @tailrec + def loop(id1Func: => String, id2Func: => String, maxTries: Long): (String, String) = { + val id1: String = id1Func + val id2: String = id2Func + if (id1 != id2 && maxTries > 0) { + Thread.sleep(1000) + loop(id1Func, id2Func, maxTries - 1) + } else (id1, id2) + } + + loop(id1Func, id2Func, duration.toSeconds) + } + +} diff --git a/it/src/test/scala/encry/it/MinerWalletBalanceTest.scala b/it/src/test/scala/encry/it/miner/MinerWalletBalanceTest.scala similarity index 98% rename from it/src/test/scala/encry/it/MinerWalletBalanceTest.scala rename to it/src/test/scala/encry/it/miner/MinerWalletBalanceTest.scala index 42be4ddfdd..d2dc5b0b7c 100644 --- a/it/src/test/scala/encry/it/MinerWalletBalanceTest.scala +++ b/it/src/test/scala/encry/it/miner/MinerWalletBalanceTest.scala @@ -1,4 +1,4 @@ -package encry.it +package encry.it.miner import com.typesafe.config.Config import encry.consensus.EncrySupplyController diff --git a/it/src/test/scala/encry/it/net/NodeRestartTest.scala b/it/src/test/scala/encry/it/net/NodeRestartTest.scala new file mode 100644 index 0000000000..c7611d4ebf --- /dev/null +++ b/it/src/test/scala/encry/it/net/NodeRestartTest.scala @@ -0,0 +1,78 @@ +package encry.it.net + +import java.nio.file.Paths + +import encry.it.configs.Configs +import encry.it.docker.Docker.defaultConf +import encry.it.docker.DockerAfterAll +import encry.it.util.WaitUtils._ +import encry.it.utils.FutureAwait._ +import org.encryfoundation.common.utils.Algos +import org.scalatest.{AsyncFunSuite, Matchers} +import scorex.utils.Random + +import scala.concurrent.duration._ + +class NodeRestartTest extends AsyncFunSuite with Matchers with DockerAfterAll { + + implicit val futureDuration: FiniteDuration = 10 minutes + + test("Nodes should sync after restart with new offlineGeneration and port") { + val node1 = docker + .startNodeInternal(Configs.mining(true) + .withFallback(Configs.nodeName("node1")) + .withFallback(Configs.offlineGeneration(true)) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(defaultConf) + ) + + val userDir = Paths.get(System.getProperty("user.dir")) + val volumeName = Algos.encode(Random.randomBytes(32)) + val containerMountPath = userDir + "/encry/data" + + val node21 = docker + .startNodeInternal(Configs.mining(true) + .withFallback(Configs.nodeName("node21")) + .withFallback(Configs.offlineGeneration(false)) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(defaultConf), + Some(volumeName, containerMountPath) + ) + + node1.connect(s"${node21.nodeIp}:9001").await + node21.connect(s"${node1.nodeIp}:9001").await + + node1.waitForFullHeight(5).await + + node21.shutdown + Thread.sleep(5000) + docker.stopNode(node21, 5) + Thread.sleep(7000) + + node1.waitForFullHeight(10).await + + val node22 = docker + .startNodeInternal(Configs.mining(true) + .withFallback(Configs.nodeName("node22")) + .withFallback(Configs.offlineGeneration(true)) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(Configs.networkAddress("0.0.0.0:9002")) + .withFallback(Configs.apiAddress("0.0.0.0:9052")) + .withFallback(defaultConf), + Some(volumeName, containerMountPath) + ) + + node1.waitForFullHeight(15).await + + node1.connect(s"${node22.nodeIp}:9002").await + node22.connect(s"${node1.nodeIp}:9001").await + + node1.waitForFullHeight(20).await + + val (bestFullHeaderId1, bestFullHeaderId2) = + waitForEqualsId(node1.bestFullHeaderId.await, node22.bestFullHeaderId.await) + + bestFullHeaderId2 shouldEqual bestFullHeaderId1 + } + +} diff --git a/it/src/test/scala/encry/it/net/ThreeNodesKnowAboutEachOtherTest.scala b/it/src/test/scala/encry/it/net/ThreeNodesKnowAboutEachOtherTest.scala new file mode 100644 index 0000000000..e8b0471341 --- /dev/null +++ b/it/src/test/scala/encry/it/net/ThreeNodesKnowAboutEachOtherTest.scala @@ -0,0 +1,59 @@ +package encry.it.net + +import encry.it.configs.Configs +import encry.it.docker.Docker.defaultConf +import encry.it.docker.DockerAfterAll +import encry.it.util.WaitUtils._ +import encry.it.utils.FutureAwait._ +import org.scalatest.{FunSuite, Matchers} + +import scala.concurrent.duration._ + +class ThreeNodesKnowAboutEachOtherTest extends FunSuite with Matchers with DockerAfterAll { + + implicit val futureDuration: FiniteDuration = 10 minutes + val heightSeparation = 10 //blocks + + test("nodes know about each other should sync") { + + val node1 = docker + .startNodeInternal(Configs.nodeName("node1") + .withFallback(Configs.mining(true)) + .withFallback(Configs.offlineGeneration(true)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(defaultConf) + ) + + node1.waitForFullHeight(heightSeparation).await + + val node2 = docker + .startNodeInternal(Configs.nodeName("node2") + .withFallback(Configs.mining(true)) + .withFallback(Configs.offlineGeneration(false)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq((node1.nodeIp, 9001)))) + .withFallback(defaultConf) + ) + + node1.connect(s"${node2.nodeIp}:9001").await + + node1.waitForFullHeight(heightSeparation * 2).await + + val node3 = docker + .startNodeInternal(Configs.nodeName("node3") + .withFallback(Configs.mining(true)) + .withFallback(Configs.offlineGeneration(false)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq((node1.nodeIp, 9001), (node2.nodeIp, 9001)))) + .withFallback(defaultConf) + ) + + waitForEqualsId(node1.bestFullHeaderId.await, node3.bestFullHeaderId.await) + + val (bestFullHeaderId2, bestFullHeaderId3) = + waitForEqualsId(node2.bestFullHeaderId.await, node3.bestFullHeaderId.await) + + bestFullHeaderId2 shouldEqual bestFullHeaderId3 + } +} diff --git a/it/src/test/scala/encry/it/net/TwoNodesTest.scala b/it/src/test/scala/encry/it/net/TwoNodesTest.scala new file mode 100644 index 0000000000..196ee9d7c4 --- /dev/null +++ b/it/src/test/scala/encry/it/net/TwoNodesTest.scala @@ -0,0 +1,44 @@ +package encry.it.net + +import encry.it.configs.Configs +import encry.it.docker.Docker.defaultConf +import encry.it.docker.{Docker, DockerAfterAll} +import encry.it.util.WaitUtils._ +import encry.it.utils.FutureAwait._ +import org.scalatest.{FunSuite, Matchers} + +import scala.concurrent.duration._ + +class TwoNodesTest extends FunSuite with Matchers with DockerAfterAll { + + implicit val futureDuration: FiniteDuration = 10 minutes + val heightSeparation = 10 //blocks + + test("Late node should sync with one node") { + + val miningNodeConfig = Configs.mining(true) + .withFallback(Configs.offlineGeneration(true)) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Docker.defaultConf) + + val node1 = docker + .startNodeInternal(miningNodeConfig.withFallback(Configs.nodeName("node1"))) + + node1.waitForFullHeight(heightSeparation).await + + val node2 = docker + .startNodeInternal( + Configs.nodeName("node2") + .withFallback(Configs.mining(false)) + .withFallback(Configs.knownPeers(Seq((node1.nodeIp, 9001)))) + .withFallback(defaultConf) + ) + + val (bestFullHeaderId1, bestFullHeaderId2) = + waitForEqualsId(node1.bestFullHeaderId.await, node2.bestFullHeaderId.await) + + bestFullHeaderId2 shouldEqual bestFullHeaderId1 + } + +} diff --git "a/it/src/test/scala/encry/it/net/TwoNodes\320\241onnectedThroughOneTest.scala" "b/it/src/test/scala/encry/it/net/TwoNodes\320\241onnectedThroughOneTest.scala" new file mode 100644 index 0000000000..ecd600073b --- /dev/null +++ "b/it/src/test/scala/encry/it/net/TwoNodes\320\241onnectedThroughOneTest.scala" @@ -0,0 +1,57 @@ +package encry.it.net + +import encry.it.configs.Configs +import encry.it.docker.Docker.defaultConf +import encry.it.docker.DockerAfterAll +import encry.it.util.WaitUtils._ +import encry.it.utils.FutureAwait._ +import org.scalatest.{FunSuite, Matchers} + +import scala.concurrent.duration._ + +class TwoNodesСonnectedThroughOneTest extends FunSuite with Matchers with DockerAfterAll { + + implicit val futureDuration: FiniteDuration = 10 minutes + val heightSeparation = 10 //blocks + + test("nodes connected with first should sync") { + + val node1 = docker + .startNodeInternal(Configs.nodeName("node1") + .withFallback(Configs.mining(true)) + .withFallback(Configs.offlineGeneration(true)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(defaultConf) + ) + + node1.waitForFullHeight(heightSeparation).await + + val node2 = docker + .startNodeInternal(Configs.nodeName("node2") + .withFallback(Configs.mining(true)) + .withFallback(Configs.offlineGeneration(false)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(defaultConf) + ) + + node1.waitForFullHeight(heightSeparation * 2).await + + val node3 = docker + .startNodeInternal(Configs.nodeName("node3") + .withFallback(Configs.mining(true)) + .withFallback(Configs.offlineGeneration(false)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq((node1.nodeIp, 9001), (node2.nodeIp, 9001)))) + .withFallback(defaultConf) + ) + + waitForEqualsId(node1.bestFullHeaderId.await, node3.bestFullHeaderId.await) + + val (bestFullHeaderId2, bestFullHeaderId3) = + waitForEqualsId(node2.bestFullHeaderId.await, node3.bestFullHeaderId.await) + + bestFullHeaderId2 shouldEqual bestFullHeaderId3 + } +} diff --git a/it/src/test/scala/encry/it/net/TwoOfflinegenDelayTest.scala b/it/src/test/scala/encry/it/net/TwoOfflinegenDelayTest.scala new file mode 100644 index 0000000000..c354cef911 --- /dev/null +++ b/it/src/test/scala/encry/it/net/TwoOfflinegenDelayTest.scala @@ -0,0 +1,55 @@ +package encry.it.net + +import encry.it.configs.Configs +import encry.it.docker.Docker.defaultConf +import encry.it.docker.DockerAfterAll +import encry.it.util.WaitUtils._ +import org.scalatest.{FunSuite, Matchers} +import encry.it.utils.FutureAwait._ + +import scala.concurrent.duration._ + + +class TwoOfflinegenDelayTest extends FunSuite with Matchers with DockerAfterAll { + + implicit val futureDuration: FiniteDuration = 20 minutes + val heightSeparation = 10 //blocks + + test("Third node should sync with two offgen nodes started with delayed") { + + val miningNodeConfig = Configs.mining(true) + .withFallback(Configs.offlineGeneration(true)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(Configs.constantsClass("SlowMiningConstants")) + .withFallback(defaultConf) + + val node1 = docker + .startNodeInternal(miningNodeConfig.withFallback(Configs.nodeName("node1"))) + + node1.waitForFullHeight(heightSeparation).await + + val node2 = docker + .startNodeInternal(miningNodeConfig.withFallback(Configs.nodeName("node2"))) + + node1.waitForFullHeight(heightSeparation * 2).await + + val node3 = docker + .startNodeInternal( + Configs.nodeName("node3") + .withFallback(Configs.mining(false)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq((node1.nodeIp, 9001), (node2.nodeIp, 9001)))) + .withFallback(defaultConf) + ) + + val (bestFullHeaderId13, bestFullHeaderId3) = + waitForEqualsId(node1.bestFullHeaderId.await, node3.bestFullHeaderId.await) + + val (bestFullHeaderId12, bestFullHeaderId2) = + waitForEqualsId(node1.bestFullHeaderId.await, node2.bestFullHeaderId.await) + + bestFullHeaderId3 shouldEqual bestFullHeaderId13 + bestFullHeaderId2 shouldEqual bestFullHeaderId12 + } +} diff --git a/it/src/test/scala/encry/it/net/TwoOfflinegenTest.scala b/it/src/test/scala/encry/it/net/TwoOfflinegenTest.scala new file mode 100644 index 0000000000..54eed94b8c --- /dev/null +++ b/it/src/test/scala/encry/it/net/TwoOfflinegenTest.scala @@ -0,0 +1,48 @@ +package encry.it.net + +import encry.it.configs.Configs +import encry.it.docker.Docker.defaultConf +import encry.it.docker.DockerAfterAll +import encry.it.util.WaitUtils._ +import encry.it.utils.FutureAwait._ +import org.scalatest.{FunSuite, Matchers} + +import scala.concurrent.duration._ + +class TwoOfflinegenTest extends FunSuite with Matchers with DockerAfterAll { + + implicit val futureDuration: FiniteDuration = 20 minutes + val heightSeparation = 5 //blocks + + test("Third node should sync with two offgen nodes") { + + val miningNodeConfig = Configs.mining(true) + .withFallback(Configs.offlineGeneration(true)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq())) + .withFallback(Configs.constantsClass("SlowMiningConstants")) + .withFallback(defaultConf) + + val node1 = docker + .startNodeInternal(miningNodeConfig.withFallback(Configs.nodeName("node1"))) + + val node2 = docker + .startNodeInternal(miningNodeConfig.withFallback(Configs.nodeName("node2"))) + + node1.waitForFullHeight(heightSeparation).await + + val node3 = docker + .startNodeInternal( + Configs.nodeName("node3") + .withFallback(Configs.mining(false)) + .withFallback(Configs.networkAddress("0.0.0.0:9001")) + .withFallback(Configs.knownPeers(Seq((node1.nodeIp, 9001), (node2.nodeIp, 9001)))) + .withFallback(defaultConf) + ) + + val (bestFullHeaderId1, bestFullHeaderId3) = + waitForEqualsId(node1.bestFullHeaderId.await, node3.bestFullHeaderId.await) + + bestFullHeaderId3 shouldEqual bestFullHeaderId1 + } +} diff --git a/it/src/test/scala/encry/it/transactions/AssetTokenTransactionTest.scala b/it/src/test/scala/encry/it/transactions/AssetTokenTransactionTest.scala index 0544b6a181..97e5a74b9f 100644 --- a/it/src/test/scala/encry/it/transactions/AssetTokenTransactionTest.scala +++ b/it/src/test/scala/encry/it/transactions/AssetTokenTransactionTest.scala @@ -1,13 +1,12 @@ package encry.it.transactions -import TransactionGenerator.CreateTransaction import com.typesafe.config.Config import com.typesafe.scalalogging.StrictLogging import encry.consensus.EncrySupplyController import encry.it.configs.Configs import encry.it.docker.NodesFromDocker import encry.it.util.KeyHelper._ -import encry.settings.Settings +import encry.it.utils.CreateTransaction import org.encryfoundation.common.crypto.{PrivateKey25519, PublicKey25519} import org.encryfoundation.common.modifiers.history.Block import org.encryfoundation.common.modifiers.mempool.transaction.EncryAddress.Address @@ -23,6 +22,7 @@ import scorex.utils.Random import scala.concurrent.{Await, Future} import scala.concurrent.duration._ +import encry.settings.Settings class AssetTokenTransactionTest extends AsyncFunSuite with Matchers diff --git a/it/src/test/scala/encry/it/transactions/DataTransactionTest.scala b/it/src/test/scala/encry/it/transactions/DataTransactionTest.scala index 7388629245..1242d4eb92 100644 --- a/it/src/test/scala/encry/it/transactions/DataTransactionTest.scala +++ b/it/src/test/scala/encry/it/transactions/DataTransactionTest.scala @@ -1,11 +1,11 @@ package encry.it.transactions -import TransactionGenerator.CreateTransaction import com.typesafe.config.Config import com.typesafe.scalalogging.StrictLogging import encry.it.configs.Configs import encry.it.docker.NodesFromDocker import encry.it.util.KeyHelper._ +import encry.it.utils.CreateTransaction import org.encryfoundation.common.crypto.PrivateKey25519 import org.encryfoundation.common.modifiers.history.Block import org.encryfoundation.common.modifiers.mempool.transaction.{PubKeyLockedContract, Transaction} @@ -13,6 +13,7 @@ import org.encryfoundation.common.modifiers.state.box.{AssetBox, EncryBaseBox} import org.scalatest.concurrent.ScalaFutures import org.scalatest.{AsyncFunSuite, Matchers} import scorex.utils.Random + import scala.concurrent.{Await, Future} import scala.concurrent.duration._ diff --git a/it/src/test/scala/encry/it/transactions/ProcessingTransferTransactionWithEncryCoinsTest.scala b/it/src/test/scala/encry/it/transactions/ProcessingTransferTransactionWithEncryCoinsTest.scala index d8ee6981e4..7c752fe7c2 100644 --- a/it/src/test/scala/encry/it/transactions/ProcessingTransferTransactionWithEncryCoinsTest.scala +++ b/it/src/test/scala/encry/it/transactions/ProcessingTransferTransactionWithEncryCoinsTest.scala @@ -1,6 +1,6 @@ package encry.it.transactions -import TransactionGenerator.CreateTransaction +import encry.it.utils.CreateTransaction import com.typesafe.config.Config import com.typesafe.scalalogging.StrictLogging import encry.consensus.EncrySupplyController diff --git a/it/src/test/scala/encry/it/utils/FutureAwait.scala b/it/src/test/scala/encry/it/utils/FutureAwait.scala new file mode 100644 index 0000000000..a76e1baa80 --- /dev/null +++ b/it/src/test/scala/encry/it/utils/FutureAwait.scala @@ -0,0 +1,12 @@ +package encry.it.utils + +import scala.concurrent.{Await, Future} +import scala.concurrent.duration.Duration + +object FutureAwait { + + implicit class FutureAwait[T](future: Future[T]) { + def await(implicit duration: Duration): T = Await.result(future, duration) + } + +} diff --git a/it/src/test/scala/TransactionGenerator/TransactionsUtil.scala b/it/src/test/scala/encry/it/utils/TransactionsUtil.scala similarity index 99% rename from it/src/test/scala/TransactionGenerator/TransactionsUtil.scala rename to it/src/test/scala/encry/it/utils/TransactionsUtil.scala index 7bb546a1fd..057f5a029e 100644 --- a/it/src/test/scala/TransactionGenerator/TransactionsUtil.scala +++ b/it/src/test/scala/encry/it/utils/TransactionsUtil.scala @@ -1,4 +1,4 @@ -package TransactionGenerator +package encry.it.utils import com.google.common.primitives.{Bytes, Longs} import com.typesafe.scalalogging.StrictLogging diff --git a/src/main/scala/encry/EncryApp.scala b/src/main/scala/encry/EncryApp.scala index 207f0f9a88..df9478e61a 100644 --- a/src/main/scala/encry/EncryApp.scala +++ b/src/main/scala/encry/EncryApp.scala @@ -1,6 +1,7 @@ package encry import java.net.InetAddress + import akka.actor.SupervisorStrategy.Restart import akka.actor.{ActorRef, ActorSystem, OneForOneStrategy, Props} import akka.http.scaladsl.Http @@ -8,7 +9,7 @@ import akka.http.scaladsl.model.HttpResponse import akka.http.scaladsl.server.ExceptionHandler import akka.stream.ActorMaterializer import com.typesafe.scalalogging.StrictLogging -import encry.api.http.{ApiRoute, CompositeHttpService, DataHolderForApi} +import encry.api.http.{ApiRoute, CompositeHttpService, DataHolderForApi, routes} import encry.api.http.routes._ import encry.cli.ConsoleListener import encry.cli.ConsoleListener.StartListening @@ -24,6 +25,7 @@ import kamon.Kamon import kamon.influxdb.InfluxDBReporter import kamon.system.SystemMetrics import org.encryfoundation.common.utils.Algos + import scala.concurrent.{Await, ExecutionContextExecutor} import scala.concurrent.duration._ import scala.io.Source @@ -80,11 +82,12 @@ object EncryApp extends App with StrictLogging { } val apiRoutes: Seq[ApiRoute] = Seq( - PeersApiRoute(settings.restApi, dataHolderForApi), + PeersApiRoute(settings.restApi, dataHolderForApi, nodeViewSynchronizer), InfoApiRoute(dataHolderForApi, settings, nodeId, timeProvider), HistoryApiRoute(dataHolderForApi, settings, nodeId), TransactionsApiRoute(dataHolderForApi, memoryPool, settings.restApi), - WalletInfoApiRoute(dataHolderForApi, settings.restApi) + WalletInfoApiRoute(dataHolderForApi, settings.restApi), + NodeApiRoute(settings.restApi) ) Http().bindAndHandle( CompositeHttpService(system, apiRoutes, settings.restApi, swaggerConfig).compositeRoute, diff --git a/src/main/scala/encry/api/http/routes/NodeApiRoute.scala b/src/main/scala/encry/api/http/routes/NodeApiRoute.scala new file mode 100644 index 0000000000..6b6eed7679 --- /dev/null +++ b/src/main/scala/encry/api/http/routes/NodeApiRoute.scala @@ -0,0 +1,25 @@ +package encry.api.http.routes + +import akka.actor.{ActorRef, ActorRefFactory} +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.server.Route +import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport +import encry.EncryApp +import encry.settings.RESTApiSettings + +case class NodeApiRoute(restApiSettings: RESTApiSettings)(implicit val context: ActorRefFactory) + extends EncryBaseApiRoute with FailFastCirceSupport { + + override val route: Route = pathPrefix("node")(shutdownR) + + override val settings: RESTApiSettings = restApiSettings + + def shutdownR: Route = path("shutdown") { + post { + complete { + EncryApp.forceStopApplication(errorMessage = "Stopped by http api") + StatusCodes.OK + } + } + } +} \ No newline at end of file diff --git a/src/main/scala/encry/api/http/routes/PeersApiRoute.scala b/src/main/scala/encry/api/http/routes/PeersApiRoute.scala index dc74e5638e..f71860475c 100755 --- a/src/main/scala/encry/api/http/routes/PeersApiRoute.scala +++ b/src/main/scala/encry/api/http/routes/PeersApiRoute.scala @@ -1,24 +1,29 @@ package encry.api.http.routes import java.net.{InetAddress, InetSocketAddress} + import akka.actor.{ActorRef, ActorRefFactory} +import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server.Route import akka.pattern.ask import encry.api.http.DataHolderForApi.{GetAllPeers, GetBannedPeers, GetConnectedPeers} import encry.api.http.routes.PeersApiRoute.PeerInfoResponse +import encry.cli.commands.AddPeer.PeerFromCli import encry.network.BlackList.{BanReason, BanTime, BanType} import encry.network.ConnectedPeersCollection.PeerInfo import encry.network.PeerConnectionHandler.ConnectedPeer import encry.settings.RESTApiSettings import io.circe.Encoder import io.circe.generic.semiauto._ + import scala.concurrent.Future +import scala.util.{Failure, Success, Try} -case class PeersApiRoute(override val settings: RESTApiSettings, - dataHolder: ActorRef)(implicit val context: ActorRefFactory) extends EncryBaseApiRoute { +case class PeersApiRoute(override val settings: RESTApiSettings, dataHolder: ActorRef, nodeViewSynchronizer: ActorRef) + (implicit val context: ActorRefFactory) extends EncryBaseApiRoute { override lazy val route: Route = pathPrefix("peers") { - connectedPeers ~ allPeers ~ bannedList + connectedPeers ~ connectPeer ~ allPeers ~ bannedList } def allPeers: Route = (path("all") & get) { @@ -40,6 +45,24 @@ case class PeersApiRoute(override val settings: RESTApiSettings, (r => complete(r)) ) + def connectPeer: Route = path("connect") { + post(entity(as[String]) { + str => + complete { + Try { + val split = str.split(':') + (split(0), split(1).toInt) + } match { + case Success((host, port)) => + nodeViewSynchronizer ! PeerFromCli(new InetSocketAddress(host, port)) + StatusCodes.OK + case Failure(_) => + StatusCodes.BadRequest + } + } + }) + } + def bannedList: Route = (path("banned") & get) { val result = (dataHolder ? GetBannedPeers) .mapTo[Seq[(InetAddress, (BanReason, BanTime, BanType))]] diff --git a/src/main/scala/encry/api/http/routes/SwaggerRoute.scala b/src/main/scala/encry/api/http/routes/SwaggerRoute.scala index 3108d5a210..7c7211a62a 100644 --- a/src/main/scala/encry/api/http/routes/SwaggerRoute.scala +++ b/src/main/scala/encry/api/http/routes/SwaggerRoute.scala @@ -9,7 +9,8 @@ object SwaggerRoute extends SwaggerHttpService { classOf[InfoApiRoute], classOf[TransactionsApiRoute], classOf[WalletInfoApiRoute], - classOf[PeersApiRoute] + classOf[PeersApiRoute], + classOf[NodeApiRoute] ) override def info: model.Info = Info(