├── .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 |
--------------------------------------------------------------------------------