├── .gitignore ├── COPYING ├── README.md ├── build-submodules.sh ├── build.sbt ├── cli.sh ├── docs ├── API.md ├── FAQ.md └── articles │ ├── components.md │ ├── modular1.md │ ├── modular2.md │ └── private-chains.md ├── lock.sbt ├── project ├── Dependencies.scala ├── build.properties └── plugins.sbt ├── release-notes.md ├── scalastyle-config.xml ├── scorex-basics ├── .gitignore ├── README.md ├── build.sbt └── src │ ├── main │ ├── resources │ │ ├── logback.xml │ │ └── swagger-ui │ │ │ ├── css │ │ │ ├── print.css │ │ │ ├── reset.css │ │ │ ├── screen.css │ │ │ ├── style.css │ │ │ └── typography.css │ │ │ ├── fonts │ │ │ ├── DroidSans-Bold.ttf │ │ │ └── DroidSans.ttf │ │ │ ├── images │ │ │ ├── collapse.gif │ │ │ ├── expand.gif │ │ │ ├── explorer_icons.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon.ico │ │ │ ├── logo_small.png │ │ │ ├── pet_store_api.png │ │ │ ├── throbber.gif │ │ │ └── wordnik_api.png │ │ │ ├── index.html │ │ │ ├── lang │ │ │ ├── en.js │ │ │ ├── es.js │ │ │ ├── fr.js │ │ │ ├── it.js │ │ │ ├── ja.js │ │ │ ├── pl.js │ │ │ ├── pt.js │ │ │ ├── ru.js │ │ │ ├── tr.js │ │ │ ├── translator.js │ │ │ └── zh-cn.js │ │ │ ├── lib │ │ │ ├── backbone-min.js │ │ │ ├── handlebars-2.0.0.js │ │ │ ├── highlight.7.3.pack.js │ │ │ ├── jquery-1.8.0.min.js │ │ │ ├── jquery.ba-bbq.min.js │ │ │ ├── jquery.slideto.min.js │ │ │ ├── jquery.wiggle.min.js │ │ │ ├── jsoneditor.min.js │ │ │ ├── marked.js │ │ │ ├── swagger-oauth.js │ │ │ ├── underscore-min.js │ │ │ └── underscore-min.map │ │ │ ├── o2c.html │ │ │ ├── swagger-ui.js │ │ │ └── swagger-ui.min.js │ └── scala │ │ └── scorex │ │ ├── account │ │ ├── Account.scala │ │ ├── PrivateKeyAccount.scala │ │ └── PublicKeyAccount.scala │ │ ├── api │ │ ├── client │ │ │ └── ApiClient.scala │ │ └── http │ │ │ ├── ApiError.scala │ │ │ ├── ApiRoute.scala │ │ │ ├── CommonApiFunctions.scala │ │ │ ├── CompositeHttpService.scala │ │ │ ├── PeersApiRoute.scala │ │ │ ├── SignedMessage.scala │ │ │ ├── UtilsApiRoute.scala │ │ │ └── swagger │ │ │ ├── CorsSupport.scala │ │ │ └── SwaggerDocService.scala │ │ ├── app │ │ ├── Application.scala │ │ └── ApplicationVersion.scala │ │ ├── block │ │ ├── Block.scala │ │ ├── BlockField.scala │ │ └── BlockProcessingModule.scala │ │ ├── consensus │ │ ├── ConsensusModule.scala │ │ └── mining │ │ │ ├── BlockGeneratorController.scala │ │ │ └── Miner.scala │ │ ├── crypto │ │ ├── EllipticCurveImpl.scala │ │ ├── ads │ │ │ └── merkle │ │ │ │ ├── AuthDataBlock.scala │ │ │ │ ├── MerkleTree.scala │ │ │ │ └── TreeStorage.scala │ │ └── hash │ │ │ ├── FastCryptographicHash.scala │ │ │ ├── ScorexHashChain.scala │ │ │ └── SecureCryptographicHash.scala │ │ ├── network │ │ ├── Buffering.scala │ │ ├── Handshake.scala │ │ ├── HistoryReplier.scala │ │ ├── HistorySynchronizer.scala │ │ ├── NetworkController.scala │ │ ├── PeerConnectionHandler.scala │ │ ├── PeerSynchronizer.scala │ │ ├── ScoreObserver.scala │ │ ├── SendingStrategy.scala │ │ ├── UPnP.scala │ │ ├── ViewSynchronizer.scala │ │ ├── message │ │ │ ├── BasicMessagesRepo.scala │ │ │ ├── Message.scala │ │ │ ├── MessageHandler.scala │ │ │ └── MessageSpec.scala │ │ └── peer │ │ │ ├── PeerDatabase.scala │ │ │ ├── PeerDatabaseImpl.scala │ │ │ └── PeerManager.scala │ │ ├── serialization │ │ ├── BytesSerializable.scala │ │ ├── Deser.scala │ │ └── JsonSerializable.scala │ │ ├── settings │ │ └── Settings.scala │ │ ├── storage │ │ └── Storage.scala │ │ ├── transaction │ │ ├── AccountTransactionsHistory.scala │ │ ├── BalanceSheet.scala │ │ ├── BlockChain.scala │ │ ├── BlockStorage.scala │ │ ├── BlockTree.scala │ │ ├── FeesStateChange.scala │ │ ├── History.scala │ │ ├── LagonakiState.scala │ │ ├── State.scala │ │ ├── StateChangeReason.scala │ │ ├── Transaction.scala │ │ ├── TransactionModule.scala │ │ └── UnconfirmedTransactionsStorage.scala │ │ ├── utils │ │ ├── JsonSerialization.scala │ │ ├── NTP.scala │ │ ├── ScorexLogging.scala │ │ └── utils.scala │ │ └── wallet │ │ └── Wallet.scala │ └── test │ ├── resources │ └── application.conf │ └── scala │ └── scorex │ ├── ScorexTestSuite.scala │ ├── account │ └── AccountSpecification.scala │ ├── crypto │ ├── SigningFunctionsSpecification.scala │ └── ads │ │ └── merkle │ │ ├── AuthDataBlockSpecification.scala │ │ ├── MerkleSpecification.scala │ │ └── MerkleTreeStorageSpecification.scala │ └── network │ ├── HandshakeSpecification.scala │ └── PeerDatabaseSpecification.scala ├── scorex-consensus ├── README.md ├── build.sbt └── src │ └── main │ └── scala │ └── scorex │ └── consensus │ ├── OneGeneratorConsensusModule.scala │ ├── PoSConsensusModule.scala │ ├── nxt │ ├── NxtConsensusBlockField.scala │ ├── NxtLikeConsensusBlockData.scala │ ├── NxtLikeConsensusModule.scala │ └── api │ │ └── http │ │ └── NxtConsensusApiRoute.scala │ └── qora │ ├── QoraConsensusBlockField.scala │ ├── QoraLikeConsensusBlockData.scala │ ├── QoraLikeConsensusModule.scala │ └── api │ └── http │ └── QoraConsensusApiRoute.scala ├── scorex-transaction ├── README.md ├── build.sbt └── src │ ├── main │ ├── resources │ │ └── application.conf │ └── scala │ │ └── scorex │ │ ├── api │ │ └── http │ │ │ ├── AddressApiRoute.scala │ │ │ ├── BlocksApiRoute.scala │ │ │ ├── CommonTransactionApiFunctions.scala │ │ │ ├── PaymentApiRoute.scala │ │ │ ├── TransactionsApiRoute.scala │ │ │ ├── WalletApiRoute.scala │ │ │ └── apiErrors.scala │ │ ├── network │ │ ├── TransactionalMessagesRepo.scala │ │ └── UnconfirmedPoolSynchronizer.scala │ │ └── transaction │ │ ├── GenesisTransaction.scala │ │ ├── LagonakiTransaction.scala │ │ ├── PaymentTransaction.scala │ │ ├── SimpleTransactionModule.scala │ │ ├── TransactionSettings.scala │ │ └── state │ │ ├── database │ │ ├── UnconfirmedTransactionsDatabaseImpl.scala │ │ ├── blockchain │ │ │ ├── StoredBlockTree.scala │ │ │ ├── StoredBlockchain.scala │ │ │ └── StoredState.scala │ │ └── state │ │ │ ├── AccState.scala │ │ │ ├── Row.scala │ │ │ └── package.scala │ │ └── wallet │ │ └── Payment.scala │ └── test │ ├── resources │ └── application.conf │ └── scala │ └── scorex │ └── transaction │ ├── GenesisTransactionSpecification.scala │ ├── RowSpecification.scala │ ├── StoredStateUnitTests.scala │ ├── TransactionGen.scala │ ├── TransactionSpecification.scala │ └── TransactionTestSuite.scala ├── src └── test │ ├── resources │ ├── application.conf │ ├── logback.xml │ ├── settings-local1.json │ ├── settings-local2.json │ ├── settings-local3.json │ └── settings-test.json │ └── scala │ └── scorex │ ├── lagonaki │ ├── BlockTestingCommons.scala │ ├── LagonakiTestSuite.scala │ ├── TestingCommons.scala │ ├── TransactionTestingCommons.scala │ ├── integration │ │ ├── BlockGeneratorSpecification.scala │ │ ├── HistoryReplierSpecification.scala │ │ ├── HistorySynchronizerSpecification.scala │ │ ├── PeerSynchronizerSpecification.scala │ │ ├── StoredStateSpecification.scala │ │ ├── ValidChainGenerationSpecification.scala │ │ └── api │ │ │ ├── AddressesAPISpecification.scala │ │ │ ├── BlockAPISpecification.scala │ │ │ ├── NxtConsensusAPISpecification.scala │ │ │ ├── PaymentAPISpecification.scala │ │ │ ├── PeersAPISpecification.scala │ │ │ ├── TransactionsAPISpecification.scala │ │ │ ├── UtilsAPISpecification.scala │ │ │ └── WalletAPISpecification.scala │ ├── mocks │ │ ├── BlockMock.scala │ │ └── ConsensusMock.scala │ ├── props │ │ └── BlockStorageSpecification.scala │ ├── server │ │ ├── LagonakiApplication.scala │ │ └── LagonakiSettings.scala │ └── unit │ │ ├── BlockSpecification.scala │ │ ├── MessageSpecification.scala │ │ └── WalletSpecification.scala │ └── transaction │ └── state │ ├── StateTest.scala │ └── database │ └── blockchain │ └── BlockTreeSpecification.scala └── version.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE/Editor files 2 | .idea 3 | .ensime 4 | .ensime_cache/ 5 | scorex.yaml 6 | 7 | # scala build folders 8 | target 9 | 10 | # dotfiles 11 | .dockerignore 12 | .editorconfig 13 | 14 | # standalone docker 15 | Dockerfile 16 | 17 | # logs 18 | *.log 19 | 20 | # non-submodules code 21 | src/main -------------------------------------------------------------------------------- /build-submodules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for submodule in basics transaction consensus; do 4 | sbt "project $submodule" "clean" "publishLocal" 5 | done 6 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Keys._ 2 | 3 | lazy val commonSettings = Seq( 4 | organization := "org.consensusresearch", 5 | version := version.value, 6 | scalaVersion := "2.11.8" 7 | ) 8 | 9 | def subModule(id: String): Project = Project(id = id, base = file(s"scorex-$id")) 10 | 11 | lazy val basics = subModule("basics") 12 | .settings(commonSettings: _*) 13 | .settings( 14 | testOptions in Test := Seq(Tests.Filter(_.matches(".*TestSuite$"))) 15 | ) 16 | 17 | lazy val transaction = subModule("transaction") 18 | .aggregate(basics) 19 | .dependsOn(basics) 20 | .settings(commonSettings: _*) 21 | .settings( 22 | testOptions in Test := Seq(Tests.Filter(_.matches(".*TestSuite$"))) 23 | ) 24 | 25 | lazy val consensus = subModule("consensus") 26 | .aggregate(basics) 27 | .dependsOn(basics) 28 | .settings(commonSettings: _*) 29 | .settings( 30 | testOptions in Test := Seq(Tests.Filter(_.matches(".*TestSuite$"))) 31 | ) 32 | 33 | lazy val root = Project(id = "scorex", base = file(".")) 34 | .aggregate(basics, transaction, consensus) 35 | .dependsOn(basics, transaction, consensus) 36 | .settings(commonSettings: _*) 37 | .settings( 38 | testOptions in Test := Seq(Tests.Filter(_.matches(".*TestSuite$"))) 39 | ) 40 | 41 | name := "scorex" 42 | 43 | resolvers ++= Seq("Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/", 44 | "SonaType" at "https://oss.sonatype.org/content/groups/public", 45 | "Typesafe maven releases" at "http://repo.typesafe.com/typesafe/maven-releases/") 46 | 47 | libraryDependencies ++= 48 | Dependencies.db ++ 49 | Dependencies.http ++ 50 | Dependencies.akka ++ 51 | Dependencies.serialization ++ 52 | Dependencies.testKit ++ 53 | Dependencies.logging 54 | 55 | scalacOptions ++= Seq("-feature", "-deprecation") 56 | 57 | javaOptions ++= Seq( 58 | "-server" 59 | ) 60 | 61 | testOptions in Test += Tests.Argument("-oD", "-u", "target/test-reports") 62 | 63 | //assembly settings 64 | assemblyJarName in assembly := "scorex.jar" 65 | 66 | test in assembly := {} 67 | 68 | mainClass in assembly := Some("scorex.lagonaki.server.Server") 69 | 70 | //publishing settings 71 | 72 | licenses in ThisBuild := Seq("CC0" -> url("https://creativecommons.org/publicdomain/zero/1.0/legalcode")) 73 | 74 | homepage in ThisBuild := Some(url("https://github.com/ConsensusResearch/Scorex-Lagonaki")) 75 | 76 | publishMavenStyle in ThisBuild := true 77 | 78 | publishArtifact in Test := false 79 | 80 | publishTo in ThisBuild := { 81 | val nexus = "https://oss.sonatype.org/" 82 | if (isSnapshot.value) 83 | Some("snapshots" at nexus + "content/repositories/snapshots") 84 | else 85 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 86 | } 87 | 88 | fork in ThisBuild := true 89 | 90 | pomIncludeRepository in ThisBuild := { _ => false } 91 | 92 | licenses in ThisBuild := Seq("CC0" -> url("https://creativecommons.org/publicdomain/zero/1.0/legalcode")) 93 | 94 | homepage in ThisBuild := Some(url("https://github.com/ConsensusResearch/Scorex-Lagonaki")) 95 | 96 | pomExtra in ThisBuild := 97 | 98 | git@github.com:ConsensusResearch/Scorex-Lagonaki.git 99 | scm:git:git@github.com:ConsensusResearch/Scorex-Lagonaki.git 100 | 101 | 102 | 103 | kushti 104 | Alexander Chepurnoy 105 | http://chepurnoy.org/ 106 | 107 | 108 | 109 | credentials in ThisBuild += Credentials(Path.userHome / ".ivy2" / ".credentials") -------------------------------------------------------------------------------- /cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | java -cp target/scala-2.11/scorex.jar scorex.api.client.ApiClient -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Please see for auto-generated API documentation 4 | 5 | # Access 6 | 7 | Access API with swagger UI () or CURL or with the command line client 8 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | Scorex FAQ 2 | ---------- 3 | 4 | # Q: What is the difference between Scorex and Scorex Lagonaki? 5 | 6 | A: Scorex is the core, Lagonaki is concrete instantiation where core is wired 7 | with simplest transactional module and Permacoin implementation. -------------------------------------------------------------------------------- /docs/articles/modular2.md: -------------------------------------------------------------------------------- 1 | On the Way to a Modular Cryptocurrency, Part 2: Stackable API 2 | ============================================================= 3 | 4 | 5 | Introduction 6 | ------------ 7 | 8 | The previous chapter, [Generic Block Structure](modular1.md) described how to split a blockchain-related 9 | core design of a cryptocurrency into two separate modules to wire concrete implementations in an application 10 | then. 11 | 12 | A cryptocurrency core application provides some API for its user. In this chapter I will show how to 13 | split API implementation into pieces to wire it in an application then. 14 | 15 | Gluing Things Together 16 | ---------------------- 17 | 18 | In the first place, some wrapper for Akka-http route is needed: 19 | 20 | trait ApiRoute { 21 | val route: akka.http.scaladsl.server.Route 22 | } 23 | 24 | Then an actor composing routes: 25 | 26 | class CompositeHttpServiceActor(val routes: ApiRoute*) extends Actor with HttpService { 27 | 28 | override def actorRefFactory = context 29 | 30 | override def receive = routes.map(_.route).reduce(_ ~ _) 31 | } 32 | 33 | And then to create a new piece of API, instance of ApiRoute overriding `route` value is needed, see "Longer 34 | Example" in [akka-http documentation](http://doc.akka.io/docs/akka/2.4.4/scala/http/routing-dsl/index.html#Longer_Example) for example of 35 | a route definition(or Scorex Lagonaki sources, scorex.api.http package). 36 | 37 | Then to glue all things together, we just create concrete actor implementation and bind it to a port. Example from 38 | Scorex Lagonaki: 39 | 40 | lazy val routes = Seq( 41 | AddressApiRoute()(wallet, storedState), 42 | BlocksApiRoute()(blockchainImpl, wallet), 43 | TransactionsApiRoute(storedState), 44 | WalletApiRoute()(wallet), 45 | PaymentApiRoute(this), 46 | PaymentApiRoute(this), 47 | ScorexApiRoute(this), 48 | SeedApiRoute 49 | ) 50 | 51 | lazy val apiActor = actorSystem.actorOf(Props(classOf[CompositeHttpServiceActor], routes), "api") 52 | 53 | IO(Http) ! Http.Bind(apiActor, interface = "0.0.0.0", port = settings.rpcPort) 54 | 55 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Dependencies { 4 | 5 | lazy val testKit = Seq( 6 | "com.typesafe.akka" %% "akka-testkit" % "2.+", 7 | "org.scalatest" %% "scalatest" % "2.+" % "test", 8 | "org.scalactic" %% "scalactic" % "2.+" % "test", 9 | "org.scalacheck" %% "scalacheck" % "1.12.+" % "test", 10 | "net.databinder.dispatch" %% "dispatch-core" % "+" % "test" 11 | ) 12 | 13 | lazy val serialization = Seq( 14 | "com.google.guava" % "guava" % "18.+", 15 | "com.typesafe.play" %% "play-json" % "2.4.+" 16 | ) 17 | 18 | lazy val akka = Seq( 19 | "com.typesafe.akka" %% "akka-actor" % "2.+" 20 | ) 21 | 22 | lazy val p2p = Seq( 23 | "org.bitlet" % "weupnp" % "0.1.+" 24 | ) 25 | 26 | lazy val db = Seq( 27 | "com.h2database" % "h2-mvstore" % "1.+", 28 | "org.mapdb" % "mapdb" % "2.+" 29 | ) 30 | 31 | lazy val logging = Seq( 32 | "ch.qos.logback" % "logback-classic" % "1.+", 33 | "ch.qos.logback" % "logback-core" % "1.+" 34 | ) 35 | 36 | lazy val http = Seq( 37 | "com.typesafe.akka" %% "akka-http-experimental" % "2.+", 38 | "com.chuusai" %% "shapeless" % "2.+", 39 | "io.swagger" %% "swagger-scala-module" % "1.+", 40 | "io.swagger" % "swagger-core" % "1.+", 41 | "io.swagger" % "swagger-annotations" % "1.+", 42 | "io.swagger" % "swagger-models" % "1.+", 43 | "io.swagger" % "swagger-jaxrs" % "1.+", 44 | "com.github.swagger-akka-http" %% "swagger-akka-http" % "0.+" 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.11 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Comment to get more information during initialization 2 | logLevel := Level.Warn 3 | 4 | // The Typesafe repository 5 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 6 | 7 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 8 | 9 | addSbtPlugin("com.github.tkawachi" % "sbt-lock" % "0.2.3") 10 | 11 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.8.0") 12 | 13 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.5") 14 | 15 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.3") 16 | 17 | libraryDependencies += "com.typesafe" % "config" % "1.3.0" 18 | 19 | -------------------------------------------------------------------------------- /scorex-basics/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | project/target 3 | -------------------------------------------------------------------------------- /scorex-basics/README.md: -------------------------------------------------------------------------------- 1 | Scorex-Basics Sub-Module 2 | ======================== 3 | 4 | The module contains utility functions & basic common structure to be used by other parts of the project: 5 | 6 | - Block structure and corresponding structures. Please note, there's no need to inherit from the Block trait, all 7 | functions to generate / parse / check a block are already in the trait & Block companion object 8 | - ConsensusModule interface to a consensus module(see "Consensus Module" section below) 9 | - TransactionModule interface to a transaction module(see "Transaction Module" section below) 10 | - Interfaces for state & history. 11 | - Basic transaction interface 12 | - Cryptographic hash functions. The only implementation in use at the moment is SHA-256 13 | - Signing/verification functions. Curve 25519 is the only current implementation 14 | - RipeMD160 / Base58 15 | - Accounts. Basically an account is just a wrapper around valid address provided as string, could be 16 | accomplished with public key or public/private keypair. 17 | - NTP client for time synchronization(but please note, there's no global time in a cryptocurrency p2p 18 | network!) 19 | - ScorexLogging trait to be mixed into classes for logging 20 | -------------------------------------------------------------------------------- /scorex-basics/build.sbt: -------------------------------------------------------------------------------- 1 | name := "scorex-basics" 2 | 3 | resolvers += "Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/" 4 | 5 | libraryDependencies ++= 6 | Dependencies.serialization ++ 7 | Dependencies.akka ++ 8 | Dependencies.p2p ++ 9 | Dependencies.db ++ 10 | Dependencies.http ++ 11 | Dependencies.testKit ++ 12 | Dependencies.db ++ 13 | Dependencies.logging ++ Seq( 14 | "org.consensusresearch" %% "scrypto" % "1.0.4", 15 | "commons-net" % "commons-net" % "3.+" 16 | ) 17 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | scorex-errors.log 6 | true 7 | 8 | %date{MM/dd HH:mm:ss} %-5level[%.15thread] %logger{1} %msg%n 9 | 10 | 11 | WARN 12 | 13 | 14 | 15 | 16 | scorex.log 17 | true 18 | 19 | %date{MM/dd HH:mm:ss} %-5level[%.15thread] %logger{1} %msg%n 20 | 21 | 22 | 23 | scorex.%d{yyyy-MM-dd}.%i.log 24 | 25 | 100MB 26 | 60 27 | 20GB 28 | 29 | 30 | 31 | 32 | System.out 33 | 34 | [%thread] >> [%-5level] %logger{36} >> %d{HH:mm:ss.SSS} %msg%n 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ 2 | html, 3 | body, 4 | div, 5 | span, 6 | applet, 7 | object, 8 | iframe, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | p, 16 | blockquote, 17 | pre, 18 | a, 19 | abbr, 20 | acronym, 21 | address, 22 | big, 23 | cite, 24 | code, 25 | del, 26 | dfn, 27 | em, 28 | img, 29 | ins, 30 | kbd, 31 | q, 32 | s, 33 | samp, 34 | small, 35 | strike, 36 | strong, 37 | sub, 38 | sup, 39 | tt, 40 | var, 41 | b, 42 | u, 43 | i, 44 | center, 45 | dl, 46 | dt, 47 | dd, 48 | ol, 49 | ul, 50 | li, 51 | fieldset, 52 | form, 53 | label, 54 | legend, 55 | table, 56 | caption, 57 | tbody, 58 | tfoot, 59 | thead, 60 | tr, 61 | th, 62 | td, 63 | article, 64 | aside, 65 | canvas, 66 | details, 67 | embed, 68 | figure, 69 | figcaption, 70 | footer, 71 | header, 72 | hgroup, 73 | menu, 74 | nav, 75 | output, 76 | ruby, 77 | section, 78 | summary, 79 | time, 80 | mark, 81 | audio, 82 | video { 83 | margin: 0; 84 | padding: 0; 85 | border: 0; 86 | font-size: 100%; 87 | font: inherit; 88 | vertical-align: baseline; 89 | } 90 | /* HTML5 display-role reset for older browsers */ 91 | article, 92 | aside, 93 | details, 94 | figcaption, 95 | figure, 96 | footer, 97 | header, 98 | hgroup, 99 | menu, 100 | nav, 101 | section { 102 | display: block; 103 | } 104 | body { 105 | line-height: 1; 106 | } 107 | ol, 108 | ul { 109 | list-style: none; 110 | } 111 | blockquote, 112 | q { 113 | quotes: none; 114 | } 115 | blockquote:before, 116 | blockquote:after, 117 | q:before, 118 | q:after { 119 | content: ''; 120 | content: none; 121 | } 122 | table { 123 | border-collapse: collapse; 124 | border-spacing: 0; 125 | } 126 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/css/typography.css: -------------------------------------------------------------------------------- 1 | /* Google Font's Droid Sans */ 2 | @font-face { 3 | font-family: 'Droid Sans'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf') format('truetype'); 7 | } 8 | /* Google Font's Droid Sans Bold */ 9 | @font-face { 10 | font-family: 'Droid Sans'; 11 | font-style: normal; 12 | font-weight: 700; 13 | src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf') format('truetype'); 14 | } 15 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/fonts/DroidSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/fonts/DroidSans-Bold.ttf -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/fonts/DroidSans.ttf -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/collapse.gif -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/expand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/expand.gif -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/explorer_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/explorer_icons.png -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/favicon-16x16.png -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/favicon-32x32.png -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/favicon.ico -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/logo_small.png -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/pet_store_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/pet_store_api.png -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/throbber.gif -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/images/wordnik_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/Scorex/ca01f65317bfdd5208e7332411afeceb8b18f887/scorex-basics/src/main/resources/swagger-ui/images/wordnik_api.png -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/en.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Warning: Deprecated", 6 | "Implementation Notes":"Implementation Notes", 7 | "Response Class":"Response Class", 8 | "Status":"Status", 9 | "Parameters":"Parameters", 10 | "Parameter":"Parameter", 11 | "Value":"Value", 12 | "Description":"Description", 13 | "Parameter Type":"Parameter Type", 14 | "Data Type":"Data Type", 15 | "Response Messages":"Response Messages", 16 | "HTTP Status Code":"HTTP Status Code", 17 | "Reason":"Reason", 18 | "Response Model":"Response Model", 19 | "Request URL":"Request URL", 20 | "Response Body":"Response Body", 21 | "Response Code":"Response Code", 22 | "Response Headers":"Response Headers", 23 | "Hide Response":"Hide Response", 24 | "Headers":"Headers", 25 | "Try it out!":"Try it out!", 26 | "Show/Hide":"Show/Hide", 27 | "List Operations":"List Operations", 28 | "Expand Operations":"Expand Operations", 29 | "Raw":"Raw", 30 | "can't parse JSON. Raw result":"can't parse JSON. Raw result", 31 | "Model Schema":"Model Schema", 32 | "Model":"Model", 33 | "Click to set as parameter value":"Click to set as parameter value", 34 | "apply":"apply", 35 | "Username":"Username", 36 | "Password":"Password", 37 | "Terms of service":"Terms of service", 38 | "Created by":"Created by", 39 | "See more at":"See more at", 40 | "Contact the developer":"Contact the developer", 41 | "api version":"api version", 42 | "Response Content Type":"Response Content Type", 43 | "Parameter content type:":"Parameter content type:", 44 | "fetching resource":"fetching resource", 45 | "fetching resource list":"fetching resource list", 46 | "Explore":"Explore", 47 | "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", 48 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Can't read from server. It may not have the appropriate access-control-origin settings.", 49 | "Please specify the protocol for":"Please specify the protocol for", 50 | "Can't read swagger JSON from":"Can't read swagger JSON from", 51 | "Finished Loading Resource Information. Rendering Swagger UI":"Finished Loading Resource Information. Rendering Swagger UI", 52 | "Unable to read api":"Unable to read api", 53 | "from path":"from path", 54 | "server returned":"server returned" 55 | }); 56 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/es.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Advertencia: Obsoleto", 6 | "Implementation Notes":"Notas de implementación", 7 | "Response Class":"Clase de la Respuesta", 8 | "Status":"Status", 9 | "Parameters":"Parámetros", 10 | "Parameter":"Parámetro", 11 | "Value":"Valor", 12 | "Description":"Descripción", 13 | "Parameter Type":"Tipo del Parámetro", 14 | "Data Type":"Tipo del Dato", 15 | "Response Messages":"Mensajes de la Respuesta", 16 | "HTTP Status Code":"Código de Status HTTP", 17 | "Reason":"Razón", 18 | "Response Model":"Modelo de la Respuesta", 19 | "Request URL":"URL de la Solicitud", 20 | "Response Body":"Cuerpo de la Respuesta", 21 | "Response Code":"Código de la Respuesta", 22 | "Response Headers":"Encabezados de la Respuesta", 23 | "Hide Response":"Ocultar Respuesta", 24 | "Try it out!":"Pruébalo!", 25 | "Show/Hide":"Mostrar/Ocultar", 26 | "List Operations":"Listar Operaciones", 27 | "Expand Operations":"Expandir Operaciones", 28 | "Raw":"Crudo", 29 | "can't parse JSON. Raw result":"no puede parsear el JSON. Resultado crudo", 30 | "Model Schema":"Esquema del Modelo", 31 | "Model":"Modelo", 32 | "apply":"aplicar", 33 | "Username":"Nombre de usuario", 34 | "Password":"Contraseña", 35 | "Terms of service":"Términos de Servicio", 36 | "Created by":"Creado por", 37 | "See more at":"Ver más en", 38 | "Contact the developer":"Contactar al desarrollador", 39 | "api version":"versión de la api", 40 | "Response Content Type":"Tipo de Contenido (Content Type) de la Respuesta", 41 | "fetching resource":"buscando recurso", 42 | "fetching resource list":"buscando lista del recurso", 43 | "Explore":"Explorar", 44 | "Show Swagger Petstore Example Apis":"Mostrar Api Ejemplo de Swagger Petstore", 45 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"No se puede leer del servidor. Tal vez no tiene la configuración de control de acceso de origen (access-control-origin) apropiado.", 46 | "Please specify the protocol for":"Por favor, especificar el protocola para", 47 | "Can't read swagger JSON from":"No se puede leer el JSON de swagger desde", 48 | "Finished Loading Resource Information. Rendering Swagger UI":"Finalizada la carga del recurso de Información. Mostrando Swagger UI", 49 | "Unable to read api":"No se puede leer la api", 50 | "from path":"desde ruta", 51 | "server returned":"el servidor retornó" 52 | }); 53 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/fr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Avertissement : Obsolète", 6 | "Implementation Notes":"Notes d'implementation", 7 | "Response Class":"Classe de la réponse", 8 | "Status":"Statut", 9 | "Parameters":"Paramètres", 10 | "Parameter":"Paramètre", 11 | "Value":"Valeur", 12 | "Description":"Description", 13 | "Parameter Type":"Type du paramètre", 14 | "Data Type":"Type de données", 15 | "Response Messages":"Messages de la réponse", 16 | "HTTP Status Code":"Code de statut HTTP", 17 | "Reason":"Raison", 18 | "Response Model":"Modèle de réponse", 19 | "Request URL":"URL appelée", 20 | "Response Body":"Corps de la réponse", 21 | "Response Code":"Code de la réponse", 22 | "Response Headers":"En-têtes de la réponse", 23 | "Hide Response":"Cacher la réponse", 24 | "Headers":"En-têtes", 25 | "Try it out!":"Testez !", 26 | "Show/Hide":"Afficher/Masquer", 27 | "List Operations":"Liste des opérations", 28 | "Expand Operations":"Développer les opérations", 29 | "Raw":"Brut", 30 | "can't parse JSON. Raw result":"impossible de décoder le JSON. Résultat brut", 31 | "Model Schema":"Définition du modèle", 32 | "Model":"Modèle", 33 | "apply":"appliquer", 34 | "Username":"Nom d'utilisateur", 35 | "Password":"Mot de passe", 36 | "Terms of service":"Conditions de service", 37 | "Created by":"Créé par", 38 | "See more at":"Voir plus sur", 39 | "Contact the developer":"Contacter le développeur", 40 | "api version":"version de l'api", 41 | "Response Content Type":"Content Type de la réponse", 42 | "fetching resource":"récupération de la ressource", 43 | "fetching resource list":"récupération de la liste de ressources", 44 | "Explore":"Explorer", 45 | "Show Swagger Petstore Example Apis":"Montrer les Apis de l'exemple Petstore de Swagger", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Impossible de lire à partir du serveur. Il se peut que les réglages access-control-origin ne soient pas appropriés.", 47 | "Please specify the protocol for":"Veuillez spécifier un protocole pour", 48 | "Can't read swagger JSON from":"Impossible de lire le JSON swagger à partir de", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Chargement des informations terminé. Affichage de Swagger UI", 50 | "Unable to read api":"Impossible de lire l'api", 51 | "from path":"à partir du chemin", 52 | "server returned":"réponse du serveur" 53 | }); 54 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/it.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Attenzione: Deprecato", 6 | "Implementation Notes":"Note di implementazione", 7 | "Response Class":"Classe della risposta", 8 | "Status":"Stato", 9 | "Parameters":"Parametri", 10 | "Parameter":"Parametro", 11 | "Value":"Valore", 12 | "Description":"Descrizione", 13 | "Parameter Type":"Tipo di parametro", 14 | "Data Type":"Tipo di dato", 15 | "Response Messages":"Messaggi della risposta", 16 | "HTTP Status Code":"Codice stato HTTP", 17 | "Reason":"Motivo", 18 | "Response Model":"Modello di risposta", 19 | "Request URL":"URL della richiesta", 20 | "Response Body":"Corpo della risposta", 21 | "Response Code":"Oggetto della risposta", 22 | "Response Headers":"Intestazioni della risposta", 23 | "Hide Response":"Nascondi risposta", 24 | "Try it out!":"Provalo!", 25 | "Show/Hide":"Mostra/Nascondi", 26 | "List Operations":"Mostra operazioni", 27 | "Expand Operations":"Espandi operazioni", 28 | "Raw":"Grezzo (raw)", 29 | "can't parse JSON. Raw result":"non è possibile parsare il JSON. Risultato grezzo (raw).", 30 | "Model Schema":"Schema del modello", 31 | "Model":"Modello", 32 | "apply":"applica", 33 | "Username":"Nome utente", 34 | "Password":"Password", 35 | "Terms of service":"Condizioni del servizio", 36 | "Created by":"Creato da", 37 | "See more at":"Informazioni aggiuntive:", 38 | "Contact the developer":"Contatta lo sviluppatore", 39 | "api version":"versione api", 40 | "Response Content Type":"Tipo di contenuto (content type) della risposta", 41 | "fetching resource":"recuperando la risorsa", 42 | "fetching resource list":"recuperando lista risorse", 43 | "Explore":"Esplora", 44 | "Show Swagger Petstore Example Apis":"Mostra le api di esempio di Swagger Petstore", 45 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Non è possibile leggere dal server. Potrebbe non avere le impostazioni di controllo accesso origine (access-control-origin) appropriate.", 46 | "Please specify the protocol for":"Si prega di specificare il protocollo per", 47 | "Can't read swagger JSON from":"Impossibile leggere JSON swagger da:", 48 | "Finished Loading Resource Information. Rendering Swagger UI":"Lettura informazioni risorse termianta. Swagger UI viene mostrata", 49 | "Unable to read api":"Impossibile leggere la api", 50 | "from path":"da cartella", 51 | "server returned":"il server ha restituito" 52 | }); 53 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/ja.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"警告: 廃止予定", 6 | "Implementation Notes":"実装メモ", 7 | "Response Class":"レスポンスクラス", 8 | "Status":"ステータス", 9 | "Parameters":"パラメータ群", 10 | "Parameter":"パラメータ", 11 | "Value":"値", 12 | "Description":"説明", 13 | "Parameter Type":"パラメータタイプ", 14 | "Data Type":"データタイプ", 15 | "Response Messages":"レスポンスメッセージ", 16 | "HTTP Status Code":"HTTPステータスコード", 17 | "Reason":"理由", 18 | "Response Model":"レスポンスモデル", 19 | "Request URL":"リクエストURL", 20 | "Response Body":"レスポンスボディ", 21 | "Response Code":"レスポンスコード", 22 | "Response Headers":"レスポンスヘッダ", 23 | "Hide Response":"レスポンスを隠す", 24 | "Headers":"ヘッダ", 25 | "Try it out!":"実際に実行!", 26 | "Show/Hide":"表示/非表示", 27 | "List Operations":"操作一覧", 28 | "Expand Operations":"操作の展開", 29 | "Raw":"Raw", 30 | "can't parse JSON. Raw result":"JSONへ解釈できません. 未加工の結果", 31 | "Model Schema":"モデルスキーマ", 32 | "Model":"モデル", 33 | "apply":"実行", 34 | "Username":"ユーザ名", 35 | "Password":"パスワード", 36 | "Terms of service":"サービス利用規約", 37 | "Created by":"Created by", 38 | "See more at":"See more at", 39 | "Contact the developer":"開発者に連絡", 40 | "api version":"APIバージョン", 41 | "Response Content Type":"レスポンス コンテンツタイプ", 42 | "fetching resource":"リソースの取得", 43 | "fetching resource list":"リソース一覧の取得", 44 | "Explore":"Explore", 45 | "Show Swagger Petstore Example Apis":"SwaggerペットストアAPIの表示", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"サーバから読み込めません. 適切なaccess-control-origin設定を持っていない可能性があります.", 47 | "Please specify the protocol for":"プロトコルを指定してください", 48 | "Can't read swagger JSON from":"次からswagger JSONを読み込めません", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"リソース情報の読み込みが完了しました. Swagger UIを描画しています", 50 | "Unable to read api":"APIを読み込めません", 51 | "from path":"次のパスから", 52 | "server returned":"サーバからの返答" 53 | }); 54 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/pl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Uwaga: Wycofane", 6 | "Implementation Notes":"Uwagi Implementacji", 7 | "Response Class":"Klasa Odpowiedzi", 8 | "Status":"Status", 9 | "Parameters":"Parametry", 10 | "Parameter":"Parametr", 11 | "Value":"Wartość", 12 | "Description":"Opis", 13 | "Parameter Type":"Typ Parametru", 14 | "Data Type":"Typ Danych", 15 | "Response Messages":"Wiadomości Odpowiedzi", 16 | "HTTP Status Code":"Kod Statusu HTTP", 17 | "Reason":"Przyczyna", 18 | "Response Model":"Model Odpowiedzi", 19 | "Request URL":"URL Wywołania", 20 | "Response Body":"Treść Odpowiedzi", 21 | "Response Code":"Kod Odpowiedzi", 22 | "Response Headers":"Nagłówki Odpowiedzi", 23 | "Hide Response":"Ukryj Odpowiedź", 24 | "Headers":"Nagłówki", 25 | "Try it out!":"Wypróbuj!", 26 | "Show/Hide":"Pokaż/Ukryj", 27 | "List Operations":"Lista Operacji", 28 | "Expand Operations":"Rozwiń Operacje", 29 | "Raw":"Nieprzetworzone", 30 | "can't parse JSON. Raw result":"nie można przetworzyć pliku JSON. Nieprzetworzone dane", 31 | "Model Schema":"Schemat Modelu", 32 | "Model":"Model", 33 | "apply":"użyj", 34 | "Username":"Nazwa użytkownika", 35 | "Password":"Hasło", 36 | "Terms of service":"Warunki używania", 37 | "Created by":"Utworzone przez", 38 | "See more at":"Zobacz więcej na", 39 | "Contact the developer":"Kontakt z deweloperem", 40 | "api version":"wersja api", 41 | "Response Content Type":"Typ Zasobu Odpowiedzi", 42 | "fetching resource":"ładowanie zasobu", 43 | "fetching resource list":"ładowanie listy zasobów", 44 | "Explore":"Eksploruj", 45 | "Show Swagger Petstore Example Apis":"Pokaż Przykładowe Api Swagger Petstore", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Brak połączenia z serwerem. Może on nie mieć odpowiednich ustawień access-control-origin.", 47 | "Please specify the protocol for":"Proszę podać protokół dla", 48 | "Can't read swagger JSON from":"Nie można odczytać swagger JSON z", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Ukończono Ładowanie Informacji o Zasobie. Renderowanie Swagger UI", 50 | "Unable to read api":"Nie można odczytać api", 51 | "from path":"ze ścieżki", 52 | "server returned":"serwer zwrócił" 53 | }); 54 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/pt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Aviso: Depreciado", 6 | "Implementation Notes":"Notas de Implementação", 7 | "Response Class":"Classe de resposta", 8 | "Status":"Status", 9 | "Parameters":"Parâmetros", 10 | "Parameter":"Parâmetro", 11 | "Value":"Valor", 12 | "Description":"Descrição", 13 | "Parameter Type":"Tipo de parâmetro", 14 | "Data Type":"Tipo de dados", 15 | "Response Messages":"Mensagens de resposta", 16 | "HTTP Status Code":"Código de status HTTP", 17 | "Reason":"Razão", 18 | "Response Model":"Modelo resposta", 19 | "Request URL":"URL requisição", 20 | "Response Body":"Corpo da resposta", 21 | "Response Code":"Código da resposta", 22 | "Response Headers":"Cabeçalho da resposta", 23 | "Headers":"Cabeçalhos", 24 | "Hide Response":"Esconder resposta", 25 | "Try it out!":"Tente agora!", 26 | "Show/Hide":"Mostrar/Esconder", 27 | "List Operations":"Listar operações", 28 | "Expand Operations":"Expandir operações", 29 | "Raw":"Cru", 30 | "can't parse JSON. Raw result":"Falha ao analisar JSON. Resulto cru", 31 | "Model Schema":"Modelo esquema", 32 | "Model":"Modelo", 33 | "apply":"Aplicar", 34 | "Username":"Usuário", 35 | "Password":"Senha", 36 | "Terms of service":"Termos do serviço", 37 | "Created by":"Criado por", 38 | "See more at":"Veja mais em", 39 | "Contact the developer":"Contate o desenvolvedor", 40 | "api version":"Versão api", 41 | "Response Content Type":"Tipo de conteúdo da resposta", 42 | "fetching resource":"busca recurso", 43 | "fetching resource list":"buscando lista de recursos", 44 | "Explore":"Explorar", 45 | "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Não é possível ler do servidor. Pode não ter as apropriadas configurações access-control-origin", 47 | "Please specify the protocol for":"Por favor especifique o protocolo", 48 | "Can't read swagger JSON from":"Não é possível ler o JSON Swagger de", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Carregar informação de recurso finalizada. Renderizando Swagger UI", 50 | "Unable to read api":"Não foi possível ler api", 51 | "from path":"do caminho", 52 | "server returned":"servidor retornou" 53 | }); 54 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/ru.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Предупреждение: Устарело", 6 | "Implementation Notes":"Заметки", 7 | "Response Class":"Пример ответа", 8 | "Status":"Статус", 9 | "Parameters":"Параметры", 10 | "Parameter":"Параметр", 11 | "Value":"Значение", 12 | "Description":"Описание", 13 | "Parameter Type":"Тип параметра", 14 | "Data Type":"Тип данных", 15 | "HTTP Status Code":"HTTP код", 16 | "Reason":"Причина", 17 | "Response Model":"Структура ответа", 18 | "Request URL":"URL запроса", 19 | "Response Body":"Тело ответа", 20 | "Response Code":"HTTP код ответа", 21 | "Response Headers":"Заголовки ответа", 22 | "Hide Response":"Спрятать ответ", 23 | "Headers":"Заголовки", 24 | "Response Messages":"Что может прийти в ответ", 25 | "Try it out!":"Попробовать!", 26 | "Show/Hide":"Показать/Скрыть", 27 | "List Operations":"Операции кратко", 28 | "Expand Operations":"Операции подробно", 29 | "Raw":"В сыром виде", 30 | "can't parse JSON. Raw result":"Не удается распарсить ответ:", 31 | "Model Schema":"Структура", 32 | "Model":"Описание", 33 | "Click to set as parameter value":"Нажмите, чтобы испльзовать в качестве значения параметра", 34 | "apply":"применить", 35 | "Username":"Имя пользователя", 36 | "Password":"Пароль", 37 | "Terms of service":"Условия использования", 38 | "Created by":"Разработано", 39 | "See more at":"Еще тут", 40 | "Contact the developer":"Связаться с разработчиком", 41 | "api version":"Версия API", 42 | "Response Content Type":"Content Type ответа", 43 | "Parameter content type:":"Content Type параметра:", 44 | "fetching resource":"Получение ресурса", 45 | "fetching resource list":"Получение ресурсов", 46 | "Explore":"Показать", 47 | "Show Swagger Petstore Example Apis":"Показать примеры АПИ", 48 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Не удается получить ответ от сервера. Возможно, проблема с настройками доступа", 49 | "Please specify the protocol for":"Пожалуйста, укажите протокол для", 50 | "Can't read swagger JSON from":"Не получается прочитать swagger json из", 51 | "Finished Loading Resource Information. Rendering Swagger UI":"Загрузка информации о ресурсах завершена. Рендерим", 52 | "Unable to read api":"Не удалось прочитать api", 53 | "from path":"по адресу", 54 | "server returned":"сервер сказал" 55 | }); 56 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/tr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Uyarı: Deprecated", 6 | "Implementation Notes":"Gerçekleştirim Notları", 7 | "Response Class":"Dönen Sınıf", 8 | "Status":"Statü", 9 | "Parameters":"Parametreler", 10 | "Parameter":"Parametre", 11 | "Value":"Değer", 12 | "Description":"Açıklama", 13 | "Parameter Type":"Parametre Tipi", 14 | "Data Type":"Veri Tipi", 15 | "Response Messages":"Dönüş Mesajı", 16 | "HTTP Status Code":"HTTP Statü Kodu", 17 | "Reason":"Gerekçe", 18 | "Response Model":"Dönüş Modeli", 19 | "Request URL":"İstek URL", 20 | "Response Body":"Dönüş İçeriği", 21 | "Response Code":"Dönüş Kodu", 22 | "Response Headers":"Dönüş Üst Bilgileri", 23 | "Hide Response":"Dönüşü Gizle", 24 | "Headers":"Üst Bilgiler", 25 | "Try it out!":"Dene!", 26 | "Show/Hide":"Göster/Gizle", 27 | "List Operations":"Operasyonları Listele", 28 | "Expand Operations":"Operasyonları Aç", 29 | "Raw":"Ham", 30 | "can't parse JSON. Raw result":"JSON çözümlenemiyor. Ham sonuç", 31 | "Model Schema":"Model Şema", 32 | "Model":"Model", 33 | "apply":"uygula", 34 | "Username":"Kullanıcı Adı", 35 | "Password":"Parola", 36 | "Terms of service":"Servis şartları", 37 | "Created by":"Oluşturan", 38 | "See more at":"Daha fazlası için", 39 | "Contact the developer":"Geliştirici ile İletişime Geçin", 40 | "api version":"api versiyon", 41 | "Response Content Type":"Dönüş İçerik Tipi", 42 | "fetching resource":"kaynak getiriliyor", 43 | "fetching resource list":"kaynak listesi getiriliyor", 44 | "Explore":"Keşfet", 45 | "Show Swagger Petstore Example Apis":"Swagger Petstore Örnek Api'yi Gör", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Sunucudan okuma yapılamıyor. Sunucu access-control-origin ayarlarınızı kontrol edin.", 47 | "Please specify the protocol for":"Lütfen istenen adres için protokol belirtiniz", 48 | "Can't read swagger JSON from":"Swagger JSON bu kaynaktan okunamıyor", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Kaynak baglantısı tamamlandı. Swagger UI gösterime hazırlanıyor", 50 | "Unable to read api":"api okunamadı", 51 | "from path":"yoldan", 52 | "server returned":"sunucuya dönüldü" 53 | }); 54 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/translator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Translator for documentation pages. 5 | * 6 | * To enable translation you should include one of language-files in your index.html 7 | * after . 8 | * For example - 9 | * 10 | * If you wish to translate some new texsts you should do two things: 11 | * 1. Add a new phrase pair ("New Phrase": "New Translation") into your language file (for example lang/ru.js). It will be great if you add it in other language files too. 12 | * 2. Mark that text it templates this way New Phrase or . 13 | * The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate. 14 | * 15 | */ 16 | window.SwaggerTranslator = { 17 | 18 | _words:[], 19 | 20 | translate: function(sel) { 21 | var $this = this; 22 | sel = sel || '[data-sw-translate]'; 23 | 24 | $(sel).each(function() { 25 | $(this).html($this._tryTranslate($(this).html())); 26 | 27 | $(this).val($this._tryTranslate($(this).val())); 28 | $(this).attr('title', $this._tryTranslate($(this).attr('title'))); 29 | }); 30 | }, 31 | 32 | _tryTranslate: function(word) { 33 | return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word; 34 | }, 35 | 36 | learn: function(wordsMap) { 37 | this._words = wordsMap; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lang/zh-cn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"警告:已过时", 6 | "Implementation Notes":"实现备注", 7 | "Response Class":"响应类", 8 | "Status":"状态", 9 | "Parameters":"参数", 10 | "Parameter":"参数", 11 | "Value":"值", 12 | "Description":"描述", 13 | "Parameter Type":"参数类型", 14 | "Data Type":"数据类型", 15 | "Response Messages":"响应消息", 16 | "HTTP Status Code":"HTTP状态码", 17 | "Reason":"原因", 18 | "Response Model":"响应模型", 19 | "Request URL":"请求URL", 20 | "Response Body":"响应体", 21 | "Response Code":"响应码", 22 | "Response Headers":"响应头", 23 | "Hide Response":"隐藏响应", 24 | "Headers":"头", 25 | "Try it out!":"试一下!", 26 | "Show/Hide":"显示/隐藏", 27 | "List Operations":"显示操作", 28 | "Expand Operations":"展开操作", 29 | "Raw":"原始", 30 | "can't parse JSON. Raw result":"无法解析JSON. 原始结果", 31 | "Model Schema":"模型架构", 32 | "Model":"模型", 33 | "apply":"应用", 34 | "Username":"用户名", 35 | "Password":"密码", 36 | "Terms of service":"服务条款", 37 | "Created by":"创建者", 38 | "See more at":"查看更多:", 39 | "Contact the developer":"联系开发者", 40 | "api version":"api版本", 41 | "Response Content Type":"响应Content Type", 42 | "fetching resource":"正在获取资源", 43 | "fetching resource list":"正在获取资源列表", 44 | "Explore":"浏览", 45 | "Show Swagger Petstore Example Apis":"显示 Swagger Petstore 示例 Apis", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"无法从服务器读取。可能没有正确设置access-control-origin。", 47 | "Please specify the protocol for":"请指定协议:", 48 | "Can't read swagger JSON from":"无法读取swagger JSON于", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"已加载资源信息。正在渲染Swagger UI", 50 | "Unable to read api":"无法读取api", 51 | "from path":"从路径", 52 | "server returned":"服务器返回" 53 | }); 54 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lib/jquery.slideto.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery); 2 | -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/lib/jquery.wiggle.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Wiggle 3 | Author: WonderGroup, Jordan Thomas 4 | URL: http://labs.wondergroup.com/demos/mini-ui/index.html 5 | License: MIT (http://en.wikipedia.org/wiki/MIT_License) 6 | */ 7 | jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('
').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);} 8 | if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});}; -------------------------------------------------------------------------------- /scorex-basics/src/main/resources/swagger-ui/o2c.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/account/Account.scala: -------------------------------------------------------------------------------- 1 | package scorex.account 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import scorex.crypto.encode.Base58 5 | import scorex.crypto.hash.SecureCryptographicHash._ 6 | import scorex.utils.ScorexLogging 7 | 8 | import scala.util.Try 9 | 10 | 11 | class Account(val address: String) extends Serializable { 12 | 13 | lazy val bytes = Base58.decode(address).get 14 | 15 | override def toString: String = address 16 | 17 | override def equals(b: Any): Boolean = b match { 18 | case a: Account => a.address == address 19 | case _ => false 20 | } 21 | 22 | override def hashCode(): Int = address.hashCode() 23 | } 24 | 25 | 26 | object Account extends ScorexLogging { 27 | 28 | val AddressVersion: Byte = 1 29 | val AddressNetwork: Byte = Try(ConfigFactory.load().getConfig("app").getString("product").head.toByte).getOrElse(0) 30 | val ChecksumLength = 4 31 | val HashLength = 20 32 | val AddressLength = 1 + 1 + ChecksumLength + HashLength 33 | 34 | /** 35 | * Create account from public key. Used in PublicKeyAccount/PrivateKeyAccount. 36 | */ 37 | def fromPublicKey(publicKey: Array[Byte]): String = { 38 | val publicKeyHash = hash(publicKey).take(HashLength) 39 | val withoutChecksum = AddressVersion +: AddressNetwork +: publicKeyHash //prepend ADDRESS_VERSION 40 | Base58.encode(withoutChecksum ++ calcCheckSum(withoutChecksum)) 41 | } 42 | 43 | def isValidAddress(address: String): Boolean = 44 | Base58.decode(address).map { addressBytes => 45 | val version = addressBytes.head 46 | val network = addressBytes.tail.head 47 | if (version != AddressVersion) { 48 | log.warn(s"Unknown address version: $version") 49 | false 50 | } else if (network != AddressNetwork) { 51 | log.warn(s"Unknown network: $network") 52 | false 53 | } else { 54 | if (addressBytes.length != Account.AddressLength) 55 | false 56 | else { 57 | val checkSum = addressBytes.takeRight(ChecksumLength) 58 | 59 | val checkSumGenerated = calcCheckSum(addressBytes.dropRight(ChecksumLength)) 60 | 61 | checkSum.sameElements(checkSumGenerated) 62 | } 63 | } 64 | }.getOrElse(false) 65 | 66 | private def calcCheckSum(withoutChecksum: Array[Byte]): Array[Byte] = hash(withoutChecksum).take(ChecksumLength) 67 | 68 | } 69 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/account/PrivateKeyAccount.scala: -------------------------------------------------------------------------------- 1 | package scorex.account 2 | 3 | import scorex.crypto.EllipticCurveImpl 4 | 5 | case class PrivateKeyAccount(seed: Array[Byte], privateKey: Array[Byte], override val publicKey: Array[Byte]) 6 | extends PublicKeyAccount(publicKey) { 7 | 8 | override val address = Account.fromPublicKey(publicKey) 9 | 10 | def this(seed: Array[Byte], keyPair: (Array[Byte], Array[Byte])) = this(seed, keyPair._1, keyPair._2) 11 | 12 | def this(seed: Array[Byte]) = this(seed, EllipticCurveImpl.createKeyPair(seed)) 13 | } 14 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/account/PublicKeyAccount.scala: -------------------------------------------------------------------------------- 1 | package scorex.account 2 | 3 | 4 | class PublicKeyAccount(val publicKey: Array[Byte]) extends Account(Account.fromPublicKey(publicKey)) 5 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/client/ApiClient.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.client 2 | 3 | import java.io.{BufferedReader, InputStreamReader} 4 | import java.net.{HttpURLConnection, URL} 5 | 6 | import play.libs.Json 7 | import scorex.settings.Settings 8 | 9 | import scala.io.StdIn 10 | import scala.util.{Failure, Success, Try} 11 | 12 | 13 | class ApiClient(settings: Settings) { 14 | 15 | def executeCommand(command: String): String = { 16 | if (command.equals("help")) { 17 | " \n Type quit to stop." 18 | } else Try { 19 | val args = command.split(" ") 20 | val method = args.head.toUpperCase 21 | val path = args(1) 22 | 23 | val content = if (method.equals("POST")) { 24 | command.substring((method + " " + path + " ").length()) 25 | } else "" 26 | 27 | val url = new URL("http://127.0.0.1:" + settings.rpcPort + "/" + path) 28 | val connection = url.openConnection().asInstanceOf[HttpURLConnection] 29 | connection.setRequestMethod(method) 30 | 31 | if (method.equals("POST")) { 32 | connection.setDoOutput(true) 33 | connection.getOutputStream.write(content.getBytes) 34 | connection.getOutputStream.flush() 35 | connection.getOutputStream.close() 36 | } 37 | 38 | val stream = connection.getResponseCode match { 39 | case 200 => connection.getInputStream 40 | case _ => connection.getErrorStream 41 | } 42 | 43 | val isReader = new InputStreamReader(stream) 44 | val br = new BufferedReader(isReader) 45 | val result = br.readLine() 46 | 47 | Try(Json.parse(result)).map(_.toString) 48 | }.flatten match { 49 | case Success(result) => result 50 | case Failure(e) => 51 | s"Problem occurred $e! \n Type help to get a list of commands." 52 | } 53 | } 54 | } 55 | 56 | object ApiClient { 57 | 58 | 59 | def main(args: Array[String]): Unit = { 60 | val settingsFilename = args.headOption.getOrElse("settings.json") 61 | val settings = new Settings { 62 | override val filename = settingsFilename 63 | } 64 | val apiClient = new ApiClient(settings) 65 | 66 | println("Welcome to the Scorex command-line client...") 67 | Iterator.continually(StdIn.readLine()).takeWhile(!_.equals("quit")).foreach { command => 68 | println(s"[$command RESULT] " + apiClient.executeCommand(command)) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/http/ApiError.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import play.api.libs.json.Json 4 | 5 | trait ApiError { 6 | val id: Int 7 | val message: String 8 | 9 | lazy val json = Json.obj("error" -> id, "message" -> message) 10 | } 11 | 12 | case object Unknown extends ApiError { 13 | override val id = 0 14 | override val message = "Error is unknown" 15 | } 16 | 17 | 18 | case object WrongJson extends ApiError { 19 | override val id = 1 20 | override val message = "failed to parse json message" 21 | } 22 | 23 | //API Auth 24 | case object ApiKeyNotValid extends ApiError { 25 | override val id: Int = 2 26 | override val message: String = "Provided API key is not correct" 27 | } 28 | 29 | //VALIDATION 30 | case object InvalidSignature extends ApiError { 31 | override val id = 101 32 | override val message = "invalid signature" 33 | } 34 | 35 | case object InvalidAddress extends ApiError { 36 | override val id = 102 37 | override val message = "invalid address" 38 | } 39 | 40 | case object InvalidSeed extends ApiError { 41 | override val id = 103 42 | override val message = "invalid seed" 43 | } 44 | 45 | case object InvalidAmount extends ApiError { 46 | override val id = 104 47 | override val message = "invalid amount" 48 | } 49 | 50 | case object InvalidFee extends ApiError { 51 | override val id = 105 52 | override val message = "invalid fee" 53 | } 54 | 55 | case object InvalidSender extends ApiError { 56 | override val id = 106 57 | override val message = "invalid sender" 58 | } 59 | 60 | case object InvalidRecipient extends ApiError { 61 | override val id = 107 62 | override val message = "invalid recipient" 63 | } 64 | 65 | case object InvalidPublicKey extends ApiError { 66 | override val id = 108 67 | override val message = "invalid public key" 68 | } 69 | 70 | case object InvalidNotNumber extends ApiError { 71 | override val id = 109 72 | override val message = "argument is not a number" 73 | } 74 | 75 | case object InvalidMessage extends ApiError { 76 | override val id = 110 77 | override val message = "invalid message" 78 | } 79 | 80 | //BLOCKS 81 | case object BlockNotExists extends ApiError { 82 | override val id: Int = 301 83 | override val message: String = "block does not exist" 84 | } 85 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/http/ApiRoute.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import akka.actor.ActorRefFactory 4 | import akka.http.scaladsl.model.headers.RawHeader 5 | import akka.http.scaladsl.model.{ContentTypes, HttpEntity} 6 | import akka.http.scaladsl.server.{Directive0, Directives, Route} 7 | import akka.util.Timeout 8 | import play.api.libs.json.JsValue 9 | import scorex.app.Application 10 | import scorex.crypto.hash.CryptographicHash.Digest 11 | import scorex.crypto.hash.SecureCryptographicHash 12 | 13 | import scala.concurrent.duration._ 14 | import scala.concurrent.{Await, Future} 15 | 16 | trait ApiRoute extends Directives with CommonApiFunctions { 17 | val application: Application 18 | val context: ActorRefFactory 19 | val route: Route 20 | 21 | implicit val timeout = Timeout(5.seconds) 22 | 23 | lazy val corsAllowed = application.settings.corsAllowed 24 | lazy val apiKeyHash = application.settings.apiKeyHash 25 | 26 | def actorRefFactory: ActorRefFactory = context 27 | 28 | def getJsonRoute(fn: Future[JsValue]): Route = 29 | jsonRoute(Await.result(fn, timeout.duration), get) 30 | 31 | def getJsonRoute(fn: JsValue): Route = jsonRoute(fn, get) 32 | 33 | def postJsonRoute(fn: JsValue): Route = jsonRoute(fn, post) 34 | 35 | def postJsonRoute(fn: Future[JsValue]): Route = jsonRoute(Await.result(fn, timeout.duration), post) 36 | 37 | def deleteJsonRoute(fn: JsValue): Route = jsonRoute(fn, delete) 38 | 39 | def deleteJsonRoute(fn: Future[JsValue]): Route = jsonRoute(Await.result(fn, timeout.duration), delete) 40 | 41 | private def jsonRoute(fn: JsValue, method: Directive0): Route = method { 42 | val resp = complete(HttpEntity(ContentTypes.`application/json`, fn.toString())) 43 | withCors(resp) 44 | } 45 | 46 | def withCors(fn: => Route): Route = { 47 | if (corsAllowed) respondWithHeaders(RawHeader("Access-Control-Allow-Origin", "*"))(fn) 48 | else fn 49 | } 50 | 51 | def withAuth(route: => Route): Route = { 52 | optionalHeaderValueByName("api_key") { case keyOpt => 53 | if (isValid(keyOpt)) route 54 | else complete(HttpEntity(ContentTypes.`application/json`, ApiKeyNotValid.json.toString())) 55 | } 56 | } 57 | 58 | private def isValid(keyOpt: Option[String]): Boolean = { 59 | lazy val keyHash: Option[Digest] = keyOpt.map(SecureCryptographicHash(_)) 60 | (apiKeyHash, keyHash) match { 61 | case (None, _) => true 62 | case (Some(expected), Some(passed)) => expected sameElements passed 63 | case _ => false 64 | } 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/http/CommonApiFunctions.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import play.api.libs.json.{JsObject, JsValue, Json} 4 | import scorex.block.Block 5 | import scorex.crypto.encode.Base58 6 | import scorex.transaction.History 7 | 8 | 9 | trait CommonApiFunctions { 10 | 11 | def json(t: Throwable): JsObject = Json.obj("error" -> Unknown.id, "message" -> t.getMessage) 12 | 13 | protected[api] def withBlock(history: History, encodedSignature: String) 14 | (action: Block => JsValue): JsValue = 15 | Base58.decode(encodedSignature).toOption.map { signature => 16 | history.blockById(signature) match { 17 | case Some(block) => action(block) 18 | case None => BlockNotExists.json 19 | } 20 | }.getOrElse(InvalidSignature.json) 21 | } 22 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/http/CompositeHttpService.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.model.StatusCodes 5 | import akka.http.scaladsl.server.Directives._ 6 | import akka.http.scaladsl.server.Route 7 | import scorex.api.http.swagger.{CorsSupport, SwaggerDocService} 8 | import scorex.settings.Settings 9 | 10 | import scala.reflect.runtime.universe.Type 11 | 12 | 13 | case class CompositeHttpService(system: ActorSystem, apiTypes: Seq[Type], routes: Seq[ApiRoute], settings: Settings) 14 | extends CorsSupport { 15 | 16 | implicit val actorSystem = system 17 | 18 | val swaggerService = new SwaggerDocService(system, apiTypes, settings) 19 | 20 | val redirectToSwagger: Route = { 21 | redirect("/swagger", StatusCodes.PermanentRedirect) 22 | } 23 | 24 | val compositeRoute = routes.map(_.route).reduce(_ ~ _) ~ corsHandler(swaggerService.routes) ~ 25 | path("swagger") { 26 | getFromResource("swagger-ui/index.html") 27 | } ~ getFromResourceDirectory("swagger-ui") ~ redirectToSwagger 28 | 29 | } 30 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/http/SignedMessage.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import play.api.libs.json.{JsPath, Reads} 4 | import play.api.libs.functional.syntax._ 5 | 6 | 7 | case class SignedMessage(message: String, signature: String, publickey: String) 8 | 9 | object SignedMessage { 10 | 11 | implicit val messageReads: Reads[SignedMessage] = ( 12 | (JsPath \ "message").read[String] and 13 | (JsPath \ "signature").read[String] and 14 | (JsPath \ "publickey").read[String] 15 | ) (SignedMessage.apply _) 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/http/UtilsApiRoute.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import java.security.SecureRandom 4 | import javax.ws.rs.Path 5 | 6 | import akka.actor.ActorRefFactory 7 | import akka.http.scaladsl.server.Route 8 | import io.swagger.annotations._ 9 | import play.api.libs.json.{JsValue, Json} 10 | import scorex.app.Application 11 | import scorex.crypto.encode.Base58 12 | import scorex.crypto.hash.{FastCryptographicHash, SecureCryptographicHash} 13 | 14 | @Path("/utils") 15 | @Api(value = "/utils", description = "Useful functions", position = 3, produces = "application/json") 16 | case class UtilsApiRoute(override val application: Application)(implicit val context: ActorRefFactory) extends ApiRoute { 17 | val SeedSize = 32 18 | 19 | private def seed(length: Int): JsValue = { 20 | val seed = new Array[Byte](length) 21 | new SecureRandom().nextBytes(seed) //seed mutated here! 22 | Json.obj("seed" -> Base58.encode(seed)) 23 | } 24 | 25 | override val route = pathPrefix("utils") { 26 | seedRoute ~ length ~ hashFast ~ hashSecure 27 | } 28 | 29 | @Path("/seed") 30 | @ApiOperation(value = "Seed", notes = "Generate random seed", httpMethod = "GET") 31 | @ApiResponses(Array( 32 | new ApiResponse(code = 200, message = "Json with peer list or error") 33 | )) 34 | def seedRoute: Route = path("seed") { 35 | getJsonRoute { 36 | seed(SeedSize) 37 | } 38 | } 39 | 40 | @Path("/seed/{length}") 41 | @ApiOperation(value = "Seed of specified length", notes = "Generate random seed of specified length", httpMethod = "GET") 42 | @ApiImplicitParams(Array( 43 | new ApiImplicitParam(name = "length", value = "Seed length ", required = true, dataType = "long", paramType = "path") 44 | )) 45 | @ApiResponse(code = 200, message = "Json with peer list or error") 46 | def length: Route = path("seed" / IntNumber) { case length => 47 | getJsonRoute { 48 | seed(length) 49 | } 50 | } 51 | 52 | @Path("/hash/secure") 53 | @ApiOperation(value = "Hash", notes = "Return FastCryptographicHash of specified message", httpMethod = "POST") 54 | @ApiImplicitParams(Array( 55 | new ApiImplicitParam(name = "message", value = "Message to hash", required = true, paramType = "body", dataType = "String") 56 | )) 57 | @ApiResponses(Array( 58 | new ApiResponse(code = 200, message = "Json with error or json like {\"message\": \"your message\",\"hash\": \"your message hash\"}") 59 | )) 60 | def hashFast: Route = { 61 | path("hash" / "secure") { 62 | entity(as[String]) { message => 63 | withAuth { 64 | postJsonRoute { 65 | Json.obj("message" -> message, "hash" -> Base58.encode(SecureCryptographicHash(message))) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | @Path("/hash/fast") 73 | @ApiOperation(value = "Hash", notes = "Return SecureCryptographicHash of specified message", httpMethod = "POST") 74 | @ApiImplicitParams(Array( 75 | new ApiImplicitParam(name = "message", value = "Message to hash", required = true, paramType = "body", dataType = "String") 76 | )) 77 | @ApiResponses(Array( 78 | new ApiResponse(code = 200, message = "Json with error or json like {\"message\": \"your message\",\"hash\": \"your message hash\"}") 79 | )) 80 | def hashSecure: Route = { 81 | path("hash" / "fast") { 82 | entity(as[String]) { message => 83 | withAuth { 84 | postJsonRoute { 85 | Json.obj("message" -> message, "hash" -> Base58.encode(FastCryptographicHash(message))) 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/http/swagger/CorsSupport.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http.swagger 2 | 3 | import akka.http.scaladsl.marshalling.ToResponseMarshallable.apply 4 | import akka.http.scaladsl.model.HttpMethods._ 5 | import akka.http.scaladsl.model.HttpResponse 6 | import akka.http.scaladsl.model.StatusCode.int2StatusCode 7 | import akka.http.scaladsl.model.headers._ 8 | import akka.http.scaladsl.server.Directive.addByNameNullaryApply 9 | import akka.http.scaladsl.server.Directives._ 10 | import akka.http.scaladsl.server.{Directive0, Route} 11 | 12 | //see https://groups.google.com/forum/#!topic/akka-user/5RCZIJt7jHo 13 | trait CorsSupport { 14 | 15 | //this directive adds access control headers to normal responses 16 | private def withAccessControlHeaders: Directive0 = { 17 | mapResponseHeaders { headers => 18 | `Access-Control-Allow-Origin`.* +: 19 | `Access-Control-Allow-Credentials`(true) +: 20 | `Access-Control-Allow-Headers`("Authorization", "Content-Type", "X-Requested-With") +: 21 | headers 22 | } 23 | } 24 | 25 | //this handles preflight OPTIONS requests. TODO: see if can be done with rejection handler, 26 | //otherwise has to be under addAccessControlHeaders 27 | private def preflightRequestHandler: Route = options { 28 | complete(HttpResponse(200).withHeaders( 29 | `Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE) 30 | ) 31 | ) 32 | } 33 | 34 | def corsHandler(r: Route) = withAccessControlHeaders { 35 | preflightRequestHandler ~ r 36 | } 37 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/api/http/swagger/SwaggerDocService.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http.swagger 2 | 3 | import akka.actor.ActorSystem 4 | import akka.stream.ActorMaterializer 5 | import com.github.swagger.akka.model.{Contact, Info, License} 6 | import com.github.swagger.akka.{HasActorSystem, SwaggerHttpService} 7 | import io.swagger.models.Swagger 8 | import scorex.settings.Settings 9 | 10 | import scala.reflect.runtime.universe.Type 11 | 12 | 13 | class SwaggerDocService(system: ActorSystem, val apiTypes: Seq[Type], settings: Settings) 14 | extends SwaggerHttpService with HasActorSystem { 15 | 16 | override implicit val actorSystem: ActorSystem = system 17 | override implicit val materializer: ActorMaterializer = ActorMaterializer() 18 | 19 | override val host = settings.bindAddress + ":" + settings.rpcPort 20 | override val apiDocsPath: String = "swagger" 21 | 22 | override val info: Info = Info("The Web Interface to the Scorex API", 23 | "1.2.7", 24 | "Scorex API", 25 | "License: Creative Commons CC0", 26 | Some(Contact("Alex", "https://scorex-dev.groups.io/g/main", "alex.chepurnoy@iohk.io")), 27 | Some(License("License: Creative Commons CC0", "https://github.com/ScorexProject/Scorex/blob/master/COPYING")) 28 | ) 29 | 30 | //Let swagger-ui determine the host and port 31 | override def swaggerConfig = new Swagger().basePath(prependSlashIfNecessary(basePath)).info(info).scheme(scheme) 32 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/app/ApplicationVersion.scala: -------------------------------------------------------------------------------- 1 | package scorex.app 2 | 3 | import com.google.common.primitives.Ints 4 | import scorex.serialization.{BytesSerializable, Deser} 5 | 6 | import scala.util.Try 7 | 8 | case class ApplicationVersion(firstDigit: Int, secondDigit: Int, thirdDigit: Int) extends BytesSerializable { 9 | lazy val bytes: Array[Byte] = Ints.toByteArray(firstDigit) ++ Ints.toByteArray(secondDigit) ++ Ints.toByteArray(thirdDigit) 10 | } 11 | 12 | object ApplicationVersion extends Deser[ApplicationVersion] { 13 | val SerializedVersionLength = 4 * 3 14 | 15 | def parseBytes(bytes: Array[Byte]): Try[ApplicationVersion] = Try { 16 | require(bytes.length == SerializedVersionLength, "Wrong bytes for application version") 17 | ApplicationVersion( 18 | Ints.fromByteArray(bytes.slice(0, 4)), 19 | Ints.fromByteArray(bytes.slice(4, 8)), 20 | Ints.fromByteArray(bytes.slice(8, 12)) 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/block/BlockField.scala: -------------------------------------------------------------------------------- 1 | package scorex.block 2 | 3 | import com.google.common.primitives.{Bytes, Ints, Longs} 4 | import play.api.libs.json.{JsObject, Json} 5 | import scorex.account.PublicKeyAccount 6 | import scorex.crypto.encode.Base58 7 | import scorex.serialization.{JsonSerializable, BytesSerializable} 8 | import scorex.transaction.Transaction 9 | 10 | /** 11 | * An abstraction of a part of a block, wrapping some data. The wrapper interface 12 | * provides binary & json serializations. 13 | * 14 | * @tparam T - type of a value wrapped into a blockfield 15 | */ 16 | abstract class BlockField[T] extends BytesSerializable with JsonSerializable { 17 | val name: String 18 | val value: T 19 | 20 | } 21 | 22 | case class ByteBlockField(override val name: String, override val value: Byte) extends BlockField[Byte] { 23 | 24 | override lazy val json: JsObject = Json.obj(name -> value.toInt) 25 | override lazy val bytes: Array[Byte] = Array(value) 26 | } 27 | 28 | case class IntBlockField(override val name: String, override val value: Int) extends BlockField[Int] { 29 | 30 | override lazy val json: JsObject = Json.obj(name -> value) 31 | override lazy val bytes: Array[Byte] = Bytes.ensureCapacity(Ints.toByteArray(value), 4, 0) 32 | } 33 | 34 | case class LongBlockField(override val name: String, override val value: Long) extends BlockField[Long] { 35 | 36 | override lazy val json: JsObject = Json.obj(name -> value) 37 | override lazy val bytes: Array[Byte] = Bytes.ensureCapacity(Longs.toByteArray(value), 8, 0) 38 | } 39 | 40 | case class BlockIdField(override val name: String, override val value: Block.BlockId) 41 | extends BlockField[Block.BlockId] { 42 | 43 | override lazy val json: JsObject = Json.obj(name -> Base58.encode(value)) 44 | override lazy val bytes: Array[Byte] = value 45 | } 46 | 47 | case class TransactionBlockField(override val name: String, override val value: Transaction) 48 | extends BlockField[Transaction] { 49 | 50 | override lazy val json: JsObject = value.json 51 | override lazy val bytes: Array[Byte] = value.bytes 52 | } 53 | 54 | case class SignerData(generator: PublicKeyAccount, signature: Array[Byte]) 55 | 56 | case class SignerDataBlockField(override val name: String, override val value: SignerData) 57 | extends BlockField[SignerData] { 58 | 59 | override lazy val json: JsObject = Json.obj("generator" -> value.generator.toString, 60 | "signature" -> Base58.encode(value.signature)) 61 | 62 | override lazy val bytes: Array[Byte] = value.generator.publicKey ++ value.signature 63 | } 64 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/block/BlockProcessingModule.scala: -------------------------------------------------------------------------------- 1 | package scorex.block 2 | 3 | import scorex.serialization.Deser 4 | 5 | import scala.util.Try 6 | 7 | /** 8 | * A generic interface with functionality to convert data into a part of a block and vice versa 9 | */ 10 | 11 | trait BlockProcessingModule[BlockPartDataType] extends Deser[BlockField[BlockPartDataType]] { 12 | def parseBytes(bytes: Array[Byte]): Try[BlockField[BlockPartDataType]] 13 | 14 | def parseBlockFields(blockFields: BlockField[BlockPartDataType]): BlockPartDataType = blockFields.value 15 | 16 | def genesisData: BlockField[BlockPartDataType] 17 | 18 | def formBlockData(data: BlockPartDataType): BlockField[BlockPartDataType] 19 | } 20 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/consensus/ConsensusModule.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus 2 | 3 | import scorex.account.{Account, PrivateKeyAccount} 4 | import scorex.block.{Block, BlockProcessingModule} 5 | import scorex.transaction.{BalanceSheet, TransactionModule} 6 | 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | import scala.concurrent.Future 9 | 10 | 11 | trait ConsensusModule[ConsensusBlockData] extends BlockProcessingModule[ConsensusBlockData] { 12 | 13 | def isValid[TransactionalBlockData](block: Block)(implicit transactionModule: TransactionModule[TransactionalBlockData]): Boolean 14 | 15 | /** 16 | * Fees could go to a single miner(forger) usually, but can go to many parties, e.g. see 17 | * Meni Rosenfeld's Proof-of-Activity proposal http://eprint.iacr.org/2014/452.pdf 18 | */ 19 | def feesDistribution(block: Block): Map[Account, Long] 20 | 21 | /** 22 | * Get block producers(miners/forgers). Usually one miner produces a block, but in some proposals not 23 | * (see e.g. Meni Rosenfeld's Proof-of-Activity paper http://eprint.iacr.org/2014/452.pdf) 24 | * @param block 25 | * @return 26 | */ 27 | def generators(block: Block): Seq[Account] 28 | 29 | def blockScore(block: Block)(implicit transactionModule: TransactionModule[_]): BigInt 30 | 31 | def generateNextBlock[TransactionalBlockData](account: PrivateKeyAccount) 32 | (implicit transactionModule: TransactionModule[TransactionalBlockData]): Future[Option[Block]] 33 | 34 | def generateNextBlocks[TransactionalBlockData](accounts: Seq[PrivateKeyAccount]) 35 | (implicit transactionModule: TransactionModule[TransactionalBlockData]): Future[Seq[Block]] = { 36 | Future.sequence(accounts.map(acc => generateNextBlock(acc))).map(_.flatten) 37 | } 38 | 39 | def consensusBlockData(block: Block): ConsensusBlockData 40 | } 41 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/consensus/mining/BlockGeneratorController.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus.mining 2 | 3 | import akka.actor._ 4 | import akka.pattern.ask 5 | import akka.util.Timeout 6 | import scorex.app.Application 7 | import scorex.consensus.mining.BlockGeneratorController._ 8 | import scorex.consensus.mining.Miner._ 9 | import scorex.utils.ScorexLogging 10 | 11 | import scala.concurrent.ExecutionContext.Implicits.global 12 | import scala.concurrent.duration._ 13 | import scala.util.Success 14 | 15 | class BlockGeneratorController(application: Application) extends Actor with ScorexLogging { 16 | 17 | val threads = application.settings.mininigThreads 18 | val FailedGenerationDelay: FiniteDuration = Math.max(10, application.settings.blockGenerationDelay.toSeconds).seconds 19 | implicit val timeout = Timeout(FailedGenerationDelay) 20 | 21 | var workers: Seq[ActorRef] = Seq.empty 22 | 23 | context.system.scheduler.schedule(FailedGenerationDelay, FailedGenerationDelay, self, CheckWorkers) 24 | 25 | 26 | override def receive: Receive = syncing 27 | 28 | def syncing: Receive = { 29 | case StartGeneration => 30 | log.info("StartGeneration") 31 | workers.foreach(w => w ! GuessABlock) 32 | context.become(generating) 33 | 34 | case GetStatus => 35 | sender() ! Syncing.name 36 | 37 | case CheckWorkers => 38 | log.info(s"Check miners: $workers vs ${context.children}") 39 | workers.foreach(w => w ! Stop) 40 | context.children.foreach(w => w ! Stop) 41 | 42 | case StopGeneration => 43 | 44 | case m => log.warn(s"Unhandled $m in Syncing") 45 | } 46 | 47 | def generating: Receive = { 48 | case StopGeneration => 49 | log.info(s"StopGeneration") 50 | workers.foreach(w => w ! Stop) 51 | context.become(syncing) 52 | 53 | case Terminated(worker) => 54 | log.info(s"Terminated $worker") 55 | workers = workers.filter(w => w != worker) 56 | 57 | case GetStatus => 58 | sender() ! Generating.name 59 | 60 | case CheckWorkers => 61 | log.info(s"Check $workers") 62 | val incTime = 63 | workers.foreach { w => 64 | (w ? GetLastGenerationTime) onComplete { 65 | case Success(LastGenerationTime(t)) if System.currentTimeMillis() - t < FailedGenerationDelay.toMillis => 66 | log.info(s"Miner $w works fine, last try was ${System.currentTimeMillis() - t} millis ago") 67 | case Success(LastGenerationTime(t)) if System.currentTimeMillis() - t > FailedGenerationDelay.toMillis => 68 | log.warn(s"Miner $w don't generate blocks") 69 | w ! GuessABlock 70 | case m => 71 | log.warn(s"Restart miner $w: $m") 72 | w ! Stop 73 | } 74 | } 75 | if (threads - workers.size > 0) workers = workers ++ newWorkers(threads - workers.size) 76 | 77 | case m => log.info(s"Unhandled $m in Generating") 78 | } 79 | 80 | def newWorkers(count: Int): Seq[ActorRef] = (1 to count).map { i => 81 | context.watch(context.actorOf(Props(classOf[Miner], application), s"Worker-${System.currentTimeMillis()}-$i")) 82 | } 83 | } 84 | 85 | object BlockGeneratorController { 86 | 87 | sealed trait Status { 88 | val name: String 89 | } 90 | 91 | case object Syncing extends Status { 92 | override val name = "syncing" 93 | } 94 | 95 | case object Generating extends Status { 96 | override val name = "generating" 97 | } 98 | 99 | case object GetStatus 100 | 101 | case object StartGeneration 102 | 103 | case object StopGeneration 104 | 105 | case object CheckWorkers 106 | } 107 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/consensus/mining/Miner.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus.mining 2 | 3 | import akka.actor.Actor 4 | import scorex.app.Application 5 | import scorex.block.Block 6 | import scorex.consensus.mining.Miner._ 7 | import scorex.utils.ScorexLogging 8 | 9 | import scala.concurrent.Await 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.duration._ 12 | import scala.util.{Failure, Try} 13 | 14 | class Miner(application: Application) extends Actor with ScorexLogging { 15 | 16 | // BlockGenerator is trying to generate a new block every $blockGenerationDelay. Should be 0 for PoW consensus model. 17 | val blockGenerationDelay = application.settings.blockGenerationDelay 18 | val BlockGenerationTimeLimit = 5.seconds 19 | 20 | var lastTryTime = 0L 21 | var stopped = false 22 | 23 | private def scheduleAGuess(delay: Option[FiniteDuration] = None): Unit = 24 | context.system.scheduler.scheduleOnce(delay.getOrElse(blockGenerationDelay), self, GuessABlock) 25 | 26 | scheduleAGuess(Some(0.millis)) 27 | 28 | override def receive: Receive = { 29 | case GuessABlock => 30 | stopped = false 31 | if (System.currentTimeMillis() - lastTryTime >= blockGenerationDelay.toMillis) tryToGenerateABlock() 32 | 33 | case GetLastGenerationTime => 34 | sender ! LastGenerationTime(lastTryTime) 35 | 36 | case Stop => 37 | stopped = true 38 | } 39 | 40 | def tryToGenerateABlock(): Unit = Try { 41 | implicit val transactionalModule = application.transactionModule 42 | 43 | lastTryTime = System.currentTimeMillis() 44 | if (blockGenerationDelay > 500.milliseconds) log.info("Trying to generate a new block") 45 | val accounts = application.wallet.privateKeyAccounts() 46 | val blocksFuture = application.consensusModule.generateNextBlocks(accounts)(application.transactionModule) 47 | val blocks: Seq[Block] = Await.result(blocksFuture, BlockGenerationTimeLimit) 48 | if (blocks.nonEmpty) application.historySynchronizer ! blocks.maxBy(application.consensusModule.blockScore) 49 | if (!stopped) scheduleAGuess() 50 | }.recoverWith { 51 | case ex => 52 | log.error(s"Failed to generate new block: ${ex.getMessage}") 53 | Failure(ex) 54 | } 55 | 56 | } 57 | 58 | object Miner { 59 | 60 | case object Stop 61 | 62 | case object GuessABlock 63 | 64 | case object GetLastGenerationTime 65 | 66 | case class LastGenerationTime(time: Long) 67 | 68 | } 69 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/crypto/EllipticCurveImpl.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto 2 | 3 | import scorex.account.PrivateKeyAccount 4 | import scorex.crypto.singing.Curve25519 5 | import scorex.crypto.singing.SigningFunctions.{MessageToSign, Signature} 6 | 7 | /** 8 | * This implementation is being used from many places in the code. We consider easy switching from one 9 | * EC implementation from another as possible option, while switching to some other signature schemes 10 | * (e.g. hash-based signatures) will require a lot of code changes around the project(at least because of 11 | * big signature size). 12 | */ 13 | object EllipticCurveImpl extends Curve25519 { 14 | def sign(account: PrivateKeyAccount, message: MessageToSign): Signature = sign(account.privateKey, message) 15 | } 16 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/crypto/ads/merkle/TreeStorage.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto.ads.merkle 2 | 3 | import java.io.File 4 | 5 | import org.mapdb.{DBMaker, HTreeMap, Serializer} 6 | import scorex.crypto.hash.CryptographicHash.Digest 7 | import scorex.storage.Storage 8 | import scorex.utils.ScorexLogging 9 | 10 | import scala.util.{Failure, Success, Try} 11 | 12 | @deprecated("Use tree storage from scrypto library", "1.2.2") 13 | class TreeStorage(fileName: String, levels: Int) extends Storage[(Int, Long), Array[Byte]] with ScorexLogging { 14 | 15 | import TreeStorage._ 16 | 17 | private val dbs = 18 | (0 to levels) map { n: Int => 19 | DBMaker.fileDB(new File(fileName + n + ".mapDB")) 20 | .fileMmapEnableIfSupported() 21 | .closeOnJvmShutdown() 22 | .checksumEnable() 23 | .make() 24 | } 25 | 26 | private val maps: Map[Int, HTreeMap[Long, Digest]] = { 27 | val t = (0 to levels) map { n: Int => 28 | val m: HTreeMap[Long, Digest] = dbs(n).hashMapCreate("map_" + n) 29 | .keySerializer(Serializer.LONG) 30 | .valueSerializer(Serializer.BYTE_ARRAY) 31 | .makeOrGet() 32 | n -> m 33 | } 34 | t.toMap 35 | } 36 | 37 | override def set(key: Key, value: Digest): Unit = Try { 38 | maps(key._1.asInstanceOf[Int]).put(key._2, value) 39 | }.recoverWith { case t: Throwable => 40 | log.warn("Failed to set key:" + key, t) 41 | Failure(t) 42 | } 43 | 44 | override def commit(): Unit = dbs.foreach(_.commit()) 45 | 46 | override def close(): Unit = { 47 | commit() 48 | dbs.foreach(_.close()) 49 | } 50 | 51 | override def get(key: Key): Option[Digest] = { 52 | Try { 53 | maps(key._1).get(key._2) 54 | } match { 55 | case Success(v) => 56 | Option(v) 57 | 58 | case Failure(e) => 59 | if (key._1 == 0) { 60 | log.debug("Enable to load key for level 0: " + key) 61 | } 62 | None 63 | } 64 | } 65 | 66 | } 67 | 68 | object TreeStorage { 69 | type Level = Int 70 | type Position = Long 71 | type Key = (Level, Position) 72 | type Value = Digest 73 | } 74 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/crypto/hash/FastCryptographicHash.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto.hash 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import scorex.crypto.hash.CryptographicHash._ 5 | import scorex.utils._ 6 | 7 | import scala.util.Try 8 | 9 | /** 10 | * Fast and secure hash function 11 | */ 12 | object FastCryptographicHash extends CryptographicHash { 13 | 14 | private val hf: CryptographicHash = Try(ConfigFactory.load().getConfig("scorex").getString("fastHash")) 15 | .flatMap(s => objectFromString[CryptographicHash](s)).getOrElse(Blake256) 16 | 17 | override val DigestSize: Int = hf.DigestSize 18 | 19 | override def hash(in: Message): Digest = hf.hash(in) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/crypto/hash/ScorexHashChain.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto.hash 2 | 3 | import scorex.crypto._ 4 | import scorex.crypto.hash.CryptographicHash._ 5 | 6 | 7 | /** 8 | * The chain of two hash functions, Blake and Keccak 9 | */ 10 | 11 | object ScorexHashChain extends CryptographicHash { 12 | 13 | override val DigestSize: Int = 32 14 | 15 | override def hash(in: Message): Digest = applyHashes(in, Blake256, Keccak256) 16 | } 17 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/crypto/hash/SecureCryptographicHash.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto.hash 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import scorex.crypto.hash.CryptographicHash._ 5 | import scorex.utils._ 6 | 7 | import scala.util.Try 8 | 9 | 10 | /** 11 | * Hash function for cases, where security is more important, then speed 12 | */ 13 | object SecureCryptographicHash extends CryptographicHash { 14 | 15 | private val hf: CryptographicHash = Try(ConfigFactory.load().getConfig("scorex").getString("secureHash")) 16 | .flatMap(s => objectFromString[CryptographicHash](s)).getOrElse(ScorexHashChain) 17 | 18 | override val DigestSize: Int = hf.DigestSize 19 | 20 | override def hash(in: Message): Digest = hf.hash(in) 21 | } 22 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/Buffering.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import java.nio.ByteOrder 4 | 5 | import akka.util.ByteString 6 | 7 | import scala.annotation.tailrec 8 | 9 | /** 10 | * Taken from https://stackoverflow.com/questions/30665811/scala-tcp-packet-frame-using-akka 11 | */ 12 | 13 | trait Buffering { 14 | 15 | //1 MB max packet size 16 | val MAX_PACKET_LEN: Int = 1024*1024 17 | 18 | /** 19 | * Extracts complete packets of the specified length, preserving remainder 20 | * data. If there is no complete packet, then we return an empty list. If 21 | * there are multiple packets available, all packets are extracted, Any remaining data 22 | * is returned to the caller for later submission 23 | * @param data A list of the packets extracted from the raw data in order of receipt 24 | * @return A list of ByteStrings containing extracted packets as well as any remaining buffer data not consumed 25 | */ 26 | 27 | def getPacket(data: ByteString): (List[ByteString], ByteString) = { 28 | 29 | val headerSize = 4 30 | 31 | @tailrec 32 | def multiPacket(packets: List[ByteString], current: ByteString): (List[ByteString], ByteString) = { 33 | if (current.length < headerSize) { 34 | (packets.reverse, current) 35 | } else { 36 | val len = current.iterator.getInt(ByteOrder.BIG_ENDIAN) 37 | if (len > MAX_PACKET_LEN || len < 0) throw new Exception(s"Invalid packet length: $len") 38 | if (current.length < len + headerSize) { 39 | (packets.reverse, current) 40 | } else { 41 | val rem = current drop headerSize // Pop off header 42 | val (front, back) = rem.splitAt(len) // Front contains a completed packet, back contains the remaining data 43 | // Pull of the packet and recurse to see if there is another packet available 44 | multiPacket(front :: packets, back) 45 | } 46 | } 47 | } 48 | multiPacket(List[ByteString](), data) 49 | } 50 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/Handshake.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import java.net.{InetAddress, InetSocketAddress} 4 | 5 | import com.google.common.primitives.{Ints, Longs} 6 | import scorex.app.ApplicationVersion 7 | import scorex.serialization.{BytesSerializable, Deser} 8 | import scorex.utils.ScorexLogging 9 | 10 | import scala.util.Try 11 | 12 | 13 | case class Handshake(applicationName: String, 14 | applicationVersion: ApplicationVersion, 15 | nodeName: String, 16 | nodeNonce: Long, 17 | declaredAddress: Option[InetSocketAddress], 18 | time: Long 19 | ) extends BytesSerializable { 20 | 21 | require(Option(applicationName).isDefined) 22 | require(Option(applicationVersion).isDefined) 23 | 24 | lazy val bytes: Array[Byte] = { 25 | val anb = applicationName.getBytes 26 | 27 | val fab = declaredAddress.map { isa => 28 | isa.getAddress.getAddress ++ Ints.toByteArray(isa.getPort) 29 | }.getOrElse(Array[Byte]()) 30 | 31 | val nodeNameBytes = nodeName.getBytes 32 | 33 | Array(anb.size.toByte) ++ anb ++ 34 | applicationVersion.bytes ++ 35 | Array(nodeNameBytes.size.toByte) ++ nodeNameBytes ++ 36 | Longs.toByteArray(nodeNonce) ++ 37 | Ints.toByteArray(fab.length) ++ fab ++ 38 | Longs.toByteArray(time) 39 | } 40 | } 41 | 42 | object Handshake extends ScorexLogging with Deser[Handshake] { 43 | def parseBytes(bytes: Array[Byte]): Try[Handshake] = Try { 44 | var position = 0 45 | val appNameSize = bytes.head 46 | require(appNameSize > 0) 47 | 48 | position += 1 49 | 50 | val an = new String(bytes.slice(position, position + appNameSize)) 51 | position += appNameSize 52 | 53 | val av = ApplicationVersion.parseBytes(bytes.slice(position, position + ApplicationVersion.SerializedVersionLength)).get 54 | position += ApplicationVersion.SerializedVersionLength 55 | 56 | val nodeNameSize = bytes.slice(position, position + 1).head 57 | position += 1 58 | 59 | val nodeName = new String(bytes.slice(position, position + nodeNameSize)) 60 | position += nodeNameSize 61 | 62 | val nonce = Longs.fromByteArray(bytes.slice(position, position + 8)) 63 | position += 8 64 | 65 | val fas = Ints.fromByteArray(bytes.slice(position, position + 4)) 66 | position += 4 67 | 68 | val isaOpt = if (fas > 0) { 69 | val fa = bytes.slice(position, position + fas - 4) 70 | position += fas - 4 71 | 72 | val port = Ints.fromByteArray(bytes.slice(position, position + 4)) 73 | position += 4 74 | 75 | Some(new InetSocketAddress(InetAddress.getByAddress(fa), port)) 76 | } else None 77 | 78 | val time = Longs.fromByteArray(bytes.slice(position, position + 8)) 79 | 80 | Handshake(an, av, nodeName, nonce, isaOpt, time) 81 | } 82 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/HistoryReplier.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import scorex.app.Application 4 | import scorex.block.Block 5 | import scorex.network.NetworkController.{DataFromPeer, SendToNetwork} 6 | import scorex.network.message.Message 7 | import scorex.utils.ScorexLogging 8 | 9 | class HistoryReplier(application: Application) extends ViewSynchronizer with ScorexLogging { 10 | 11 | import application.basicMessagesSpecsRepo._ 12 | 13 | override val messageSpecs = Seq(GetSignaturesSpec, GetBlockSpec) 14 | protected override lazy val networkControllerRef = application.networkController 15 | 16 | override def receive: Receive = { 17 | 18 | //todo: check sender and otherSigs type 19 | case DataFromPeer(msgId, otherSigs: Seq[Block.BlockId]@unchecked, remote) 20 | if msgId == GetSignaturesSpec.messageCode => 21 | 22 | log.info(s"Got GetSignaturesMessage with ${otherSigs.length} sigs within") 23 | 24 | otherSigs.exists { parent => 25 | val headers = application.history.lookForward(parent, application.settings.MaxBlocksChunks) 26 | 27 | if (headers.nonEmpty) { 28 | val msg = Message(SignaturesSpec, Right(Seq(parent) ++ headers), None) 29 | val ss = SendToChosen(Seq(remote)) 30 | networkControllerRef ! SendToNetwork(msg, ss) 31 | true 32 | } else false 33 | } 34 | 35 | //todo: check sender? 36 | case DataFromPeer(msgId, sig: Block.BlockId@unchecked, remote) 37 | if msgId == GetBlockSpec.messageCode => 38 | 39 | application.history.blockById(sig).foreach { b => 40 | val msg = Message(BlockMessageSpec, Right(b), None) 41 | val ss = SendToChosen(Seq(remote)) 42 | networkControllerRef ! SendToNetwork(msg, ss) 43 | } 44 | 45 | //the signal to initialize 46 | case Unit => 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/PeerSynchronizer.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import java.net.InetSocketAddress 4 | 5 | import akka.pattern.ask 6 | import akka.util.Timeout 7 | import scorex.app.Application 8 | import scorex.network.NetworkController.{DataFromPeer, SendToNetwork} 9 | import scorex.network.message.Message 10 | import scorex.network.peer.PeerManager 11 | import scorex.network.peer.PeerManager.RandomPeers 12 | import scorex.utils.ScorexLogging 13 | import shapeless.syntax.typeable._ 14 | 15 | import scala.concurrent.ExecutionContext.Implicits.global 16 | import scala.concurrent.duration._ 17 | 18 | 19 | class PeerSynchronizer(application: Application) extends ViewSynchronizer with ScorexLogging { 20 | 21 | import application.basicMessagesSpecsRepo._ 22 | 23 | private implicit val timeout = Timeout(5.seconds) 24 | 25 | override val messageSpecs = Seq(GetPeersSpec, PeersSpec) 26 | override val networkControllerRef = application.networkController 27 | 28 | private val peerManager = application.peerManager 29 | 30 | override def preStart: Unit = { 31 | super.preStart() 32 | 33 | val stn = NetworkController.SendToNetwork(Message(GetPeersSpec, Right(), None), SendToRandom) 34 | context.system.scheduler.schedule(2.seconds, 10.seconds)(networkControllerRef ! stn) 35 | } 36 | 37 | override def receive: Receive = { 38 | case DataFromPeer(msgId, peers: Seq[InetSocketAddress]@unchecked, remote) 39 | if msgId == PeersSpec.messageCode && peers.cast[Seq[InetSocketAddress]].isDefined => 40 | 41 | peers.foreach(isa => peerManager ! PeerManager.AddOrUpdatePeer(isa, None, None)) 42 | 43 | case DataFromPeer(msgId, _, remote) if msgId == GetPeersSpec.messageCode => 44 | 45 | //todo: externalize the number, check on receiving 46 | (peerManager ? RandomPeers(3)) 47 | .mapTo[Seq[InetSocketAddress]] 48 | .foreach { peers => 49 | val msg = Message(PeersSpec, Right(peers), None) 50 | networkControllerRef ! SendToNetwork(msg, SendToChosen(Seq(remote))) 51 | } 52 | 53 | case nonsense: Any => log.warn(s"PeerSynchronizer: got something strange $nonsense") 54 | } 55 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/ScoreObserver.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import akka.actor.{Actor, ActorRef} 4 | import scorex.transaction.History._ 5 | import scorex.utils.ScorexLogging 6 | 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | import scala.concurrent.duration._ 9 | 10 | //todo: break a connection if no score message from remote for some time? 11 | 12 | class ScoreObserver(historySynchronizer: ActorRef) extends Actor with ScorexLogging { 13 | 14 | import ScoreObserver._ 15 | 16 | private case class Candidate(peer: ConnectedPeer, score: BlockchainScore, seen: Long) 17 | 18 | private var candidates = Seq[Candidate]() 19 | 20 | private def consider(candidates: Seq[Candidate]): (Option[Candidate], Seq[Candidate]) = 21 | candidates.isEmpty match { 22 | case true => (None, Seq()) 23 | case false => 24 | val bestNetworkScore = candidates.maxBy(_.score).score 25 | val witnesses = candidates.filter(_.score == bestNetworkScore) 26 | (witnesses.headOption, witnesses) 27 | } 28 | 29 | private def clearOld(candidates: Seq[Candidate]): Seq[Candidate] = { 30 | //todo: make configurable? 31 | val threshold = System.currentTimeMillis() - 1.minute.toMillis 32 | candidates.filter(_.seen > threshold) 33 | } 34 | 35 | override def preStart: Unit = { 36 | //todo: make configurable? 37 | context.system.scheduler.schedule(5.seconds, 5.seconds)(self ! UpdateScore(None)) 38 | } 39 | 40 | override def receive: Receive = { 41 | case UpdateScore(scoreToAddOpt) => 42 | val oldScore = candidates.headOption.map(_.score) 43 | candidates = clearOld(candidates) 44 | 45 | scoreToAddOpt.foreach { case (connectedPeer, value) => 46 | candidates = candidates.filter(_.peer != connectedPeer) 47 | candidates = candidates :+ Candidate(connectedPeer, value, System.currentTimeMillis()) 48 | } 49 | 50 | val ct = consider(candidates) 51 | candidates = ct._2 52 | 53 | val newScore = ct._1.map(_.score) 54 | val witnesses = candidates.map(_.peer) 55 | 56 | if (newScore.getOrElse(BigInt(0)) != oldScore.getOrElse(BigInt(0))) { 57 | historySynchronizer ! ConsideredValue(newScore, witnesses) 58 | } 59 | 60 | case GetScore => 61 | candidates = clearOld(candidates) 62 | candidates.headOption.map(_.score) match { 63 | case None => context.system.scheduler.scheduleOnce(1.second, sender(), ConsideredValue(None, Seq())) 64 | case score => sender() ! ConsideredValue(score, candidates.map(_.peer)) 65 | } 66 | } 67 | } 68 | 69 | object ScoreObserver { 70 | 71 | case class UpdateScore(scoreToAdd: Option[(ConnectedPeer, BlockchainScore)]) 72 | 73 | case object GetScore 74 | 75 | case class ConsideredValue(value: Option[BlockchainScore], witnesses: Seq[ConnectedPeer]) 76 | 77 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/SendingStrategy.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import scala.util.Random 4 | 5 | trait SendingStrategy { 6 | def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] 7 | } 8 | 9 | object SendToRandom extends SendingStrategy { 10 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = peers.nonEmpty match { 11 | case true => Seq(peers(Random.nextInt(peers.length))) 12 | case false => Seq() 13 | } 14 | } 15 | 16 | case object Broadcast extends SendingStrategy { 17 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = peers 18 | } 19 | 20 | case class BroadcastExceptOf(exceptOf: Seq[ConnectedPeer]) extends SendingStrategy { 21 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = 22 | peers.filterNot(exceptOf.contains) 23 | } 24 | 25 | case class SendToChosen(chosenPeers: Seq[ConnectedPeer]) extends SendingStrategy { 26 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = chosenPeers 27 | } 28 | 29 | case class SendToRandomFromChosen(chosenPeers: Seq[ConnectedPeer]) extends SendingStrategy { 30 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = 31 | Seq(chosenPeers(Random.nextInt(chosenPeers.length))) 32 | } 33 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/UPnP.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import java.net.InetAddress 4 | 5 | import org.bitlet.weupnp.{GatewayDevice, GatewayDiscover} 6 | import scorex.settings.Settings 7 | import scorex.utils.ScorexLogging 8 | 9 | import scala.collection.JavaConversions._ 10 | import scala.util.Try 11 | 12 | class UPnP(settings:Settings) extends ScorexLogging { 13 | 14 | private var gateway: Option[GatewayDevice] = None 15 | 16 | lazy val localAddress = gateway.map(_.getLocalAddress) 17 | lazy val externalAddress = gateway.map(_.getExternalIPAddress).map(InetAddress.getByName) 18 | 19 | Try { 20 | log.info("Looking for UPnP gateway device...") 21 | val defaultHttpReadTimeout = settings.upnpGatewayTimeout.getOrElse(GatewayDevice.getHttpReadTimeout) 22 | GatewayDevice.setHttpReadTimeout(defaultHttpReadTimeout) 23 | val discover = new GatewayDiscover() 24 | val defaultDiscoverTimeout = settings.upnpDiscoverTimeout.getOrElse(discover.getTimeout) 25 | discover.setTimeout(defaultDiscoverTimeout) 26 | 27 | val gatewayMap = Option(discover.discover).map(_.toMap).getOrElse(Map()) 28 | if (gatewayMap.isEmpty) { 29 | log.debug("There are no UPnP gateway devices") 30 | } else { 31 | gatewayMap.foreach { case (addr, _) => 32 | log.debug("UPnP gateway device found on " + addr.getHostAddress) 33 | } 34 | Option(discover.getValidGateway) match { 35 | case None => log.debug("There is no connected UPnP gateway device") 36 | case Some(device) => 37 | gateway = Some(device) 38 | log.debug("Using UPnP gateway device on " + localAddress.map(_.getHostAddress).getOrElse("err")) 39 | log.info("External IP address is " + externalAddress.map(_.getHostAddress).getOrElse("err")) 40 | } 41 | } 42 | }.recover { case t: Throwable => 43 | log.error("Unable to discover UPnP gateway devices: " + t.toString) 44 | } 45 | 46 | def addPort(port: Int): Try[Unit] = Try { 47 | if (gateway.get.addPortMapping(port, port, localAddress.get.getHostAddress, "TCP", "Scorex")) { 48 | log.debug("Mapped port [" + externalAddress.get.getHostAddress + "]:" + port) 49 | } else { 50 | log.debug("Unable to map port " + port) 51 | } 52 | }.recover { case t: Throwable => 53 | log.error("Unable to map port " + port + ": " + t.toString) 54 | } 55 | 56 | def deletePort(port: Int): Try[Unit] = Try { 57 | if (gateway.get.deletePortMapping(port, "TCP")) { 58 | log.debug("Mapping deleted for port " + port) 59 | } else { 60 | log.debug("Unable to delete mapping for port " + port) 61 | } 62 | }.recover { case t: Throwable => 63 | log.error("Unable to delete mapping for port " + port + ": " + t.toString) 64 | } 65 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/ViewSynchronizer.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import akka.actor.{Actor, ActorRef} 4 | import scorex.network.message.MessageSpec 5 | 6 | 7 | /** 8 | * Synchronizing network & local views of an object, e.g. history(blockchain or blocktree), known peers list, 9 | * segments dataset subset etc. 10 | * 11 | * todo: anti-ddos? 12 | */ 13 | trait ViewSynchronizer extends Actor { 14 | 15 | protected val networkControllerRef: ActorRef 16 | 17 | val messageSpecs: Seq[MessageSpec[_]] 18 | 19 | override def preStart: Unit = { 20 | networkControllerRef ! NetworkController.RegisterMessagesHandler(messageSpecs, self) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/message/Message.scala: -------------------------------------------------------------------------------- 1 | package scorex.network.message 2 | 3 | import com.google.common.primitives.{Bytes, Ints} 4 | import scorex.crypto.hash.FastCryptographicHash._ 5 | import scorex.network.ConnectedPeer 6 | import scorex.serialization.BytesSerializable 7 | 8 | import scala.util.{Success, Try} 9 | 10 | case class Message[Content](spec: MessageSpec[Content], 11 | input: Either[Array[Byte], Content], 12 | source: Option[ConnectedPeer]) extends BytesSerializable { 13 | 14 | import Message.{ChecksumLength, MAGIC} 15 | 16 | lazy val dataBytes = input match { 17 | case Left(db) => db 18 | case Right(d) => spec.serializeData(d) 19 | } 20 | 21 | lazy val data: Try[Content] = input match { 22 | case Left(db) => spec.deserializeData(db) 23 | case Right(d) => Success(d) 24 | } 25 | 26 | lazy val dataLength: Int = dataBytes.length 27 | 28 | lazy val bytes: Array[Byte] = { 29 | val dataWithChecksum = if (dataLength > 0) { 30 | val checksum = hash(dataBytes).take(ChecksumLength) 31 | Bytes.concat(checksum, dataBytes) 32 | } else dataBytes //empty array 33 | 34 | MAGIC ++ Array(spec.messageCode) ++ Ints.toByteArray(dataLength) ++ dataWithChecksum 35 | } 36 | } 37 | 38 | 39 | object Message { 40 | type MessageCode = Byte 41 | 42 | val MAGIC = Array(0x12: Byte, 0x34: Byte, 0x56: Byte, 0x78: Byte) 43 | 44 | val MagicLength = MAGIC.length 45 | 46 | val ChecksumLength = 4 47 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/message/MessageHandler.scala: -------------------------------------------------------------------------------- 1 | package scorex.network.message 2 | 3 | import java.nio.ByteBuffer 4 | 5 | import scorex.crypto.hash.FastCryptographicHash._ 6 | import scorex.network.ConnectedPeer 7 | 8 | import scala.util.Try 9 | 10 | case class MessageHandler(specs: Seq[MessageSpec[_]]) { 11 | 12 | import Message._ 13 | 14 | private val specsMap = Map(specs.map(s => s.messageCode -> s): _*) 15 | .ensuring(m => m.size == specs.size, "Duplicate message codes") 16 | 17 | //MAGIC ++ Array(spec.messageCode) ++ Ints.toByteArray(dataLength) ++ dataWithChecksum 18 | // TODO Deser[Message[_]] 19 | def parseBytes(bytes: ByteBuffer, sourceOpt: Option[ConnectedPeer]): Try[Message[_]] = Try { 20 | val magic = new Array[Byte](MagicLength) 21 | bytes.get(magic) 22 | 23 | assert(magic.sameElements(Message.MAGIC), "Wrong magic bytes" + magic.mkString) 24 | 25 | val msgCode = bytes.get 26 | 27 | val length = bytes.getInt 28 | assert(length >= 0, "Data length is negative!") 29 | 30 | val msgData: Array[Byte] = length > 0 match { 31 | case true => 32 | val data = new Array[Byte](length) 33 | //READ CHECKSUM 34 | val checksum = new Array[Byte](Message.ChecksumLength) 35 | bytes.get(checksum) 36 | 37 | //READ DATA 38 | bytes.get(data) 39 | 40 | //VALIDATE CHECKSUM 41 | val digest = hash(data).take(Message.ChecksumLength) 42 | 43 | //CHECK IF CHECKSUM MATCHES 44 | assert(checksum.sameElements(digest), s"Invalid data checksum length = $length") 45 | data 46 | 47 | case false => Array() 48 | } 49 | 50 | val spec = specsMap.get(msgCode).ensuring(_.isDefined, "No message handler").get 51 | 52 | Message(spec, Left(msgData), sourceOpt) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/message/MessageSpec.scala: -------------------------------------------------------------------------------- 1 | package scorex.network.message 2 | 3 | import scala.util.Try 4 | 5 | trait MessageSpec[Content] { 6 | val messageCode: Message.MessageCode 7 | val messageName: String 8 | 9 | def deserializeData(bytes: Array[Byte]): Try[Content] 10 | 11 | def serializeData(data: Content): Array[Byte] 12 | 13 | override def toString: String = s"MessageSpec($messageCode: $messageName)" 14 | } 15 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/peer/PeerDatabase.scala: -------------------------------------------------------------------------------- 1 | package scorex.network.peer 2 | 3 | import java.net.InetSocketAddress 4 | 5 | //todo: add optional nonce 6 | case class PeerInfo(lastSeen: Long, nonce: Option[Long] = None, nodeName: Option[String] = None) 7 | 8 | trait PeerDatabase { 9 | def addOrUpdateKnownPeer(peer: InetSocketAddress, peerInfo: PeerInfo): Unit 10 | 11 | def knownPeers(forSelf: Boolean): Map[InetSocketAddress, PeerInfo] 12 | 13 | def blacklistPeer(peer: InetSocketAddress): Unit 14 | 15 | def blacklistedPeers(): Seq[String] 16 | 17 | def isBlacklisted(address: InetSocketAddress): Boolean 18 | } 19 | 20 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/network/peer/PeerDatabaseImpl.scala: -------------------------------------------------------------------------------- 1 | package scorex.network.peer 2 | 3 | import java.net.InetSocketAddress 4 | 5 | import org.h2.mvstore.{MVMap, MVStore} 6 | import scorex.settings.Settings 7 | 8 | import scala.collection.JavaConversions._ 9 | 10 | class PeerDatabaseImpl(settings: Settings, filename: Option[String]) extends PeerDatabase { 11 | 12 | 13 | val database = filename match { 14 | case Some(file) => new MVStore.Builder().fileName(file).compress().open() 15 | case None => new MVStore.Builder().open() 16 | } 17 | 18 | private val whitelistPersistence: MVMap[InetSocketAddress, PeerInfo] = database.openMap("whitelist") 19 | private val blacklist: MVMap[String, Long] = database.openMap("blacklist") 20 | 21 | private lazy val ownNonce = settings.nodeNonce 22 | 23 | override def addOrUpdateKnownPeer(address: InetSocketAddress, peerInfo: PeerInfo): Unit = { 24 | val updatedPeerInfo = Option(whitelistPersistence.get(address)).map { case dbPeerInfo => 25 | val nonceOpt = peerInfo.nonce.orElse(dbPeerInfo.nonce) 26 | val nodeNameOpt = peerInfo.nodeName.orElse(dbPeerInfo.nodeName) 27 | PeerInfo(peerInfo.lastSeen, nonceOpt, nodeNameOpt) 28 | }.getOrElse(peerInfo) 29 | whitelistPersistence.put(address, updatedPeerInfo) 30 | database.commit() 31 | } 32 | 33 | override def blacklistPeer(address: InetSocketAddress): Unit = { 34 | whitelistPersistence.remove(address) 35 | if (!isBlacklisted(address)) blacklist += address.getHostName -> System.currentTimeMillis() 36 | database.commit() 37 | } 38 | 39 | override def isBlacklisted(address: InetSocketAddress): Boolean = { 40 | blacklist.synchronized(blacklist.contains(address.getHostName)) 41 | } 42 | 43 | override def knownPeers(excludeSelf: Boolean): Map[InetSocketAddress, PeerInfo] = 44 | (excludeSelf match { 45 | case true => knownPeers(false).filter(_._2.nonce.getOrElse(-1) != ownNonce) 46 | case false => whitelistPersistence.keys.flatMap(k => Option(whitelistPersistence.get(k)).map(v => k -> v)) 47 | }).toMap 48 | 49 | override def blacklistedPeers(): Seq[String] = blacklist.keys.toSeq 50 | 51 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/serialization/BytesSerializable.scala: -------------------------------------------------------------------------------- 1 | package scorex.serialization 2 | 3 | trait BytesSerializable extends Serializable { 4 | 5 | def bytes: Array[Byte] 6 | } 7 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/serialization/Deser.scala: -------------------------------------------------------------------------------- 1 | package scorex.serialization 2 | 3 | import scala.util.Try 4 | 5 | /** 6 | * Interface for objects, that can deserialize bytes to instance of T 7 | */ 8 | trait Deser[T] { 9 | 10 | def parseBytes(bytes: Array[Byte]): Try[T] 11 | 12 | } 13 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/serialization/JsonSerializable.scala: -------------------------------------------------------------------------------- 1 | package scorex.serialization 2 | 3 | import play.api.libs.json.JsObject 4 | 5 | trait JsonSerializable { 6 | 7 | def json: JsObject 8 | } 9 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/storage/Storage.scala: -------------------------------------------------------------------------------- 1 | package scorex.storage 2 | 3 | trait Storage[Key, Value] { 4 | 5 | def set(key: Key, value: Value): Unit 6 | 7 | def get(key: Key): Option[Value] 8 | 9 | def commit(): Unit 10 | 11 | def close(): Unit 12 | 13 | def containsKey(key: Key): Boolean = get(key).isDefined 14 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/AccountTransactionsHistory.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.account.Account 4 | 5 | trait AccountTransactionsHistory { 6 | def accountTransactions(address: String): Array[_ <: Transaction] = { 7 | Account.isValidAddress(address) match { 8 | case false => Array() 9 | case true => 10 | val acc = new Account(address) 11 | accountTransactions(acc) 12 | } 13 | } 14 | 15 | def accountTransactions(account: Account): Array[_ <: Transaction] 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/BalanceSheet.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.account.Account 4 | 5 | 6 | trait BalanceSheet { 7 | def balance(address: String, height: Option[Int] = None): Long 8 | 9 | /** 10 | * 11 | * @return Minimum balance from current block to balance confirmation blocks ago 12 | */ 13 | def balanceWithConfirmations(address: String, confirmations: Int): Long 14 | } 15 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/BlockChain.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.block.Block 4 | import scorex.block.Block.BlockId 5 | import scorex.transaction.History.BlockchainScore 6 | import scorex.utils.ScorexLogging 7 | 8 | trait BlockChain extends History with ScorexLogging { 9 | 10 | def blockAt(height: Int): Option[Block] 11 | 12 | def genesisBlock: Option[Block] = blockAt(1) 13 | 14 | override def parent(block: Block, back: Int = 1): Option[Block] = { 15 | require(back > 0) 16 | heightOf(block.referenceField.value).flatMap(referenceHeight => blockAt(referenceHeight - back + 1)) 17 | } 18 | 19 | private[transaction] def discardBlock(): BlockChain 20 | 21 | override def lastBlocks(howMany: Int): Seq[Block] = 22 | (Math.max(1, height() - howMany + 1) to height()).flatMap(blockAt).reverse 23 | 24 | def lookForward(parentSignature: BlockId, howMany: Int): Seq[BlockId] = 25 | heightOf(parentSignature).map { h => 26 | (h + 1).to(Math.min(height(), h + howMany: Int)).flatMap(blockAt).map(_.uniqueId) 27 | }.getOrElse(Seq()) 28 | 29 | def children(block: Block): Seq[Block] 30 | 31 | override lazy val genesis: Block = blockAt(1).get 32 | } 33 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/BlockStorage.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import org.h2.mvstore.MVStore 4 | import scorex.block.Block 5 | import scorex.block.Block.BlockId 6 | import scorex.crypto.encode.Base58 7 | import scorex.utils.ScorexLogging 8 | 9 | import scala.util.{Failure, Success, Try} 10 | 11 | /** 12 | * Storage interface combining both history(blockchain/blocktree) and state 13 | */ 14 | trait BlockStorage extends ScorexLogging { 15 | 16 | val db: MVStore 17 | 18 | val MaxRollback: Int 19 | 20 | val history: History 21 | 22 | def state: LagonakiState 23 | 24 | //Append block to current state 25 | def appendBlock(block: Block): Try[Unit] = synchronized { 26 | //TODO Rollback state for blocktree 27 | history.appendBlock(block).map { blocks => 28 | blocks foreach { b => 29 | state.processBlock(b) match { 30 | case Failure(e) => 31 | log.error("Failed to apply block to state", e) 32 | db.rollback() 33 | case Success(m) => 34 | db.commit() 35 | } 36 | } 37 | }.recoverWith { case e => 38 | log.error("Failed to append block:", e) 39 | Failure(e) 40 | } 41 | } 42 | 43 | //Should be used for linear blockchain only 44 | def removeAfter(signature: BlockId): Unit = synchronized { 45 | history match { 46 | case h: BlockChain => h.heightOf(signature) match { 47 | case Some(height) => 48 | while (!h.lastBlock.uniqueId.sameElements(signature)) h.discardBlock() 49 | state.rollbackTo(height) 50 | case None => 51 | log.warn(s"RemoveAfter non-existing block ${Base58.encode(signature)}") 52 | } 53 | case _ => 54 | throw new RuntimeException("Not available for other option than linear blockchain") 55 | } 56 | } 57 | 58 | 59 | } 60 | 61 | object BlockStorage { 62 | 63 | sealed trait Direction 64 | 65 | case object Forward extends Direction 66 | 67 | case object Reversed extends Direction 68 | 69 | /* 70 | * Block and direction to process it 71 | */ 72 | type BlocksToProcess = Seq[Block] 73 | } 74 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/BlockTree.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.utils.ScorexLogging 4 | 5 | trait BlockTree extends History with ScorexLogging 6 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/FeesStateChange.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import com.google.common.primitives.Longs 4 | 5 | case class FeesStateChange(fee: Long) extends StateChangeReason { 6 | override def bytes: Array[Byte] = Longs.toByteArray(fee) 7 | 8 | override val signature: Array[Byte] = Array.empty 9 | } -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/History.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.account.Account 4 | import scorex.block.Block 5 | import scorex.block.Block.BlockId 6 | import scorex.crypto.encode.Base58 7 | import scorex.transaction.BlockStorage.BlocksToProcess 8 | 9 | import scala.util.Try 10 | 11 | /** 12 | * History of a blockchain system is some blocktree in fact(like this: http://image.slidesharecdn.com/sfbitcoindev-chepurnoy-2015-150322043044-conversion-gate01/95/proofofstake-its-improvements-san-francisco-bitcoin-devs-hackathon-12-638.jpg), 13 | * where longest chain is being considered as canonical one, containing right kind of history. 14 | * 15 | * In cryptocurrencies of today blocktree view is usually implicit, means code supports only linear history, 16 | * but other options are possible. 17 | * 18 | * To say "longest chain" is the canonical one is simplification, usually some kind of "cumulative difficulty" 19 | * function has been used instead, even in PoW systems. 20 | */ 21 | 22 | trait History { 23 | 24 | import scorex.transaction.History.BlockchainScore 25 | 26 | /** 27 | * Height of the a chain, or a longest chain in the explicit block-tree 28 | */ 29 | def height(): Int 30 | 31 | /** 32 | * Quality score of a best chain, e.g. cumulative difficulty in case of Bitcoin / Nxt 33 | * @return 34 | */ 35 | def score(): BlockchainScore 36 | 37 | /** 38 | * Is there's no history, even genesis block 39 | * @return 40 | */ 41 | def isEmpty: Boolean = height() == 0 42 | 43 | def contains(block: Block): Boolean = contains(block.uniqueId) 44 | 45 | def contains(id: BlockId): Boolean = blockById(id).isDefined 46 | 47 | def blockById(blockId: Block.BlockId): Option[Block] 48 | 49 | def blockById(blockId: String): Option[Block] = Base58.decode(blockId).toOption.flatMap(blockById) 50 | 51 | /** 52 | * Height of a block if it's in the blocktree 53 | */ 54 | def heightOf(block: Block): Option[Int] = heightOf(block.uniqueId) 55 | 56 | def heightOf(blockId: Block.BlockId): Option[Int] 57 | 58 | /** 59 | * Use BlockStorage.appendBlock(block: Block) if you want to automatically update state 60 | * 61 | * Append block to a chain, based on it's reference 62 | * @param block - block to append 63 | * @return Blocks to process in state 64 | */ 65 | private[transaction] def appendBlock(block: Block): Try[BlocksToProcess] 66 | 67 | def parent(block: Block, back: Int = 1): Option[Block] 68 | 69 | def confirmations(block: Block): Option[Int] = 70 | heightOf(block).map(height() - _) 71 | 72 | /** 73 | * 74 | * @return Get list of blocks generated by specified address in specified interval of blocks 75 | */ 76 | def generatedBy(account: Account, from: Int, to: Int): Seq[Block] 77 | 78 | /** 79 | * Block with maximum blockchain score 80 | */ 81 | def lastBlock: Block = lastBlocks(1).head 82 | 83 | def lastBlocks(howMany: Int): Seq[Block] 84 | 85 | def lastBlockIds(howMany: Int): Seq[BlockId] = lastBlocks(howMany).map(b => b.uniqueId) 86 | 87 | /** 88 | * Return $howMany blocks starting from $parentSignature 89 | */ 90 | def lookForward(parentSignature: BlockId, howMany: Int): Seq[BlockId] 91 | 92 | /** 93 | * Average delay in milliseconds between last $blockNum blocks starting from $block 94 | */ 95 | def averageDelay(block: Block, blockNum: Int): Try[Long] = Try { 96 | (block.timestampField.value - parent(block, blockNum).get.timestampField.value) / blockNum 97 | } 98 | 99 | val genesis: Block 100 | 101 | } 102 | 103 | object History { 104 | type BlockchainScore = BigInt 105 | } 106 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/LagonakiState.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | trait LagonakiState extends State with BalanceSheet with AccountTransactionsHistory 4 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/State.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.block.Block 4 | 5 | import scala.util.Try 6 | 7 | /** 8 | * Abstract functional interface of state which is a result of a sequential blocks applying 9 | */ 10 | trait State { 11 | private[transaction] def processBlock(block: Block): Try[State] 12 | 13 | def isValid(tx: Transaction): Boolean = isValid(Seq(tx)) 14 | 15 | def isValid(txs: Seq[Transaction], height: Option[Int] = None): Boolean = validate(txs, height).size == txs.size 16 | 17 | def validate(txs: Seq[Transaction], height: Option[Int] = None): Seq[Transaction] 18 | 19 | def included(signature: Array[Byte], heightOpt: Option[Int]): Option[Int] 20 | 21 | def included(tx: Transaction, heightOpt: Option[Int] = None): Option[Int] = included(tx.signature, heightOpt) 22 | 23 | private[transaction] def rollbackTo(height: Int): State 24 | } 25 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/StateChangeReason.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.serialization.BytesSerializable 4 | 5 | /** 6 | * reason to change account balance 7 | */ 8 | trait StateChangeReason extends BytesSerializable { 9 | 10 | val signature: Array[Byte] 11 | } 12 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/Transaction.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.account.Account 4 | import scorex.serialization.JsonSerializable 5 | 6 | 7 | /** 8 | * A transaction is an atomic state modifier 9 | */ 10 | 11 | trait Transaction extends StateChangeReason with JsonSerializable { 12 | val fee: Long 13 | 14 | val timestamp: Long 15 | val recipient: Account 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/TransactionModule.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import scorex.block.{Block, BlockProcessingModule} 4 | 5 | trait TransactionModule[TransactionBlockData] extends BlockProcessingModule[TransactionBlockData] { 6 | 7 | val blockStorage: BlockStorage 8 | 9 | val utxStorage: UnconfirmedTransactionsStorage 10 | 11 | def isValid(block: Block): Boolean 12 | 13 | def transactions(block: Block): Seq[Transaction] 14 | 15 | def packUnconfirmed(): TransactionBlockData 16 | 17 | def clearFromUnconfirmed(data: TransactionBlockData): Unit 18 | 19 | def onNewOffchainTransaction(transaction: Transaction): Unit 20 | 21 | lazy val balancesSupport: Boolean = blockStorage.state match { 22 | case _: State with BalanceSheet => true 23 | case _ => false 24 | } 25 | 26 | lazy val accountWatchingSupport: Boolean = blockStorage.state match { 27 | case _: State with AccountTransactionsHistory => true 28 | case _ => false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/transaction/UnconfirmedTransactionsStorage.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | 4 | trait UnconfirmedTransactionsStorage { 5 | val SizeLimit: Int 6 | 7 | def putIfNew(tx: Transaction): Boolean 8 | 9 | def all(): Seq[Transaction] 10 | 11 | def getBySignature(signature: Array[Byte]): Option[Transaction] 12 | 13 | def remove(tx: Transaction) 14 | } 15 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/utils/JsonSerialization.scala: -------------------------------------------------------------------------------- 1 | package scorex.utils 2 | 3 | import play.api.libs.json._ 4 | import scorex.crypto.encode.Base58 5 | 6 | import scala.util.{Failure, Success} 7 | 8 | trait JsonSerialization { 9 | 10 | type Bytes = Array[Byte] 11 | 12 | implicit val bytesWrites = new Writes[Bytes] { 13 | def writes(bytes: Bytes) = JsString(Base58.encode(bytes)) 14 | } 15 | 16 | implicit def bytesReads: Reads[Bytes] = new Reads[Bytes] { 17 | def reads(json: JsValue): JsResult[Bytes] = json match { 18 | case JsString(encoded) => 19 | Base58.decode(encoded) match { 20 | case Success(decoded) => 21 | JsSuccess(decoded) 22 | case Failure(e) => 23 | throw new RuntimeException(s"Failed to parse Base58 encoded bytes") 24 | } 25 | case m => 26 | throw new RuntimeException(s"Bigint MUST be represented as string in json $m ${m.getClass} given") 27 | } 28 | } 29 | 30 | implicit val bigIntWrites = new Writes[BigInt] { 31 | def writes(bitInt: BigInt) = JsString(bitInt.toString) 32 | } 33 | 34 | implicit def bigIntReads: Reads[BigInt] = new Reads[BigInt] { 35 | def reads(json: JsValue): JsResult[BigInt] = json match { 36 | case JsString(bigint) => 37 | JsSuccess(BigInt(bigint)) 38 | case JsNumber(bigint) => 39 | JsSuccess(BigInt(bigint.toString)) 40 | case m => 41 | throw new RuntimeException(s"Bigint MUST be represented as string in json $m ${m.getClass} given") 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/utils/NTP.scala: -------------------------------------------------------------------------------- 1 | package scorex.utils 2 | 3 | import java.net.InetAddress 4 | 5 | import org.apache.commons.net.ntp.NTPUDPClient 6 | 7 | import scala.util.Try 8 | 9 | object NTP extends ScorexLogging { 10 | private val TimeTillUpdate = 1000 * 60 * 10L 11 | private val NtpServer = "pool.ntp.org" 12 | 13 | private var lastUpdate = 0L 14 | private var offset = 0L 15 | 16 | def correctedTime(): Long = { 17 | //CHECK IF OFFSET NEEDS TO BE UPDATED 18 | if (System.currentTimeMillis() > lastUpdate + TimeTillUpdate) { 19 | Try { 20 | updateOffSet() 21 | lastUpdate = System.currentTimeMillis() 22 | 23 | log.info("Adjusting time with " + offset + " milliseconds.") 24 | } recover { 25 | case e: Throwable => 26 | log.warn("Enable to get corrected time", e) 27 | } 28 | } 29 | 30 | //CALCULATE CORRECTED TIME 31 | System.currentTimeMillis() + offset 32 | } 33 | 34 | private def updateOffSet() { 35 | val client = new NTPUDPClient() 36 | client.setDefaultTimeout(10000) 37 | 38 | try { 39 | client.open() 40 | 41 | val info = client.getTime(InetAddress.getByName(NtpServer)) 42 | info.computeDetails() 43 | if (Option(info.getOffset).isDefined) offset = info.getOffset 44 | } catch { 45 | case t: Throwable => log.warn("Problems with NTP: ", t) 46 | } finally { 47 | client.close() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/utils/ScorexLogging.scala: -------------------------------------------------------------------------------- 1 | package scorex.utils 2 | 3 | import org.slf4j.LoggerFactory 4 | 5 | trait ScorexLogging { 6 | protected def log = LoggerFactory.getLogger(this.getClass) 7 | } 8 | -------------------------------------------------------------------------------- /scorex-basics/src/main/scala/scorex/utils/utils.scala: -------------------------------------------------------------------------------- 1 | package scorex 2 | 3 | import java.security.SecureRandom 4 | 5 | import scala.annotation.tailrec 6 | import scala.concurrent.duration._ 7 | import scala.reflect.runtime.universe 8 | import scala.util._ 9 | 10 | package object utils { 11 | 12 | @tailrec 13 | final def untilTimeout[T](timeout: FiniteDuration, 14 | delay: FiniteDuration = 100.milliseconds)(fn: => T): T = { 15 | Try { 16 | fn 17 | } match { 18 | case Success(x) => x 19 | case _ if timeout > delay => 20 | Thread.sleep(delay.toMillis) 21 | untilTimeout(timeout - delay, delay)(fn) 22 | case Failure(e) => throw e 23 | } 24 | } 25 | 26 | def randomBytes(howMany: Int = 32): Array[Byte] = { 27 | val r = new Array[Byte](howMany) 28 | new SecureRandom().nextBytes(r) //overrides r 29 | r 30 | } 31 | 32 | def objectFromString[T](fullClassName: String): Try[T] = Try { 33 | val runtimeMirror = universe.runtimeMirror(getClass.getClassLoader) 34 | val module = runtimeMirror.staticModule(fullClassName) 35 | val obj = runtimeMirror.reflectModule(module) 36 | obj.instance.asInstanceOf[T] 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /scorex-basics/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | app { 2 | product = "Scorex" 3 | } 4 | -------------------------------------------------------------------------------- /scorex-basics/src/test/scala/scorex/ScorexTestSuite.scala: -------------------------------------------------------------------------------- 1 | package scorex 2 | 3 | import org.scalatest.Suites 4 | import scorex.account.AccountSpecification 5 | import scorex.crypto.SigningFunctionsSpecification 6 | import scorex.crypto.ads.merkle.{AuthDataBlockSpecification, MerkleSpecification, MerkleTreeStorageSpecification} 7 | import scorex.network.HandshakeSpecification 8 | 9 | class ScorexTestSuite extends Suites( 10 | new AccountSpecification, 11 | new AuthDataBlockSpecification, 12 | new SigningFunctionsSpecification, 13 | new MerkleSpecification, 14 | new MerkleTreeStorageSpecification, 15 | new HandshakeSpecification 16 | ) 17 | -------------------------------------------------------------------------------- /scorex-basics/src/test/scala/scorex/account/AccountSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.account 2 | 3 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 4 | import org.scalatest.{Matchers, PropSpec} 5 | import scorex.crypto.encode.Base58 6 | import scorex.crypto.hash.SecureCryptographicHash._ 7 | 8 | class AccountSpecification extends PropSpec with PropertyChecks with GeneratorDrivenPropertyChecks with Matchers { 9 | 10 | property("Account.AddressNetwork should be taken from application.conf") { 11 | Account.AddressNetwork shouldBe (83: Byte) 12 | } 13 | 14 | property("Account.fromPublicKey should generate valid account") { 15 | forAll { data: Array[Byte] => 16 | Account.isValidAddress(Account.fromPublicKey(data)) shouldBe true 17 | } 18 | } 19 | 20 | property("Account.isValidAddress should return false for another address version") { 21 | forAll { (data: Array[Byte], AddressVersion2: Byte) => 22 | val publicKeyHash = hash(data).take(Account.HashLength) 23 | val withoutChecksum = AddressVersion2 +: Account.AddressNetwork +: publicKeyHash 24 | val addressVersion2 = Base58.encode(withoutChecksum ++ hash(withoutChecksum).take(Account.ChecksumLength)) 25 | Account.isValidAddress(addressVersion2) shouldBe (AddressVersion2 == Account.AddressVersion) 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /scorex-basics/src/test/scala/scorex/crypto/SigningFunctionsSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto 2 | 3 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 4 | import org.scalatest.{Matchers, PropSpec} 5 | import scorex.account.PrivateKeyAccount 6 | 7 | 8 | class SigningFunctionsSpecification extends PropSpec 9 | with PropertyChecks 10 | with GeneratorDrivenPropertyChecks 11 | with Matchers { 12 | 13 | property("signed message should be verifiable with appropriate public key") { 14 | forAll { (seed1: Array[Byte], seed2: Array[Byte], 15 | message1: Array[Byte], message2: Array[Byte]) => 16 | whenever(!seed1.sameElements(seed2) && !message1.sameElements(message2)) { 17 | val acc = new PrivateKeyAccount(seed1) 18 | val sig = EllipticCurveImpl.sign(acc, message1) 19 | val rightKey = acc.publicKey 20 | EllipticCurveImpl.verify(sig, message1, rightKey) should be (true) 21 | 22 | val wrongKey = new PrivateKeyAccount(seed2).publicKey 23 | EllipticCurveImpl.verify(sig, message1, wrongKey) shouldNot be (true) 24 | 25 | EllipticCurveImpl.verify(sig, message2, rightKey) shouldNot be (true) 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /scorex-basics/src/test/scala/scorex/crypto/ads/merkle/AuthDataBlockSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto.ads.merkle 2 | 3 | import org.scalacheck.Arbitrary 4 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 5 | import org.scalatest.{Matchers, PropSpec} 6 | import scorex.utils._ 7 | 8 | class AuthDataBlockSpecification extends PropSpec with PropertyChecks with GeneratorDrivenPropertyChecks with Matchers { 9 | 10 | val keyVal = for { 11 | key: Long <- Arbitrary.arbitrary[Long] 12 | value <- Arbitrary.arbitrary[String] 13 | } yield AuthDataBlock(value.getBytes, Seq(randomBytes(), randomBytes())) 14 | 15 | property("decode-encode roundtrip") { 16 | forAll(keyVal) { case b: AuthDataBlock[Array[Byte]] => 17 | val bytes = AuthDataBlock.encode(b) 18 | val decoded = AuthDataBlock.decode(bytes).get 19 | decoded.data shouldBe b.data 20 | decoded.merklePath.size shouldBe b.merklePath.size 21 | decoded.merklePath.head shouldBe b.merklePath.head 22 | decoded.merklePath(1) shouldBe b.merklePath(1) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /scorex-basics/src/test/scala/scorex/crypto/ads/merkle/MerkleSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto.ads.merkle 2 | 3 | import java.io.{File, FileOutputStream} 4 | 5 | import org.scalacheck.Gen 6 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 7 | import org.scalatest.{Matchers, PropSpec} 8 | import scorex.crypto.hash.FastCryptographicHash 9 | 10 | import scala.util.Random 11 | 12 | class MerkleSpecification extends PropSpec with PropertyChecks with GeneratorDrivenPropertyChecks with Matchers { 13 | 14 | 15 | property("value returned from byIndex() is valid for random dataset") { 16 | //fix block numbers for faster tests 17 | for (blocks <- List(7, 8, 9, 128)) { 18 | val smallInteger = Gen.choose(0, blocks - 1) 19 | val (treeDirName: String, _, tempFile: String) = generateFile(blocks) 20 | val tree = MerkleTree.fromFile(tempFile, treeDirName, 1024) 21 | forAll(smallInteger) { (index: Int) => 22 | val leafOption = tree.byIndex(index) 23 | leafOption should not be None 24 | val leaf = leafOption.get 25 | val resp = leaf.check(index, tree.rootHash)(FastCryptographicHash) 26 | resp shouldBe true 27 | } 28 | tree.storage.close() 29 | } 30 | } 31 | 32 | property("hash root is the same") { 33 | //fix block numbers for faster tests 34 | for (blocks <- List(7, 8, 9, 128)) { 35 | val (treeDirName: String, _, tempFile: String) = generateFile(blocks, "2") 36 | 37 | val fileTree = MerkleTree.fromFile(tempFile, treeDirName, 1024) 38 | val rootHash = fileTree.rootHash 39 | 40 | fileTree.storage.close() 41 | 42 | val tree = new MerkleTree(treeDirName, fileTree.nonEmptyBlocks, 1024) 43 | val newRootHash = tree.rootHash 44 | tree.storage.close() 45 | rootHash shouldBe newRootHash 46 | } 47 | } 48 | 49 | 50 | def generateFile(blocks: Int, subdir: String = "1"): (String, File, String) = { 51 | val treeDirName = "/tmp/scorex-test/test/" + subdir + "/" 52 | val treeDir = new File(treeDirName) 53 | val tempFile = treeDirName + "/data.file" 54 | 55 | 56 | val data = new Array[Byte](1024 * blocks) 57 | Random.nextBytes(data) 58 | treeDir.mkdirs() 59 | for (file <- treeDir.listFiles) file.delete 60 | 61 | val fos = new FileOutputStream(tempFile) 62 | fos.write(data) 63 | fos.close() 64 | (treeDirName, treeDir, tempFile) 65 | } 66 | } -------------------------------------------------------------------------------- /scorex-basics/src/test/scala/scorex/crypto/ads/merkle/MerkleTreeStorageSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.crypto.ads.merkle 2 | 3 | import java.io.File 4 | 5 | import org.scalacheck.{Arbitrary, Gen} 6 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 7 | import org.scalatest.{Matchers, PropSpec} 8 | import scorex.crypto.ads.merkle.TreeStorage.Key 9 | import scorex.crypto.hash.CryptographicHash.Digest 10 | 11 | 12 | class MerkleTreeStorageSpecification extends PropSpec with PropertyChecks with GeneratorDrivenPropertyChecks with Matchers { 13 | 14 | val treeDirName = "/tmp/scorex-test/test/MapDBStorageSpecification/" 15 | val treeDir = new File(treeDirName) 16 | treeDir.mkdirs() 17 | val dbFile = new File(treeDirName + "/db.file") 18 | val maxLevel = 50 19 | dbFile.delete() 20 | 21 | val keyVal = for { 22 | level: Int <- Gen.choose(1, maxLevel) 23 | key: Long <- Arbitrary.arbitrary[Long] 24 | value <- Arbitrary.arbitrary[String] 25 | } yield ((level, math.abs(key)), value.getBytes) 26 | 27 | 28 | property("set value and get it") { 29 | lazy val storage = new TreeStorage(treeDirName + "/test_db", maxLevel) 30 | 31 | forAll(keyVal) { case(key: Key, value: Digest) => 32 | whenever(key._1 >= 0.toLong && key._2 >= 0.toLong) { 33 | storage.set(key, value) 34 | assert(storage.get(key).get sameElements value) 35 | } 36 | } 37 | storage.close() 38 | } 39 | } -------------------------------------------------------------------------------- /scorex-basics/src/test/scala/scorex/network/HandshakeSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import java.net.{InetAddress, InetSocketAddress} 4 | 5 | import org.scalacheck.Gen 6 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 7 | import org.scalatest.{Matchers, PropSpec} 8 | import scorex.app.ApplicationVersion 9 | 10 | 11 | class HandshakeSpecification extends PropSpec 12 | with PropertyChecks 13 | with GeneratorDrivenPropertyChecks 14 | with Matchers { 15 | 16 | val MaxVersion = 999 17 | val MaxIp = 255 18 | val MaxPort = 65535 19 | 20 | val appVersionGen = for { 21 | fd <- Gen.choose(0, MaxVersion) 22 | sd <- Gen.choose(0, MaxVersion) 23 | td <- Gen.choose(0, MaxVersion) 24 | } yield ApplicationVersion(fd, sd, td) 25 | 26 | val isGen = for { 27 | ip1 <- Gen.choose(0, MaxIp) 28 | ip2 <- Gen.choose(0, MaxIp) 29 | ip3 <- Gen.choose(0, MaxIp) 30 | ip4 <- Gen.choose(0, MaxIp) 31 | port <- Gen.choose(0, MaxPort) 32 | } yield new InetSocketAddress(InetAddress.getByName(s"$ip1.$ip2.$ip3.$ip4"), port) 33 | 34 | val validNumbers = 35 | for (n <- Gen.choose(Integer.MIN_VALUE + 1, Integer.MAX_VALUE)) yield n 36 | 37 | property("handshake should remain the same after serialization/deserialization") { 38 | forAll(Gen.alphaStr suchThat (_.size > 0), appVersionGen, Gen.alphaStr, isGen, Gen.posNum[Long], Gen.posNum[Long]) { 39 | (appName: String, 40 | av: ApplicationVersion, 41 | nodeName: String, 42 | isa: InetSocketAddress, 43 | nonce: Long, 44 | time: Long) => 45 | 46 | val h1 = Handshake(appName, av, nodeName, nonce, None, time) 47 | val hr1 = Handshake.parseBytes(h1.bytes).get 48 | hr1.applicationName should be(h1.applicationName) 49 | hr1.applicationVersion should be(h1.applicationVersion) 50 | hr1.declaredAddress should be(h1.declaredAddress) 51 | hr1.nodeNonce should be(h1.nodeNonce) 52 | hr1.time should be(h1.time) 53 | 54 | val h2 = Handshake(appName, av, nodeName, nonce, Some(isa), time) 55 | val hr2 = Handshake.parseBytes(h2.bytes).get 56 | hr2.applicationName should be(h2.applicationName) 57 | hr2.applicationVersion should be(h2.applicationVersion) 58 | hr2.declaredAddress should be(h2.declaredAddress) 59 | hr2.nodeNonce should be(h2.nodeNonce) 60 | hr2.time should be(h2.time) 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /scorex-basics/src/test/scala/scorex/network/PeerDatabaseSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import java.net.{InetAddress, InetSocketAddress} 4 | 5 | import org.scalacheck.Gen 6 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 7 | import org.scalatest.{Matchers, PropSpec} 8 | import play.api.libs.json.{JsObject, Json} 9 | import scorex.network.peer.{PeerDatabaseImpl, PeerInfo} 10 | import scorex.settings.Settings 11 | 12 | 13 | class PeerDatabaseSpecification extends PropSpec with PropertyChecks with GeneratorDrivenPropertyChecks 14 | with Matchers { 15 | 16 | object TestSettings extends Settings { 17 | override lazy val settingsJSON: JsObject = Json.obj() 18 | override val filename: String = "" 19 | } 20 | 21 | val db = new PeerDatabaseImpl(TestSettings, None) 22 | val pi = PeerInfo(System.currentTimeMillis(), None, None) 23 | val portGen = Gen.choose(1, 0xFFFF) 24 | val addGen = for { 25 | a1: Int <- Gen.choose(1, 255) 26 | a2: Int <- Gen.choose(1, 255) 27 | a3: Int <- Gen.choose(1, 255) 28 | a4: Int <- Gen.choose(1, 255) 29 | } yield s"$a1.$a2.$a3.$a4" 30 | 31 | 32 | property("peer blacklisting") { 33 | forAll(addGen, portGen) { (add: String, port: Int) => 34 | val address = new InetSocketAddress(InetAddress.getByName(add), port) 35 | db.addOrUpdateKnownPeer(address, pi) 36 | db.knownPeers(false).contains(address) shouldBe true 37 | 38 | db.blacklistPeer(address) 39 | db.knownPeers(false).contains(address) shouldBe false 40 | db.blacklistedPeers().contains(address.getHostName) shouldBe true 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /scorex-consensus/README.md: -------------------------------------------------------------------------------- 1 | Scorex-Consensus Sub-Module 2 | ======================== 3 | 4 | The module contains interface and different implementations of consensus protocol. 5 | For now, Nxt and Quora consensus protocols are implemented. 6 | -------------------------------------------------------------------------------- /scorex-consensus/build.sbt: -------------------------------------------------------------------------------- 1 | name := "scorex-consensus" 2 | 3 | libraryDependencies ++= Dependencies.testKit ++ Dependencies.serialization ++ Dependencies.logging 4 | -------------------------------------------------------------------------------- /scorex-consensus/src/main/scala/scorex/consensus/OneGeneratorConsensusModule.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus 2 | 3 | import scorex.account.Account 4 | import scorex.block.Block 5 | 6 | trait OneGeneratorConsensusModule { 7 | 8 | /** 9 | * In most of algorithms there's only one block generator 10 | */ 11 | def feesDistribution(block: Block): Map[Account, Long] = { 12 | val forger = block.consensusModule.generators(block).ensuring(_.size == 1).head 13 | val fee = block.transactions.map(_.fee).sum 14 | Map(forger -> fee) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scorex-consensus/src/main/scala/scorex/consensus/PoSConsensusModule.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus 2 | 3 | import scorex.account.Account 4 | import scorex.block.Block 5 | import scorex.transaction.{BalanceSheet, TransactionModule} 6 | 7 | /** 8 | * Data and functions related to a Proof-of-Stake consensus algo 9 | */ 10 | trait PoSConsensusModule[ConsensusBlockData] extends ConsensusModule[ConsensusBlockData] { 11 | 12 | def generatingBalance[TransactionalBlockData](address: String) 13 | (implicit transactionModule: TransactionModule[TransactionalBlockData]): Long = { 14 | transactionModule.blockStorage.state.asInstanceOf[BalanceSheet] 15 | .balanceWithConfirmations(address, generatingBalanceDepth) 16 | } 17 | 18 | def generatingBalance[TransactionalBlockData](account: Account) 19 | (implicit transactionModule: TransactionModule[TransactionalBlockData]): Long = 20 | generatingBalance(account.address) 21 | 22 | val generatingBalanceDepth: Int 23 | 24 | override def generators(block: Block): Seq[Account] 25 | } 26 | -------------------------------------------------------------------------------- /scorex-consensus/src/main/scala/scorex/consensus/nxt/NxtConsensusBlockField.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus.nxt 2 | 3 | import com.google.common.primitives.{Bytes, Longs} 4 | import play.api.libs.json.{JsObject, Json} 5 | import scorex.block.BlockField 6 | import scorex.crypto.encode.Base58 7 | 8 | 9 | case class NxtConsensusBlockField(override val value: NxtLikeConsensusBlockData) 10 | extends BlockField[NxtLikeConsensusBlockData] { 11 | 12 | override val name: String = "nxt-consensus" 13 | 14 | override def bytes: Array[Byte] = 15 | Bytes.ensureCapacity(Longs.toByteArray(value.baseTarget), 8, 0) ++ 16 | value.generationSignature 17 | 18 | 19 | override def json: JsObject = Json.obj(name -> Json.obj( 20 | "base-target" -> value.baseTarget, 21 | "generation-signature" -> Base58.encode(value.generationSignature) 22 | )) 23 | } 24 | -------------------------------------------------------------------------------- /scorex-consensus/src/main/scala/scorex/consensus/nxt/NxtLikeConsensusBlockData.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus.nxt 2 | 3 | trait NxtLikeConsensusBlockData { 4 | val baseTarget: Long 5 | val generationSignature: Array[Byte] 6 | } 7 | -------------------------------------------------------------------------------- /scorex-consensus/src/main/scala/scorex/consensus/qora/QoraConsensusBlockField.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus.qora 2 | 3 | import com.google.common.primitives.{Bytes, Longs} 4 | import play.api.libs.json.{JsObject, Json} 5 | import scorex.block.BlockField 6 | import scorex.crypto.encode.Base58 7 | 8 | 9 | case class QoraConsensusBlockField(override val value: QoraLikeConsensusBlockData) 10 | extends BlockField[QoraLikeConsensusBlockData] { 11 | 12 | override val name: String = "qora-consensus" 13 | 14 | override def bytes: Array[Byte] = 15 | Bytes.ensureCapacity(Longs.toByteArray(value.generatingBalance), 8, 0) ++ 16 | value.generatorSignature 17 | 18 | 19 | override def json: JsObject = Json.obj(name -> Json.obj( 20 | "base-target" -> value.generatingBalance, 21 | "generation-signature" -> Base58.encode(value.generatorSignature) 22 | )) 23 | } 24 | -------------------------------------------------------------------------------- /scorex-consensus/src/main/scala/scorex/consensus/qora/QoraLikeConsensusBlockData.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus.qora 2 | 3 | trait QoraLikeConsensusBlockData { 4 | val generatingBalance: Long 5 | val generatorSignature: Array[Byte] 6 | } 7 | -------------------------------------------------------------------------------- /scorex-consensus/src/main/scala/scorex/consensus/qora/api/http/QoraConsensusApiRoute.scala: -------------------------------------------------------------------------------- 1 | package scorex.consensus.qora.api.http 2 | 3 | import javax.ws.rs.Path 4 | 5 | import akka.actor.ActorRefFactory 6 | import akka.http.scaladsl.server.Route 7 | import io.swagger.annotations._ 8 | import play.api.libs.json.Json 9 | import scorex.api.http.{ApiRoute, CommonApiFunctions, InvalidNotNumber} 10 | import scorex.app.Application 11 | import scorex.consensus.qora.QoraLikeConsensusModule 12 | 13 | import scala.util.Try 14 | 15 | @Path("/consensus") 16 | @Api(value = "/consensus", description = "Consensus-related calls") 17 | case class QoraConsensusApiRoute(override val application: Application) 18 | (implicit val context: ActorRefFactory) 19 | extends ApiRoute with CommonApiFunctions { 20 | 21 | private val consensusModule = application.consensusModule.asInstanceOf[QoraLikeConsensusModule] 22 | private val blockStorage = application.blockStorage 23 | 24 | override val route: Route = 25 | pathPrefix("consensus") { 26 | algo ~ time ~ timeForBalance ~ nextGenerating ~ generating 27 | } 28 | 29 | @Path("/generatingbalance/{blockId}") 30 | @ApiOperation(value = "Generating balance", notes = "Generating balance of a block with given id", httpMethod = "GET") 31 | @ApiImplicitParams(Array( 32 | new ApiImplicitParam(name = "blockId", value = "Block id", required = true, dataType = "String", paramType = "path") 33 | )) 34 | def generating: Route = { 35 | path("generatingbalance" / Segment) { case encodedSignature => 36 | getJsonRoute { 37 | withBlock(blockStorage.history, encodedSignature) { block => 38 | Json.obj( 39 | "generatingbalance" -> consensusModule.consensusBlockData(block).generatingBalance 40 | ) 41 | } 42 | } 43 | } 44 | } 45 | 46 | @Path("/generatingbalance") 47 | @ApiOperation(value = "Next generating balance", notes = "Generating balance of a next block", httpMethod = "GET") 48 | def nextGenerating: Route = { 49 | path("generatingbalance") { 50 | getJsonRoute { 51 | val generatingBalance = consensusModule.getNextBlockGeneratingBalance(blockStorage.history) 52 | Json.obj("generatingbalance" -> generatingBalance) 53 | } 54 | } 55 | } 56 | 57 | @Path("/time/{balance}") 58 | @ApiOperation(value = "Balance time", notes = "estimated time before next block with given generating balance", httpMethod = "GET") 59 | @ApiImplicitParams(Array( 60 | new ApiImplicitParam(name = "balance", value = "Generating balance", required = true, dataType = "Long", paramType = "path") 61 | )) 62 | def timeForBalance: Route = { 63 | path("time" / Segment) { case generatingBalance => 64 | getJsonRoute { 65 | val jsRes = Try { 66 | val timePerBlock = consensusModule.getBlockTime(generatingBalance.toLong) 67 | Json.obj("time" -> timePerBlock) 68 | }.getOrElse(InvalidNotNumber.json) 69 | jsRes 70 | } 71 | } 72 | } 73 | 74 | @Path("/time") 75 | @ApiOperation(value = "Time", notes = "Estimated time before next block", httpMethod = "GET") 76 | def time: Route = { 77 | path("time") { 78 | getJsonRoute { 79 | val block = blockStorage.history.lastBlock 80 | val genBalance = consensusModule.consensusBlockData(block).generatingBalance 81 | val timePerBlock = consensusModule.getBlockTime(genBalance) 82 | Json.obj("time" -> timePerBlock) 83 | } 84 | } 85 | } 86 | 87 | @Path("/algo") 88 | @ApiOperation(value = "Consensus algo", notes = "Shows which consensus algo being using", httpMethod = "GET") 89 | def algo: Route = { 90 | path("algo") { 91 | getJsonRoute { 92 | Json.obj("consensusAlgo" -> "qora") 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scorex-transaction/README.md: -------------------------------------------------------------------------------- 1 | Scorex-Transaction Sub-Module 2 | ======================== 3 | 4 | The module contains interface and implementation of transaction protocol. 5 | -------------------------------------------------------------------------------- /scorex-transaction/build.sbt: -------------------------------------------------------------------------------- 1 | name := "scorex-transaction" 2 | 3 | libraryDependencies ++= 4 | Dependencies.testKit ++ 5 | Dependencies.db ++ 6 | Dependencies.serialization ++ 7 | Dependencies.logging ++ 8 | Seq( 9 | "com.github.pathikrit" %% "better-files" % "2.13.0" 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | io { 3 | tcp { 4 | direct-buffer-size = 1536 KiB 5 | trace-logging = off 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/api/http/CommonTransactionApiFunctions.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import play.api.libs.json.{JsObject, JsValue} 4 | import scorex.account.Account 5 | import scorex.wallet.Wallet 6 | 7 | 8 | trait CommonTransactionApiFunctions extends CommonApiFunctions { 9 | 10 | protected[api] def walletExists()(implicit wallet: Wallet): Option[JsObject] = 11 | if (wallet.exists()) Some(WalletAlreadyExists.json) else None 12 | 13 | protected[api] def withPrivateKeyAccount(wallet: Wallet, address: String) 14 | (action: Account => JsValue): JsValue = 15 | walletNotExists(wallet).getOrElse { 16 | if (!Account.isValidAddress(address)) { 17 | InvalidAddress.json 18 | } else { 19 | wallet.privateKeyAccount(address) match { 20 | case None => WalletAddressNotExists.json 21 | case Some(account) => action(account) 22 | } 23 | } 24 | } 25 | 26 | protected[api] def walletNotExists(wallet: Wallet): Option[JsObject] = 27 | if (!wallet.exists()) Some(WalletNotExist.json) else None 28 | } 29 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/api/http/PaymentApiRoute.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import javax.ws.rs.Path 4 | 5 | import akka.actor.ActorRefFactory 6 | import akka.http.scaladsl.server.Route 7 | import io.swagger.annotations._ 8 | import play.api.libs.json.{JsError, JsSuccess, Json} 9 | import scorex.app.Application 10 | import scorex.transaction.LagonakiTransaction.ValidationResult 11 | import scorex.transaction.SimpleTransactionModule 12 | import scorex.transaction.state.wallet.Payment 13 | 14 | import scala.util.Try 15 | 16 | @Path("/payment") 17 | @Api(value = "/payment", description = "Payment operations.", position = 1) 18 | case class PaymentApiRoute(override val application: Application)(implicit val context: ActorRefFactory) 19 | extends ApiRoute with CommonTransactionApiFunctions { 20 | 21 | // TODO asInstanceOf 22 | implicit lazy val transactionModule: SimpleTransactionModule = application.transactionModule.asInstanceOf[SimpleTransactionModule] 23 | lazy val wallet = application.wallet 24 | 25 | override lazy val route = payment 26 | 27 | @ApiOperation(value = "Send payment", 28 | notes = "Send payment to another wallet", 29 | httpMethod = "POST", 30 | produces = "application/json", 31 | consumes = "application/json") 32 | @ApiImplicitParams(Array( 33 | new ApiImplicitParam( 34 | name = "body", 35 | value = "Json with data", 36 | required = true, 37 | paramType = "body", 38 | dataType = "scorex.transaction.state.wallet.Payment", 39 | defaultValue = "{\n\t\"amount\":400,\n\t\"fee\":1,\n\t\"sender\":\"senderId\",\n\t\"recipient\":\"recipientId\"\n}" 40 | ) 41 | )) 42 | @ApiResponses(Array( 43 | new ApiResponse(code = 200, message = "Json with response or error") 44 | )) 45 | def payment: Route = path("payment") { 46 | entity(as[String]) { body => 47 | withAuth { 48 | postJsonRoute { 49 | walletNotExists(wallet).getOrElse { 50 | Try(Json.parse(body)).map { js => 51 | js.validate[Payment] match { 52 | case err: JsError => 53 | WrongTransactionJson(err).json 54 | case JsSuccess(payment: Payment, _) => 55 | val txOpt = transactionModule.createPayment(payment, wallet) 56 | txOpt match { 57 | case Some(tx) => 58 | tx.validate match { 59 | case ValidationResult.ValidateOke => 60 | tx.json 61 | 62 | case ValidationResult.InvalidAddress => 63 | InvalidAddress.json 64 | 65 | case ValidationResult.NegativeAmount => 66 | NegativeAmount.json 67 | 68 | case ValidationResult.NegativeFee => 69 | NegativeFee.json 70 | 71 | case ValidationResult.NoBalance => 72 | NoBalance.json 73 | } 74 | case None => 75 | InvalidSender.json 76 | } 77 | } 78 | }.getOrElse(WrongJson.json) 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/api/http/WalletApiRoute.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import javax.ws.rs.Path 4 | 5 | import akka.actor.ActorRefFactory 6 | import akka.http.scaladsl.server.Route 7 | import io.swagger.annotations._ 8 | import play.api.libs.json.Json 9 | import scorex.app.Application 10 | import scorex.crypto.encode.Base58 11 | 12 | @Path("/wallet") 13 | @Api(value = "/wallet", description = "Wallet-related calls") 14 | case class WalletApiRoute(override val application: Application)(implicit val context: ActorRefFactory) 15 | extends ApiRoute with CommonTransactionApiFunctions { 16 | 17 | private val wallet = application.wallet 18 | 19 | override lazy val route = root ~ seed 20 | 21 | @Path("/seed") 22 | @ApiOperation(value = "Seed", notes = "Export wallet seed", httpMethod = "GET") 23 | def seed: Route = { 24 | path("wallet" / "seed") { 25 | withAuth { 26 | getJsonRoute { 27 | lazy val seedJs = Json.obj("seed" -> Base58.encode(wallet.seed)) 28 | walletNotExists(wallet).getOrElse(seedJs) 29 | } 30 | } 31 | } 32 | } 33 | 34 | @Path("/") 35 | @ApiOperation(value = "Wallet", notes = "Display whether wallet exists or not", httpMethod = "GET") 36 | def root: Route = { 37 | path("wallet") { 38 | getJsonRoute { 39 | Json.obj("exists" -> wallet.exists()) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/api/http/apiErrors.scala: -------------------------------------------------------------------------------- 1 | package scorex.api.http 2 | 3 | import play.api.libs.json.{JsArray, JsError, JsString} 4 | 5 | //WALLET 6 | 7 | case object WalletNotExist extends ApiError { 8 | override val id: Int = 201 9 | override val message: String = "wallet does not exist" 10 | } 11 | 12 | case object WalletAddressNotExists extends ApiError { 13 | override val id: Int = 202 14 | override val message: String = "address does not exist in wallet" 15 | } 16 | 17 | case object WalletLocked extends ApiError { 18 | override val id: Int = 203 19 | override val message: String = "wallet is locked" 20 | } 21 | 22 | case object WalletAlreadyExists extends ApiError { 23 | override val id: Int = 204 24 | override val message: String = "wallet already exists" 25 | } 26 | 27 | case object WalletSeedExportFailed extends ApiError { 28 | override val id: Int = 205 29 | override val message: String = "seed exporting failed" 30 | } 31 | 32 | 33 | //TRANSACTIONS 34 | case object TransactionNotExists extends ApiError { 35 | override val id: Int = 311 36 | override val message: String = "transactions does not exist" 37 | } 38 | 39 | 40 | case object NoBalance extends ApiError { 41 | override val id: Int = 2 42 | override val message: String = "not enough balance" 43 | } 44 | 45 | case object NegativeAmount extends ApiError { 46 | override val id: Int = 111 47 | override val message: String = "negative amount" 48 | } 49 | 50 | case object NegativeFee extends ApiError { 51 | override val id: Int = 112 52 | override val message: String = "negative fee" 53 | } 54 | 55 | case class WrongTransactionJson(err: JsError) extends ApiError { 56 | override val id: Int = 113 57 | override val message: String = 58 | err.errors.map(e => s"Validation failed for field '${e._1}', errors:${e._2}. ").mkString("\n") 59 | } 60 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/network/TransactionalMessagesRepo.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import scorex.network.message.Message.MessageCode 4 | import scorex.network.message.MessageSpec 5 | import scorex.transaction.{LagonakiTransaction, Transaction} 6 | 7 | import scala.util.Try 8 | 9 | object TransactionalMessagesRepo { 10 | 11 | object TransactionMessageSpec extends MessageSpec[Transaction] { 12 | override val messageCode: MessageCode = 25: Byte 13 | 14 | override val messageName: String = "Transaction message" 15 | 16 | override def deserializeData(bytes: Array[MessageCode]): Try[Transaction] = 17 | LagonakiTransaction.parseBytes(bytes) 18 | 19 | override def serializeData(tx: Transaction): Array[MessageCode] = tx.bytes 20 | } 21 | 22 | val specs = Seq(TransactionMessageSpec) 23 | } 24 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/network/UnconfirmedPoolSynchronizer.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import scorex.app.Application 4 | import scorex.network.NetworkController.DataFromPeer 5 | import scorex.network.TransactionalMessagesRepo.TransactionMessageSpec 6 | import scorex.transaction.{LagonakiTransaction, Transaction} 7 | import scorex.utils.ScorexLogging 8 | 9 | /** 10 | * Synchronizing transactions that are not in blockchain yet 11 | */ 12 | class UnconfirmedPoolSynchronizer(application: Application) extends ViewSynchronizer with ScorexLogging { 13 | 14 | override val messageSpecs = Seq(TransactionMessageSpec) 15 | 16 | override val networkControllerRef = application.networkController 17 | 18 | val transactionModule = application.transactionModule 19 | 20 | override def receive: Receive = { 21 | case DataFromPeer(msgId, tx: Transaction, remote) if msgId == TransactionMessageSpec.messageCode => 22 | log.debug(s"Got tx: $tx") 23 | (tx, transactionModule.blockStorage.state.isValid(tx)) match { 24 | case (ltx: LagonakiTransaction, true) => transactionModule.utxStorage.putIfNew(ltx) 25 | case (atx, false) => log.error(s"Transaction $atx is not valid") 26 | case m => log.error(s"Got unexpected transaction: $m") 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/transaction/LagonakiTransaction.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import com.google.common.primitives.Ints 4 | import play.api.libs.json.Json 5 | import scorex.account.Account 6 | import scorex.crypto.encode.Base58 7 | import scorex.serialization.{BytesSerializable, Deser} 8 | import scorex.transaction.LagonakiTransaction.TransactionType 9 | 10 | import scala.concurrent.duration._ 11 | import scala.util.{Failure, Try} 12 | 13 | 14 | abstract class LagonakiTransaction(val transactionType: TransactionType.Value, 15 | override val recipient: Account, 16 | val amount: Long, 17 | override val fee: Long, 18 | override val timestamp: Long, 19 | override val signature: Array[Byte]) extends Transaction with BytesSerializable { 20 | 21 | import LagonakiTransaction._ 22 | 23 | lazy val deadline = timestamp + 24.hours.toMillis 24 | 25 | lazy val feePerByte = fee / dataLength.toDouble 26 | lazy val hasMinimumFee = fee >= MinimumFee 27 | lazy val hasMinimumFeePerByte = { 28 | val minFeePerByte = 1.0 / MaxBytesPerToken 29 | feePerByte >= minFeePerByte 30 | } 31 | 32 | val TypeId = transactionType.id 33 | 34 | //PARSE/CONVERT 35 | val dataLength: Int 36 | 37 | val creator: Option[Account] 38 | 39 | 40 | val signatureValid: Boolean 41 | 42 | //VALIDATE 43 | def validate: ValidationResult.Value 44 | 45 | def involvedAmount(account: Account): Long 46 | 47 | def balanceChanges(): Seq[(Account, Long)] 48 | 49 | override def equals(other: Any): Boolean = other match { 50 | case tx: LagonakiTransaction => signature.sameElements(tx.signature) 51 | case _ => false 52 | } 53 | 54 | override def hashCode(): Int = Ints.fromByteArray(signature) 55 | 56 | protected def jsonBase() = { 57 | Json.obj("type" -> transactionType.id, 58 | "fee" -> fee, 59 | "timestamp" -> timestamp, 60 | "signature" -> Base58.encode(this.signature) 61 | ) 62 | } 63 | } 64 | 65 | object LagonakiTransaction extends Deser[LagonakiTransaction] { 66 | 67 | val MaxBytesPerToken = 512 68 | 69 | //MINIMUM FEE 70 | val MinimumFee = 1 71 | val RecipientLength = Account.AddressLength 72 | val TypeLength = 1 73 | val TimestampLength = 8 74 | val AmountLength = 8 75 | 76 | object ValidationResult extends Enumeration { 77 | type ValidationResult = Value 78 | 79 | val ValidateOke = Value(1) 80 | val InvalidAddress = Value(2) 81 | val NegativeAmount = Value(3) 82 | val NegativeFee = Value(4) 83 | val NoBalance = Value(5) 84 | } 85 | 86 | //TYPES 87 | object TransactionType extends Enumeration { 88 | val GenesisTransaction = Value(1) 89 | val PaymentTransaction = Value(2) 90 | } 91 | 92 | def parseBytes(data: Array[Byte]): Try[LagonakiTransaction] = 93 | data.head match { 94 | case txType: Byte if txType == TransactionType.GenesisTransaction.id => 95 | GenesisTransaction.parseTail(data.tail) 96 | 97 | case txType: Byte if txType == TransactionType.PaymentTransaction.id => 98 | PaymentTransaction.parseTail(data.tail) 99 | 100 | case txType => Failure(new Exception(s"Invalid transaction type: $txType")) 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/transaction/TransactionSettings.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import play.api.libs.json.JsObject 4 | 5 | trait TransactionSettings { 6 | val settingsJSON: JsObject 7 | 8 | private val DefaultHistory = "blockchain" 9 | lazy val history = (settingsJSON \ "history").asOpt[String].getOrElse(DefaultHistory) 10 | 11 | private val DefaultMaxRollback = 100 12 | lazy val MaxRollback = (settingsJSON \ "maxRollback").asOpt[Int].getOrElse(DefaultMaxRollback) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/transaction/state/database/UnconfirmedTransactionsDatabaseImpl.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction.state.database 2 | 3 | import com.google.common.primitives.Longs 4 | import scorex.transaction.{Transaction, UnconfirmedTransactionsStorage} 5 | import scorex.utils.ScorexLogging 6 | 7 | import scala.collection.concurrent.TrieMap 8 | 9 | 10 | class UnconfirmedTransactionsDatabaseImpl(val SizeLimit: Int = 1000) extends UnconfirmedTransactionsStorage with ScorexLogging { 11 | 12 | val transactions = TrieMap[Long, Transaction]() 13 | 14 | //using Long instead of Array[Byte] just for performance improvement 15 | private def key(signature: Array[Byte]): Long = 16 | Longs.fromByteArray(signature.take(8)) 17 | 18 | private def key(tx: Transaction): Long = key(tx.signature) 19 | 20 | override def putIfNew(tx: Transaction): Boolean = if (transactions.size < SizeLimit) { 21 | transactions.putIfAbsent(key(tx), tx).isEmpty 22 | } else { 23 | log.warn("Transaction pool size limit is reached") 24 | false 25 | } 26 | 27 | override def remove(tx: Transaction): Unit = transactions -= key(tx) 28 | 29 | override def all(): Seq[Transaction] = transactions.values.toSeq 30 | 31 | override def getBySignature(signature: Array[Byte]): Option[Transaction] = transactions.get(key(signature)) 32 | } 33 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/transaction/state/database/state/AccState.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction.state.database.state 2 | 3 | case class AccState(balance: Long) -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/transaction/state/database/state/Row.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction.state.database.state 2 | 3 | import java.nio.ByteBuffer 4 | 5 | import com.google.common.primitives.{Ints, Longs} 6 | import org.h2.mvstore.WriteBuffer 7 | import org.h2.mvstore.`type`.DataType 8 | import scorex.serialization.BytesSerializable 9 | import scorex.transaction.{FeesStateChange, LagonakiTransaction, StateChangeReason} 10 | 11 | case class Row(state: AccState, reason: Reason, lastRowHeight: Int) extends DataType with BytesSerializable { 12 | 13 | lazy val bytes: Array[Byte] = Ints.toByteArray(lastRowHeight) ++ 14 | Longs.toByteArray(state.balance) ++ 15 | Ints.toByteArray(reason.length) ++ 16 | reason.foldLeft(Array.empty: Array[Byte]) { (b, scr) => 17 | b ++ Ints.toByteArray(scr.bytes.length) ++ scr.bytes 18 | } 19 | 20 | override def compare(a: scala.Any, b: scala.Any): Int = (a, b) match { 21 | case (o1: Row, o2: Row) => BigInt(o1.bytes).compare(BigInt(o2.bytes)) 22 | case _ => 1 23 | } 24 | 25 | override def write(buff: WriteBuffer, obj: scala.Any): Unit = { 26 | buff.put(obj.asInstanceOf[Row].bytes) 27 | } 28 | 29 | override def write(buff: WriteBuffer, obj: Array[AnyRef], len: Int, key: Boolean): Unit = 30 | obj.foreach(o => write(buff, o)) 31 | 32 | override def read(buff: ByteBuffer): AnyRef = Row.deserialize(buff) 33 | 34 | override def read(buff: ByteBuffer, obj: Array[AnyRef], len: Int, key: Boolean): Unit = { 35 | (0 until len) foreach { i => 36 | obj(i) = read(buff); 37 | } 38 | } 39 | 40 | override def getMemory(obj: scala.Any): Int = bytes.length 41 | } 42 | 43 | object Row { 44 | def deserialize(bytes: Array[Byte]): Row = { 45 | val b = ByteBuffer.allocate(bytes.length) 46 | b.put(bytes) 47 | b.flip() 48 | deserialize(b) 49 | } 50 | 51 | 52 | def deserialize(b: ByteBuffer): Row = { 53 | val lrh = b.getInt 54 | val accBalance = b.getLong 55 | val reasonLength = b.getInt 56 | val reason: Seq[StateChangeReason] = (0 until reasonLength) map { i => 57 | val txSize = b.getInt 58 | val tx = new Array[Byte](txSize) 59 | b.get(tx) 60 | if (txSize == 8) FeesStateChange(Longs.fromByteArray(tx)) 61 | else LagonakiTransaction.parseBytes(tx).get //todo: .get w/out catching 62 | } 63 | Row(AccState(accBalance), reason.toList, lrh) 64 | } 65 | } -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/transaction/state/database/state/package.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction.state.database 2 | 3 | import scorex.transaction.StateChangeReason 4 | 5 | package object state { 6 | type Address = String 7 | type Reason = List[StateChangeReason] 8 | 9 | } 10 | -------------------------------------------------------------------------------- /scorex-transaction/src/main/scala/scorex/transaction/state/wallet/Payment.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction.state.wallet 2 | 3 | import play.api.libs.functional.syntax._ 4 | import play.api.libs.json.{JsPath, Reads, Writes} 5 | 6 | case class Payment(amount: Long, fee: Long, sender: String, recipient: String) 7 | 8 | object Payment { 9 | implicit val paymentWrites: Writes[Payment] = ( 10 | (JsPath \ "amount").write[Long] and 11 | (JsPath \ "fee").write[Long] and 12 | (JsPath \ "sender").write[String] and 13 | (JsPath \ "recipient").write[String] 14 | ) (unlift(Payment.unapply)) 15 | 16 | implicit val paymentReads: Reads[Payment] = ( 17 | (JsPath \ "amount").read[Long] and 18 | (JsPath \ "fee").read[Long] and 19 | (JsPath \ "sender").read[String] and 20 | (JsPath \ "recipient").read[String] 21 | ) (Payment.apply _) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /scorex-transaction/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | app { 2 | product = "Scorex" 3 | release = "Lagonaki" 4 | } 5 | -------------------------------------------------------------------------------- /scorex-transaction/src/test/scala/scorex/transaction/GenesisTransactionSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import org.scalatest._ 4 | import org.scalatest.prop.PropertyChecks 5 | import scorex.account.{PrivateKeyAccount, PublicKeyAccount} 6 | import scorex.crypto.encode.Base58 7 | 8 | class GenesisTransactionSpecification extends PropSpec with PropertyChecks with Matchers { 9 | 10 | val defaultRecipient = new PublicKeyAccount(Array.fill(32)(0: Byte)) 11 | 12 | property("GenesisTransaction Signature should be the same") { 13 | val balance = 457L 14 | val timestamp = 2398762345L 15 | val signature = GenesisTransaction.generateSignature(defaultRecipient, balance, timestamp) 16 | 17 | val expected = "3dzTukYksn9UK3fCwuYQsGrjzqjqUEXyvWXCBmFQtCiKF8YZK6eutuhuBmJhzNPm1vRz6SXcEJnyMzGRnKULgdnK" 18 | val actual = Base58.encode(signature) 19 | 20 | assert(actual == expected) 21 | } 22 | 23 | property("GenesisTransaction parse from Bytes should work fine") { 24 | val bytes = Base58.decode("5GoidY2PcCc7ENdrcapZcmmdq2H57NuiXEdgVkpfnnzkB4o8R575WVR1Xw").get 25 | 26 | val actualTransaction = GenesisTransaction.parseBytes(bytes).get 27 | 28 | val balance = 149857264546L 29 | val timestamp = 4598723454L 30 | val expectedTransaction = new GenesisTransaction(defaultRecipient, balance, timestamp) 31 | 32 | actualTransaction should equal(expectedTransaction) 33 | } 34 | 35 | property("GenesisTransaction serialize/deserialize roundtrip") { 36 | forAll { (recipientSeed: Array[Byte], 37 | time: Long, 38 | amount: Long) => 39 | val recipient = new PrivateKeyAccount(recipientSeed) 40 | val source = new GenesisTransaction(recipient, amount, time) 41 | val bytes = source.bytes 42 | val dest = GenesisTransaction.parseBytes(bytes).get 43 | 44 | source should equal(dest) 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /scorex-transaction/src/test/scala/scorex/transaction/RowSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import java.io._ 4 | 5 | import org.scalacheck.Gen 6 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 7 | import org.scalatest.{Matchers, PropSpec} 8 | import scorex.transaction.state.database.state._ 9 | 10 | class RowSpecification extends PropSpec 11 | with PropertyChecks 12 | with GeneratorDrivenPropertyChecks 13 | with Matchers 14 | with TransactionGen { 15 | 16 | val FileName = "object.data" 17 | 18 | property("Row serialize and deserialize") { 19 | forAll(paymentGenerator, Gen.posNum[Long], Gen.posNum[Long], Gen.posNum[Int]) { (payment: PaymentTransaction, 20 | balance: Long, 21 | fee: Long, 22 | lastRowHeight: Int) => 23 | 24 | val txs = List(FeesStateChange(fee), payment) 25 | LagonakiTransaction.parseBytes(payment.bytes).get shouldBe payment 26 | val row = Row(AccState(balance), txs, lastRowHeight) 27 | 28 | val objectOutputStream = new ObjectOutputStream(new FileOutputStream(FileName)) 29 | objectOutputStream.writeObject(row) 30 | objectOutputStream.close() 31 | 32 | val objectInputStream = new ObjectInputStream(new FileInputStream(FileName)) 33 | val des = objectInputStream.readObject().asInstanceOf[Row] 34 | des shouldBe row 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /scorex-transaction/src/test/scala/scorex/transaction/StoredStateUnitTests.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import java.io.File 4 | 5 | import org.h2.mvstore.MVStore 6 | import org.scalacheck.Gen 7 | import org.scalatest._ 8 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 9 | import scorex.transaction.state.database.blockchain.StoredState 10 | import scorex.transaction.state.database.state._ 11 | 12 | class StoredStateUnitTests extends PropSpec with PropertyChecks with GeneratorDrivenPropertyChecks with Matchers 13 | with PrivateMethodTester with OptionValues with TransactionGen { 14 | 15 | val folder = "/tmp/scorex/test/" 16 | new File(folder).mkdirs() 17 | val stateFile = folder + "state.dat" 18 | new File(stateFile).delete() 19 | 20 | val db = new MVStore.Builder().fileName(stateFile).compress().open() 21 | val state = new StoredState(db) 22 | val testAdd = "aPFwzRp5TXCzi6DSuHmpmbQunopXRuxLk" 23 | val applyMethod = PrivateMethod[Unit]('applyChanges) 24 | 25 | property("private methods") { 26 | 27 | forAll(paymentGenerator, Gen.posNum[Long]) { (tx: PaymentTransaction, 28 | balance: Long) => 29 | state.balance(testAdd) shouldBe 0 30 | state invokePrivate applyMethod(Map(testAdd ->(AccState(balance), Seq(FeesStateChange(balance), tx, tx)))) 31 | state.balance(testAdd) shouldBe balance 32 | state.included(tx).value shouldBe state.stateHeight 33 | state invokePrivate applyMethod(Map(testAdd ->(AccState(0L), Seq(tx)))) 34 | } 35 | } 36 | 37 | property("Reopen state") { 38 | val balance = 1234L 39 | state invokePrivate applyMethod(Map(testAdd ->(AccState(balance), Seq(FeesStateChange(balance))))) 40 | state.balance(testAdd) shouldBe balance 41 | db.close() 42 | 43 | val state2 = new StoredState(new MVStore.Builder().fileName(stateFile).compress().open()) 44 | state2.balance(testAdd) shouldBe balance 45 | state2 invokePrivate applyMethod(Map(testAdd ->(AccState(0L), Seq()))) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /scorex-transaction/src/test/scala/scorex/transaction/TransactionGen.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import org.scalacheck.{Arbitrary, Gen} 4 | import scorex.account.{Account, PrivateKeyAccount} 5 | import scorex.utils._ 6 | 7 | trait TransactionGen { 8 | val sender = PrivateKeyAccount(randomBytes(32), randomBytes(32), randomBytes(32)) 9 | 10 | val paymentGenerator: Gen[PaymentTransaction] = for { 11 | amount: Long <- Arbitrary.arbitrary[Long] 12 | fee: Long <- Arbitrary.arbitrary[Long] 13 | timestamp: Long <- Arbitrary.arbitrary[Long] 14 | } yield PaymentTransaction(sender, new Account(Account.fromPublicKey(randomBytes(32))), amount, fee, timestamp, randomBytes(64)) 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scorex-transaction/src/test/scala/scorex/transaction/TransactionTestSuite.scala: -------------------------------------------------------------------------------- 1 | package scorex.transaction 2 | 3 | import org.scalatest.Suites 4 | 5 | class TransactionTestSuite extends Suites( 6 | new TransactionSpecification, 7 | new StoredStateUnitTests, 8 | new RowSpecification, 9 | new GenesisTransactionSpecification 10 | ) 11 | -------------------------------------------------------------------------------- /src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | app { 2 | product = "Scorex" 3 | release = "Lagonaki" 4 | version = "1.2.8" 5 | consensusAlgo = "nxt" 6 | } -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | scorex-errors.log 6 | true 7 | 8 | %date{MM/dd HH:mm:ss} %-5level[%.15thread] %logger{1} %msg%n 9 | 10 | 11 | WARN 12 | 13 | 14 | 15 | 16 | scorex.log 17 | true 18 | 19 | %date{MM/dd HH:mm:ss} %-5level[%.15thread] %logger{1} %msg%n 20 | 21 | 22 | 23 | scorex.%d{yyyy-MM-dd}.%i.log 24 | 25 | 100MB 26 | 60 27 | 20GB 28 | 29 | 30 | 31 | 32 | System.out 33 | 34 | [%thread] >> [%-5level] %logger{36} >> %d{HH:mm:ss.SSS} %msg%n 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/test/resources/settings-local1.json: -------------------------------------------------------------------------------- 1 | { 2 | "p2p": { 3 | "fuzzingDelay": 100, 4 | "nodeName": "Node1", 5 | "localOnly": true, 6 | "myAddress": "127.0.0.1", 7 | "bindAddress": "127.0.0.1", 8 | "upnp": false, 9 | "upnpGatewayTimeout": 7000, 10 | "upnpDiscoverTimeout": 3000, 11 | "port": 9084, 12 | "knownPeers": ["127.0.0.2:9088"], 13 | "maxConnections": 10 14 | }, 15 | "walletDir": "/tmp/scorex/wallet1", 16 | "walletPassword": "cookies", 17 | "walletSeed": "FQgbSAm6swGbtqA3NE8PttijPhT4N3Ufh4bHFAkyVnQz", 18 | "dataDir": "/tmp/scorex/data1", 19 | "rpcPort": 9085, 20 | "rpcAllowed": [ 21 | "127.0.0.1", 22 | "123.123.123.123" 23 | ], 24 | "maxRollback": 100, 25 | "offlineGeneration": true, 26 | "perma": { 27 | "treeDir": "/tmp/scorex/data1/tree/", 28 | "authDataStorage": "authDataStorage1.mapDB", 29 | "isTrustedDealer": true 30 | }, 31 | "apiKeyHash": "EfwB3nNEwDSc885diz76v3mnPw2EhjyUSnDfw1XPbz92", 32 | "blockGenerationDelay": 5001 33 | } -------------------------------------------------------------------------------- /src/test/resources/settings-local2.json: -------------------------------------------------------------------------------- 1 | { 2 | "p2p": { 3 | "nodeName": "Node2", 4 | "localOnly": true, 5 | "bindAddress": "127.0.0.2", 6 | "myAddress": "127.0.0.2", 7 | "upnp": false, 8 | "upnpGatewayTimeout": 7000, 9 | "upnpDiscoverTimeout": 3000, 10 | "port": 9088, 11 | "knownPeers": ["127.0.0.1:9084"], 12 | "maxConnections": 10 13 | }, 14 | "walletDir": "/tmp/scorex/wallet2", 15 | "walletPassword": "cookies", 16 | "walletSeed": "111", 17 | "dataDir": "/tmp/scorex/data2", 18 | "rpcPort": 9086, 19 | "rpcAllowed": [ 20 | "127.0.0.2", 21 | "123.123.123.123" 22 | ], 23 | "maxRollback": 100, 24 | "offlineGeneration": false, 25 | "perma": { 26 | "treeDir": "/tmp/scorex/data2/tree/", 27 | "authDataStorage": "authDataStorage2.mapDB" 28 | }, 29 | "blockGenerationDelay": 501 30 | } -------------------------------------------------------------------------------- /src/test/resources/settings-local3.json: -------------------------------------------------------------------------------- 1 | { 2 | "p2p": { 3 | "fuzzingDelay": 3000, 4 | "nodeName": "Node3", 5 | "bindAddress": "127.0.0.3", 6 | "upnp": true, 7 | "upnpGatewayTimeout": 7000, 8 | "upnpDiscoverTimeout": 3000, 9 | "knownPeers": [ 10 | "127.0.0.2:9088" 11 | ], 12 | "port": 9089, 13 | "maxConnections": 10 14 | }, 15 | "walletDir": "/tmp/scorex/wallet3", 16 | "walletPassword": "cookies", 17 | "walletSeed": "3", 18 | "dataDir": "/tmp/scorex/data3", 19 | "rpcPort": 9087, 20 | "rpcAllowed": [ 21 | "127.0.0.1", 22 | "123.123.123.123" 23 | ], 24 | "maxRollback": 100, 25 | "offlineGeneration": false, 26 | "perma": { 27 | "treeDir": "/tmp/scorex/data3/tree/", 28 | "authDataStorage": "authDataStorage3.mapDB" 29 | }, 30 | "blockGenerationDelay": 501 31 | } -------------------------------------------------------------------------------- /src/test/resources/settings-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "p2p": { 3 | "localOnly": true, 4 | "bindAddress": "127.0.0.1", 5 | "upnp": false, 6 | "knownPeers": ["127.0.0.1"], 7 | "port": 9091, 8 | "maxConnections": 10 9 | }, 10 | 11 | "walletPassword": "cookies", 12 | "walletSeed": "FQgbSAm6swGbtqA3NE8PttijPhT4N3Ufh4bHFAkyVnQz", 13 | 14 | "rpcPort": 9092, 15 | "rpcAllowed": [ 16 | "127.0.0.1", 17 | "123.123.123.123" 18 | ], 19 | "perma": { 20 | "treeDir": "/tmp/scorex-test/perma/data/tree", 21 | "authDataStorage": "authDataStorage.mapDB" 22 | }, 23 | "maxRollback": 100, 24 | "history": "blockchain", 25 | "apiKeyHash": "EfwB3nNEwDSc885diz76v3mnPw2EhjyUSnDfw1XPbz92", 26 | "offlineGeneration": false 27 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/BlockTestingCommons.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki 2 | 3 | import scorex.account.PrivateKeyAccount 4 | import scorex.block.Block 5 | import scorex.block.Block._ 6 | import scorex.consensus.nxt.{NxtLikeConsensusBlockData, NxtLikeConsensusModule} 7 | import scorex.lagonaki.mocks.ConsensusMock 8 | import scorex.transaction.{PaymentTransaction, SimpleTransactionModule, Transaction} 9 | 10 | import scala.util.Random 11 | 12 | trait BlockTestingCommons extends TestingCommons { 13 | 14 | val genesis: Block = Block.genesis() 15 | val gen = new PrivateKeyAccount(Array.fill(32)(Random.nextInt(Byte.MaxValue).toByte)) 16 | 17 | protected var lastBlockId: BlockId = genesis.uniqueId 18 | 19 | def genBlock(bt: Long, gs: Array[Byte], seed: Array[Byte], parentId: Option[BlockId] = None, 20 | transactions: Seq[Transaction] = Seq.empty) 21 | (implicit consensusModule: NxtLikeConsensusModule, transactionModule: SimpleTransactionModule): Block = { 22 | 23 | val reference = parentId.getOrElse(lastBlockId) 24 | 25 | val tbd = if (transactions.isEmpty) Seq(genTransaction(seed)) else transactions 26 | val cbd = new NxtLikeConsensusBlockData { 27 | override val generationSignature: Array[Byte] = gs 28 | override val baseTarget: Long = math.max(math.abs(bt), 1) 29 | } 30 | 31 | val version = 1: Byte 32 | val timestamp = System.currentTimeMillis() 33 | 34 | val block = Block.buildAndSign(version, timestamp, reference, cbd, tbd, gen) 35 | lastBlockId = block.uniqueId 36 | block 37 | } 38 | 39 | def genTransaction(seed: Array[Byte]): Transaction = { 40 | val sender = new PrivateKeyAccount(seed) 41 | PaymentTransaction(sender, gen, 1, 1, System.currentTimeMillis() - 5000) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/LagonakiTestSuite.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki 2 | 3 | import org.scalatest.{BeforeAndAfterAll, Suites} 4 | import scorex.lagonaki.TestingCommons._ 5 | import scorex.lagonaki.integration._ 6 | import scorex.lagonaki.integration.api._ 7 | import scorex.lagonaki.unit._ 8 | import scorex.transaction.state.StateTest 9 | import scorex.transaction.state.database.blockchain.BlockTreeSpecification 10 | 11 | class LagonakiTestSuite extends Suites( 12 | //unit tests 13 | new MessageSpecification 14 | , new BlockSpecification 15 | // , new BlockStorageSpecification 16 | , new WalletSpecification 17 | , new BlockGeneratorSpecification 18 | , new BlockTreeSpecification 19 | , new StateTest 20 | , new StoredStateSpecification 21 | // API tests 22 | , new UtilsAPISpecification 23 | , new PeersAPISpecification 24 | , new WalletAPISpecification 25 | , new AddressesAPISpecification 26 | , new TransactionsAPISpecification 27 | , new PaymentAPISpecification 28 | , new BlockAPISpecification 29 | //integration tests - slow! 30 | , new ValidChainGenerationSpecification 31 | 32 | ) with BeforeAndAfterAll { 33 | 34 | override protected def beforeAll() = {} 35 | 36 | override protected def afterAll() = { 37 | applications.foreach(_.stopAll()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/TransactionTestingCommons.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki 2 | 3 | import scorex.account.PrivateKeyAccount 4 | import scorex.block.Block 5 | import scorex.lagonaki.TestingCommons._ 6 | import scorex.transaction.{History, GenesisTransaction, Transaction} 7 | 8 | import scala.concurrent.Await 9 | import scala.concurrent.duration._ 10 | import scala.util.Random 11 | 12 | trait TransactionTestingCommons extends TestingCommons { 13 | if (transactionModule.blockStorage.history.isEmpty) { 14 | transactionModule.blockStorage.appendBlock(Block.genesis()) 15 | } 16 | val wallet = application.wallet 17 | if (wallet.privateKeyAccounts().size < 3) { 18 | wallet.generateNewAccounts(3) 19 | } 20 | val accounts = wallet.privateKeyAccounts() 21 | .filter(a => consensusModule.generatingBalance(a) > 0) 22 | 23 | val ab = accounts.map(consensusModule.generatingBalance(_)).sum 24 | require(ab > 2) 25 | 26 | def genValidBlock(): Block = { 27 | Await.result(consensusModule.generateNextBlocks(accounts)(transactionModule), 10.seconds).headOption match { 28 | case Some(block: Block) if block.isValid => block 29 | case None => 30 | Thread.sleep(500) 31 | genValidBlock() 32 | } 33 | } 34 | 35 | val genesisAccs = application.blockStorage.history.genesis.transactions.flatMap(_ match { 36 | case gtx: GenesisTransaction => Some(gtx.recipient) 37 | case _ => None 38 | }) 39 | 40 | def genValidTransaction(randomAmnt: Boolean = true, 41 | recepientOpt: Option[PrivateKeyAccount] = None, 42 | senderOpt: Option[PrivateKeyAccount] = None 43 | ): Transaction = { 44 | val senderAcc = senderOpt.getOrElse(accounts(Random.nextInt(accounts.size))) 45 | val senderBalance = consensusModule.generatingBalance(senderAcc) 46 | require(senderBalance > 0) 47 | val fee = Random.nextInt(5).toLong + 1 48 | if (senderBalance <= fee) { 49 | genValidTransaction(randomAmnt, recepientOpt, senderOpt) 50 | } else { 51 | val amt = if (randomAmnt) Math.abs(Random.nextLong() % (senderBalance - fee)) 52 | else senderBalance - fee 53 | val recepient: PrivateKeyAccount = recepientOpt.getOrElse(accounts(Random.nextInt(accounts.size))) 54 | val tx = transactionModule.createPayment(senderAcc, recepient, amt, fee) 55 | if (transactionModule.blockStorage.state.isValid(tx)) tx 56 | else genValidTransaction(randomAmnt, recepientOpt, senderOpt) 57 | } 58 | } 59 | 60 | def includedTransactions(b: Block, history: History): Seq[Transaction] = { 61 | if (b.transactions.isEmpty) includedTransactions(history.parent(b).get, history) 62 | else b.transactions 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/BlockGeneratorSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration 2 | 3 | import akka.actor.ActorSystem 4 | import akka.testkit._ 5 | import org.scalatest.{Matchers, WordSpecLike} 6 | import scorex.consensus.mining.BlockGeneratorController._ 7 | import scorex.lagonaki.TestingCommons 8 | import scorex.utils.untilTimeout 9 | 10 | import scala.concurrent.duration._ 11 | 12 | class BlockGeneratorSpecification(_system: ActorSystem) 13 | extends TestKit(_system) 14 | with ImplicitSender 15 | with WordSpecLike 16 | with Matchers 17 | with TestingCommons { 18 | 19 | import TestingCommons._ 20 | 21 | def this() = this(ActorSystem("MySpec")) 22 | 23 | val bg = application.blockGenerator 24 | 25 | "BlockGenerator actor" must { 26 | 27 | "generate after downloading state" in { 28 | bg ! StartGeneration 29 | //Wait up to 5 seconds to download blockchain and become generating 30 | untilTimeout(5.seconds) { 31 | bg ! GetStatus 32 | expectMsg(Generating.name) 33 | } 34 | } 35 | 36 | "StopGeneration command change state to syncing from generating" in { 37 | bg ! StartGeneration 38 | untilTimeout(5.seconds) { 39 | bg ! GetStatus 40 | expectMsg(Generating.name) 41 | } 42 | bg ! StopGeneration 43 | bg ! GetStatus 44 | expectMsg(Syncing.name) 45 | } 46 | 47 | "StopGeneration command don't change state from syncing" in { 48 | bg ! StartGeneration 49 | bg ! StopGeneration 50 | bg ! GetStatus 51 | expectMsg(Syncing.name) 52 | bg ! StopGeneration 53 | bg ! GetStatus 54 | expectMsg(Syncing.name) 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/HistoryReplierSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | import akka.testkit.{ImplicitSender, TestKit, TestProbe} 5 | import org.scalatest.{Matchers, WordSpecLike} 6 | import scorex.lagonaki.TestingCommons 7 | import scorex.network.HistorySynchronizer 8 | 9 | class HistoryReplierSpecification(_system: ActorSystem) 10 | extends TestKit(_system) 11 | with ImplicitSender 12 | with WordSpecLike 13 | with Matchers 14 | with TestingCommons { 15 | 16 | def this() = this(ActorSystem("HistoryReplierSpecification")) 17 | 18 | val probe = new TestProbe(system) 19 | 20 | val hs = system.actorOf(Props(classOf[HistorySynchronizer], application)) 21 | 22 | lazy val application = TestingCommons.application 23 | 24 | //todo: get tests done 25 | "HistoryReplier actor" must { 26 | "return block for GetBlock" in { 27 | 28 | } 29 | 30 | "return sigs for GetSignaturesMessage" in { 31 | 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/HistorySynchronizerSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration 2 | 3 | 4 | import java.net.{InetAddress, InetSocketAddress} 5 | 6 | import akka.actor.ActorSystem 7 | import akka.pattern.ask 8 | import akka.testkit.{ImplicitSender, TestKit, TestProbe} 9 | import akka.util.Timeout 10 | import org.scalatest.concurrent.ScalaFutures 11 | import org.scalatest.{Matchers, WordSpecLike} 12 | import scorex.consensus.mining.BlockGeneratorController._ 13 | import scorex.lagonaki.TestingCommons 14 | import scorex.network.ConnectedPeer 15 | import scorex.network.ScoreObserver.ConsideredValue 16 | 17 | import scala.concurrent.ExecutionContext.Implicits.global 18 | import scala.concurrent.duration._ 19 | 20 | class HistorySynchronizerSpecification(_system: ActorSystem) 21 | extends TestKit(_system) 22 | with ScalaFutures 23 | with ImplicitSender 24 | with WordSpecLike 25 | with Matchers 26 | with TestingCommons { 27 | 28 | implicit val timeout = Timeout(5.seconds) 29 | 30 | def this() = this(ActorSystem("HistorySynchronizerSpecification")) 31 | 32 | val probe = new TestProbe(system) 33 | 34 | val peer = new ConnectedPeer(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 1), probe.ref) 35 | 36 | lazy val peers = Seq(peer) 37 | 38 | lazy val application = TestingCommons.application 39 | 40 | val hs = { 41 | val res = application.historySynchronizer 42 | res ! Unit 43 | Thread.sleep(2.seconds.toMillis) 44 | res 45 | } 46 | 47 | "HistorySynchronizer actor" must { 48 | "start in synced state with blocks generation" in { 49 | val fStatus = (application.blockGenerator ? GetStatus).map(_.toString) 50 | whenReady(fStatus) { status => 51 | status should equal(Generating.name) 52 | } 53 | } 54 | 55 | "stop block generation on better network score" in { 56 | Thread.sleep(1.second.toMillis) 57 | 58 | hs ! ConsideredValue(Some(BigInt(Long.MaxValue)), peers) 59 | 60 | Thread.sleep(1.second.toMillis) 61 | 62 | val fStatus = (application.blockGenerator ? GetStatus).map(_.toString) 63 | whenReady(fStatus) { status => 64 | status should equal(Syncing.name) 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/PeerSynchronizerSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration 2 | 3 | import java.net.{InetAddress, InetSocketAddress} 4 | import java.util.concurrent.TimeUnit 5 | 6 | import akka.actor.{ActorSystem, Props} 7 | import akka.pattern.ask 8 | import akka.testkit._ 9 | import akka.util.Timeout 10 | import org.scalatest.concurrent.ScalaFutures 11 | import org.scalatest.time.{Millis, Seconds, Span} 12 | import org.scalatest.{Matchers, WordSpecLike} 13 | import scorex.lagonaki.TestingCommons 14 | import scorex.network.NetworkController.DataFromPeer 15 | import scorex.network.message.Message 16 | import scorex.network.peer.PeerManager 17 | import scorex.network.peer.PeerManager.{KnownPeers, RandomPeers} 18 | import scorex.network.{ConnectedPeer, PeerSynchronizer} 19 | 20 | //TODO move to basics 21 | class PeerSynchronizerSpecification(_system: ActorSystem) 22 | extends TestKit(_system) 23 | with ImplicitSender 24 | with WordSpecLike 25 | with Matchers 26 | with ScalaFutures 27 | with TestingCommons { 28 | 29 | import TestingCommons._ 30 | import application.basicMessagesSpecsRepo._ 31 | 32 | def this() = this(ActorSystem("PeerSynchronizerSpecification")) 33 | 34 | val probe = new TestProbe(system) 35 | 36 | val ps = system.actorOf(Props(classOf[PeerSynchronizer], application)) 37 | 38 | val peer = new ConnectedPeer(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 1), probe.ref) 39 | 40 | implicit val config = PatienceConfig(Span(2, Seconds), Span(5, Millis)) 41 | 42 | implicit val timeout = Timeout(5, TimeUnit.SECONDS) 43 | 44 | "PeerSynchronizer actor" must { 45 | "response with known peers" in { 46 | ps ! DataFromPeer(GetPeersSpec.messageCode, Right, peer) 47 | val peers = (application.peerManager ? RandomPeers(3)) 48 | .mapTo[Seq[InetSocketAddress]] 49 | .futureValue 50 | 51 | peers.length shouldBe 1 52 | val msg = Message(PeersSpec, Right(peers), None) 53 | probe.expectMsg(msg) 54 | 55 | val newPeer = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 2) 56 | application.peerManager ! PeerManager.AddOrUpdatePeer(newPeer, None, None) 57 | 58 | val newPeers = (application.peerManager ? RandomPeers(3)) 59 | .mapTo[Seq[InetSocketAddress]] 60 | .futureValue 61 | newPeers.length shouldBe 2 62 | ps ! DataFromPeer(GetPeersSpec.messageCode, Right, peer) 63 | probe.expectMsg(Message(PeersSpec, Right(newPeers), None)) 64 | } 65 | 66 | "add more peers" in { 67 | val peersBefore = (application.peerManager ? KnownPeers) 68 | .mapTo[Seq[InetSocketAddress]] 69 | .futureValue 70 | 71 | peersBefore.length shouldBe 2 72 | val newPeer = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 3) 73 | val peers: Seq[InetSocketAddress] = Seq(newPeer) 74 | ps ! DataFromPeer(PeersSpec.messageCode, peers, peer) 75 | ps ! DataFromPeer(GetPeersSpec.messageCode, Right, peer) 76 | probe.expectMsg(Message(PeersSpec, Right(peersBefore ++ Seq(newPeer)), None)) 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/api/NxtConsensusAPISpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration.api 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import scorex.crypto.EllipticCurveImpl 5 | import scorex.crypto.encode.Base58 6 | import scorex.lagonaki.TestingCommons 7 | 8 | class NxtConsensusAPISpecification extends FunSuite with Matchers { 9 | 10 | import TestingCommons._ 11 | 12 | val wallet = application.wallet 13 | if (wallet.privateKeyAccounts().size < 10) wallet.generateNewAccounts(10) 14 | val accounts = wallet.privateKeyAccounts() 15 | val account = accounts.head 16 | val address = account.address 17 | 18 | test("/consensus/generatingbalance/{address} API route") { 19 | val response = GET.request(s"/consensus/generatingbalance/$address") 20 | (response \ "address").as[String] shouldBe address 21 | (response \ "balance").as[Long] should be >= 0L 22 | } 23 | 24 | 25 | 26 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/api/PaymentAPISpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration.api 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import scorex.lagonaki.TransactionTestingCommons 5 | 6 | class PaymentAPISpecification extends FunSuite with Matchers with TransactionTestingCommons { 7 | 8 | import scorex.lagonaki.TestingCommons._ 9 | 10 | test("POST /payment API route") { 11 | POST.incorrectApiKeyTest("/payment") 12 | val s = accounts.head.address 13 | val r = accounts.last.address 14 | val amount = 2 15 | val fee = 1 16 | 17 | val json = "{\"amount\":" + amount + ",\"fee\":" + fee + ",\"sender\":\"" + s + "\",\"recipient\":\"" + r + "\"\n}" 18 | val req = POST.request("/payment", body = json) 19 | (req \ "type").as[Int] shouldBe 2 20 | (req \ "fee").as[Int] shouldBe 1 21 | (req \ "amount").as[Int] shouldBe amount 22 | (req \ "timestamp").asOpt[Long].isDefined shouldBe true 23 | (req \ "signature").asOpt[String].isDefined shouldBe true 24 | (req \ "sender").as[String] shouldBe s 25 | (req \ "recipient").as[String] shouldBe r 26 | 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/api/PeersAPISpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration.api 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | 5 | class PeersAPISpecification extends FunSuite with Matchers { 6 | 7 | import scorex.lagonaki.TestingCommons._ 8 | 9 | test("/peers/connected API route") { 10 | val connected = GET.request("/peers/connected") 11 | (connected \\ "declaredAddress").toList.size should be >= 1 12 | (connected \\ "peerName").toList.size should be >= 1 13 | (connected \\ "peerNonce").toList.size should be >= 1 14 | } 15 | 16 | test("/peers/all API route") { 17 | val all = GET.request("/peers/all") 18 | (all \\ "address").toList.size should be >= 1 19 | (all \\ "nodeName").toList.size should be >= 1 20 | (all \\ "nodeNonce").toList.size should be >= 1 21 | } 22 | 23 | test("/peers/blacklisted API route") { 24 | val blacklisted = GET.request("/peers/blacklisted") 25 | blacklisted.toString() shouldBe "[]" 26 | } 27 | 28 | test("/peers/connect API route") { 29 | POST.incorrectApiKeyTest("/peers/connect") 30 | 31 | val req = POST.request("/peers/connect", body = "{\"host\":\"127.0.0.1\",\"port\":123}") 32 | (req \ s"status").as[String] shouldBe "Trying to connect" 33 | (req \ "hostname").asOpt[String].isDefined shouldBe true 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/api/TransactionsAPISpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration.api 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import play.api.libs.json.JsValue 5 | import scorex.block.Block 6 | import scorex.crypto.encode.Base58 7 | import scorex.lagonaki.TransactionTestingCommons 8 | 9 | class TransactionsAPISpecification extends FunSuite with Matchers with TransactionTestingCommons { 10 | 11 | import scorex.lagonaki.TestingCommons._ 12 | 13 | if (wallet.privateKeyAccounts().size < 10) wallet.generateNewAccounts(10) 14 | val addresses = accounts.map(_.address) 15 | val account = accounts.head 16 | val address = account.address 17 | 18 | test("/transactions/unconfirmed API route") { 19 | (1 to 20) foreach (i => genValidTransaction()) 20 | val unconfirmed = transactionModule.utxStorage.all() 21 | unconfirmed.size should be > 0 22 | val tr = GET.request("/transactions/unconfirmed") 23 | (tr \\ "signature").toList.size shouldBe unconfirmed.size 24 | } 25 | 26 | test("/transactions/address/{address} API route") { 27 | addresses.foreach { a => 28 | checkTransactionList(GET.request(s"/transactions/address/$a")) 29 | } 30 | } 31 | 32 | test("/transactions/address/{address}/limit/{limit} API route") { 33 | addresses.foreach { a => 34 | val tr = GET.request(s"/transactions/address/$a/limit/2") 35 | (tr \\ "amount").toList.size should be <= 2 36 | checkTransactionList(tr) 37 | } 38 | } 39 | 40 | test("/transactions/info/{signature} API route") { 41 | val genesisTx = Block.genesis().transactions.head 42 | val tr = GET.request(s"/transactions/info/${Base58.encode(genesisTx.signature)}") 43 | (tr \ "signature").as[String] shouldBe Base58.encode(genesisTx.signature) 44 | (tr \ "type").as[Int] shouldBe 1 45 | (tr \ "fee").as[Int] shouldBe 0 46 | (tr \ "amount").as[Long] should be > 0L 47 | (tr \ "height").as[Int] shouldBe 1 48 | (tr \ "recipient").as[String] shouldBe genesisTx.recipient.address 49 | } 50 | 51 | def checkTransactionList(tr: JsValue): Unit = { 52 | (tr \\ "amount").toList.foreach(amount => amount.as[Long] should be > 0L) 53 | (tr \\ "fee").toList.foreach(amount => amount.as[Long] should be >= 0L) 54 | (tr \\ "type").toList.foreach(amount => amount.as[Int] should be >= 0) 55 | (tr \\ "timestamp").toList.foreach(amount => amount.as[Long] should be >= 0L) 56 | (tr \\ "signature").toList.size should be >= 0 57 | (tr \\ "sender").toList.size should be >= 0 58 | (tr \\ "recipient").toList.size should be >= 0 59 | } 60 | 61 | 62 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/api/UtilsAPISpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration.api 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import scorex.crypto.encode.Base58 5 | import scorex.crypto.hash.{FastCryptographicHash, SecureCryptographicHash} 6 | 7 | import scala.util.Random 8 | 9 | class UtilsAPISpecification extends FunSuite with Matchers { 10 | 11 | import scorex.lagonaki.TestingCommons._ 12 | 13 | test("/utils/hash/secure API route") { 14 | val msg = "test" 15 | val resp = POST.request("/utils/hash/secure", body = msg) 16 | (resp \ "message").as[String] shouldBe msg 17 | (resp \ "hash").as[String] shouldBe Base58.encode(SecureCryptographicHash(msg)) 18 | } 19 | 20 | test("/utils/hash/fast API route") { 21 | val msg = "test" 22 | val resp = POST.request("/utils/hash/fast", body = msg) 23 | (resp \ "message").as[String] shouldBe msg 24 | (resp \ "hash").as[String] shouldBe Base58.encode(FastCryptographicHash(msg)) 25 | } 26 | 27 | test("/utils/seed API route") { 28 | Base58.decode((GET.request("/utils/seed") \ "seed").as[String]).isSuccess shouldBe true 29 | } 30 | 31 | test("/utils/seed/{length} API route") { 32 | val length = Random.nextInt(4096) 33 | Base58.decode((GET.request(s"/utils/seed/$length") \ "seed").as[String]).get.length shouldBe length 34 | } 35 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/integration/api/WalletAPISpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.integration.api 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import scorex.crypto.encode.Base58 5 | 6 | class WalletAPISpecification extends FunSuite with Matchers { 7 | 8 | import scorex.lagonaki.TestingCommons._ 9 | 10 | test("/wallet/ API route") { 11 | (GET.request("/wallet") \ "exists").as[Boolean] shouldBe true 12 | } 13 | 14 | test("/wallet/seed API route") { 15 | GET.incorrectApiKeyTest("/wallet/seed") 16 | 17 | val response = GET.request("/wallet/seed", headers = Map("api_key" -> "test")) 18 | (response \ "seed").as[String] shouldBe Base58.encode(application.settings.walletSeed.get) 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/mocks/BlockMock.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.mocks 2 | 3 | import scorex.account.PublicKeyAccount 4 | import scorex.block.Block.BlockId 5 | import scorex.block._ 6 | import scorex.consensus.ConsensusModule 7 | import scorex.consensus.nxt.{NxtLikeConsensusBlockData, NxtLikeConsensusModule} 8 | import scorex.crypto.EllipticCurveImpl 9 | import scorex.transaction.{Transaction, TransactionModule, TransactionsBlockField} 10 | 11 | class BlockMock(txs: Seq[Transaction]) extends Block { 12 | 13 | 14 | override lazy val transactions = txs 15 | override implicit val consensusModule: ConsensusModule[NxtLikeConsensusBlockData] = new NxtLikeConsensusModule 16 | override val signerDataField: SignerDataBlockField = new SignerDataBlockField("signature", 17 | SignerData(new PublicKeyAccount(Array.fill(32)(0)), Array.fill(EllipticCurveImpl.SignatureLength)(0))) 18 | 19 | override type ConsensusDataType = NxtLikeConsensusBlockData 20 | override type TransactionDataType = Seq[Transaction] 21 | override val versionField: ByteBlockField = ByteBlockField("version", 0: Byte) 22 | 23 | 24 | override val transactionDataField: BlockField[TransactionDataType] = TransactionsBlockField(txs) 25 | override val referenceField: BlockIdField = 26 | BlockIdField("reference", Array.fill(EllipticCurveImpl.SignatureLength)(0: Byte)) 27 | override val uniqueId: BlockId = Array.fill(EllipticCurveImpl.SignatureLength)(0: Byte) 28 | override val timestampField: LongBlockField = LongBlockField("timestamp", 0L) 29 | 30 | //TODO implement mock? 31 | override implicit lazy val transactionModule: TransactionModule[TransactionDataType] = { 32 | new Error("").printStackTrace() 33 | throw new Error("Transaction module is not defined in mock block") 34 | } 35 | override lazy val consensusDataField: BlockField[ConsensusDataType] = ??? 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/mocks/ConsensusMock.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.mocks 2 | 3 | import scorex.account.PublicKeyAccount 4 | import scorex.consensus.nxt.{NxtLikeConsensusBlockData, NxtLikeConsensusModule} 5 | import scorex.transaction.TransactionModule 6 | 7 | class ConsensusMock extends NxtLikeConsensusModule { 8 | 9 | override val generatingBalanceDepth: Int = 0 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/props/BlockStorageSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.props 2 | 3 | import org.scalacheck.{Arbitrary, Gen} 4 | import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} 5 | import org.scalatest.{Matchers, PropSpec} 6 | import scorex.account.PrivateKeyAccount 7 | import scorex.block.Block 8 | import scorex.lagonaki.BlockTestingCommons 9 | import scorex.utils._ 10 | 11 | class BlockStorageSpecification extends PropSpec with PropertyChecks with GeneratorDrivenPropertyChecks with Matchers 12 | with BlockTestingCommons { 13 | 14 | // val smallInteger: Gen[Int] = Gen.choose(0, 2) 15 | val blockGen: Gen[Block] = for { 16 | gb <- Arbitrary.arbitrary[Long] 17 | gs <- Arbitrary.arbitrary[Array[Byte]] 18 | seed <- Arbitrary.arbitrary[Array[Byte]] 19 | } yield genBlock(gb, gs, seed) 20 | 21 | val storage = transactionModule.blockStorage 22 | storage.appendBlock(genesis) 23 | 24 | property("Add correct blocks") { 25 | forAll(blockGen) { (block: Block) => 26 | val prevH = storage.history.height() 27 | val prevTx = storage.state.accountTransactions(gen).length 28 | storage.state.included(block.transactions.head) shouldBe None 29 | storage.appendBlock(block).isSuccess shouldBe true 30 | storage.history.height() shouldBe prevH + 1 31 | storage.state.accountTransactions(gen).length shouldBe prevTx + 1 32 | storage.state.included(block.transactions.head).get shouldBe storage.history.heightOf(block) 33 | } 34 | } 35 | 36 | property("Don't add incorrect blocks") { 37 | val wrongBlockId = Some("wrong".getBytes) 38 | forAll { (gb: Long, gs: Array[Byte], seed: Array[Byte]) => 39 | val prevTx = storage.state.accountTransactions(gen).length 40 | val block = genBlock(gb, gs, seed, wrongBlockId) 41 | val prevH = storage.history.height() 42 | storage.state.included(block.transactions.head) shouldBe None 43 | storage.appendBlock(block).isSuccess shouldBe false 44 | storage.state.included(block.transactions.head) shouldBe None 45 | storage.history.height() shouldBe prevH 46 | storage.state.accountTransactions(gen).length shouldBe prevTx 47 | } 48 | } 49 | 50 | property("Update to branch with better score") { 51 | val branchPoint = storage.history.lastBlock 52 | val senderSeed = randomBytes() 53 | val sender = new PrivateKeyAccount(senderSeed) 54 | val bt = 20 55 | val biggerBt = 19 56 | 57 | //Add block to best chain 58 | val firstBlock = genBlock(bt, randomBytes(), senderSeed, Some(branchPoint.uniqueId)) 59 | val firstBlockTransaction = firstBlock.transactions.head 60 | storage.appendBlock(firstBlock).isSuccess shouldBe true 61 | storage.history.lastBlock.uniqueId should contain theSameElementsAs firstBlock.uniqueId 62 | storage.state.accountTransactions(sender).length shouldBe 1 63 | storage.state.included(firstBlockTransaction).get shouldBe storage.history.heightOf(firstBlock) 64 | 65 | //Add block with the same score to branch point 66 | val branchedBlock = genBlock(bt, randomBytes(), senderSeed, Some(branchPoint.uniqueId)) 67 | storage.appendBlock(branchedBlock).isSuccess shouldBe true 68 | storage.history.lastBlock.uniqueId should contain theSameElementsAs firstBlock.uniqueId 69 | storage.state.accountTransactions(sender).length shouldBe 1 70 | storage.state.included(firstBlockTransaction).get shouldBe storage.history.heightOf(firstBlock) 71 | 72 | //Add block with the better score to branch point 73 | val bestBlock = genBlock(biggerBt, randomBytes(), senderSeed, Some(branchPoint.uniqueId)) 74 | storage.appendBlock(bestBlock).isSuccess shouldBe true 75 | storage.history.lastBlock.uniqueId should contain theSameElementsAs bestBlock.uniqueId 76 | storage.state.accountTransactions(sender).length shouldBe 1 77 | storage.state.included(firstBlockTransaction) shouldBe None 78 | } 79 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/server/LagonakiApplication.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.server 2 | 3 | import akka.actor.Props 4 | import com.typesafe.config.ConfigFactory 5 | import scorex.api.http._ 6 | import scorex.app.{Application, ApplicationVersion} 7 | import scorex.consensus.nxt.api.http.NxtConsensusApiRoute 8 | import scorex.lagonaki.mocks.ConsensusMock 9 | import scorex.network._ 10 | import scorex.transaction._ 11 | 12 | import scala.reflect.runtime.universe._ 13 | 14 | class LagonakiApplication(val settingsFilename: String) extends Application { 15 | 16 | override val applicationName = "lagonaki" 17 | 18 | private val appConf = ConfigFactory.load().getConfig("app") 19 | 20 | override val appVersion = { 21 | val raw = appConf.getString("version") 22 | val parts = raw.split("\\.") 23 | ApplicationVersion(parts(0).toInt, parts(1).toInt, parts(2).split("-").head.toInt) 24 | } 25 | 26 | override implicit lazy val settings = new LagonakiSettings(settingsFilename) 27 | 28 | override implicit lazy val consensusModule = new ConsensusMock 29 | 30 | override implicit lazy val transactionModule: SimpleTransactionModule = new SimpleTransactionModule()(settings, this) 31 | 32 | override lazy val blockStorage = transactionModule.blockStorage 33 | 34 | lazy val consensusApiRoute = new NxtConsensusApiRoute(this) 35 | 36 | override lazy val apiRoutes = Seq( 37 | BlocksApiRoute(this), 38 | TransactionsApiRoute(this), 39 | consensusApiRoute, 40 | WalletApiRoute(this), 41 | PaymentApiRoute(this), 42 | UtilsApiRoute(this), 43 | PeersApiRoute(this), 44 | AddressApiRoute(this) 45 | ) 46 | 47 | override lazy val apiTypes = Seq( 48 | typeOf[BlocksApiRoute], 49 | typeOf[TransactionsApiRoute], 50 | typeOf[NxtConsensusApiRoute], 51 | typeOf[WalletApiRoute], 52 | typeOf[PaymentApiRoute], 53 | typeOf[UtilsApiRoute], 54 | typeOf[PeersApiRoute], 55 | typeOf[AddressApiRoute] 56 | ) 57 | 58 | override lazy val additionalMessageSpecs = TransactionalMessagesRepo.specs 59 | 60 | //checks 61 | require(transactionModule.balancesSupport) 62 | require(transactionModule.accountWatchingSupport) 63 | 64 | actorSystem.actorOf(Props(classOf[UnconfirmedPoolSynchronizer], this)) 65 | } 66 | -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/server/LagonakiSettings.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.server 2 | 3 | import scorex.settings.Settings 4 | import scorex.transaction.TransactionSettings 5 | 6 | class LagonakiSettings(override val filename: String) extends Settings with TransactionSettings 7 | -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/unit/BlockSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.unit 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import scorex.account.PrivateKeyAccount 5 | import scorex.block.Block 6 | import scorex.consensus.nxt.{NxtLikeConsensusBlockData, NxtLikeConsensusModule} 7 | import scorex.consensus.qora.{QoraLikeConsensusBlockData, QoraLikeConsensusModule} 8 | import scorex.lagonaki.TestingCommons 9 | import scorex.transaction._ 10 | 11 | import scala.util.Random 12 | 13 | class BlockSpecification extends FunSuite with Matchers with TestingCommons { 14 | 15 | import TestingCommons._ 16 | 17 | test("Nxt block with txs bytes/parse roundtrip") { 18 | implicit val consensusModule = new NxtLikeConsensusModule() 19 | implicit val transactionModule = new SimpleTransactionModule()(application.settings, application) 20 | 21 | val reference = Array.fill(Block.BlockIdLength)(Random.nextInt(100).toByte) 22 | val gen = new PrivateKeyAccount(reference) 23 | 24 | val bt = Random.nextLong() 25 | val gs = Array.fill(NxtLikeConsensusModule.GeneratorSignatureLength)(Random.nextInt(100).toByte) 26 | 27 | val sender = new PrivateKeyAccount(reference.dropRight(2)) 28 | val tx: Transaction = PaymentTransaction(sender, gen, 5, 1000, System.currentTimeMillis() - 5000) 29 | 30 | val tbd = Seq(tx) 31 | val cbd = new NxtLikeConsensusBlockData { 32 | override val generationSignature: Array[Byte] = gs 33 | override val baseTarget: Long = bt 34 | } 35 | 36 | val version = 1: Byte 37 | val timestamp = System.currentTimeMillis() 38 | 39 | val block = Block.buildAndSign(version, timestamp, reference, cbd, tbd, gen) 40 | val parsedBlock = Block.parseBytes(block.bytes).get 41 | 42 | assert(parsedBlock.consensusDataField.value.asInstanceOf[NxtLikeConsensusBlockData].generationSignature.sameElements(gs)) 43 | assert(parsedBlock.versionField.value == version) 44 | assert(parsedBlock.signerDataField.value.generator.publicKey.sameElements(gen.publicKey)) 45 | } 46 | 47 | test("Qora block with txs bytes/parse roundtrip") { 48 | implicit val consensusModule = new QoraLikeConsensusModule() 49 | implicit val transactionModule = new SimpleTransactionModule()(application.settings, application) 50 | 51 | val reference = Array.fill(Block.BlockIdLength)(Random.nextInt(100).toByte) 52 | val gen = new PrivateKeyAccount(reference) 53 | 54 | val gb = Random.nextLong() 55 | val gs = Array.fill(QoraLikeConsensusModule.GeneratorSignatureLength)(Random.nextInt(100).toByte) 56 | 57 | val sender = new PrivateKeyAccount(reference.dropRight(2)) 58 | val tx: Transaction = PaymentTransaction(sender, gen, 5, 1000, System.currentTimeMillis() - 5000) 59 | 60 | val tbd = Seq(tx) 61 | val cbd = new QoraLikeConsensusBlockData { 62 | override val generatorSignature: Array[Byte] = gs 63 | override val generatingBalance: Long = gb 64 | } 65 | 66 | val cbdBytes = consensusModule.formBlockData(cbd).bytes 67 | assert(cbdBytes.takeRight(QoraLikeConsensusModule.GeneratorSignatureLength).sameElements(gs)) 68 | 69 | val version = 1: Byte 70 | val timestamp = System.currentTimeMillis() 71 | 72 | val block = Block.buildAndSign(version, timestamp, reference, cbd, tbd, gen) 73 | val parsedBlock = Block.parseBytes(block.bytes).get 74 | 75 | val parsedCdf = parsedBlock.consensusDataField.value.asInstanceOf[QoraLikeConsensusBlockData] 76 | assert(parsedCdf.generatingBalance == gb) 77 | assert(parsedCdf.generatorSignature.sameElements(gs)) 78 | assert(parsedBlock.versionField.value == version) 79 | assert(parsedBlock.signerDataField.value.generator.publicKey.sameElements(gen.publicKey)) 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/unit/MessageSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.unit 2 | 3 | import java.nio.ByteBuffer 4 | 5 | import org.scalatest.FunSuite 6 | import scorex.block.Block 7 | import scorex.consensus.nxt.NxtLikeConsensusModule 8 | import scorex.crypto.EllipticCurveImpl.SignatureLength 9 | import scorex.lagonaki.TestingCommons 10 | import scorex.network.message.{BasicMessagesRepo, Message, MessageHandler} 11 | import scorex.transaction.{History, SimpleTransactionModule} 12 | import shapeless.syntax.typeable._ 13 | 14 | class MessageSpecification extends FunSuite with TestingCommons { 15 | 16 | private lazy val repo = new BasicMessagesRepo() 17 | private lazy val handler = new MessageHandler(repo.specs) 18 | 19 | test("ScoreMessage roundtrip 1") { 20 | val s1 = BigInt(Long.MaxValue) * 1000000000L 21 | 22 | val msg = Message(repo.ScoreMessageSpec, Right(s1), None) 23 | 24 | handler.parseBytes(ByteBuffer.wrap(msg.bytes), None).get.data.get match { 25 | case scoreRestored: History.BlockchainScore => 26 | assert(s1 == scoreRestored) 27 | 28 | case _ => 29 | fail("wrong data type restored") 30 | } 31 | } 32 | 33 | test("GetSignaturesMessage roundtrip 1") { 34 | val e1 = 33: Byte 35 | val e2 = 34: Byte 36 | val s1: Block.BlockId = e2 +: Array.fill(SignatureLength - 1)(e1) 37 | 38 | val msg = Message(repo.GetSignaturesSpec, Right(Seq(s1)), None) 39 | val ss = handler.parseBytes(ByteBuffer.wrap(msg.bytes), None).get.data.get.asInstanceOf[Seq[Block.BlockId]] 40 | assert(ss.head.sameElements(s1)) 41 | } 42 | 43 | test("SignaturesMessage roundtrip 1") { 44 | val e1 = 33: Byte 45 | val e2 = 34: Byte 46 | val s1 = e2 +: Array.fill(SignatureLength - 1)(e1) 47 | val s2 = e1 +: Array.fill(SignatureLength - 1)(e2) 48 | 49 | val msg = Message(repo.SignaturesSpec, Right(Seq(s1, s2)), None) 50 | val ss = handler.parseBytes(ByteBuffer.wrap(msg.bytes), None).get.data.get.asInstanceOf[Seq[Block.BlockId]] 51 | assert(ss.head.sameElements(s1)) 52 | assert(ss.tail.head.sameElements(s2)) 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/lagonaki/unit/WalletSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.lagonaki.unit 2 | 3 | import org.scalatest.{Matchers, FunSuite} 4 | import scorex.crypto.encode.Base58 5 | import scorex.wallet.Wallet 6 | 7 | import scala.util.Random 8 | 9 | class WalletSpecification extends FunSuite with Matchers { 10 | 11 | private val walletSize = 10 12 | val w = new Wallet(None, "cookies", Base58.decode("FQgbSAm6swGbtqA3NE8PttijPhT4N3Ufh4bHFAkyVnQz").toOption) 13 | 14 | test("wallet - acc creation") { 15 | w.generateNewAccounts(walletSize) 16 | 17 | w.privateKeyAccounts().size shouldBe walletSize 18 | w.privateKeyAccounts().map(_.address) shouldBe Seq("3Mb4mR4taeYS3wci78SntztFwLoaS6Wbg81", "3MkcXZiczXxqEQYVVhRqkUnVaTfzaeMs15e", "3MoFqpXVnxVpMwN1egMj2yH6EEWWYVruvfn", "3MjLQqH3cU6DZ4yFC6SAPmXkHf3ajWmV3LK", "3MXTTi9tjPSjossb4Tx5nokg895tCfroWLB", "3MUc8z9WMuMbRR93NU8Jxh1DgwCkfHr78Fu", "3Mo4k51wnV4EYmCmneh4fqDdfKYxcgZgCUT", "3MiSmwnRGJtLmZeNLYg75bje6StYhCEKCrX", "3MjQ5FJEBGggnwBWBNjZtgcwyrYTn4r4shg", "3MkjMybqczyo6SyouuaSniTuSiZqTsMhQQx") 19 | 20 | } 21 | 22 | test("wallet - acc deletion") { 23 | 24 | val head = w.privateKeyAccounts().head 25 | w.deleteAccount(head) 26 | assert(w.privateKeyAccounts().size == walletSize - 1) 27 | 28 | w.deleteAccount(w.privateKeyAccounts().head) 29 | assert(w.privateKeyAccounts().size == walletSize - 2) 30 | 31 | w.privateKeyAccounts().foreach(w.deleteAccount) 32 | 33 | assert(w.privateKeyAccounts().isEmpty) 34 | } 35 | 36 | test("reopening") { 37 | 38 | //todo read folder from settings 39 | val walletFile = new java.io.File(s"/tmp/wallet${Random.nextLong()}.dat") 40 | 41 | val w = new Wallet(Some(walletFile), "cookies", Base58.decode("FQgbSAm6swGbtqA3NE8PttijPhT4N3Ufh4bHFAkyVnQz").toOption) 42 | w.generateNewAccounts(10) 43 | val nonce = w.nonce() 44 | w.close() 45 | assert(w.exists()) 46 | 47 | val w2 = new Wallet(Some(walletFile), "cookies", None) 48 | w2.privateKeyAccounts().head.address should not be null 49 | w2.nonce() shouldBe nonce 50 | } 51 | } -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "1.2.8" 2 | --------------------------------------------------------------------------------