diff --git a/Readme.md b/Readme.md index 8e76080..ccdc7a3 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,4 @@ -# PermaScorex +# Lagonaki This is permacoin implementation on top of Scorex framework. diff --git a/build.sbt b/build.sbt index 2e9f247..980c954 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,10 @@ scalaVersion := "2.11.8" resolvers += "SonaType" at "https://oss.sonatype.org/content/groups/public" libraryDependencies ++= Seq( - "org.consensusresearch" %% "scorex" % "1.2.1" + "org.consensusresearch" %% "scorex-basics" % "1.2.+", + "org.consensusresearch" %% "scorex-consensus" % "1.2.+", + "org.consensusresearch" %% "scorex-perma" % "1.2.+", + "org.consensusresearch" %% "scorex-transaction" % "1.2.+" ) mainClass in assembly := Some("scorex.perma.Application") \ No newline at end of file diff --git a/lock.sbt b/lock.sbt index ef61b17..0150f0c 100644 --- a/lock.sbt +++ b/lock.sbt @@ -39,11 +39,10 @@ dependencyOverrides in ThisBuild ++= Set( "javax.ws.rs" % "jsr311-api" % "1.1.1", "joda-time" % "joda-time" % "2.8.1", "org.bitlet" % "weupnp" % "0.1.4", - "org.consensusresearch" % "scorex-basics_2.11" % "1.2.1", - "org.consensusresearch" % "scorex-consensus_2.11" % "1.2.1", - "org.consensusresearch" % "scorex-perma_2.11" % "1.2.1", - "org.consensusresearch" % "scorex-transaction_2.11" % "1.2.1", - "org.consensusresearch" % "scorex_2.11" % "1.2.1", + "org.consensusresearch" % "scorex-basics_2.11" % "1.2.2", + "org.consensusresearch" % "scorex-consensus_2.11" % "1.2.2", + "org.consensusresearch" % "scorex-perma_2.11" % "1.2.2", + "org.consensusresearch" % "scorex-transaction_2.11" % "1.2.2", "org.consensusresearch" % "scrypto_2.11" % "1.0.4", "org.joda" % "joda-convert" % "1.7", "org.json4s" % "json4s-ast_2.11" % "3.2.11", @@ -62,4 +61,4 @@ dependencyOverrides in ThisBuild ++= Set( "org.slf4j" % "slf4j-api" % "1.7.18", "org.whispersystems" % "curve25519-java" % "0.2.4" ) -// LIBRARY_DEPENDENCIES_HASH 011cd9994bdbf98994a54c73e5e7423f83a91480 +// LIBRARY_DEPENDENCIES_HASH 111038646dc7c922ca4f2f8b646d0e96e0d35c99 diff --git a/pack.sbt b/pack.sbt index f4caeab..cb32e42 100644 --- a/pack.sbt +++ b/pack.sbt @@ -9,7 +9,7 @@ enablePlugins(JavaAppPackaging) enablePlugins(DebianPlugin) linuxPackageMappings in Debian := linuxPackageMappings.value name in Debian := name.value -version in Debian := "0.1.0" +version in Debian := "1.2.1" genChanges in Debian := new File("changelog.md") name in Universal := name.value diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf new file mode 100644 index 0000000..1c5245c --- /dev/null +++ b/src/main/resources/application.conf @@ -0,0 +1,6 @@ +app { + product = "Scorex" + release = "Lagonaki" + version = "1.2.2" + consensusAlgo = "perma" +} \ No newline at end of file diff --git a/src/main/scala/scorex/perma/Application.scala b/src/main/scala/scorex/lagonaki/Application.scala similarity index 56% rename from src/main/scala/scorex/perma/Application.scala rename to src/main/scala/scorex/lagonaki/Application.scala index 3d7889f..cc42c4f 100644 --- a/src/main/scala/scorex/perma/Application.scala +++ b/src/main/scala/scorex/lagonaki/Application.scala @@ -1,14 +1,15 @@ -package scorex.perma +package scorex.lagonaki import java.io.File import akka.actor.Props import com.typesafe.config.ConfigFactory +import scorex.account.Account import scorex.api.http._ import scorex.app.ApplicationVersion import scorex.crypto.ads.merkle.AuthDataBlock -import scorex.lagonaki.api.http.{DebugApiRoute, PaymentApiRoute, PeersHttpService, ScorexApiRoute} -import scorex.lagonaki.server.LagonakiSettings +import scorex.lagonaki.http.{ScorexApiRoute, DebugApiRoute} +import scorex.lagonaki.settings.LagonakiSettings import scorex.network.{TransactionalMessagesRepo, UnconfirmedPoolSynchronizer} import scorex.perma.api.http.PermaConsensusApiRoute import scorex.perma.consensus.PermaConsensusModule @@ -16,25 +17,12 @@ import scorex.perma.network.{PermacoinMessagesRepo, SegmentsSynchronizer} import scorex.perma.settings.PermaConstants._ import scorex.perma.storage.AuthDataStorage import scorex.storage.Storage -import scorex.transaction.SimpleTransactionModule +import scorex.transaction.{BalanceSheet, GenesisTransaction, SimpleTransactionModule, Transaction} import scorex.utils.ScorexLogging +import scala.concurrent.duration._ import scala.reflect.runtime.universe._ - - -object Application extends App with ScorexLogging { - - log.debug("Start server with args: {} ", args) - val filename = args.headOption.getOrElse("settings.json") - - val application = new Application(filename) - - log.debug("PermaScorex has been started") - application.run() - - if (application.wallet.privateKeyAccounts().isEmpty) application.wallet.generateNewAccounts(1) - -} +import scala.util.Random class Application(val settingsFilename: String) extends scorex.app.Application { @@ -52,7 +40,7 @@ class Application(val settingsFilename: String) extends scorex.app.Application { override implicit lazy val consensusModule = { new File(settings.treeDir).mkdirs() - val authDataStorage: Storage[Long, AuthDataBlock[DataSegment]] = new AuthDataStorage(settings.authDataStorage) + val authDataStorage: Storage[Long, AuthDataBlock[DataSegment]] = new AuthDataStorage(Some(settings.authDataStorage)) val rootHash = settings.rootHash actorSystem.actorOf(Props(classOf[SegmentsSynchronizer], this, rootHash, authDataStorage)) new PermaConsensusModule(rootHash, Some(networkController))(authDataStorage) @@ -98,4 +86,65 @@ class Application(val settingsFilename: String) extends scorex.app.Application { actorSystem.actorOf(Props(classOf[UnconfirmedPoolSynchronizer], this)) -} \ No newline at end of file +} + +object Application extends App with ScorexLogging { + + log.debug("Start server with args: {} ", args) + val filename = args.headOption.getOrElse("settings.json") + + val application = new Application(filename) + + log.debug("PermaScorex has been started") + application.run() + + if (application.wallet.privateKeyAccounts().isEmpty) application.wallet.generateNewAccounts(1) + + def testingScript(application: Application): Unit = { + log.info("Going to execute testing scenario") + log.info("Current state is:" + application.blockStorage.state) + val wallet = application.wallet + + if (wallet.privateKeyAccounts().isEmpty) { + wallet.generateNewAccounts(3) + log.info("Generated Accounts:\n" + wallet.privateKeyAccounts().toList.map(_.address).mkString("\n")) + } + + log.info("Executing testing scenario with accounts" + + s"(${wallet.privateKeyAccounts().size}) : " + + wallet.privateKeyAccounts().mkString(" ")) + + require(wallet.privateKeyAccounts().nonEmpty) + + Thread.sleep(3.seconds.toMillis) + + val genesisBlock = application.blockStorage.history.genesis + val genesisAccs = genesisBlock.transactions.flatMap(_ match { + case gtx: GenesisTransaction => + Some(gtx.recipient) + case _ => + log.error("Non-genesis tx in the genesis block!") + None + }) + + def genPayment(recipient: Option[Account] = None, amtOpt: Option[Long] = None): Option[Transaction] = { + val pkAccs = wallet.privateKeyAccounts().ensuring(_.nonEmpty) + val senderAcc = pkAccs(Random.nextInt(pkAccs.size)) + val senderBalance = application.blockStorage.state.asInstanceOf[BalanceSheet].generationBalance(senderAcc) + val recipientAcc = recipient.getOrElse(genesisAccs(Random.nextInt(genesisAccs.size))) + val fee = Random.nextInt(5).toLong + 1 + if (senderBalance - fee > 0) { + val amt = amtOpt.getOrElse(Math.abs(Random.nextLong() % (senderBalance - fee))) + Some(application.transactionModule.createPayment(senderAcc, recipientAcc, amt, fee)) + } else None + } + + log.info("Generate 200 transactions") + (1 to 200) foreach (_ => genPayment()) + + (1 to Int.MaxValue).foreach { _ => + Thread.sleep(Random.nextInt(5.seconds.toMillis.toInt)) + log.info(s"Payment created: ${genPayment()}") + } + } +} diff --git a/src/main/scala/scorex/lagonaki/http/DebugApiRoute.scala b/src/main/scala/scorex/lagonaki/http/DebugApiRoute.scala new file mode 100644 index 0000000..dd3bc56 --- /dev/null +++ b/src/main/scala/scorex/lagonaki/http/DebugApiRoute.scala @@ -0,0 +1,103 @@ +package scorex.lagonaki.http + +import javax.ws.rs.Path + +import akka.actor.ActorRefFactory +import com.wordnik.swagger.annotations._ +import play.api.libs.json.Json +import scorex.api.http._ +import scorex.app.Application +import scorex.crypto.encode.Base58 +import scorex.crypto.hash.FastCryptographicHash +import scorex.transaction.state.database.blockchain.StoredState +import spray.routing.Route + +@Api(value = "/debug", description = "Debug methods", position = 1) +case class DebugApiRoute(override val application: Application)(implicit val context: ActorRefFactory) + extends ApiRoute with CommonTransactionApiFunctions { + + implicit lazy val transactionModule = application.transactionModule + lazy val wallet = application.wallet + + override lazy val route = pathPrefix("debug") { + blocks ~ state ~ stateAt ~ info ~ settings + } + + @Path("/blocks/{howMany}") + @ApiOperation(value = "Blocks", notes = "Get sizes and full hashes for last blocks", httpMethod = "GET") + @ApiImplicitParams(Array( + new ApiImplicitParam( + name = "howMany", + value = "How many last blocks to take", + required = true, + dataType = "String", + paramType = "path") + )) + def blocks: Route = { + path("blocks" / IntNumber) { case howMany => + jsonRoute { + Json.arr(application.blockStorage.history.lastBlocks(howMany).map { block => + val bytes = block.bytes + Json.obj(bytes.length.toString -> Base58.encode(FastCryptographicHash(bytes))) + }).toString() + } + } + } + + @Path("/state") + @ApiOperation(value = "State", notes = "Get current state", httpMethod = "GET") + @ApiResponses(Array( + new ApiResponse(code = 200, message = "Json state") + )) + def state: Route = { + path("state") { + jsonRoute { + application.blockStorage.state.toString + } + } + } + + @Path("/state/{height}") + @ApiOperation(value = "State at block", notes = "Get state at specified height", httpMethod = "GET") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "height", value = "height", required = true, dataType = "Int", paramType = "path") + )) + def stateAt: Route = { + path("state" / IntNumber) { case height => + jsonRoute { + application.blockStorage.state.asInstanceOf[StoredState].toJson(Some(height)).toString + } + } + } + + @Path("/info") + @ApiOperation(value = "State", notes = "All info you need to debug", httpMethod = "GET") + @ApiResponses(Array( + new ApiResponse(code = 200, message = "Json state") + )) + def info: Route = { + path("info") { + jsonRoute { + val state = application.blockStorage.state.asInstanceOf[StoredState] + Json.obj( + "stateHeight" -> state.stateHeight, + "stateHash" -> state.hash + ).toString + } + } + } + + @Path("/settings") + @ApiOperation(value = "State", notes = "Settings file", httpMethod = "GET") + @ApiResponses(Array( + new ApiResponse(code = 200, message = "Json state") + )) + def settings: Route = { + path("settings") { + jsonRoute { + application.settings.settingsJSON.toString() + } + } + } + +} diff --git a/src/main/scala/scorex/lagonaki/http/ScorexApiRoute.scala b/src/main/scala/scorex/lagonaki/http/ScorexApiRoute.scala new file mode 100644 index 0000000..b0605ec --- /dev/null +++ b/src/main/scala/scorex/lagonaki/http/ScorexApiRoute.scala @@ -0,0 +1,65 @@ +package scorex.lagonaki.http + +import javax.ws.rs.Path + +import akka.actor.ActorRefFactory +import akka.pattern.ask +import com.wordnik.swagger.annotations._ +import play.api.libs.json.Json +import scorex.api.http.{ApiRoute, CommonApiFunctions} +import scorex.app.Application +import scorex.consensus.mining.BlockGeneratorController._ +import scorex.lagonaki.settings.Constants +import scorex.network.HistorySynchronizer +import spray.routing.Route + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +@Api(value = "scorex", description = "General commands & information", position = 0) +case class ScorexApiRoute(override val application: Application)(implicit val context: ActorRefFactory) + extends ApiRoute with CommonApiFunctions { + + override lazy val route = + pathPrefix("scorex") { + scorex ~ status ~ version + } + + @Path("/version") + @ApiOperation(value = "Version", notes = "get Scorex version", httpMethod = "GET") + @ApiResponses(Array( + new ApiResponse(code = 200, message = "Json Scorex version") + )) + def version: Route = { + path("version") { + jsonRoute { + Json.obj("version" -> Constants.AgentName).toString() + } + } + } + + @Path("/stop") + @ApiOperation(value = "Stop", notes = "Stop the app", httpMethod = "POST") + def scorex: Route = path("stop") { + jsonRoute({ + Future(application.stopAll()) + Json.obj("stopped" -> true).toString() + }, post) + } + + @Path("/status") + @ApiOperation(value = "Status", notes = "Get status of the running core(Offline/Syncing/Generating)", httpMethod = "GET") + def status: Route = path("status") { + jsonRoute { + def bgf = (application.blockGenerator ? GetStatus).map(_.toString) + def hsf = (application.historySynchronizer ? HistorySynchronizer.GetStatus).map(_.toString) + + Future.sequence(Seq(bgf, hsf)).map { case statusesSeq => + Json.obj( + "block_generator_status" -> statusesSeq.head, + "history_synchronization_status" -> statusesSeq.tail.head + ).toString() + } + } + } +} diff --git a/src/main/scala/scorex/lagonaki/settings/Constants.scala b/src/main/scala/scorex/lagonaki/settings/Constants.scala new file mode 100644 index 0000000..a485988 --- /dev/null +++ b/src/main/scala/scorex/lagonaki/settings/Constants.scala @@ -0,0 +1,17 @@ +package scorex.lagonaki.settings + +import com.typesafe.config.ConfigFactory +import scorex.utils.ScorexLogging + +/** + * System constants here. + */ + +object Constants extends ScorexLogging { + private val appConf = ConfigFactory.load().getConfig("app") + + val Product = appConf.getString("product") + val Release = appConf.getString("release") + val VersionString = appConf.getString("version") + val AgentName = s"$Product - $Release v. $VersionString" +} diff --git a/src/main/scala/scorex/lagonaki/settings/LagonakiSettings.scala b/src/main/scala/scorex/lagonaki/settings/LagonakiSettings.scala new file mode 100644 index 0000000..9ad4592 --- /dev/null +++ b/src/main/scala/scorex/lagonaki/settings/LagonakiSettings.scala @@ -0,0 +1,7 @@ +package scorex.lagonaki.settings + +import scorex.perma.settings.PermaSettings +import scorex.settings.Settings +import scorex.transaction.TransactionSettings + +class LagonakiSettings(override val filename: String) extends Settings with TransactionSettings with PermaSettings \ No newline at end of file