diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index 7eb5e611aa..ab007ec1a0 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -661,7 +661,7 @@ paths: default: 0 # todo from timestamp? get: - summary: Get current of the unconfirmed transactions pool + summary: Get current pool of the unconfirmed transactions pool operationId: getUnconfirmedTransactions tags: - transactions @@ -709,7 +709,7 @@ paths: /peers/connect: post: - summary: Get current size of the unconfirmed transactions pool + summary: Add address to peers list operationId: connectToPeer tags: - peers diff --git a/src/main/scala/org/ergoplatform/api/routes/TransactionsApiRoute.scala b/src/main/scala/org/ergoplatform/api/routes/TransactionsApiRoute.scala index 6a97eca394..6c1a48024b 100644 --- a/src/main/scala/org/ergoplatform/api/routes/TransactionsApiRoute.scala +++ b/src/main/scala/org/ergoplatform/api/routes/TransactionsApiRoute.scala @@ -11,13 +11,10 @@ import io.circe.syntax._ import org.ergoplatform.modifiers.mempool.AnyoneCanSpendTransaction import org.ergoplatform.modifiers.mempool.proposition.AnyoneCanSpendProposition import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} -import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader} -import org.ergoplatform.nodeView.mempool.{ErgoMemPool, ErgoMemPoolReader} -import org.ergoplatform.nodeView.state.{DigestState, UtxoState} -import org.ergoplatform.nodeView.wallet.ErgoWallet +import org.ergoplatform.nodeView.history.ErgoHistoryReader +import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import scorex.core.LocalInterface.LocallyGeneratedTransaction import scorex.core.ModifierId -import scorex.core.NodeViewHolder.GetDataFromCurrentView import scorex.core.settings.RESTApiSettings import scorex.crypto.encode.Base16 @@ -27,23 +24,11 @@ case class TransactionsApiRoute(readersHolder: ActorRef, nodeViewActorRef: Actor (implicit val context: ActorRefFactory) extends ErgoBaseApiRoute with FailFastCirceSupport { override val route: Route = pathPrefix("transactions") { - concat(getUnconfirmedTransactionsR, sendTransactionR, getTransactionByIdR) + getUnconfirmedTransactionsR ~ sendTransactionR ~getTransactionByIdR } override val settings: RESTApiSettings = restApiSettings - private val historyRequest = if (digest) { - GetDataFromCurrentView[ErgoHistory, DigestState, ErgoWallet, ErgoMemPool, ErgoHistory](_.history) - } else { - GetDataFromCurrentView[ErgoHistory, UtxoState, ErgoWallet, ErgoMemPool, ErgoHistory](_.history) - } - - private val poolRequest = if (digest) { - GetDataFromCurrentView[ErgoHistory, DigestState, ErgoWallet, ErgoMemPool, ErgoMemPool](_.pool) - } else { - GetDataFromCurrentView[ErgoHistory, UtxoState, ErgoWallet, ErgoMemPool, ErgoMemPool](_.pool) - } - private def getHistory: Future[ErgoHistoryReader] = (readersHolder ? GetReaders).mapTo[Readers].map(_.h.get) private def getMemPool: Future[ErgoMemPoolReader] = (readersHolder ? GetReaders).mapTo[Readers].map(_.m.get) @@ -59,33 +44,20 @@ case class TransactionsApiRoute(readersHolder: ActorRef, nodeViewActorRef: Actor _.take(limit).toSeq }.map(_.map(_.json).asJson) - def sendTransactionR: Route = - post { - entity(as[AnyoneCanSpendTransaction]) { tx => - complete { - // todo validation? - nodeViewActorRef ! LocallyGeneratedTransaction[AnyoneCanSpendProposition.type, AnyoneCanSpendTransaction](tx) - StatusCodes.OK - } - } - } + //todo There in no codec for "AnyoneCanSpendTransaction" need to make one. + def sendTransactionR: Route = (post & entity(as[AnyoneCanSpendTransaction])) { tx => + // todo validation? + nodeViewActorRef ! LocallyGeneratedTransaction[AnyoneCanSpendProposition.type, AnyoneCanSpendTransaction](tx) + complete(StatusCodes.OK) + } // todo tx id validation - def getTransactionByIdR: Route = path(Segment) { id => - get { - toJsonOptionalResponse { - getTransactionById(ModifierId @@ Base16.decode(id)) - } - } + def getTransactionByIdR: Route = (path(Segment) & get) { id => + getTransactionById(ModifierId @@ Base16.decode(id)).okJson() } - def getUnconfirmedTransactionsR: Route = path("unconfirmed") { - get { - parameters('limit.as[Int] ? 50, 'offset.as[Int] ? 0) { - case (limit, offset) => - // todo offset - toJsonResponse(getUnconfirmedTransactions(limit)) - } - } - } + def getUnconfirmedTransactionsR: Route = (path("unconfirmed") & get & paging) { (offset, limit) => + // todo offset + getUnconfirmedTransactions(limit).okJson() + } } diff --git a/src/test/scala/org/ergoplatform/api/routes/BlocksApiRouteSpec.scala b/src/test/scala/org/ergoplatform/api/routes/BlocksApiRouteSpec.scala index 437a63678d..856c474f1d 100644 --- a/src/test/scala/org/ergoplatform/api/routes/BlocksApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/api/routes/BlocksApiRouteSpec.scala @@ -30,6 +30,13 @@ class BlocksApiRouteSpec extends FlatSpec } } + ignore should "post block correclty" in { + //TODO wait until method will be implemented on api + Post(prefix) ~> route ~> check { + ??? + } + } + it should "get last headers" in { Get(prefix + "/lastHeaders/1") ~> route ~> check { status shouldBe StatusCodes.OK diff --git a/src/test/scala/org/ergoplatform/api/routes/InfoRoutesSpec.scala b/src/test/scala/org/ergoplatform/api/routes/InfoRoutesSpec.scala index 6d6f1d76f7..8df1cc45fb 100644 --- a/src/test/scala/org/ergoplatform/api/routes/InfoRoutesSpec.scala +++ b/src/test/scala/org/ergoplatform/api/routes/InfoRoutesSpec.scala @@ -20,7 +20,7 @@ class InfoRoutesSpec extends FlatSpec it should "return info" in { Get("/info") ~> route ~> check { status shouldBe StatusCodes.OK - InfoRoute.makeInfoJson(nodeId, minerInfo, 0, readers, "digest").toString shouldEqual responseAs[String] + InfoRoute.makeInfoJson(nodeId, minerInfo, connectedPeers.length, readers, "digest").toString shouldEqual responseAs[String] } } } diff --git a/src/test/scala/org/ergoplatform/api/routes/PeersApiRouteSpec.scala b/src/test/scala/org/ergoplatform/api/routes/PeersApiRouteSpec.scala new file mode 100644 index 0000000000..a43453ad2c --- /dev/null +++ b/src/test/scala/org/ergoplatform/api/routes/PeersApiRouteSpec.scala @@ -0,0 +1,70 @@ +package org.ergoplatform.api.routes + +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} +import akka.testkit.TestDuration +import io.circe.syntax._ +import org.scalatest.{FlatSpec, Matchers} +import scorex.core.api.http.PeersApiRoute +import scorex.core.settings.RESTApiSettings + +import scala.concurrent.duration._ + +//TODO move this tests to scorex +class PeersApiRouteSpec extends FlatSpec + with Matchers + with ScalatestRouteTest + with Stubs { + + implicit val timeout = RouteTestTimeout(15.seconds dilated) + + val restApiSettings = RESTApiSettings("localhost", 8080, None, false, 10 seconds) + val prefix = "/peers" + val routes = PeersApiRoute(pmRef, networkControllerRef, restApiSettings).route + + val peersResp = peers.map { case (address, peerInfo) => + Seq( + Some("address" -> address.toString.asJson), + Some("lastSeen" -> peerInfo.lastSeen.asJson), + peerInfo.nodeName.map(name => "name" -> name.asJson), + peerInfo.nonce.map(nonce => "nonce" -> nonce.asJson)).flatten.toMap + }.asJson.toString + + val connectedPeersResp = connectedPeers.map { handshake => + Map( + "address" -> handshake.declaredAddress.toString.asJson, + "name" -> handshake.nodeName.asJson, + "nonce" -> handshake.nodeNonce.asJson, + "lastSeen" -> handshake.time.asJson + ).asJson + }.asJson + + it should "get all peers" in { + Get(prefix + "/all") ~> routes ~> check { + status shouldBe StatusCodes.OK + peersResp shouldBe responseAs[String] + } + } + + //can't check it cause original node using now() timestamp for last seen field + ignore should "get connected peers" in { + Get(prefix + "/connected") ~> routes ~> check { + status shouldBe StatusCodes.OK + connectedPeersResp shouldBe responseAs[String] + } + } + + it should "connect to peer" in { + Post(prefix + "/connect", "localhost:8080") ~> routes ~> check { + status shouldBe StatusCodes.OK + } + } + + it should "get blacklisted peers" in { + Get(prefix + "/blacklisted") ~> routes ~> check { + status shouldBe StatusCodes.OK + println(responseAs[String]) + Map("address" -> blacklistedPeers).asJson.toString shouldBe responseAs[String] + } + } +} diff --git a/src/test/scala/org/ergoplatform/api/routes/Stubs.scala b/src/test/scala/org/ergoplatform/api/routes/Stubs.scala index 56bd55942a..670f229635 100644 --- a/src/test/scala/org/ergoplatform/api/routes/Stubs.scala +++ b/src/test/scala/org/ergoplatform/api/routes/Stubs.scala @@ -1,5 +1,7 @@ package org.ergoplatform.api.routes +import java.net.InetSocketAddress + import akka.actor.{Actor, ActorSystem, Props} import org.ergoplatform.ErgoSanity.HT import org.ergoplatform.local.ErgoMiner.{MiningStatusRequest, MiningStatusResponse} @@ -12,8 +14,9 @@ import org.ergoplatform.nodeView.state.DigestState import org.ergoplatform.settings.Constants.hashLength import org.ergoplatform.settings._ import org.ergoplatform.utils.{ChainGenerator, ErgoGenerators, ErgoTestHelpers} +import scorex.core.app.Version import scorex.core.network.Handshake -import scorex.core.network.peer.PeerManager +import scorex.core.network.peer.{PeerInfo, PeerManager} import scorex.core.settings.ScorexSettings import scorex.core.utils.NetworkTime import scorex.crypto.authds.ADDigest @@ -37,12 +40,37 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with DigestState.create(Some(wus.version), Some(wus.rootHash), createTempDir, settings.nodeSettings).get } }.sample.get - lazy val memPool = ErgoMemPool.empty + + val txs = chain.head.transactions + + lazy val memPool = ErgoMemPool.empty.put(txs).get lazy val readers = Readers(Some(history), Some(state), Some(memPool)) + + val inetAddr1 = new InetSocketAddress("92.92.92.92",27017) + val inetAddr2 = new InetSocketAddress("93.93.93.93",27017) + val ts1 = System.currentTimeMillis() - 100 + val ts2 = System.currentTimeMillis() + 100 + + val peers = Map( + inetAddr1 -> PeerInfo(ts1, Some(1L), Some("first")), + inetAddr2 -> PeerInfo(ts2, Some(2L), Some("second")) + ) + + val protocolVersion = Version("1.1.1") + + val connectedPeers = Seq( + Handshake("node_pop", protocolVersion, "first", 1L, Some(inetAddr1), ts1), + Handshake("node_pop", protocolVersion, "second", 1L, Some(inetAddr2), ts2) + ) + + val blacklistedPeers = Seq("4.4.4.4:1111", "8.8.8.8:2222") + class PeersManagerStub extends Actor { def receive = { - case PeerManager.GetConnectedPeers => sender() ! Seq.empty[Handshake] + case PeerManager.GetConnectedPeers => sender() ! connectedPeers + case PeerManager.GetAllPeers => sender() ! peers + case PeerManager.GetBlacklistedPeers => sender() ! blacklistedPeers } } @@ -60,6 +88,23 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with def props() = Props(new MinerStub) } + class NodeViewStub extends Actor { + def receive = { case _ => println("hey") } + } + + object NodeViewStub { + def props() = Props(new NodeViewStub) + } + + class NetworkControllerStub extends Actor { + def receive = { case _ => println("hey") } + } + + object NetworkControllerStub { + def props() = Props(new NetworkControllerStub) + } + + class ReadersStub extends Actor { def receive = { case GetReaders => @@ -75,6 +120,8 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with lazy val readersRef = system.actorOf(ReadersStub.props()) lazy val minerRef = system.actorOf(MinerStub.props()) lazy val pmRef = system.actorOf(PeersManagerStub.props()) + lazy val nodeViewRef = system.actorOf(NodeViewStub.props()) + lazy val networkControllerRef = system.actorOf(NetworkControllerStub.props()) def generateHistory(verifyTransactions: Boolean = true, ADState: Boolean = true, diff --git a/src/test/scala/org/ergoplatform/api/routes/TransactionApiRouteSpec.scala b/src/test/scala/org/ergoplatform/api/routes/TransactionApiRouteSpec.scala new file mode 100644 index 0000000000..e7a8fc478f --- /dev/null +++ b/src/test/scala/org/ergoplatform/api/routes/TransactionApiRouteSpec.scala @@ -0,0 +1,47 @@ +package org.ergoplatform.api.routes + +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} +import akka.testkit.TestDuration +import io.circe.syntax._ +import org.ergoplatform.modifiers.mempool.AnyoneCanSpendTransaction +import org.scalatest.{FlatSpec, Matchers} +import scorex.core.settings.RESTApiSettings + +import scala.concurrent.duration._ + +class TransactionApiRouteSpec extends FlatSpec + with Matchers + with ScalatestRouteTest + with Stubs { + + implicit val timeout = RouteTestTimeout(15.seconds dilated) + + val restApiSettings = RESTApiSettings("localhost", 8080, None, false, 10 seconds) + val prefix = "/transactions" + val route = TransactionsApiRoute(readersRef, nodeViewRef, restApiSettings, digest = true).route + + val tx = AnyoneCanSpendTransaction(IndexedSeq(1L,2L), IndexedSeq(3L, 4L)) + + //TODO: Not fully implemented yet. There is no codec for tx. + ignore should "post transaction" in { + Post(prefix, tx.json.toString()) ~> route ~> check { + status shouldBe StatusCodes.OK + } + } + + //TODO: Not implemented yet + ignore should "get tx by id" in { + Get(prefix + "/txod") ~> route ~> check { + status shouldBe StatusCodes.OK + } + } + + it should "get unconfirmed from mempool" in { + Get(prefix + "/unconfirmed") ~> route ~> check { + status shouldBe StatusCodes.OK + memPool.take(50).toSeq.map(_.json).asJson.toString shouldBe responseAs[String] + } + } + +} diff --git a/src/test/scala/org/ergoplatform/api/routes/UtilsApiRouteSpec.scala b/src/test/scala/org/ergoplatform/api/routes/UtilsApiRouteSpec.scala new file mode 100644 index 0000000000..0d08bf7443 --- /dev/null +++ b/src/test/scala/org/ergoplatform/api/routes/UtilsApiRouteSpec.scala @@ -0,0 +1,51 @@ +package org.ergoplatform.api.routes + +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} +import akka.testkit.TestDuration +import org.scalatest.{FlatSpec, Matchers} +import scorex.core.api.http.UtilsApiRoute +import scorex.core.settings.RESTApiSettings + +import scala.concurrent.duration._ + +//TODO move this tests to scorex +class UtilsApiRouteSpec extends FlatSpec + with Matchers + with ScalatestRouteTest + with Stubs { + + implicit val timeout = RouteTestTimeout(15.seconds dilated) + + val restApiSettings = RESTApiSettings("localhost", 8080, None, false, 10 seconds) + val prefix = "/utils" + val routes = UtilsApiRoute(restApiSettings).route + + it should "send random seeds" in { + Get(prefix + "/seed") ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[String] should not be empty + } + + Get(prefix + "/seed/32") ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[String] should not be empty + } + + Get(prefix + "/seed/64") ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[String] should not be empty + } + } + + val msg = "hash_me" + + it should "hash string with blake2b" in { + Post(prefix + "/hash/blake2b", msg) ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[String] should not be empty + responseAs[String] should not be msg + } + } + +}