├── doc └── peer │ ├── main.toc │ ├── main.bbl │ ├── main.pdf │ ├── main.out │ ├── compile.sh │ └── main.blg ├── project ├── build.properties └── plugins.sbt ├── examples ├── src │ ├── main │ │ ├── resources │ │ │ ├── reference.conf │ │ │ ├── scripts │ │ │ │ ├── twinschainStatus.sh │ │ │ │ ├── twinschainRebuild.sh │ │ │ │ ├── ips.sh │ │ │ │ ├── twinschainRebuildAndStart.sh │ │ │ │ ├── twinschainRerun.sh │ │ │ │ └── twinschainConfigGenerator.sh │ │ │ ├── settings.conf │ │ │ ├── settings2.conf │ │ │ ├── settings3.conf │ │ │ ├── settings8.conf │ │ │ ├── settings4.conf │ │ │ ├── settings5.conf │ │ │ ├── settings9.conf │ │ │ ├── settings7.conf │ │ │ ├── settings6.conf │ │ │ └── settings10.conf │ │ └── scala │ │ │ └── examples │ │ │ ├── trimchain │ │ │ ├── README.md │ │ │ ├── core │ │ │ │ ├── core.scala │ │ │ │ ├── Constants.scala │ │ │ │ └── Ticket.scala │ │ │ ├── modifiers │ │ │ │ ├── TModifier.scala │ │ │ │ ├── UtxoSnapshot.scala │ │ │ │ ├── BlockHeader.scala │ │ │ │ └── TBlock.scala │ │ │ └── simulation │ │ │ │ ├── SpaceSavingsCalculator.scala │ │ │ │ ├── Simulators.scala │ │ │ │ └── ValidationSimulator.scala │ │ │ ├── hybrid │ │ │ ├── README.md │ │ │ ├── blocks │ │ │ │ └── HybridBlock.scala │ │ │ ├── util │ │ │ │ ├── FileFunctions.scala │ │ │ │ └── Cancellable.scala │ │ │ ├── validation │ │ │ │ ├── SemanticBlockValidator.scala │ │ │ │ ├── ParentBlockValidator.scala │ │ │ │ └── DifficultyBlockValidator.scala │ │ │ ├── mining │ │ │ │ └── HybridMiningSettings.scala │ │ │ ├── api │ │ │ │ └── http │ │ │ │ │ └── StatsApiRoute.scala │ │ │ └── history │ │ │ │ └── HybridSyncInfo.scala │ │ │ ├── spv │ │ │ ├── Constants.scala │ │ │ └── simulation │ │ │ │ └── SPVSimulator.scala │ │ │ └── commons │ │ │ ├── FileLogger.scala │ │ │ ├── curvepos.scala │ │ │ ├── SimpleBoxTransactionMemPool.scala │ │ │ └── PublicKey25519NoncedBox.scala │ └── test │ │ ├── scala │ │ ├── hybrid │ │ │ ├── NodeViewHolderSpec.scala │ │ │ ├── NodeViewSynchronizerSpec.scala │ │ │ ├── StoreGenerators.scala │ │ │ ├── HybridTypes.scala │ │ │ ├── validation │ │ │ │ └── SemanticBlockValidatorSpecification.scala │ │ │ ├── state │ │ │ │ └── HBoxStoredStateSpecification.scala │ │ │ ├── primitives │ │ │ │ └── PrivateKey25519Suite.scala │ │ │ ├── HybridSanity.scala │ │ │ ├── StateGenerators.scala │ │ │ ├── transaction │ │ │ │ └── TransactionSuite.scala │ │ │ ├── HistoryGenerators.scala │ │ │ └── NodeViewHolderGenerators.scala │ │ ├── spv │ │ │ ├── serialization │ │ │ │ └── SerializationTests.scala │ │ │ └── SPVGenerators.scala │ │ ├── trimchain │ │ │ ├── serialization │ │ │ │ └── SerializationTests.scala │ │ │ └── TrimchainGenerators.scala │ │ └── commons │ │ │ └── ExamplesCommonGenerators.scala │ │ └── resources │ │ └── settings.conf ├── project │ └── plugins.sbt ├── build.sbt └── README.md ├── testkit ├── project │ └── plugins.sbt ├── src │ └── main │ │ └── scala │ │ └── scorex │ │ └── testkit │ │ ├── utils │ │ ├── NoShrink.scala │ │ ├── AkkaFixture.scala │ │ └── FileUtils.scala │ │ ├── TestkitHelpers.scala │ │ ├── generators │ │ ├── CoreGenerators.scala │ │ ├── SemanticallyInvalidModifierProducer.scala │ │ ├── SemanticallyValidModifierProducer.scala │ │ ├── SyntacticallyTargetedModifierProducer.scala │ │ ├── TotallyValidModifierProducer.scala │ │ ├── CustomModifierProducer.scala │ │ ├── ArbitraryTransactionsCarryingModifierProducer.scala │ │ ├── SemanticallyValidTransactionsCarryingModifier.scala │ │ └── AllModifierProducers.scala │ │ ├── properties │ │ ├── mempool │ │ │ ├── MemoryPoolTest.scala │ │ │ ├── MempoolRemovalTest.scala │ │ │ └── MempoolFilterPerformanceTest.scala │ │ ├── state │ │ │ ├── box │ │ │ │ ├── BoxStateTests.scala │ │ │ │ ├── BoxStateApplyChangesTest.scala │ │ │ │ └── BoxStateChangesGenerationTest.scala │ │ │ └── StateTests.scala │ │ └── WalletSecretsTest.scala │ │ ├── SerializationTests.scala │ │ ├── BlockchainPerformance.scala │ │ └── BlockchainSanity.scala └── build.sbt ├── .github ├── settings.yml └── workflows │ ├── release.yml │ └── ci.yml ├── src ├── main │ ├── resources │ │ ├── swagger-ui │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── index.html │ │ │ └── oauth2-redirect.html │ │ └── logback.xml │ └── scala │ │ └── scorex │ │ ├── core │ │ ├── NodeViewComponent.scala │ │ ├── utils │ │ │ ├── SerializationConstants.scala │ │ │ ├── TimeProvider.scala │ │ │ ├── LocalTimeProvider.scala │ │ │ ├── ScorexEncoding.scala │ │ │ ├── BlockTypeable.scala │ │ │ ├── ActorHelper.scala │ │ │ ├── NetworkUtils.scala │ │ │ ├── ScorexEncoder.scala │ │ │ ├── NetworkTime.scala │ │ │ └── utils.scala │ │ ├── block │ │ │ ├── BlockValidator.scala │ │ │ └── Block.scala │ │ ├── serialization │ │ │ ├── BytesSerializable.scala │ │ │ ├── SerializerRegistry.scala │ │ │ └── ScorexSerializer.scala │ │ ├── transaction │ │ │ ├── wallet │ │ │ │ ├── VaultReader.scala │ │ │ │ └── Vault.scala │ │ │ ├── state │ │ │ │ ├── StateReader.scala │ │ │ │ ├── BoxStateChanges.scala │ │ │ │ └── MinimalState.scala │ │ │ ├── box │ │ │ │ ├── proposition │ │ │ │ │ └── Proposition.scala │ │ │ │ ├── Box.scala │ │ │ │ └── BoxUnlocker.scala │ │ │ ├── BoxTransaction.scala │ │ │ ├── Transaction.scala │ │ │ ├── proof │ │ │ │ ├── Proof.scala │ │ │ │ └── Signature25519.scala │ │ │ ├── MempoolReader.scala │ │ │ ├── account │ │ │ │ └── PublicKeyNoncedBox.scala │ │ │ └── MemoryPool.scala │ │ ├── network │ │ │ ├── MaliciousBehaviorException.scala │ │ │ ├── ConnectionDirection.scala │ │ │ ├── ConnectionDescription.scala │ │ │ ├── Handshake.scala │ │ │ ├── ConnectionId.scala │ │ │ ├── peer │ │ │ │ ├── PeerDatabase.scala │ │ │ │ ├── PenaltyType.scala │ │ │ │ ├── LocalAddressPeerFeature.scala │ │ │ │ ├── PeerInfo.scala │ │ │ │ └── SessionIdPeerFeature.scala │ │ │ ├── PeerFeature.scala │ │ │ ├── message │ │ │ │ ├── MessageSpec.scala │ │ │ │ └── Message.scala │ │ │ ├── ModifiersStatus.scala │ │ │ ├── ConnectedPeer.scala │ │ │ ├── SendingStrategy.scala │ │ │ └── Synchronizer.scala │ │ ├── api │ │ │ ├── http │ │ │ │ ├── ApiErrorHandler.scala │ │ │ │ ├── ApiTry.scala │ │ │ │ ├── swagger │ │ │ │ │ └── SwaggerConfigRoute.scala │ │ │ │ ├── ApiDirectives.scala │ │ │ │ ├── ApiRouteWithFullView.scala │ │ │ │ ├── ApiRoute.scala │ │ │ │ ├── CompositeHttpService.scala │ │ │ │ ├── UtilsApiRoute.scala │ │ │ │ ├── CorsHandler.scala │ │ │ │ ├── ApiRejectionHandler.scala │ │ │ │ ├── ApiError.scala │ │ │ │ └── ApiResponse.scala │ │ │ └── client │ │ │ │ └── ApiClient.scala │ │ ├── consensus │ │ │ ├── ConsensusSettings.scala │ │ │ ├── SyncInfo.scala │ │ │ ├── ModifierSemanticValidity.scala │ │ │ ├── ContainsModifiers.scala │ │ │ └── HistoryReader.scala │ │ ├── app │ │ │ ├── ScorexContext.scala │ │ │ └── Version.scala │ │ ├── validation │ │ │ ├── ValidationSettings.scala │ │ │ └── ModifierError.scala │ │ ├── settings │ │ │ └── SettingsReaders.scala │ │ ├── core.scala │ │ └── NodeViewModifier.scala │ │ └── util │ │ └── serialization │ │ ├── VLQByteStringWriter.scala │ │ └── VLQByteStringReader.scala └── test │ └── scala │ └── scorex │ ├── network │ ├── PeerConnectionHandlerSpecification.scala │ └── NetworkTests.scala │ ├── util │ ├── serialization │ │ └── VQLByteStringReaderWriterSpecification.scala │ └── ScorexLoggingSpec.scala │ ├── core │ ├── transaction │ │ └── box │ │ │ └── proposition │ │ │ └── PublicKey25519PropositionSpecification.scala │ ├── network │ │ ├── peer │ │ │ └── PeerManagerSpec.scala │ │ └── HandshakeSpecification.scala │ ├── api │ │ └── http │ │ │ └── UtilsApiRouteSpec.scala │ └── serialization │ │ └── SerializerRegistrySpec.scala │ └── crypto │ └── SigningFunctionsSpecification.scala ├── .gitignore ├── ci └── import_gpg.sh └── release-notes.md /doc/peer/main.toc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /doc/peer/main.bbl: -------------------------------------------------------------------------------- 1 | \begin{thebibliography}{} 2 | 3 | \end{thebibliography} 4 | -------------------------------------------------------------------------------- /examples/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | app { 2 | modifierIdSize = 32 3 | } -------------------------------------------------------------------------------- /examples/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") -------------------------------------------------------------------------------- /testkit/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") -------------------------------------------------------------------------------- /doc/peer/main.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-labs/Scorex/HEAD/doc/peer/main.pdf -------------------------------------------------------------------------------- /doc/peer/main.out: -------------------------------------------------------------------------------- 1 | \BOOKMARK [0][-]{chapter.1}{A Flexible Peer Management Architecture \040for Blockchain Systems}{}% 1 2 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | 5 | repository: 6 | name: Scorex 7 | archived: true 8 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/trimchain/README.md: -------------------------------------------------------------------------------- 1 | TrimChain consensus protocol implementation 2 | =========================================== 3 | -------------------------------------------------------------------------------- /src/main/resources/swagger-ui/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-labs/Scorex/HEAD/src/main/resources/swagger-ui/favicon-16x16.png -------------------------------------------------------------------------------- /src/main/resources/swagger-ui/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-labs/Scorex/HEAD/src/main/resources/swagger-ui/favicon-32x32.png -------------------------------------------------------------------------------- /src/main/scala/scorex/core/NodeViewComponent.scala: -------------------------------------------------------------------------------- 1 | package scorex.core 2 | 3 | trait NodeViewComponent { 4 | self => 5 | 6 | type NVCT >: self.type <: NodeViewComponent 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/utils/SerializationConstants.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.utils 2 | 3 | object SerializationConstants { 4 | val IntSize: Int = 4 5 | val LongSize: Int = 8 6 | } 7 | -------------------------------------------------------------------------------- /doc/peer/compile.sh: -------------------------------------------------------------------------------- 1 | rm main.pdf 2 | pdflatex main 3 | bibtex main 4 | pdflatex main 5 | pdflatex main 6 | rm main.aux 7 | rm main.log 8 | rm main.blg 9 | rm main.bbl 10 | rm main.out 11 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/hybrid/README.md: -------------------------------------------------------------------------------- 1 | Hybrid Proof-of-Work/Proof-of-Stake consensus protocol implementation 2 | ====================================================================== 3 | 4 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/block/BlockValidator.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.block 2 | 3 | import scala.util.Try 4 | 5 | trait BlockValidator[PM <: Block[_]] { 6 | def validate(block: PM): Try[Unit] 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/utils/TimeProvider.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.utils 2 | 3 | object TimeProvider { 4 | type Time = Long 5 | } 6 | 7 | trait TimeProvider { 8 | def time(): TimeProvider.Time 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/utils/LocalTimeProvider.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.utils 2 | 3 | object LocalTimeProvider extends TimeProvider { 4 | override def time(): TimeProvider.Time = { 5 | System.currentTimeMillis() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/utils/NoShrink.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.utils 2 | 3 | import org.scalacheck.Shrink 4 | 5 | trait NoShrink { 6 | protected implicit def noShrink[A]: Shrink[A] = Shrink(_ => Stream.empty) 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/utils/ScorexEncoding.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.utils 2 | 3 | /** 4 | * Trait with bytes to string encoder 5 | * TODO extract to ScorexUtils project 6 | */ 7 | trait ScorexEncoding { 8 | implicit val encoder: ScorexEncoder = ScorexEncoder.default 9 | } 10 | -------------------------------------------------------------------------------- /examples/src/main/resources/scripts/twinschainStatus.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my_dir="$(dirname "$0")" 4 | source "$my_dir/ips.sh" 5 | 6 | for ip in "${allIps[@]}" 7 | do 8 | echo "$ip" 9 | curl -s -X GET --header 'Accept: application/json' 'http://'$ip':9085/debug/info' | grep height 10 | done -------------------------------------------------------------------------------- /examples/src/main/scala/examples/hybrid/blocks/HybridBlock.scala: -------------------------------------------------------------------------------- 1 | package examples.hybrid.blocks 2 | 3 | import examples.commons.SimpleBoxTransaction 4 | import scorex.core.PersistentNodeViewModifier 5 | import scorex.core.block.Block 6 | 7 | trait HybridBlock extends PersistentNodeViewModifier with Block[SimpleBoxTransaction] 8 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/spv/Constants.scala: -------------------------------------------------------------------------------- 1 | package examples.spv 2 | 3 | import scorex.crypto.hash.Blake2b256 4 | 5 | object Constants { 6 | val hashfn: Blake2b256.type = Blake2b256 7 | 8 | lazy val MaxTarget: BigInt = BigInt(1, Array.fill(32)(Byte.MinValue)) 9 | val InitialDifficulty: BigInt = BigInt(1) 10 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/core/serialization/BytesSerializable.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.serialization 2 | 3 | trait BytesSerializable extends Serializable { 4 | 5 | type M >: this.type <: BytesSerializable 6 | 7 | def bytes: Array[Byte] = serializer.toBytes(this) 8 | 9 | def serializer: ScorexSerializer[M] 10 | } 11 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/TestkitHelpers.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit 2 | 3 | trait TestkitHelpers { 4 | 5 | val MinTestsOk = 100 6 | 7 | def check(minTestsOk:Int)(f: Int => Unit): Unit = (0 until minTestsOk) foreach (i => f(i)) 8 | 9 | def check(f: Int => Unit): Unit = check(MinTestsOk)(f) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/wallet/VaultReader.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.wallet 2 | 3 | import scorex.core.NodeViewComponent 4 | 5 | /** 6 | * Reader for vault. 7 | * As vault is implementation-specific component, it does not contain any mandatory methods. 8 | */ 9 | trait VaultReader extends NodeViewComponent -------------------------------------------------------------------------------- /src/test/scala/scorex/network/PeerConnectionHandlerSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | class PeerConnectionHandlerSpecification extends AnyFlatSpec with Matchers { 7 | //todo: for Dmitry (?) : write tests for handshaking process 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/state/StateReader.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.state 2 | 3 | import scorex.core.{NodeViewComponent, VersionTag} 4 | 5 | trait StateReader extends NodeViewComponent { 6 | 7 | //must be ID of last applied modifier 8 | def version: VersionTag 9 | 10 | def maxRollbackDepth: Int 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/MaliciousBehaviorException.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network 2 | 3 | /** 4 | * Custom exception to distinguish malicious behaviour of external peers from non-adversarial network issues 5 | * 6 | * @param message - exception message 7 | */ 8 | case class MaliciousBehaviorException(message: String) extends Exception(message) -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/ApiErrorHandler.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import akka.http.scaladsl.server.ExceptionHandler 4 | 5 | import scala.util.control.NonFatal 6 | 7 | object ApiErrorHandler { 8 | 9 | implicit val exceptionHandler: ExceptionHandler = ExceptionHandler { 10 | case NonFatal(e) => ApiError(e) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/box/proposition/Proposition.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.box.proposition 2 | 3 | import scorex.core.serialization.BytesSerializable 4 | import scorex.core.transaction.state.Secret 5 | 6 | trait Proposition extends BytesSerializable 7 | 8 | trait ProofOfKnowledgeProposition[S <: Secret] extends Proposition 9 | 10 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/hybrid/util/FileFunctions.scala: -------------------------------------------------------------------------------- 1 | package examples.hybrid.util 2 | 3 | import java.io.FileWriter 4 | 5 | object FileFunctions { 6 | def append(filename: String, record: String): Unit = { 7 | val fw = new FileWriter(filename, true) 8 | try { 9 | fw.write(s"$record\n") 10 | } 11 | finally fw.close() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/consensus/ConsensusSettings.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.consensus 2 | 3 | import io.circe.Json 4 | 5 | trait ConsensusSettings { 6 | val settingsJSON: Map[String, Json] 7 | 8 | private val DefaultMaxRollback = 100 9 | lazy val MaxRollback = settingsJSON.get("max-rollback").flatMap(_.asNumber).flatMap(_.toInt).getOrElse(DefaultMaxRollback) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/ApiTry.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import akka.http.scaladsl.server.Route 4 | 5 | import scala.util.{Failure, Success, Try} 6 | 7 | object ApiTry { 8 | 9 | def apply(f: => Route): Route = Try { 10 | f 11 | } match { 12 | case Success(r) => r 13 | case Failure(e) => e.printStackTrace(); ApiError(e) 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/trimchain/core/core.scala: -------------------------------------------------------------------------------- 1 | package examples.trimchain 2 | 3 | import supertagged.TaggedType 4 | 5 | package object core { 6 | 7 | object StateRoot extends TaggedType[Array[Byte]] 8 | 9 | object TransactionsRoot extends TaggedType[Array[Byte]] 10 | 11 | type StateRoot = StateRoot.Type 12 | 13 | type TransactionsRoot = TransactionsRoot.Type 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/consensus/SyncInfo.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.consensus 2 | 3 | import scorex.core.serialization.BytesSerializable 4 | 5 | /** 6 | * Syncing info provides information about starting points this node recommends another to start 7 | * synchronization from 8 | */ 9 | trait SyncInfo extends BytesSerializable { 10 | def startingPoints: History.ModifierIds 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/CoreGenerators.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import org.scalacheck.Gen 4 | import scorex.ObjectGenerators 5 | import scorex.core.{VersionTag, idToVersion} 6 | 7 | //Generators of objects from scorex-core 8 | trait CoreGenerators extends ObjectGenerators { 9 | lazy val versionTagGen: Gen[VersionTag] = modifierIdGen.map(id => idToVersion(id)) 10 | } -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/SemanticallyInvalidModifierProducer.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import scorex.core.PersistentNodeViewModifier 4 | import scorex.core.transaction.state.MinimalState 5 | 6 | 7 | trait SemanticallyInvalidModifierProducer[PM <: PersistentNodeViewModifier, ST <: MinimalState[PM, ST]] { 8 | def semanticallyInvalidModifier(state: ST): PM 9 | } 10 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/SemanticallyValidModifierProducer.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import scorex.core.PersistentNodeViewModifier 4 | import scorex.core.transaction.state.MinimalState 5 | 6 | 7 | trait SemanticallyValidModifierProducer[PM <: PersistentNodeViewModifier, ST <: MinimalState[PM, ST]] { 8 | def semanticallyValidModifier(state: ST): PM 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/trimchain/modifiers/TModifier.scala: -------------------------------------------------------------------------------- 1 | package examples.trimchain.modifiers 2 | 3 | import scorex.core._ 4 | 5 | 6 | trait TModifier extends PersistentNodeViewModifier 7 | 8 | object TModifier { 9 | val Header: ModifierTypeId = ModifierTypeId @@ 0.toByte 10 | val UtxoSnapshot: ModifierTypeId = ModifierTypeId @@ 1.toByte 11 | val Block: ModifierTypeId = ModifierTypeId @@ 2.toByte 12 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/ConnectionDirection.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network 2 | 3 | sealed trait ConnectionDirection { 4 | def isIncoming: Boolean 5 | def isOutgoing: Boolean = !isIncoming 6 | } 7 | 8 | case object Incoming extends ConnectionDirection { 9 | override val isIncoming: Boolean = true 10 | } 11 | 12 | case object Outgoing extends ConnectionDirection { 13 | override val isIncoming: Boolean = false 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/ConnectionDescription.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network 2 | 3 | import java.net.InetSocketAddress 4 | 5 | import akka.actor.ActorRef 6 | 7 | case class ConnectionDescription(connection: ActorRef, 8 | connectionId: ConnectionId, 9 | ownSocketAddress: Option[InetSocketAddress], 10 | localFeatures: Seq[PeerFeature]) 11 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/BoxTransaction.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction 2 | 3 | import scorex.core.transaction.box.proposition.Proposition 4 | import scorex.core.transaction.box.{Box, BoxUnlocker} 5 | 6 | 7 | abstract class BoxTransaction[P <: Proposition, BX <: Box[P]] extends Transaction { 8 | 9 | val unlockers: Traversable[BoxUnlocker[P]] 10 | val newBoxes: Traversable[BX] 11 | 12 | val fee: Long 13 | 14 | val timestamp: Long 15 | 16 | } 17 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/properties/mempool/MemoryPoolTest.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.properties.mempool 2 | 3 | import org.scalacheck.Gen 4 | import scorex.core.transaction.box.proposition.Proposition 5 | import scorex.core.transaction.{MemoryPool, Transaction} 6 | 7 | 8 | trait MemoryPoolTest[TX <: Transaction, MPool <: MemoryPool[TX, MPool]] { 9 | val memPool: MPool 10 | val memPoolGenerator: Gen[MPool] 11 | val transactionGenerator: Gen[TX] 12 | } 13 | -------------------------------------------------------------------------------- /testkit/build.sbt: -------------------------------------------------------------------------------- 1 | name := "scorex-testkit" 2 | 3 | libraryDependencies ++= Seq( 4 | "org.scalactic" %% "scalactic" % "3.0.1", 5 | "org.scalatest" %% "scalatest" % "3.1.1", 6 | "org.scalacheck" %% "scalacheck" % "1.14.+", 7 | "org.scalatestplus" %% "scalatestplus-scalacheck" % "3.1.0.0-RC2", 8 | "com.typesafe.akka" %% "akka-testkit" % "2.6.10" 9 | ) 10 | 11 | fork in Test := true 12 | 13 | javaOptions in Test ++= Seq("-Xmx2G") 14 | 15 | parallelExecution in Test := false -------------------------------------------------------------------------------- /examples/src/main/scala/examples/commons/FileLogger.scala: -------------------------------------------------------------------------------- 1 | package examples.commons 2 | 3 | import java.nio.file.{Files, Path, Paths, StandardOpenOption} 4 | 5 | class FileLogger(filePath: String) { 6 | 7 | val path: Path = Paths.get(filePath) 8 | private val f = path.toFile 9 | f.getParentFile().mkdirs() 10 | f.createNewFile() 11 | 12 | def appendString(string: String): Unit = { 13 | Files.write(path, (string + "\n").getBytes(), StandardOpenOption.APPEND); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/scala/scorex/util/serialization/VQLByteStringReaderWriterSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.util.serialization 2 | 3 | import akka.util.ByteString 4 | 5 | class VQLByteStringReaderWriterSpecification extends VLQReaderWriterSpecification { 6 | 7 | override def byteBufReader(bytes: Array[Byte]): VLQReader = { 8 | new VLQByteStringReader(ByteString(bytes)) 9 | } 10 | 11 | override def byteArrayWriter(): VLQWriter = { 12 | new VLQByteStringWriter() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/SyntacticallyTargetedModifierProducer.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import scorex.core.PersistentNodeViewModifier 4 | import scorex.core.consensus.{History, SyncInfo} 5 | 6 | 7 | trait SyntacticallyTargetedModifierProducer[PM <: PersistentNodeViewModifier, SI <: SyncInfo, HT <: History[PM, SI, HT]] { 8 | def syntacticallyValidModifier(history: HT): PM 9 | 10 | def syntacticallyInvalidModifier(history: HT): PM 11 | } 12 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/utils/AkkaFixture.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.utils 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | 5 | import akka.actor.ActorSystem 6 | import akka.testkit.{ImplicitSender, TestKit} 7 | 8 | object SysId { 9 | private val i = new AtomicInteger() 10 | def incrementAndGet(): Int = i.incrementAndGet() 11 | } 12 | 13 | class AkkaFixture 14 | extends TestKit(ActorSystem("WithIsoFix-%d".format(SysId.incrementAndGet()))) 15 | with ImplicitSender 16 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/box/Box.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.box 2 | 3 | import scorex.core.serialization.BytesSerializable 4 | import scorex.core.transaction.box.proposition.Proposition 5 | import scorex.crypto.authds._ 6 | 7 | /** 8 | * Box is a state element locked by some proposition. 9 | */ 10 | trait Box[P <: Proposition] extends BytesSerializable { 11 | val value: Box.Amount 12 | val proposition: P 13 | 14 | val id: ADKey 15 | } 16 | 17 | object Box { 18 | type Amount = Long 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/utils/BlockTypeable.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.utils 2 | 3 | import scorex.core.block.Block 4 | import scorex.core.transaction.Transaction 5 | import shapeless.Typeable 6 | 7 | class BlockTypeable[TX <: Transaction] extends Typeable[Block[TX]] { 8 | 9 | def cast(t: Any): Option[Block[TX]] = t match { 10 | case b: Block[TX] => Some(b) 11 | case _ => None 12 | } 13 | 14 | def describe: String = "Block[TX <: Transaction]" 15 | 16 | override def toString: String = s"Typeable[$describe]" 17 | } -------------------------------------------------------------------------------- /examples/src/main/scala/examples/trimchain/core/Constants.scala: -------------------------------------------------------------------------------- 1 | package examples.trimchain.core 2 | 3 | import scorex.crypto.hash.Blake2b256 4 | 5 | 6 | object Constants { 7 | val n: Int = 20 8 | val k: Int = 1 9 | val NElementsInProof: Int = 10 10 | 11 | val hashfn: Blake2b256.type = Blake2b256 12 | 13 | val StateRootLength: Int = hashfn.DigestSize 14 | 15 | val TxRootLength: Int = hashfn.DigestSize 16 | 17 | 18 | lazy val MaxTarget = BigInt(1, Array.fill(32)(Byte.MinValue)) 19 | lazy val Difficulty = BigInt("2") 20 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/box/BoxUnlocker.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.box 2 | 3 | import scorex.core.transaction.box.proposition.Proposition 4 | import scorex.core.transaction.proof.Proof 5 | import scorex.core.utils.ScorexEncoding 6 | import scorex.crypto.authds.ADKey 7 | 8 | trait BoxUnlocker[P <: Proposition] extends ScorexEncoding { 9 | val closedBoxId: ADKey 10 | val boxKey: Proof[P] 11 | 12 | override def toString: String = s"BoxUnlocker(id: ${encoder.encode(closedBoxId)}, boxKey: $boxKey)" 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/utils/ActorHelper.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.utils 2 | 3 | import akka.actor.ActorRef 4 | import akka.pattern.ask 5 | import akka.util.Timeout 6 | 7 | import scala.concurrent.Future 8 | import scala.reflect.ClassTag 9 | 10 | /** 11 | * Helper that encapsulates ask patter for actors and returns Future[_] 12 | */ 13 | trait ActorHelper { 14 | 15 | def askActor[A: ClassTag](actorRef: ActorRef, question: Any) 16 | (implicit timeout: Timeout): Future[A] = (actorRef ? question).mapTo[A] 17 | 18 | } 19 | -------------------------------------------------------------------------------- /examples/src/main/resources/scripts/twinschainRebuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my_dir="$(dirname "$0")" 4 | source "$my_dir/ips.sh" 5 | 6 | for ip in "${ips[@]}" 7 | do 8 | echo "$ip" 9 | ssh ubuntu@$ip rm -rf /home/ubuntu/Scorex/examples/target/scala-2.12/twinsChain.jar 10 | ssh ubuntu@$ip rm -rf /home/ubuntu/data/twinsChain.jar 11 | ssh ubuntu@$ip "cd /home/ubuntu/Scorex && git pull origin master && sbt publishLocal && sbt \"project examples\" \"assembly\" && cp /home/ubuntu/Scorex/examples/target/scala-2.12/twinsChain.jar /home/ubuntu/data/twinsChain.jar" 12 | done 13 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/TotallyValidModifierProducer.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import scorex.core.PersistentNodeViewModifier 4 | import scorex.core.consensus.{History, SyncInfo} 5 | import scorex.core.transaction.state.MinimalState 6 | 7 | 8 | trait TotallyValidModifierProducer[PM <: PersistentNodeViewModifier, ST <: MinimalState[PM, ST], 9 | SI <: SyncInfo, HT <: History[PM, SI, HT]] { 10 | 11 | def totallyValidModifier(history: HT, state: ST): PM 12 | 13 | def totallyValidModifiers(history: HT, state: ST, count: Int): Seq[PM] 14 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/util/ScorexLoggingSpec.scala: -------------------------------------------------------------------------------- 1 | package scorex.util 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | class ScorexLoggingSpec extends AnyFlatSpec with Matchers with ScorexLogging { 7 | 8 | "Logger" should "evaluate messages only if the respective log level is enabled" in { 9 | var i = 0 10 | log.info(s"Info level message, should be evaluated ${i = 1} i = $i") 11 | i shouldBe 1 12 | log.trace(s"Trace level message, should not be evaluated ${i = 2} i = $i") 13 | i shouldBe 1 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/core/app/ScorexContext.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.app 2 | 3 | import java.net.InetSocketAddress 4 | 5 | import scorex.core.network.{PeerFeature, UPnPGateway} 6 | import scorex.core.network.message.MessageSpec 7 | import scorex.core.utils.TimeProvider 8 | 9 | case class ScorexContext(messageSpecs: Seq[MessageSpec[_]], 10 | features: Seq[PeerFeature], 11 | upnpGateway: Option[UPnPGateway], 12 | timeProvider: TimeProvider, 13 | externalNodeAddress: Option[InetSocketAddress]) 14 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/Handshake.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network 2 | 3 | /** 4 | * Network message to be send when nodes establish a new connection. 5 | * When a node creates an outgoing connection, it will immediately advertise its Handshake. 6 | * The remote node will respond with its Handshake. 7 | * No further communication is possible until both peers have exchanged their handshakes. 8 | * 9 | * @param peerSpec - general (declared) information about peer 10 | * @param time - handshake time 11 | */ 12 | case class Handshake(peerSpec: PeerSpec, time: Long) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE/Editor files 2 | .idea 3 | .ensime 4 | .ensime_cache/ 5 | scorex.yaml 6 | 7 | # Eclipse/Scala IDE files 8 | .classpath 9 | .project 10 | .settings 11 | .cache-main 12 | .cache-tests 13 | /bin/ 14 | 15 | # SBT's temporary files 16 | .history 17 | 18 | # scala build folders 19 | target 20 | 21 | # dotfiles 22 | .dockerignore 23 | .editorconfig 24 | 25 | # standalone docker 26 | Dockerfile 27 | 28 | # logs 29 | *.log 30 | 31 | # db files 32 | 33 | *.data 34 | 35 | 36 | # for temporary experiments 37 | sheet.scala 38 | examples/src/main/scala/examples/sigmastate/ 39 | examples/lib/ 40 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/ConnectionId.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network 2 | 3 | import java.net.InetSocketAddress 4 | 5 | /** 6 | * Wraps (remoteAddress, localAddress, direction) tuple, which allows to precisely identify peer. 7 | */ 8 | final case class ConnectionId(remoteAddress: InetSocketAddress, 9 | localAddress: InetSocketAddress, 10 | direction: ConnectionDirection) { 11 | 12 | override def toString: String = 13 | s"ConnectionId(remote=${remoteAddress.toString}, local=${localAddress.toString}, direction=$direction)" 14 | } 15 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/commons/curvepos.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import io.iohk.iodb.ByteArrayWrapper 4 | import scorex.core.{VersionTag, versionToBytes} 5 | import scorex.util.{ModifierId, idToBytes} 6 | import supertagged.TaggedType 7 | 8 | package object commons { 9 | 10 | object Value extends TaggedType[Long] 11 | object Nonce extends TaggedType[Long] 12 | 13 | type Value = Value.Type 14 | type Nonce = Nonce.Type 15 | 16 | def idToBAW(id: ModifierId): ByteArrayWrapper = ByteArrayWrapper(idToBytes(id)) 17 | 18 | def versionToBAW(id: VersionTag): ByteArrayWrapper = ByteArrayWrapper(versionToBytes(id)) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/validation/ValidationSettings.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.validation 2 | 3 | import scorex.core.validation.ValidationResult.Invalid 4 | 5 | /** 6 | * Specifies the strategy to by used (fail-fast or error-accumulative), a set of 7 | * activated validation rules with corresponding error messages 8 | */ 9 | abstract class ValidationSettings { 10 | val isFailFast: Boolean 11 | 12 | def getError(id: Short, e: Throwable): Invalid = getError(id, e.getMessage) 13 | 14 | def getError(id: Short, details: String): Invalid 15 | 16 | def getError(id: Short): Invalid = getError(id, "") 17 | 18 | def isActive(id: Short): Boolean 19 | } 20 | -------------------------------------------------------------------------------- /examples/src/main/resources/scripts/ips.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #seed="52.40.210.252" 4 | #ips=("52.88.43.127" "35.167.200.197" "35.163.27.226" "35.167.74.210" 5 | #"54.70.0.50" "52.89.211.42" "35.167.43.13" "35.167.184.105" "35.165.178.93" 6 | #"54.191.32.214" "52.26.217.230" "52.24.190.196" "34.209.224.39" "52.25.11.49" 7 | #"52.41.61.115" "35.163.92.164" "35.161.164.5" "52.40.158.125" "52.24.217.224") 8 | 9 | seed="34.210.250.171" 10 | ips=("52.41.112.126" "34.211.124.152" "52.11.218.194" "34.212.72.35" "34.208.192.219" "52.41.191.24" "35.160.179.146" 11 | "34.211.207.229" "52.43.112.221" "34.209.49.46" "35.166.124.70") 12 | 13 | allIps=("${ips[@]}") 14 | allIps+=("$seed") -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/swagger/SwaggerConfigRoute.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http.swagger 2 | 3 | import akka.actor.ActorRefFactory 4 | import akka.http.scaladsl.model.{ContentTypes, HttpEntity} 5 | import akka.http.scaladsl.server.Route 6 | import scorex.core.api.http.ApiRoute 7 | import scorex.core.settings.RESTApiSettings 8 | 9 | class SwaggerConfigRoute(swaggerConf: String, override val settings: RESTApiSettings)(implicit val context: ActorRefFactory) 10 | extends ApiRoute { 11 | 12 | override val route: Route = { 13 | (get & path("api-docs" / "swagger.conf")) { 14 | complete(HttpEntity(ContentTypes.`application/json`, swaggerConf)) 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/settings/SettingsReaders.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.settings 2 | 3 | import java.io.File 4 | import java.net.InetSocketAddress 5 | 6 | import com.typesafe.config.Config 7 | import net.ceedubs.ficus.readers.ValueReader 8 | 9 | trait SettingsReaders { 10 | 11 | implicit val fileReader: ValueReader[File] = (cfg, path) => new File(cfg.getString(path)) 12 | implicit val byteValueReader: ValueReader[Byte] = (cfg, path) => cfg.getInt(path).toByte 13 | implicit val inetSocketAddressReader: ValueReader[InetSocketAddress] = { (config: Config, path: String) => 14 | val split = config.getString(path).split(":") 15 | new InetSocketAddress(split(0), split(1).toInt) 16 | } 17 | } -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/NodeViewHolderSpec.scala: -------------------------------------------------------------------------------- 1 | package hybrid 2 | 3 | import examples.commons.{SimpleBoxTransaction, SimpleBoxTransactionMemPool} 4 | import examples.hybrid.blocks.HybridBlock 5 | import examples.hybrid.history.{HybridHistory, HybridSyncInfo} 6 | import examples.hybrid.state.HBoxStoredState 7 | import examples.hybrid.wallet.HBoxWallet 8 | import scorex.testkit.properties.NodeViewHolderTests 9 | 10 | class NodeViewHolderSpec extends NodeViewHolderTests[SimpleBoxTransaction, HybridBlock, HBoxStoredState, 11 | HybridSyncInfo, HybridHistory, SimpleBoxTransactionMemPool] 12 | with HybridGenerators { 13 | type VL = HBoxWallet 14 | } 15 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/SerializationTests.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit 2 | 3 | import org.scalacheck.Gen 4 | import org.scalatest.Assertion 5 | import org.scalatest.matchers.should.Matchers 6 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 7 | import scorex.core.serialization.ScorexSerializer 8 | 9 | trait SerializationTests extends ScalaCheckPropertyChecks with Matchers { 10 | def checkSerializationRoundtrip[A](generator: Gen[A], serializer: ScorexSerializer[A]): Assertion = { 11 | forAll(generator) { b: A => 12 | val recovered = serializer.parseBytes(serializer.toBytes(b)) 13 | serializer.toBytes(b) shouldEqual serializer.toBytes(recovered) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/properties/state/box/BoxStateTests.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.properties.state.box 2 | 3 | import scorex.core.PersistentNodeViewModifier 4 | import scorex.core.transaction.BoxTransaction 5 | import scorex.core.transaction.box.Box 6 | import scorex.core.transaction.box.proposition.Proposition 7 | import scorex.mid.state.BoxMinimalState 8 | import scorex.testkit.properties.state.StateTests 9 | 10 | 11 | trait BoxStateTests[P <: Proposition, 12 | B <: Box[P], 13 | TX <: BoxTransaction[P, B], 14 | PM <: PersistentNodeViewModifier, 15 | BST <: BoxMinimalState[P, B, TX, PM, BST]] extends StateTests[PM, BST]{ 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/Transaction.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction 2 | 3 | import scorex.core.{EphemerealNodeViewModifier, ModifierTypeId} 4 | import scorex.crypto.hash.Blake2b256 5 | import scorex.util.{ModifierId, bytesToId} 6 | 7 | 8 | /** 9 | * A transaction is an atomic state modifier 10 | */ 11 | trait Transaction extends EphemerealNodeViewModifier { 12 | override val modifierTypeId: ModifierTypeId = Transaction.ModifierTypeId 13 | 14 | val messageToSign: Array[Byte] 15 | 16 | override lazy val id: ModifierId = bytesToId(Blake2b256(messageToSign)) 17 | } 18 | 19 | 20 | object Transaction { 21 | val ModifierTypeId: scorex.core.ModifierTypeId = scorex.core.ModifierTypeId @@ 2.toByte 22 | } 23 | -------------------------------------------------------------------------------- /examples/src/main/resources/settings.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data/blockchain 3 | logDir = /tmp/scorex/data/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.1:9085" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "generatorNode1" 12 | bindAddress = "127.0.0.1:9084" 13 | knownPeers = [] 14 | agentName = "2-Hop" 15 | } 16 | 17 | 18 | 19 | miner { 20 | offlineGeneration = true 21 | targetBlockDelay = 5s 22 | blockGenerationDelay = 100ms 23 | rParamX10 = 8 24 | initialDifficulty = 1 25 | posAttachmentSize = 100 26 | } 27 | 28 | wallet { 29 | seed = "minerNode1" 30 | password = "cookies" 31 | walletDir = "/tmp/scorex/data/wallet" 32 | } 33 | } -------------------------------------------------------------------------------- /examples/src/main/resources/settings2.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data2/blockchain 3 | logDir = /tmp/scorex/data2/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.2:9089" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node2" 12 | bindAddress = "127.0.0.2:9088" 13 | knownPeers = ["127.0.0.1:9084"] 14 | agentName = "2-Hop" 15 | } 16 | 17 | miner { 18 | offlineGeneration = false 19 | targetBlockDelay = 5s 20 | blockGenerationDelay = 100ms 21 | rParamX10 = 8 22 | initialDifficulty = 1 23 | posAttachmentSize = 100 24 | } 25 | 26 | wallet { 27 | seed = "minerNode2" 28 | password = "cookies2" 29 | walletDir = "/tmp/scorex/data2/wallet" 30 | } 31 | } -------------------------------------------------------------------------------- /examples/src/main/resources/settings3.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data3/blockchain 3 | logDir = /tmp/scorex/data3/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.3:9093" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node3" 12 | bindAddress = "127.0.0.3:9092" 13 | knownPeers = ["127.0.0.1:9084"] 14 | agentName = "2-Hop" 15 | } 16 | 17 | miner { 18 | offlineGeneration = false 19 | targetBlockDelay = 100s 20 | blockGenerationDelay = 100ms 21 | rParamX10 = 8 22 | initialDifficulty = 1 23 | posAttachmentSize = 100 24 | } 25 | 26 | wallet { 27 | seed = "minerNode3" 28 | password = "cookies3" 29 | walletDir = "/tmp/scorex/data3/wallet" 30 | } 31 | } -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/NodeViewSynchronizerSpec.scala: -------------------------------------------------------------------------------- 1 | package hybrid 2 | 3 | import examples.commons.{SimpleBoxTransaction, SimpleBoxTransactionMemPool} 4 | import examples.hybrid.blocks.HybridBlock 5 | import examples.hybrid.history.{HybridHistory, HybridSyncInfo} 6 | import examples.hybrid.state.HBoxStoredState 7 | import scorex.testkit.properties.NodeViewSynchronizerTests 8 | 9 | class NodeViewSynchronizerSpec 10 | extends NodeViewSynchronizerTests[SimpleBoxTransaction, HybridBlock, HBoxStoredState, HybridSyncInfo, 11 | HybridHistory, SimpleBoxTransactionMemPool] with HybridGenerators { 12 | 13 | override lazy val memPool: SimpleBoxTransactionMemPool = SimpleBoxTransactionMemPool.emptyPool 14 | } 15 | -------------------------------------------------------------------------------- /examples/src/main/resources/settings8.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data7/blockchain 3 | logDir = /tmp/scorex/data7/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.8:9200" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node8" 12 | bindAddress = "127.0.0.8:9201" 13 | knownPeers = ["127.0.0.1:9084"] 14 | agentName = "2-Hop" 15 | } 16 | 17 | miner { 18 | offlineGeneration = false 19 | targetBlockDelay = 100s 20 | blockGenerationDelay = 100ms 21 | rParamX10 = 8 22 | initialDifficulty = 1 23 | posAttachmentSize = 100 24 | } 25 | 26 | wallet { 27 | seed = "node8seed" 28 | password = "cookies8" 29 | walletDir = "/tmp/scorex/data8/wallet" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/src/test/resources/settings.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex-test/data/blockchain 3 | logDir = /tmp/scorex-test/data/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.1:9085" 7 | apiKeyHash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node1" 12 | bindAddress = "127.0.0.1:9084" 13 | knownPeers = ["127.0.0.2:9088"] 14 | agentName = "2-Hop" 15 | } 16 | 17 | miner { 18 | offlineGeneration = true 19 | targetBlockDelay = 100s 20 | blockGenerationDelay = 100ms 21 | rParamX10 = 8 22 | initialDifficulty = 1 23 | posAttachmentSize = 100 24 | } 25 | 26 | wallet { 27 | seed = "genesisoo" 28 | password = "cookies" 29 | walletDir = "/tmp/scorex-test/data/wallet" 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/proof/Proof.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.proof 2 | 3 | import scorex.core.serialization.BytesSerializable 4 | import scorex.core.transaction.box.proposition.{ProofOfKnowledgeProposition, Proposition} 5 | import scorex.core.transaction.state.Secret 6 | 7 | /** 8 | * The most general abstraction of fact a prover can provide a non-interactive proof 9 | * to open a box or to modify an account 10 | * 11 | * A proof is non-interactive and thus serializable 12 | */ 13 | 14 | trait Proof[P <: Proposition] extends BytesSerializable { 15 | def isValid(proposition: P, message: Array[Byte]): Boolean 16 | } 17 | 18 | trait ProofOfKnowledge[S <: Secret, P <: ProofOfKnowledgeProposition[S]] extends Proof[P] 19 | -------------------------------------------------------------------------------- /examples/src/main/resources/settings4.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data4/blockchain 3 | logDir = /tmp/scorex/data4/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.4:9095" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node4" 12 | bindAddress = "127.0.0.3:9094" 13 | knownPeers = ["127.0.0.1:9084", "127.0.0.3:9092"] 14 | agentName = "2-Hop" 15 | } 16 | 17 | miner { 18 | offlineGeneration = false 19 | targetBlockDelay = 100s 20 | blockGenerationDelay = 100ms 21 | rParamX10 = 8 22 | initialDifficulty = 1 23 | posAttachmentSize = 100 24 | } 25 | 26 | wallet { 27 | seed = "node4seed" 28 | password = "cookies4" 29 | walletDir = "/tmp/scorex/data4/wallet" 30 | } 31 | } -------------------------------------------------------------------------------- /examples/src/main/resources/settings5.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data5/blockchain 3 | logDir = /tmp/scorex/data5/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.5:9097" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node5" 12 | bindAddress = "127.0.0.3:9096" 13 | knownPeers = ["127.0.0.1:9084", "127.0.0.3:9092"] 14 | agentName = "2-Hop" 15 | } 16 | 17 | miner { 18 | offlineGeneration = false 19 | targetBlockDelay = 100s 20 | blockGenerationDelay = 100ms 21 | rParamX10 = 8 22 | initialDifficulty = 1 23 | posAttachmentSize = 100 24 | } 25 | 26 | wallet { 27 | seed = "node5seed" 28 | password = "cookies5" 29 | walletDir = "/tmp/scorex/data5/wallet" 30 | } 31 | } -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/CustomModifierProducer.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import scorex.core.PersistentNodeViewModifier 4 | import scorex.core.consensus.{History, SyncInfo} 5 | import scorex.core.transaction.state.MinimalState 6 | 7 | sealed trait ModifierProducerTemplateItem 8 | 9 | case object SynInvalid extends ModifierProducerTemplateItem 10 | case object Valid extends ModifierProducerTemplateItem 11 | 12 | trait CustomModifierProducer[PM <: PersistentNodeViewModifier, ST <: MinimalState[PM, ST], 13 | SI <: SyncInfo, HT <: History[PM, SI, HT]] { 14 | 15 | def customModifiers(history: HT, 16 | state: ST, 17 | template: Seq[ModifierProducerTemplateItem]): Seq[PM] 18 | } 19 | -------------------------------------------------------------------------------- /examples/src/main/resources/settings9.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data7/blockchain 3 | logDir = /tmp/scorex/data7/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.9:9304" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node9" 12 | bindAddress = "127.0.0.9:9303" 13 | knownPeers = ["127.0.0.1:9084","127.0.0.7:9100"] 14 | agentName = "2-Hop" 15 | } 16 | 17 | miner { 18 | offlineGeneration = false 19 | targetBlockDelay = 100s 20 | blockGenerationDelay = 100ms 21 | rParamX10 = 8 22 | initialDifficulty = 1 23 | posAttachmentSize = 100 24 | } 25 | 26 | wallet { 27 | seed = "node9seed" 28 | password = "cookies9" 29 | walletDir = "/tmp/scorex/data9/wallet" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/build.sbt: -------------------------------------------------------------------------------- 1 | name := "scorex-examples" 2 | 3 | libraryDependencies ++= Seq( 4 | "org.scalactic" %% "scalactic" % "3.0.1" % "test", 5 | "org.scalatest" %% "scalatest" % "3.1.1" % "test", 6 | "org.scalacheck" %% "scalacheck" % "1.14.+" % "test", 7 | "org.scalatestplus" %% "scalatestplus-scalacheck" % "3.1.0.0-RC2" % Test, 8 | "org.scorexfoundation" %% "iodb" % "0.3.2", 9 | "com.typesafe.akka" %% "akka-testkit" % "2.6.10" % "test" 10 | ) 11 | 12 | mainClass in assembly := Some("examples.hybrid.HybridApp") 13 | 14 | assemblyJarName in assembly := "twinsChain.jar" 15 | 16 | parallelExecution in Test := true 17 | 18 | testForkedParallel in Test := true 19 | 20 | test in assembly := {} 21 | 22 | coverageExcludedPackages := "examples\\.hybrid\\.api\\.http.*" 23 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/BlockchainPerformance.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit 2 | 3 | import scorex.core.PersistentNodeViewModifier 4 | import scorex.core.consensus.{History, SyncInfo} 5 | import scorex.core.transaction.box.proposition.Proposition 6 | import scorex.core.transaction.state.MinimalState 7 | import scorex.core.transaction.{MemoryPool, Transaction} 8 | import scorex.testkit.properties.mempool.MempoolFilterPerformanceTest 9 | 10 | /** 11 | * Performance test for implementations 12 | */ 13 | trait BlockchainPerformance[ 14 | TX <: Transaction, 15 | PM <: PersistentNodeViewModifier, 16 | SI <: SyncInfo, 17 | MPool <: MemoryPool[TX, MPool], 18 | ST <: MinimalState[PM, ST], 19 | HT <: History[PM, SI, HT]] extends MempoolFilterPerformanceTest[TX, MPool] 20 | -------------------------------------------------------------------------------- /examples/src/main/resources/settings7.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data7/blockchain 3 | logDir = /tmp/scorex/data7/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.7:9101" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node7" 12 | bindAddress = "127.0.0.7:9100" 13 | knownPeers = ["127.0.0.1:9084"] 14 | agentName = "2-Hop" 15 | addedMaxDelay = 3s 16 | } 17 | 18 | miner { 19 | offlineGeneration = false 20 | targetBlockDelay = 100s 21 | blockGenerationDelay = 100ms 22 | rParamX10 = 8 23 | initialDifficulty = 1 24 | posAttachmentSize = 100 25 | } 26 | 27 | wallet { 28 | seed = "node7seed" 29 | password = "cookies7" 30 | walletDir = "/tmp/scorex/data7/wallet" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/src/test/scala/spv/serialization/SerializationTests.scala: -------------------------------------------------------------------------------- 1 | package spv.serialization 2 | 3 | import examples.spv.{Header, HeaderSerializer} 4 | import org.scalatest.matchers.should.Matchers 5 | import org.scalatest.propspec.AnyPropSpec 6 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 7 | import spv.SPVGenerators 8 | 9 | class SerializationTests extends AnyPropSpec 10 | with ScalaCheckPropertyChecks 11 | with Matchers 12 | with SPVGenerators { 13 | 14 | property("BlockHeader serialization") { 15 | forAll(blockHeaderGen) { b: Header => 16 | val serializer = HeaderSerializer 17 | val parsed = serializer.parseBytes(serializer.toBytes(b)) 18 | serializer.toBytes(b) shouldEqual serializer.toBytes(parsed) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/src/main/resources/settings6.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data6/blockchain 3 | logDir = /tmp/scorex/data6/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.6:9099" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node6" 12 | bindAddress = "127.0.0.6:9098" 13 | knownPeers = ["127.0.0.1:9084", "127.0.0.4:9094"] 14 | agentName = "2-Hop" 15 | addedMaxDelay = 5s 16 | } 17 | 18 | miner { 19 | offlineGeneration = false 20 | targetBlockDelay = 100s 21 | blockGenerationDelay = 0ms 22 | rParamX10 = 8 23 | initialDifficulty = 1 24 | posAttachmentSize = 100 25 | } 26 | 27 | wallet { 28 | seed = "node6seed" 29 | password = "cookies6" 30 | walletDir = "/tmp/scorex/data6/wallet" 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/scala/scorex/core/transaction/box/proposition/PublicKey25519PropositionSpecification.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.box.proposition 2 | 3 | import org.scalatest.matchers.should.Matchers 4 | import org.scalatest.propspec.AnyPropSpec 5 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 6 | import scorex.core.transaction.state.PrivateKey25519Companion 7 | 8 | class PublicKey25519PropositionSpecification extends AnyPropSpec 9 | with ScalaCheckPropertyChecks 10 | with Matchers { 11 | 12 | property("PublicKey25519Proposition generates valid addresses") { 13 | forAll() { (seed: Array[Byte]) => 14 | val pub = PrivateKey25519Companion.generateKeys(seed)._2 15 | PublicKey25519Proposition.validPubKey(pub.address).isSuccess shouldBe true 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /examples/src/main/resources/settings10.conf: -------------------------------------------------------------------------------- 1 | scorex { 2 | dataDir = /tmp/scorex/data10/blockchain 3 | logDir = /tmp/scorex/data10/log 4 | 5 | restApi { 6 | bindAddress = "127.0.0.10:9495" 7 | api-key-hash = "" 8 | } 9 | 10 | network { 11 | nodeName = "node10" 12 | bindAddress = "127.0.0.10:9494" 13 | knownPeers = ["127.0.0.1:9084","127.0.0.9:9303"] 14 | agentName = "2-Hop" 15 | addedMaxDelay = 2s 16 | } 17 | 18 | miner { 19 | offlineGeneration = false 20 | targetBlockDelay = 100s 21 | blockGenerationDelay = 100ms 22 | rParamX10 = 8 23 | initialDifficulty = 1 24 | posAttachmentSize = 100 25 | } 26 | 27 | wallet { 28 | seed = "node10seed" 29 | password = "cookies10" 30 | walletDir = "/tmp/scorex/data10/wallet" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/StoreGenerators.scala: -------------------------------------------------------------------------------- 1 | package hybrid 2 | 3 | import io.iohk.iodb.{LSMStore, QuickStore} 4 | import org.scalacheck.Gen 5 | import scorex.testkit.utils.FileUtils 6 | 7 | trait StoreGenerators extends FileUtils { 8 | 9 | protected val minKeepVersions = 10 10 | protected val maxKeepVersions = 20 11 | 12 | protected lazy val keepVersionsGen = Gen.chooseNum(minKeepVersions, maxKeepVersions) 13 | 14 | lazy val lsmStoreGen: Gen[LSMStore] = for { 15 | dir <- tempDirGen 16 | keepVersions <- keepVersionsGen 17 | } yield new LSMStore(dir, keepVersions = keepVersions) 18 | 19 | lazy val quickStoreGen: Gen[QuickStore] = for { 20 | dir <- tempDirGen 21 | keepVersions <- keepVersionsGen 22 | } yield new QuickStore(dir, keepVersions = keepVersions) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /examples/src/test/scala/spv/SPVGenerators.scala: -------------------------------------------------------------------------------- 1 | package spv 2 | 3 | import commons.ExamplesCommonGenerators 4 | import examples.spv._ 5 | import org.scalacheck.{Arbitrary, Gen} 6 | import scorex.util.ModifierId 7 | 8 | trait SPVGenerators extends ExamplesCommonGenerators { 9 | 10 | val blockHeaderGen: Gen[Header] = for { 11 | parentId: ModifierId <- modifierIdGen 12 | innerchainLinks: Seq[ModifierId] <- Gen.listOf(modifierIdGen).map(_.take(128)) 13 | txRoot: Array[Byte] <- genBytes(Constants.hashfn.DigestSize) 14 | stateRoot: Array[Byte] <- genBytes(Constants.hashfn.DigestSize) 15 | timestamp: Long <- Arbitrary.arbitrary[Long].map(t => Math.abs(t)) 16 | powNonce: Int <- Arbitrary.arbitrary[Int] 17 | } yield Header(parentId, innerchainLinks, stateRoot, txRoot, timestamp, powNonce) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/ArbitraryTransactionsCarryingModifierProducer.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import scorex.core.{PersistentNodeViewModifier, TransactionsCarryingPersistentNodeViewModifier} 4 | import scorex.core.transaction.{MemoryPool, Transaction} 5 | import scorex.core.transaction.box.proposition.Proposition 6 | 7 | /** 8 | * Produces a modifier with transactions, not necessary syntatically or semantically valid 9 | */ 10 | trait ArbitraryTransactionsCarryingModifierProducer[ 11 | TX <: Transaction, 12 | MPool <: MemoryPool[TX, MPool], 13 | PM <: PersistentNodeViewModifier, 14 | CTM <: PM with TransactionsCarryingPersistentNodeViewModifier[TX]] { 15 | 16 | def modifierWithTransactions(memoryPoolOpt: Option[MPool], customTransactionsOpt: Option[Seq[TX]]): CTM 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/peer/PeerDatabase.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network.peer 2 | 3 | import java.net.{InetAddress, InetSocketAddress} 4 | 5 | trait PeerDatabase { 6 | 7 | def get(peer: InetSocketAddress): Option[PeerInfo] 8 | 9 | def isEmpty: Boolean 10 | 11 | /** 12 | * Add peer to the database, or update it 13 | * @param peerInfo - peer record 14 | */ 15 | def addOrUpdateKnownPeer(peerInfo: PeerInfo): Unit 16 | 17 | def knownPeers: Map[InetSocketAddress, PeerInfo] 18 | 19 | def addToBlacklist(address: InetSocketAddress, penaltyType: PenaltyType): Unit 20 | 21 | def removeFromBlacklist(address: InetAddress): Unit 22 | 23 | def blacklistedPeers: Seq[InetAddress] 24 | 25 | def isBlacklisted(address: InetAddress): Boolean 26 | 27 | def remove(address: InetSocketAddress): Unit 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/PeerFeature.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network 2 | 3 | import scorex.core.serialization.{BytesSerializable, ScorexSerializer} 4 | 5 | /** 6 | * An abstract trait to describe peer capabilities. 7 | * During a handshake peers are sending list of their "features" to each other. 8 | * It is assumed that features are not changing when the node runs. 9 | * Maximum theoretical size of a serialized feature is 32,767 bytes. 10 | * However, handshake size limit is also to be considered 11 | * (for all the features to be sent during the handshake). 12 | */ 13 | trait PeerFeature extends BytesSerializable { 14 | override type M >: this.type <: PeerFeature 15 | val featureId: PeerFeature.Id 16 | } 17 | 18 | object PeerFeature { 19 | type Id = Byte 20 | type Serializers = Map[Id, ScorexSerializer[_ <: PeerFeature]] 21 | } -------------------------------------------------------------------------------- /examples/src/main/resources/scripts/twinschainRebuildAndStart.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my_dir="$(dirname "$0")" 4 | source "$my_dir/ips.sh" 5 | 6 | for ip in "${ips[@]}" 7 | do 8 | echo "$ip" 9 | ssh ubuntu@$ip "pkill -f twinsChain" 10 | ssh ubuntu@$ip "rm -f /tmp/l.log && rm -rf /home/ubuntu/data/data && rm -f /home/ubuntu/data/l.log && rm -rf /home/ubuntu/Scorex/examples/target/scala-2.12/twinsChain.jar && rm -rf /home/ubuntu/data/twinsChain.jar" 11 | 12 | ssh ubuntu@$ip "cd /home/ubuntu/Scorex && git pull origin master && sbt publishLocal && sbt \"project examples\" \"assembly\" && cp /home/ubuntu/Scorex/examples/target/scala-2.12/twinsChain.jar /home/ubuntu/data/twinsChain.jar" 13 | 14 | ssh ubuntu@$ip "nohup java -jar -XX:-UseGCOverheadLimit /home/ubuntu/data/twinsChain.jar /home/ubuntu/data/settings.json > /tmp/l.log 2>&1 &" 15 | sleep 30 16 | done 17 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/ApiDirectives.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import akka.http.scaladsl.server.{AuthorizationFailedRejection, Directive0} 4 | import scorex.core.settings.RESTApiSettings 5 | import scorex.core.utils.ScorexEncoding 6 | import scorex.crypto.hash.Blake2b256 7 | 8 | trait ApiDirectives extends CorsHandler with ScorexEncoding { 9 | val settings: RESTApiSettings 10 | val apiKeyHeaderName: String 11 | 12 | lazy val withAuth: Directive0 = optionalHeaderValueByName(apiKeyHeaderName).flatMap { 13 | case _ if settings.apiKeyHash.isEmpty => pass 14 | case None => reject(AuthorizationFailedRejection) 15 | case Some(key) => 16 | val keyHashStr: String = encoder.encode(Blake2b256(key)) 17 | if (settings.apiKeyHash.contains(keyHashStr)) pass 18 | else reject(AuthorizationFailedRejection) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/consensus/ModifierSemanticValidity.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.consensus 2 | 3 | sealed trait ModifierSemanticValidity { 4 | val code: Byte 5 | } 6 | 7 | object ModifierSemanticValidity { 8 | def restoreFromCode(code: Byte): ModifierSemanticValidity = 9 | if (code == Valid.code) Valid 10 | else if (code == Unknown.code) Unknown 11 | else if (code == Invalid.code) Invalid 12 | else Absent 13 | 14 | case object Absent extends ModifierSemanticValidity { 15 | override val code: Byte = 0 16 | } 17 | 18 | case object Unknown extends ModifierSemanticValidity { 19 | override val code: Byte = 1 20 | } 21 | 22 | case object Valid extends ModifierSemanticValidity { 23 | override val code: Byte = 2 24 | } 25 | 26 | case object Invalid extends ModifierSemanticValidity { 27 | override val code: Byte = 3 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/HybridTypes.scala: -------------------------------------------------------------------------------- 1 | package hybrid 2 | 3 | import examples.commons.{SimpleBoxTransaction, SimpleBoxTransactionMemPool} 4 | import examples.hybrid.HybridNodeViewHolder 5 | import examples.hybrid.blocks._ 6 | import examples.hybrid.history.{HybridHistory, HybridSyncInfo, HybridSyncInfoMessageSpec} 7 | import examples.hybrid.state.HBoxStoredState 8 | import scorex.core.consensus.SyncInfo 9 | import scorex.core.transaction.box.proposition.PublicKey25519Proposition 10 | 11 | trait HybridTypes { 12 | 13 | type P = PublicKey25519Proposition 14 | type TX = SimpleBoxTransaction 15 | type PM = HybridBlock 16 | type SI = SyncInfo 17 | type HSI = HybridSyncInfo 18 | type SIS = HybridSyncInfoMessageSpec.type 19 | 20 | type NODE = HybridNodeViewHolder 21 | type ST = HBoxStoredState 22 | type HT = HybridHistory 23 | type MP = SimpleBoxTransactionMemPool 24 | 25 | } -------------------------------------------------------------------------------- /ci/import_gpg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # setting up gpg2 for reading passphrase from parameters 3 | # via https://github.com/beautiful-scala/scalastyle/blob/master/.github/workflows/release.yml#L16 4 | # from https://github.com/olafurpg/sbt-ci-release/issues/95 5 | 6 | # setup gpg 7 | mkdir ~/.gnupg && chmod 700 ~/.gnupg 8 | echo use-agent >> ~/.gnupg/gpg.conf 9 | echo pinentry-mode loopback >> ~/.gnupg/gpg.conf 10 | echo allow-loopback-pinentry >> ~/.gnupg/gpg-agent.conf 11 | chmod 600 ~/.gnupg/* 12 | echo RELOADAGENT | gpg-connect-agent 13 | 14 | # decode key 15 | # private key should be previously exported with: 16 | # gpg --export-secret-keys [id] | base64 | pbcopy 17 | # and stored as github repository secret under the following name (see env var name below) 18 | printf "$GPG_SIGNING_KEY" | base64 --decode > ~/.gnupg/private.key 19 | 20 | # import key 21 | gpg --no-tty --batch --yes --import ~/.gnupg/private.key 22 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/trimchain/modifiers/UtxoSnapshot.scala: -------------------------------------------------------------------------------- 1 | package examples.trimchain.modifiers 2 | 3 | import examples.trimchain.utxo.PersistentAuthenticatedUtxo 4 | import scorex.core.ModifierTypeId 5 | import scorex.core.serialization.ScorexSerializer 6 | import scorex.util.ModifierId 7 | 8 | class UtxoSnapshot(override val parentId: ModifierId, 9 | header: BlockHeader, 10 | utxo: PersistentAuthenticatedUtxo) extends TModifier { 11 | 12 | override val modifierTypeId: ModifierTypeId = TModifier.UtxoSnapshot 13 | 14 | //todo: check statically or dynamically output size 15 | override def id: ModifierId = header.id 16 | 17 | //todo: for Dmitry: implement header + utxo root printing 18 | 19 | override type M = UtxoSnapshot 20 | 21 | //todo: for Dmitry: implement: dump all the boxes 22 | override def serializer: ScorexSerializer[M] = ??? 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/wallet/Vault.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.wallet 2 | 3 | import scorex.core.transaction.Transaction 4 | import scorex.core.{PersistentNodeViewModifier, VersionTag} 5 | 6 | import scala.util.Try 7 | 8 | /** 9 | * Abstract interface for Vault, a storage for node-specific information 10 | */ 11 | 12 | trait Vault[TX <: Transaction, PMOD <: PersistentNodeViewModifier, V <: Vault[TX, PMOD, V]] extends VaultReader { 13 | self: V => 14 | 15 | def scanOffchain(tx: TX): V 16 | 17 | def scanOffchain(txs: Seq[TX]): V 18 | 19 | def scanPersistent(modifier: PMOD): V 20 | 21 | def scanPersistent(modifiers: Option[PMOD]): V = modifiers.foldLeft(this) { case (v, mod) => 22 | v.scanPersistent(mod) 23 | } 24 | 25 | def rollback(to: VersionTag): Try[V] 26 | 27 | /** 28 | * @return read-only copy of this state 29 | */ 30 | def getReader: VaultReader = this 31 | 32 | } -------------------------------------------------------------------------------- /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 "https://repo.typesafe.com/typesafe/releases/" 6 | 7 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") 8 | 9 | libraryDependencies += "com.typesafe" % "config" % "1.3.0" 10 | 11 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.6") 12 | 13 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.0") 14 | 15 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") 16 | 17 | addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.2") 18 | 19 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.0") 20 | 21 | addSbtPlugin("com.github.sbt" % "sbt-findbugs" % "2.0.0") 22 | 23 | addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.2.1") 24 | 25 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.8") 26 | 27 | addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") 28 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/hybrid/util/Cancellable.scala: -------------------------------------------------------------------------------- 1 | package examples.hybrid.util 2 | 3 | import scala.concurrent.{Future, Promise} 4 | import scala.util.Success 5 | 6 | 7 | trait CancellableStatus { 8 | def isCancelled: Boolean 9 | 10 | def nonCancelled: Boolean = !isCancelled 11 | } 12 | 13 | trait Cancellable { 14 | def cancel(): Boolean 15 | 16 | def status: CancellableStatus 17 | } 18 | 19 | object Cancellable { 20 | def apply(): Cancellable = new Cancellable { 21 | val p = Promise[Unit]() 22 | 23 | override def cancel(): Boolean = p.tryComplete(Success(())) 24 | 25 | val status: CancellableStatus = new CancellableStatus { 26 | override def isCancelled: Boolean = p.future.value.isDefined 27 | } 28 | } 29 | 30 | def run()(cont: CancellableStatus => Future[Unit]): Cancellable = { 31 | val cancellable = Cancellable() 32 | cont(cancellable.status) // run continuation feeding status 33 | cancellable 34 | } 35 | } -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/validation/SemanticBlockValidatorSpecification.scala: -------------------------------------------------------------------------------- 1 | package hybrid.validation 2 | 3 | import examples.hybrid.validation.SemanticBlockValidator 4 | import hybrid.HybridGenerators 5 | import org.scalatest.matchers.should.Matchers 6 | import org.scalatest.propspec.AnyPropSpec 7 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 8 | import scorex.crypto.hash.Blake2b256 9 | 10 | 11 | class SemanticBlockValidatorSpecification extends AnyPropSpec 12 | with ScalaCheckPropertyChecks 13 | with Matchers 14 | with HybridGenerators { 15 | 16 | private val validator = new SemanticBlockValidator(Blake2b256) 17 | 18 | property("Generated PoS block semantics is valid") { 19 | forAll(posBlockGen) { posBlock => 20 | validator.validate(posBlock).get 21 | } 22 | } 23 | 24 | property("Generated PoW block semantics is valid") { 25 | forAll(powBlockGen) { powBlock => 26 | validator.validate(powBlock).get 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/ApiRouteWithFullView.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import akka.actor.ActorRef 4 | import akka.http.scaladsl.server.Route 5 | import akka.pattern.ask 6 | import scorex.core.NodeViewHolder.CurrentView 7 | 8 | import scala.concurrent.Future 9 | 10 | trait ApiRouteWithFullView[HIS, MS, VL, MP] extends ApiRoute { 11 | 12 | import scorex.core.NodeViewHolder.ReceivableMessages.GetDataFromCurrentView 13 | 14 | val nodeViewHolderRef: ActorRef 15 | 16 | def withNodeView(f: CurrentView[HIS, MS, VL, MP] => Route): Route = onSuccess(viewAsync())(f) 17 | 18 | //TODO Data received in current view is mutable and may be inconsistent. 19 | //Better get concrete data you need from NodeViewHolder 20 | protected def viewAsync(): Future[CurrentView[HIS, MS, VL, MP]] = { 21 | def f(v: CurrentView[HIS, MS, VL, MP]) = v 22 | (nodeViewHolderRef ? GetDataFromCurrentView(f)) 23 | .mapTo[CurrentView[HIS, MS, VL, MP]] 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/SemanticallyValidTransactionsCarryingModifier.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import scorex.core.{PersistentNodeViewModifier, TransactionsCarryingPersistentNodeViewModifier} 4 | import scorex.core.transaction.Transaction 5 | import scorex.core.transaction.box.proposition.Proposition 6 | import scorex.core.transaction.state.MinimalState 7 | 8 | 9 | trait SemanticallyValidTransactionsCarryingModifier[TX <: Transaction, 10 | PM <: PersistentNodeViewModifier, 11 | CTM <: PM with TransactionsCarryingPersistentNodeViewModifier[TX], 12 | ST <: MinimalState[PM, ST]] { 13 | 14 | def semanticallyValidModifier(state: ST): CTM 15 | def genValidTransactionPair(state: ST): Seq[TX] 16 | def semanticallyValidModifierWithCustomTransactions(state: ST, transactions: Seq[TX]): CTM 17 | } 18 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/properties/state/StateTests.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.properties.state 2 | 3 | import org.scalacheck.Gen 4 | import org.scalatest.matchers.should.Matchers 5 | import org.scalatest.propspec.AnyPropSpec 6 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 7 | import scorex.core.PersistentNodeViewModifier 8 | import scorex.core.transaction.state.MinimalState 9 | import scorex.testkit.TestkitHelpers 10 | import scorex.testkit.generators.{SemanticallyValidModifierProducer, SemanticallyInvalidModifierProducer, CoreGenerators} 11 | 12 | trait StateTests[PM <: PersistentNodeViewModifier, ST <: MinimalState[PM, ST]] 13 | extends AnyPropSpec 14 | with ScalaCheckPropertyChecks 15 | with Matchers 16 | with CoreGenerators 17 | with TestkitHelpers 18 | with SemanticallyValidModifierProducer[PM, ST] 19 | with SemanticallyInvalidModifierProducer[PM, ST] { 20 | 21 | val checksToMake = 10 22 | 23 | val stateGen: Gen[ST] 24 | } 25 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/generators/AllModifierProducers.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.generators 2 | 3 | import scorex.core.consensus.{History, SyncInfo} 4 | import scorex.core.{PersistentNodeViewModifier, TransactionsCarryingPersistentNodeViewModifier} 5 | import scorex.core.transaction.{MemoryPool, Transaction} 6 | import scorex.core.transaction.state.MinimalState 7 | 8 | 9 | trait AllModifierProducers[ 10 | TX <: Transaction, 11 | MPool <: MemoryPool[TX, MPool], 12 | PM <: PersistentNodeViewModifier, 13 | CTM <: PM with TransactionsCarryingPersistentNodeViewModifier[TX], 14 | ST <: MinimalState[PM, ST], 15 | SI <: SyncInfo, HT <: History[PM, SI, HT]] 16 | extends SemanticallyValidModifierProducer[PM, ST] 17 | with SyntacticallyTargetedModifierProducer[PM, SI, HT] 18 | with ArbitraryTransactionsCarryingModifierProducer[TX, MPool, PM, CTM] 19 | with TotallyValidModifierProducer[PM, ST, SI, HT] 20 | with SemanticallyValidTransactionsCarryingModifier[TX, PM, CTM, ST] 21 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/MempoolReader.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction 2 | 3 | import scorex.core.consensus.ContainsModifiers 4 | import scorex.core.NodeViewComponent 5 | import scorex.util.ModifierId 6 | 7 | /** 8 | * Unconfirmed transactions pool 9 | * 10 | * @tparam TX -type of transaction the pool contains 11 | */ 12 | trait MempoolReader[TX <: Transaction] extends NodeViewComponent with ContainsModifiers[TX] { 13 | 14 | //getters 15 | override def modifierById(modifierId: ModifierId): Option[TX] 16 | 17 | @deprecated("use modifierById instead", "2018-08-14") 18 | def getById(id: ModifierId): Option[TX] = modifierById(id) 19 | 20 | def contains(id: ModifierId): Boolean 21 | 22 | //get ids from Seq, not presenting in mempool 23 | def notIn(ids: Seq[ModifierId]): Seq[ModifierId] = ids.filter(id => !contains(id)) 24 | 25 | def getAll(ids: Seq[ModifierId]): Seq[TX] 26 | 27 | def size: Int 28 | 29 | def take(limit: Int): Iterable[TX] 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/serialization/SerializerRegistry.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.serialization 2 | 3 | import io.circe.{Encoder, Json} 4 | 5 | import scala.reflect.{ClassTag, classTag} 6 | 7 | 8 | object SerializerRegistry { 9 | 10 | case class SerializerRecord[T: ClassTag](enc: Encoder[T]) { 11 | val ct: ClassTag[T] = classTag[T] 12 | } 13 | 14 | def apply(records: Seq[SerializerRecord[_]]): SerializerRegistry = new SerializerRegistry(records) 15 | } 16 | 17 | sealed class SerializerRegistry(ss: Seq[SerializerRegistry.SerializerRecord[_]]) { 18 | 19 | private val evTypeAndEncoder = ss.map { sr => 20 | (sr.ct.runtimeClass, sr.enc)}.toMap[Class[_], Encoder[_]] 21 | 22 | 23 | def toJson[C](key: Class[_], c: C): Either[Throwable, Json] = { 24 | evTypeAndEncoder.get(key) match { 25 | case Some(e) => Right(e.asInstanceOf[Encoder[C]].apply(c)) 26 | case None => Left(new RuntimeException(s"Circe encoder is not registered for $c")) 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/consensus/ContainsModifiers.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.consensus 2 | 3 | import scorex.core.NodeViewModifier 4 | import scorex.util.ModifierId 5 | 6 | /** 7 | * Object that contains modifiers of type `MOD` 8 | */ 9 | trait ContainsModifiers[MOD <: NodeViewModifier] { 10 | 11 | /** 12 | * 13 | * @param persistentModifier - modifier to check 14 | * @return `true` if this object contains this modifier, `false` otherwise 15 | */ 16 | def contains(persistentModifier: MOD): Boolean = contains(persistentModifier.id) 17 | 18 | /** 19 | * 20 | * @param id - modifier's id 21 | * @return `true` if this object contains modifier with specified id, `false` otherwise 22 | */ 23 | def contains(id: ModifierId): Boolean = modifierById(id).isDefined 24 | 25 | /** 26 | * @param modifierId - modifier id to get 27 | * @return modifier of type MOD with id == modifierId if exist 28 | */ 29 | def modifierById(modifierId: ModifierId): Option[MOD] 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/peer/PenaltyType.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network.peer 2 | 3 | /** 4 | * A trait describing all possible types of the network participant misbehavior. 5 | * `penaltyScore` - a number defining how bad concrete kind of misbehavior is, 6 | * `isPermanent` - a flag defining whether a penalty is permanent. 7 | */ 8 | sealed trait PenaltyType { 9 | val penaltyScore: Int 10 | val isPermanent: Boolean = false 11 | } 12 | 13 | object PenaltyType { 14 | 15 | case object NonDeliveryPenalty extends PenaltyType { 16 | override val penaltyScore: Int = 2 17 | } 18 | 19 | case object MisbehaviorPenalty extends PenaltyType { 20 | override val penaltyScore: Int = 10 21 | } 22 | 23 | case object SpamPenalty extends PenaltyType { 24 | override val penaltyScore: Int = 25 25 | } 26 | 27 | case object PermanentPenalty extends PenaltyType { 28 | override val penaltyScore: Int = 1000000000 29 | override val isPermanent: Boolean = true 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/ApiRoute.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import akka.actor.ActorRefFactory 4 | import akka.http.scaladsl.server.Route 5 | import akka.http.scaladsl.unmarshalling.PredefinedFromEntityUnmarshallers 6 | import akka.util.Timeout 7 | import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport 8 | import io.circe.Printer 9 | import scorex.core.utils.ActorHelper 10 | import scorex.util.ScorexLogging 11 | 12 | import scala.language.implicitConversions 13 | 14 | trait ApiRoute 15 | extends ApiDirectives 16 | with ActorHelper 17 | with FailFastCirceSupport 18 | with PredefinedFromEntityUnmarshallers 19 | with ScorexLogging { 20 | 21 | def context: ActorRefFactory 22 | def route: Route 23 | 24 | //TODO: should we move it to the settings? 25 | override val apiKeyHeaderName: String = "api_key" 26 | 27 | implicit val printer: Printer = Printer.spaces2.copy(dropNullValues = true) 28 | implicit lazy val timeout: Timeout = Timeout(settings.timeout) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /testkit/src/main/scala/scorex/testkit/properties/WalletSecretsTest.scala: -------------------------------------------------------------------------------- 1 | package scorex.testkit.properties 2 | 3 | import org.scalatest.matchers.should.Matchers 4 | import org.scalatest.propspec.AnyPropSpec 5 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 6 | import scorex.core.PersistentNodeViewModifier 7 | import scorex.core.transaction.Transaction 8 | import scorex.core.transaction.box.proposition.Proposition 9 | import scorex.core.transaction.wallet.BoxWallet 10 | 11 | trait WalletSecretsTest[P <: Proposition, TX <: Transaction, PM <: PersistentNodeViewModifier] 12 | extends AnyPropSpec 13 | with ScalaCheckPropertyChecks 14 | with Matchers { 15 | 16 | val wallet: BoxWallet[P, TX, PM, _] 17 | 18 | property("Wallet should contain secrets for all it's public propositions") { 19 | val publicImages = wallet.publicKeys 20 | assert(publicImages.nonEmpty, "please provide wallet with at least one secret") 21 | publicImages.foreach(pi => wallet.secretByPublicImage(pi).isDefined shouldBe true) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/utils/NetworkUtils.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.utils 2 | 3 | import java.net.{Inet4Address, InetSocketAddress, NetworkInterface} 4 | import scala.collection.JavaConverters._ 5 | 6 | object NetworkUtils { 7 | 8 | def getListenAddresses(bindAddress: InetSocketAddress): Set[InetSocketAddress] = { 9 | if (bindAddress.getAddress.isAnyLocalAddress || bindAddress.getAddress.isLoopbackAddress) { 10 | NetworkInterface.getNetworkInterfaces.asScala 11 | .flatMap(_.getInetAddresses.asScala) 12 | .collect { case a: Inet4Address => a} 13 | .map(a => new InetSocketAddress(a, bindAddress.getPort)) 14 | .toSet 15 | } else { 16 | Set(bindAddress) 17 | } 18 | } 19 | 20 | def isSelf(peerAddress: InetSocketAddress, 21 | bindAddress: InetSocketAddress, 22 | externalNodeAddress: Option[InetSocketAddress]): Boolean = { 23 | NetworkUtils.getListenAddresses(bindAddress).contains(peerAddress) || 24 | externalNodeAddress.contains(peerAddress) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/message/MessageSpec.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network.message 2 | 3 | import scorex.core.app.Version 4 | import scorex.core.serialization.ScorexSerializer 5 | 6 | /** 7 | * Base trait for app p2p messages in the network 8 | */ 9 | trait MessageSpec[Content] extends ScorexSerializer[Content] { 10 | 11 | /** 12 | * The p2p protocol version in which this message type first appeared 13 | */ 14 | val protocolVersion: Version 15 | 16 | /** 17 | * Code which identifies what message type is contained in the payload 18 | */ 19 | 20 | val messageCode: Message.MessageCode 21 | 22 | /** 23 | * Name of this message type. For debug purposes only. 24 | */ 25 | val messageName: String 26 | 27 | override def toString: String = s"MessageSpec($messageCode: $messageName)" 28 | } 29 | 30 | /** 31 | * P2p messages, that where implemented since the beginning. 32 | */ 33 | trait MessageSpecV1[Content] extends MessageSpec[Content] { 34 | 35 | override val protocolVersion: Version = Version.initial 36 | 37 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/account/PublicKeyNoncedBox.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.account 2 | 3 | import com.google.common.primitives.Longs 4 | import scorex.core.transaction.box.Box 5 | import scorex.core.transaction.box.proposition.PublicKey25519Proposition 6 | import scorex.crypto.authds.ADKey 7 | import scorex.crypto.hash.Blake2b256 8 | 9 | trait PublicKeyNoncedBox[PKP <: PublicKey25519Proposition] extends Box[PKP] { 10 | val nonce: Long 11 | 12 | lazy val id: ADKey = PublicKeyNoncedBox.idFromBox(proposition, nonce) 13 | 14 | lazy val publicKey: PKP = proposition 15 | 16 | override def equals(obj: Any): Boolean = obj match { 17 | case acc: PublicKeyNoncedBox[PKP] => java.util.Arrays.equals(acc.id, this.id) && acc.value == this.value 18 | case _ => false 19 | } 20 | 21 | override def hashCode(): Int = proposition.hashCode() 22 | } 23 | 24 | object PublicKeyNoncedBox { 25 | def idFromBox[PKP <: PublicKey25519Proposition](prop: PKP, nonce: Long): ADKey = 26 | ADKey @@ Blake2b256(prop.pubKeyBytes ++ Longs.toByteArray(nonce)) 27 | } -------------------------------------------------------------------------------- /examples/src/main/scala/examples/hybrid/validation/SemanticBlockValidator.scala: -------------------------------------------------------------------------------- 1 | package examples.hybrid.validation 2 | 3 | import examples.hybrid.blocks.{HybridBlock, PosBlock, PowBlock} 4 | import scorex.core.block.BlockValidator 5 | import scorex.crypto.hash.{CryptographicHash, Digest} 6 | 7 | import scala.util.Try 8 | 9 | class SemanticBlockValidator(hash: CryptographicHash[_ <: Digest]) extends BlockValidator[HybridBlock] { 10 | 11 | def validate(block: HybridBlock): Try[Unit] = Try { 12 | block match { 13 | case powBlock: PowBlock => 14 | require(powBlock.brothersCount >= 0) 15 | require(powBlock.timestamp >= 0) 16 | 17 | //check brothers data 18 | require(powBlock.brothers.size == powBlock.brothersCount) 19 | if (powBlock.brothersCount > 0) { 20 | require(java.util.Arrays.equals(hash(powBlock.brotherBytes), powBlock.brothersHash)) 21 | } 22 | case posBlock: PosBlock => 23 | require(posBlock.timestamp >= 0) 24 | require(PosBlock.signatureValid(posBlock)) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/ModifiersStatus.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network 2 | 3 | sealed trait ModifiersStatus 4 | 5 | object ModifiersStatus { 6 | 7 | /** 8 | * This modifier is unknown to our node 9 | */ 10 | case object Unknown extends ModifiersStatus 11 | 12 | /** 13 | * Our node have requested this modifier from other peers but did not received it yet. 14 | */ 15 | case object Requested extends ModifiersStatus 16 | 17 | /** 18 | * Our node have received this modifier from other peers but did not applied yet. 19 | * The modifier might be in ModifiersCache or on the way to it 20 | */ 21 | case object Received extends ModifiersStatus 22 | 23 | /** 24 | * This modifier is already on NodeViewHoder - applied to History if it is PersistentModifier or 25 | * in MemPool if it is Ephemereal modifier. 26 | */ 27 | case object Held extends ModifiersStatus 28 | 29 | /** 30 | * This modifier is permanently invalid - never try to download it 31 | */ 32 | case object Invalid extends ModifiersStatus 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/scala/scorex/network/NetworkTests.scala: -------------------------------------------------------------------------------- 1 | package scorex.network 2 | 3 | import java.net.InetSocketAddress 4 | 5 | import org.scalatest.flatspec.AnyFlatSpec 6 | import org.scalatest.matchers.should.Matchers 7 | import scorex.core.app.Version 8 | import scorex.core.network.PeerSpec 9 | import scorex.core.network.peer.PeerInfo 10 | import scorex.core.settings.ScorexSettings 11 | import scorex.core.utils.{NetworkTimeProvider, TimeProvider} 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | 14 | class NetworkTests extends AnyFlatSpec with Matchers { 15 | 16 | protected val settings: ScorexSettings = ScorexSettings.read(None) 17 | protected val timeProvider: NetworkTimeProvider = new NetworkTimeProvider(settings.ntp) 18 | 19 | protected def currentTime(): TimeProvider.Time = timeProvider.time() 20 | 21 | protected def getPeerInfo(address: InetSocketAddress, nameOpt: Option[String] = None): PeerInfo = { 22 | val data = PeerSpec("full node", Version.last, nameOpt.getOrElse(address.toString), Some(address), Seq()) 23 | PeerInfo(data, currentTime(), None) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/MemoryPool.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction 2 | 3 | import scala.util.Try 4 | 5 | /** 6 | * Unconfirmed transactions pool 7 | * 8 | * @tparam TX -type of transaction the pool contains 9 | */ 10 | trait MemoryPool[TX <: Transaction, M <: MemoryPool[TX, M]] extends MempoolReader[TX] { 11 | 12 | /** 13 | * Method to put a transaction into the memory pool. Validation of tha transactions against 14 | * the state is done in NodeVieHolder. This put() method can check whether a transaction is valid 15 | * @param tx 16 | * @return Success(updatedPool), if transaction successfully added to the pool, Failure(_) otherwise 17 | */ 18 | def put(tx: TX): Try[M] 19 | 20 | def put(txs: Iterable[TX]): Try[M] 21 | 22 | def putWithoutCheck(txs: Iterable[TX]): M 23 | 24 | def remove(tx: TX): M 25 | 26 | def filter(txs: Seq[TX]): M = filter(t => !txs.exists(_.id == t.id)) 27 | 28 | def filter(condition: TX => Boolean): M 29 | 30 | /** 31 | * @return read-only copy of this history 32 | */ 33 | def getReader: MempoolReader[TX] = this 34 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/ConnectedPeer.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network 2 | 3 | import akka.actor.ActorRef 4 | import scorex.core.network.peer.PeerInfo 5 | 6 | /** 7 | * Peer connected to our node 8 | * 9 | * @param connectionId - connection address 10 | * @param handlerRef - reference to PeerConnectionHandler that is responsible for communication with this peer 11 | * @param lastMessage - timestamp of last received message 12 | * @param peerInfo - information about this peer. May be None if peer is connected, but is not handshaked yet 13 | */ 14 | case class ConnectedPeer(connectionId: ConnectionId, 15 | handlerRef: ActorRef, 16 | var lastMessage: Long, 17 | peerInfo: Option[PeerInfo]) { 18 | 19 | override def hashCode(): Int = connectionId.hashCode() 20 | 21 | override def equals(obj: Any): Boolean = obj match { 22 | case that: ConnectedPeer => this.connectionId.remoteAddress == that.connectionId.remoteAddress 23 | case _ => false 24 | } 25 | 26 | override def toString: String = s"ConnectedPeer($connectionId)" 27 | } 28 | -------------------------------------------------------------------------------- /doc/peer/main.blg: -------------------------------------------------------------------------------- 1 | This is BibTeX, Version 0.99d (TeX Live 2016) 2 | Capacity: max_strings=35307, hash_size=35307, hash_prime=30011 3 | The top-level auxiliary file: main.aux 4 | The style file: alpha.bst 5 | I found no \citation commands---while reading file main.aux 6 | Database file #1: bibliography.bib 7 | You've used 0 entries, 8 | 2543 wiz_defined-function locations, 9 | 558 strings with 4439 characters, 10 | and the built_in function-call counts, 24 in all, are: 11 | = -- 0 12 | > -- 0 13 | < -- 0 14 | + -- 0 15 | - -- 0 16 | * -- 2 17 | := -- 10 18 | add.period$ -- 0 19 | call.type$ -- 0 20 | change.case$ -- 0 21 | chr.to.int$ -- 0 22 | cite$ -- 0 23 | duplicate$ -- 0 24 | empty$ -- 1 25 | format.name$ -- 0 26 | if$ -- 2 27 | int.to.chr$ -- 1 28 | int.to.str$ -- 0 29 | missing$ -- 0 30 | newline$ -- 3 31 | num.names$ -- 0 32 | pop$ -- 0 33 | preamble$ -- 1 34 | purify$ -- 0 35 | quote$ -- 0 36 | skip$ -- 2 37 | stack$ -- 0 38 | substring$ -- 0 39 | swap$ -- 0 40 | text.length$ -- 0 41 | text.prefix$ -- 0 42 | top$ -- 0 43 | type$ -- 0 44 | warning$ -- 0 45 | while$ -- 0 46 | width$ -- 0 47 | write$ -- 2 48 | (There was 1 error message) 49 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/CompositeHttpService.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.model.StatusCodes 5 | import akka.http.scaladsl.server.Route 6 | import akka.http.scaladsl.server.directives.RouteDirectives 7 | import scorex.core.api.http.swagger.SwaggerConfigRoute 8 | import scorex.core.settings.RESTApiSettings 9 | 10 | case class CompositeHttpService(system: ActorSystem, routes: Seq[ApiRoute], settings: RESTApiSettings, swaggerConf: String) 11 | extends CorsHandler { 12 | 13 | implicit val actorSystem: ActorSystem = system 14 | 15 | val swaggerService: SwaggerConfigRoute = new SwaggerConfigRoute(swaggerConf: String, settings: RESTApiSettings) 16 | 17 | val redirectToSwagger: Route = path("" | "/") { 18 | redirect("/swagger", StatusCodes.PermanentRedirect) 19 | } 20 | 21 | val compositeRoute: Route = 22 | routes.map(_.route).reduceOption(_ ~ _).getOrElse(RouteDirectives.reject) ~ 23 | swaggerService.route ~ 24 | path("swagger")(getFromResource("swagger-ui/index.html")) ~ 25 | getFromResourceDirectory("swagger-ui") ~ 26 | redirectToSwagger 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/serialization/ScorexSerializer.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.serialization 2 | 3 | import java.nio.ByteBuffer 4 | 5 | import akka.util.ByteString 6 | import scorex.util.ByteArrayBuilder 7 | import scorex.util.serialization._ 8 | 9 | import scala.util.Try 10 | 11 | trait ScorexSerializer[T] extends Serializer[T, T, Reader, Writer] { 12 | 13 | def toByteString(obj: T): ByteString = { 14 | val writer = new VLQByteStringWriter() 15 | serialize(obj, writer) 16 | writer.result() 17 | } 18 | 19 | def parseByteString(byteString: ByteString): T = { 20 | val reader = new VLQByteStringReader(byteString) 21 | parse(reader) 22 | } 23 | 24 | def parseByteStringTry(byteString: ByteString): Try[T] = { 25 | Try(parseByteString(byteString)) 26 | } 27 | 28 | def toBytes(obj: T): Array[Byte] = { 29 | val writer = new VLQByteBufferWriter(new ByteArrayBuilder()) 30 | serialize(obj, writer) 31 | writer.result().toBytes 32 | } 33 | 34 | def parseBytes(bytes: Array[Byte]): T = { 35 | val reader = new VLQByteBufferReader(ByteBuffer.wrap(bytes)) 36 | parse(reader) 37 | } 38 | 39 | def parseBytesTry(bytes: Array[Byte]): Try[T] = { 40 | Try(parseBytes(bytes)) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/state/BoxStateChanges.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.state 2 | 3 | import scorex.core.transaction.box.Box 4 | import scorex.core.transaction.box.proposition.Proposition 5 | import scorex.core.utils.ScorexEncoding 6 | import scorex.crypto.authds._ 7 | 8 | abstract class BoxStateChangeOperation[P <: Proposition, BX <: Box[P]] 9 | 10 | case class Removal[P <: Proposition, BX <: Box[P]](boxId: ADKey) extends BoxStateChangeOperation[P, BX] with ScorexEncoding { 11 | override def toString: String = s"Removal(id: ${encoder.encode(boxId)})" 12 | } 13 | 14 | case class Insertion[P <: Proposition, BX <: Box[P]](box: BX) extends BoxStateChangeOperation[P, BX] 15 | 16 | case class BoxStateChanges[P <: Proposition, BX <: Box[P]](operations: Seq[BoxStateChangeOperation[P, BX]]) { 17 | lazy val toAppend: Seq[Insertion[P, BX]] = operations.filter { op => 18 | op match { 19 | case _: Insertion[P, BX] => true 20 | case _ => false 21 | } 22 | }.asInstanceOf[Seq[Insertion[P, BX]]] 23 | 24 | lazy val toRemove: Seq[Removal[P, BX]] = operations.filter { op => 25 | op match { 26 | case _: Removal[P, BX] => true 27 | case _ => false 28 | } 29 | }.asInstanceOf[Seq[Removal[P, BX]]] 30 | } 31 | -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/state/HBoxStoredStateSpecification.scala: -------------------------------------------------------------------------------- 1 | package hybrid.state 2 | 3 | import examples.hybrid.blocks.HybridBlock 4 | import examples.hybrid.state.HBoxStoredState 5 | import hybrid.HybridGenerators 6 | import org.scalatest.matchers.should.Matchers 7 | import org.scalatest.propspec.AnyPropSpec 8 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 9 | import scorex.core.transaction.state.{Insertion, Removal} 10 | import scorex.testkit.properties.state.StateTests 11 | 12 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial")) 13 | class HBoxStoredStateSpecification extends AnyPropSpec 14 | with ScalaCheckPropertyChecks 15 | with Matchers 16 | with HybridGenerators 17 | with StateTests[HybridBlock, HBoxStoredState] { 18 | 19 | property("added boxes are always there") { 20 | forAll(stateGen){state => 21 | var st = state 22 | check(checksToMake) { _ => 23 | val c = stateChangesGenerator(state).sample.get 24 | st = st.applyChanges(c, versionTagGen.sample.get).get 25 | c.toAppend.foreach { case Insertion(b) => 26 | st.closedBox(b.id) shouldBe Some(b) 27 | } 28 | c.toRemove.foreach { case Removal(bid) => 29 | st.closedBox(bid) shouldBe None 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish a release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish_release: 9 | name: Publish release to Sonatype 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup Java and Scala 15 | uses: olafurpg/setup-scala@v10 16 | with: 17 | java-version: adopt@1.8 18 | 19 | - name: Cache sbt 20 | uses: actions/cache@v2 21 | with: 22 | path: | 23 | ~/.sbt 24 | ~/.ivy2/cache 25 | ~/.coursier/cache/v1 26 | ~/.cache/coursier/v1 27 | ~/AppData/Local/Coursier/Cache/v1 28 | ~/Library/Caches/Coursier/v1 29 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 30 | 31 | - name: Import GPG key 32 | run: ci/import_gpg.sh 33 | env: 34 | GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} 35 | 36 | - name: Publish release 37 | run: sbt +publishSigned sonatypeBundleRelease 38 | env: 39 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 40 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 41 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 42 | -------------------------------------------------------------------------------- /examples/src/main/resources/scripts/twinschainRerun.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my_dir="$(dirname "$0")" 4 | source "$my_dir/ips.sh" 5 | 6 | for ip in "${ips[@]}" 7 | do 8 | echo "ubuntu@$ip" 9 | ssh ubuntu@$ip "pkill -f twinsChain" 10 | ssh ubuntu@$ip "rm -f /tmp/l.log && rm -rf /home/ubuntu/data/data && rm -f /home/ubuntu/data/l.log" 11 | 12 | # ssh ubuntu@$ip "sed -i '/52.40.210.252:9084/c\ \"34.210.250.171:9084\"' /home/ubuntu/data/settings.json" 13 | ssh ubuntu@$ip "sed -i '/34.210.250.171:9084/c\ \"34.210.250.171:9084\"' /home/ubuntu/data/settings.json" 14 | # ssh ubuntu@$ip "sed -i '/\"node1\"/c\ \"name\": \"genesisNode\",' /home/ubuntu/data/settings.json" 15 | # ssh ubuntu@$ip "sed -i '/\"RparamX10\"/c\ \"RparamX10\": 2,' /home/ubuntu/data/settings.json" 16 | # ssh ubuntu@$ip "sed -i '/\"addedMaxDelay\"/c\ \"addedMaxDelay\": 0,' /home/ubuntu/data/settings.json" 17 | # ssh ubuntu@$ip "sed -i '/\"targetBlockDelayMillis\"/c\ \"targetBlockDelayMillis\": 60000,' /home/ubuntu/data/settings.json" 18 | 19 | ssh ubuntu@$ip "nohup java -jar -XX:-UseGCOverheadLimit /home/ubuntu/data/twinsChain.jar /home/ubuntu/data/settings.json > /tmp/l.log 2>&1 &" 20 | # sleep 30 21 | curl -s -X GET --header 'Accept: application/json' 'http://'$ip':9085/debug/info' | grep height 22 | done 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/peer/LocalAddressPeerFeature.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network.peer 2 | 3 | import java.net.{InetAddress, InetSocketAddress} 4 | 5 | import scorex.core.network.PeerFeature 6 | import scorex.core.network.PeerFeature.Id 7 | import scorex.util.serialization._ 8 | import scorex.core.serialization.ScorexSerializer 9 | import scorex.util.Extensions._ 10 | 11 | case class LocalAddressPeerFeature(address: InetSocketAddress) extends PeerFeature { 12 | override type M = LocalAddressPeerFeature 13 | override val featureId: Id = LocalAddressPeerFeature.featureId 14 | 15 | override def serializer: LocalAddressPeerFeatureSerializer.type = LocalAddressPeerFeatureSerializer 16 | } 17 | 18 | object LocalAddressPeerFeature { 19 | val featureId: Id = 2: Byte 20 | } 21 | 22 | object LocalAddressPeerFeatureSerializer extends ScorexSerializer[LocalAddressPeerFeature] { 23 | 24 | private val AddressLength = 4 25 | 26 | override def serialize(obj: LocalAddressPeerFeature, w: Writer): Unit = { 27 | w.putBytes(obj.address.getAddress.getAddress) 28 | w.putUInt(obj.address.getPort) 29 | } 30 | 31 | override def parse(r: Reader): LocalAddressPeerFeature = { 32 | val fa = r.getBytes(AddressLength) 33 | val port = r.getUInt().toIntExact 34 | LocalAddressPeerFeature(new InetSocketAddress(InetAddress.getByAddress(fa), port)) 35 | } 36 | } -------------------------------------------------------------------------------- /examples/src/main/scala/examples/hybrid/validation/ParentBlockValidator.scala: -------------------------------------------------------------------------------- 1 | package examples.hybrid.validation 2 | 3 | import examples.hybrid.blocks.{HybridBlock, PosBlock, PowBlock} 4 | import examples.hybrid.history.HistoryStorage 5 | import scorex.core.block.BlockValidator 6 | import scorex.core.utils.ScorexEncoding 7 | 8 | import scala.util.Try 9 | 10 | class ParentBlockValidator(storage: HistoryStorage) 11 | extends BlockValidator[HybridBlock] with ScorexEncoding { 12 | 13 | def validate(block: HybridBlock): Try[Unit] = Try { 14 | block match { 15 | case powBlock: PowBlock => if (!storage.isGenesis(powBlock)) { 16 | //check PoW parent id ??? 17 | require(storage.modifierById(powBlock.parentId).isDefined, s"Parent ${encoder.encodeId(powBlock.parentId)} missed") 18 | //check referenced PoS block exists as well 19 | // TODO: review me - .get 20 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial")) 21 | val posBlock = storage.modifierById(powBlock.prevPosId).get 22 | 23 | //check referenced PoS block points to parent PoW block 24 | require(posBlock.parentId == posBlock.parentId, "ref rule broken") 25 | } 26 | case posBlock: PosBlock => 27 | //check PoW block exists 28 | require(storage.modifierById(posBlock.parentId).isDefined) 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/UtilsApiRoute.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import java.security.SecureRandom 4 | 5 | import akka.actor.ActorRefFactory 6 | import akka.http.scaladsl.server.Route 7 | import io.circe.Json 8 | import scorex.core.settings.RESTApiSettings 9 | import scorex.core.utils.ScorexEncoding 10 | import scorex.crypto.hash.Blake2b256 11 | 12 | 13 | case class UtilsApiRoute(override val settings: RESTApiSettings)(implicit val context: ActorRefFactory) 14 | extends ApiRoute with ScorexEncoding { 15 | 16 | private val SeedSize = 32 17 | 18 | private def seed(length: Int): String = { 19 | val seed = new Array[Byte](length) 20 | new SecureRandom().nextBytes(seed) //seed mutated here! 21 | encoder.encode(seed) 22 | } 23 | 24 | override val route: Route = pathPrefix("utils") { 25 | seedRoute ~ length ~ hashBlake2b 26 | } 27 | 28 | def seedRoute: Route = (get & path("seed")) { 29 | ApiResponse(seed(SeedSize)) 30 | } 31 | 32 | def length: Route = (get & path("seed" / IntNumber)) { length => 33 | ApiResponse(seed(length)) 34 | } 35 | 36 | def hashBlake2b: Route = { 37 | (post & path("hash" / "blake2b") & entity(as[Json])) { json => 38 | json.asString match { 39 | case Some(message) => ApiResponse(encoder.encode(Blake2b256(message))) 40 | case None => ApiError.BadRequest 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/peer/PeerInfo.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network.peer 2 | 3 | import java.net.InetSocketAddress 4 | 5 | import scorex.core.app.Version 6 | import scorex.core.network.{ConnectionDirection, PeerSpec} 7 | 8 | /** 9 | * Information about peer to be stored in PeerDatabase 10 | * 11 | * @param peerSpec - general information about the peer 12 | * @param lastHandshake - timestamp when last handshake was done 13 | * @param connectionType - type of connection (Incoming/Outgoing) established to this peer if any 14 | */ 15 | case class PeerInfo(peerSpec: PeerSpec, 16 | lastHandshake: Long, 17 | connectionType: Option[ConnectionDirection] = None) 18 | 19 | /** 20 | * Information about P2P layer status 21 | * 22 | * @param lastIncomingMessage - timestamp of last received message from any peer 23 | * @param currentNetworkTime - current network time 24 | */ 25 | case class PeersStatus(lastIncomingMessage: Long, currentNetworkTime: Long) 26 | 27 | object PeerInfo { 28 | 29 | /** 30 | * Create peer info from address only, when we don't know other fields 31 | * (e.g. we got this information from config or from API) 32 | */ 33 | def fromAddress(address: InetSocketAddress): PeerInfo = { 34 | val peerSpec = PeerSpec("unknown", Version.initial, s"unknown-$address", Some(address), Seq()) 35 | PeerInfo(peerSpec, 0L, None) 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/primitives/PrivateKey25519Suite.scala: -------------------------------------------------------------------------------- 1 | package hybrid.primitives 2 | 3 | import examples.commons.PublicKey25519NoncedBox 4 | import hybrid.HybridGenerators 5 | import org.scalatest.matchers.should.Matchers 6 | import org.scalatest.propspec.AnyPropSpec 7 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 8 | import scorex.core.transaction.state.PrivateKey25519Companion 9 | 10 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial")) 11 | class PrivateKey25519Suite extends AnyPropSpec 12 | with ScalaCheckPropertyChecks 13 | with Matchers 14 | with HybridGenerators { 15 | 16 | property("Public key is deterministic") { 17 | forAll(genBytes(32), minSuccessful(1000)){ seed => 18 | val pair1 = PrivateKey25519Companion.generateKeys(seed) 19 | val pair2 = PrivateKey25519Companion.generateKeys(seed) 20 | 21 | pair1._1.privKeyBytes shouldBe pair2._1.privKeyBytes 22 | pair1._1.publicKeyBytes shouldBe pair2._1.publicKeyBytes 23 | pair1._1.publicKeyBytes shouldBe pair2._2.pubKeyBytes 24 | pair1._1.publicKeyBytes shouldBe pair2._2.bytes 25 | } 26 | } 27 | 28 | property("PublicKey25519NoncedBox right owner") { 29 | forAll(key25519Gen, nonceGen, valueGen, minSuccessful(1000)){ case(keyPair, nonce, amount) => 30 | val box = PublicKey25519NoncedBox(keyPair._2, nonce, amount) 31 | PrivateKey25519Companion.owns(keyPair._1, box) shouldBe true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/SendingStrategy.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.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] = { 11 | if (peers.nonEmpty) { 12 | Seq(peers(Random.nextInt(peers.length))) 13 | } else { 14 | Seq.empty 15 | } 16 | } 17 | } 18 | 19 | case object Broadcast extends SendingStrategy { 20 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = peers 21 | } 22 | 23 | case class BroadcastExceptOf(exceptOf: Seq[ConnectedPeer]) extends SendingStrategy { 24 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = 25 | peers.filterNot(exceptOf.contains) 26 | } 27 | 28 | case class SendToPeer(chosenPeer: ConnectedPeer) extends SendingStrategy { 29 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = Seq(chosenPeer) 30 | } 31 | 32 | case class SendToPeers(chosenPeers: Seq[ConnectedPeer]) extends SendingStrategy { 33 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = chosenPeers 34 | } 35 | 36 | case class SendToRandomFromChosen(chosenPeers: Seq[ConnectedPeer]) extends SendingStrategy { 37 | override def choose(peers: Seq[ConnectedPeer]): Seq[ConnectedPeer] = 38 | Seq(chosenPeers(Random.nextInt(chosenPeers.length))) 39 | } 40 | -------------------------------------------------------------------------------- /examples/src/main/scala/examples/spv/simulation/SPVSimulator.scala: -------------------------------------------------------------------------------- 1 | package examples.spv.simulation 2 | 3 | import examples.spv.{Header, KMZProofSerializer, SpvAlgos} 4 | import scorex.core.transaction.state.PrivateKey25519Companion 5 | import scorex.crypto.hash.Blake2b256 6 | 7 | object SPVSimulator extends App with SimulatorFuctions { 8 | 9 | private val Height = 500000 10 | private val Difficulty = BigInt(1) 11 | private val stateRoot = Blake2b256("") 12 | private val minerKeys = PrivateKey25519Companion.generateKeys(stateRoot) 13 | 14 | private val genesis = genGenesisHeader(stateRoot, minerKeys._2) 15 | val st: Long = System.currentTimeMillis() 16 | private val headerChain: Seq[Header] = genChain(Height, Difficulty, stateRoot, IndexedSeq(genesis)) 17 | 18 | val lastBlock: Option[Header] = headerChain.lastOption 19 | var minDiff: BigInt = Difficulty 20 | 21 | private val k = 6 22 | 23 | println(s"Chain of length $Height, k=$k") 24 | println("m,proofLength,blockNum,uniqueBlockNum") 25 | 26 | Seq(6, 15, 30, 50, 100, 127) foreach { m => 27 | val proof = SpvAlgos.constructKMZProof(m, k, headerChain).get 28 | proof.valid.get 29 | val blocks:Seq[Header] = proof.suffix ++ proof.prefixProofs.flatten 30 | val blockNum = blocks.length 31 | val uniqueBlockNum = blocks.map(_.encodedId).toSet.size 32 | println( m + "," + KMZProofSerializer.toBytes(proof).length + "," + blockNum + "," + uniqueBlockNum) 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/state/MinimalState.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.state 2 | 3 | import scorex.core.transaction._ 4 | import scorex.core.transaction.box.proposition.Proposition 5 | import scorex.core.{PersistentNodeViewModifier, VersionTag} 6 | 7 | import scala.util.Try 8 | 9 | /** 10 | * Abstract functional interface of state which is a result of a sequential blocks applying 11 | */ 12 | trait MinimalState[M <: PersistentNodeViewModifier, MS <: MinimalState[M, MS]] extends StateReader { 13 | self: MS => 14 | 15 | def applyModifier(mod: M): Try[MS] 16 | 17 | def rollbackTo(version: VersionTag): Try[MS] 18 | 19 | /** 20 | * @return read-only copy of this state 21 | */ 22 | def getReader: StateReader = this 23 | 24 | } 25 | 26 | 27 | trait StateFeature 28 | 29 | trait TransactionValidation[TX <: Transaction] extends StateFeature { 30 | def isValid(tx: TX): Boolean = validate(tx).isSuccess 31 | 32 | def filterValid(txs: Seq[TX]): Seq[TX] = txs.filter(isValid) 33 | 34 | def validate(tx: TX): Try[Unit] 35 | } 36 | 37 | trait ModifierValidation[M <: PersistentNodeViewModifier] extends StateFeature { 38 | def validate(mod: M): Try[Unit] 39 | } 40 | 41 | trait BalanceSheet[P <: Proposition] extends StateFeature { 42 | def balance(id: P, height: Option[Int] = None): Long 43 | } 44 | 45 | trait AccountTransactionsHistory[P <: Proposition, TX <: Transaction] extends StateFeature { 46 | def accountTransactions(id: P): Array[TX] 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/CorsHandler.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import akka.http.scaladsl.marshalling.ToResponseMarshallable.apply 4 | import akka.http.scaladsl.model.HttpMethods._ 5 | import akka.http.scaladsl.model.headers._ 6 | import akka.http.scaladsl.model.{HttpResponse, StatusCodes} 7 | import akka.http.scaladsl.server.Directive.addByNameNullaryApply 8 | import akka.http.scaladsl.server.{Directive0, Directives, Route} 9 | 10 | /** 11 | * Provides tools for handling a Cross-Origin Resource Sharing spec workflow 12 | * (including `OPTIONS` pre-flight requests). 13 | */ 14 | trait CorsHandler extends Directives { 15 | 16 | private val corsResponseHeaders: List[ModeledHeader] = List[ModeledHeader]( 17 | `Access-Control-Allow-Origin`.*, 18 | `Access-Control-Allow-Credentials`(true), 19 | `Access-Control-Allow-Headers`("Authorization", "Content-Type", "X-Requested-With", "api_key") 20 | ) 21 | 22 | def corsHandler(r: Route): Route = addAccessControlHeaders { 23 | preflightRequestHandler ~ r 24 | } 25 | 26 | def addCorsHeaders(response: HttpResponse): HttpResponse = 27 | response.withHeaders(corsResponseHeaders) 28 | 29 | private def addAccessControlHeaders: Directive0 = 30 | respondWithHeaders(corsResponseHeaders) 31 | 32 | private def preflightRequestHandler: Route = options { 33 | complete { 34 | HttpResponse(StatusCodes.OK) 35 | .withHeaders(`Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE)) 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /examples/src/test/scala/trimchain/serialization/SerializationTests.scala: -------------------------------------------------------------------------------- 1 | package trimchain.serialization 2 | 3 | import examples.trimchain.core.{Ticket, TicketSerializer} 4 | import examples.trimchain.modifiers.{BlockHeader, BlockHeaderSerializer, TBlock, TBlockSerializer} 5 | import org.scalatest.matchers.should.Matchers 6 | import org.scalatest.propspec.AnyPropSpec 7 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks 8 | import trimchain.TrimchainGenerators 9 | 10 | class SerializationTests extends AnyPropSpec 11 | with ScalaCheckPropertyChecks 12 | with Matchers 13 | with TrimchainGenerators { 14 | 15 | property("Ticket serialization") { 16 | forAll(ticketGen) { b: Ticket => 17 | val serializer = TicketSerializer 18 | val parsed = serializer.parseBytes(serializer.toBytes(b)) 19 | b.minerKey shouldEqual parsed.minerKey 20 | serializer.toBytes(b) shouldEqual serializer.toBytes(parsed) 21 | } 22 | } 23 | 24 | property("BlockHeader serialization") { 25 | forAll(blockHeaderGen) { b: BlockHeader => 26 | val serializer = BlockHeaderSerializer 27 | val parsed = serializer.parseBytes(serializer.toBytes(b)) 28 | serializer.toBytes(b) shouldEqual serializer.toBytes(parsed) 29 | } 30 | } 31 | 32 | property("TBlock serialization") { 33 | forAll(TBlockGen) { b: TBlock => 34 | val serializer = TBlockSerializer 35 | val parsed = serializer.parseBytes(serializer.toBytes(b)) 36 | serializer.toBytes(b) shouldEqual serializer.toBytes(parsed) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/HybridSanity.scala: -------------------------------------------------------------------------------- 1 | package hybrid 2 | 3 | import examples.commons.{PublicKey25519NoncedBox, SimpleBoxTransaction, SimpleBoxTransactionMemPool} 4 | import examples.hybrid.blocks.{HybridBlock, PosBlock} 5 | import examples.hybrid.history.{HybridHistory, HybridSyncInfo} 6 | import examples.hybrid.state.HBoxStoredState 7 | import examples.hybrid.wallet.HBoxWallet 8 | import org.scalacheck.Gen 9 | import scorex.core.transaction.box.proposition.PublicKey25519Proposition 10 | import scorex.testkit.{BlockchainPerformance, BlockchainSanity} 11 | 12 | 13 | class HybridSanity extends BlockchainSanity[PublicKey25519Proposition, 14 | SimpleBoxTransaction, 15 | HybridBlock, 16 | PosBlock, 17 | HybridSyncInfo, 18 | PublicKey25519NoncedBox, 19 | SimpleBoxTransactionMemPool, 20 | HBoxStoredState, 21 | HybridHistory] with BlockchainPerformance[SimpleBoxTransaction, HybridBlock, HybridSyncInfo, 22 | SimpleBoxTransactionMemPool, HBoxStoredState, HybridHistory] 23 | with HybridGenerators { 24 | 25 | private val walletSettings = originalSettings.walletSettings.copy(seed = "p") 26 | 27 | //Node view components 28 | override lazy val memPool: SimpleBoxTransactionMemPool = SimpleBoxTransactionMemPool.emptyPool 29 | override lazy val memPoolGenerator: Gen[SimpleBoxTransactionMemPool] = emptyMemPoolGen 30 | override lazy val transactionGenerator: Gen[TX] = simpleBoxTransactionGen 31 | override lazy val wallet = (0 until 100).foldLeft(HBoxWallet.readOrGenerate(walletSettings))((w, _) => w.generateNewSecret()) 32 | } -------------------------------------------------------------------------------- /src/main/scala/scorex/util/serialization/VLQByteStringWriter.scala: -------------------------------------------------------------------------------- 1 | package scorex.util.serialization 2 | 3 | import java.nio.ByteOrder 4 | 5 | import akka.util.ByteString 6 | 7 | class VLQByteStringWriter extends VLQWriter { 8 | override type CH = ByteString 9 | private implicit val byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN 10 | 11 | @inline 12 | override def newWriter(): Writer.Aux[CH] = { 13 | new VLQByteStringWriter() 14 | } 15 | 16 | private val builder = ByteString.createBuilder 17 | 18 | @inline 19 | override def length(): Int = builder.length 20 | 21 | @inline 22 | override def putChunk(byteString: ByteString): this.type = { 23 | builder.append(byteString) 24 | this 25 | } 26 | 27 | @inline 28 | override def put(x: Byte): this.type = { 29 | builder.putByte(x) 30 | this 31 | } 32 | 33 | @inline 34 | override def putBoolean(x: Boolean): this.type = { 35 | val byte: Byte = if (x) 0x01 else 0x00 36 | builder.putByte(byte) 37 | this 38 | } 39 | 40 | override def putBytes(xs: Array[Byte], 41 | offset: Int, 42 | length: Int): VLQByteStringWriter.this.type = { 43 | builder.putBytes(xs, offset, length) 44 | this 45 | } 46 | 47 | @inline 48 | override def putBytes(xs: Array[Byte]): this.type = { 49 | builder.putBytes(xs) 50 | this 51 | } 52 | 53 | @inline 54 | override def result(): ByteString = { 55 | builder.result() 56 | } 57 | 58 | @inline override def toBytes: Array[Byte] = { 59 | builder.result().toArray 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/src/test/scala/trimchain/TrimchainGenerators.scala: -------------------------------------------------------------------------------- 1 | package trimchain 2 | 3 | import commons.ExamplesCommonGenerators 4 | import examples.commons.SimpleBoxTransaction 5 | import examples.trimchain.core._ 6 | import examples.trimchain.modifiers.{BlockHeader, TBlock} 7 | import org.scalacheck.{Arbitrary, Gen} 8 | import scorex.util.ModifierId 9 | import scorex.crypto.authds.SerializedAdProof 10 | 11 | trait TrimchainGenerators extends ExamplesCommonGenerators { 12 | 13 | lazy val stateRootGen: Gen[StateRoot] = genBytes(Constants.StateRootLength).map(r => StateRoot @@ r) 14 | lazy val txRootGen: Gen[TransactionsRoot] = genBytes(Constants.TxRootLength).map(r => TransactionsRoot @@ r) 15 | 16 | val ticketGen: Gen[Ticket] = for { 17 | minerKey: Array[Byte] <- genBytes(TicketSerializer.MinerKeySize) 18 | partialProofs: Seq[SerializedAdProof] <- Gen.nonEmptyListOf(nonEmptyBytesGen).map(b => SerializedAdProof @@ b) 19 | } yield Ticket(minerKey, partialProofs) 20 | 21 | val blockHeaderGen: Gen[BlockHeader] = for { 22 | parentId: ModifierId <- modifierIdGen 23 | stateRoot: StateRoot <- stateRootGen 24 | txRoot: TransactionsRoot <- txRootGen 25 | powNonce: Long <- Arbitrary.arbitrary[Long] 26 | ticket: Ticket <- ticketGen 27 | } yield BlockHeader(parentId, stateRoot, txRoot, ticket, powNonce) 28 | 29 | val TBlockGen: Gen[TBlock] = for { 30 | header: BlockHeader <- blockHeaderGen 31 | body: Seq[SimpleBoxTransaction] <- Gen.listOf(simpleBoxTransactionGen) 32 | timestamp: Long <- Arbitrary.arbitrary[Long] 33 | } yield TBlock(header, body, timestamp) 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/api/http/ApiRejectionHandler.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.api.http 2 | 3 | import akka.http.scaladsl.server._ 4 | 5 | object ApiRejectionHandler { 6 | 7 | implicit val rejectionHandler: RejectionHandler = RejectionHandler.newBuilder() 8 | .handleAll[SchemeRejection] { rejections => 9 | val schemes = rejections.map(_.supported).mkString(", ") 10 | ApiError.BadRequest(s"Uri scheme not allowed, supported schemes: $schemes") 11 | } 12 | .handle { 13 | case AuthorizationFailedRejection => 14 | ApiError.Forbidden("The supplied authentication is not authorized to access this resource") 15 | } 16 | .handle { 17 | case MalformedRequestContentRejection(msg, _) => 18 | ApiError.BadRequest("The request content was malformed:\n" + msg) 19 | } 20 | .handle { 21 | case InvalidOriginRejection(allowedOrigins) => 22 | ApiError.Forbidden(s"Allowed `Origin` header values: ${allowedOrigins.mkString(", ")}") 23 | } 24 | .handle { 25 | case MissingQueryParamRejection(paramName) => 26 | ApiError.NotExists(s"Request is missing required query parameter '$paramName'") 27 | } 28 | .handle { 29 | case RequestEntityExpectedRejection => 30 | ApiError.BadRequest("Request entity expected but not supplied") 31 | } 32 | .handle { case ValidationRejection(msg, _) => ApiError.BadRequest(msg) } 33 | .handle { case x => ApiError.InternalError(s"Unhandled rejection: $x") } 34 | .handleNotFound { ApiError.NotExists("The requested resource could not be found.") } 35 | .result() 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/network/peer/SessionIdPeerFeature.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.network.peer 2 | 3 | import scorex.core.network.PeerFeature 4 | import scorex.core.network.PeerFeature.Id 5 | import scorex.core.network.message.Message 6 | import scorex.util.serialization._ 7 | import scorex.core.serialization.ScorexSerializer 8 | 9 | /** 10 | * This peer feature allows to more reliably detect connections to self node and connections from other networks 11 | * 12 | * @param networkMagic network magic bytes (taken from settings) 13 | * @param sessionId randomly generated 64-bit session identifier 14 | */ 15 | case class SessionIdPeerFeature(networkMagic: Array[Byte], 16 | sessionId: Long = scala.util.Random.nextLong()) extends PeerFeature { 17 | 18 | override type M = SessionIdPeerFeature 19 | override val featureId: Id = SessionIdPeerFeature.featureId 20 | 21 | override def serializer: SessionIdPeerFeatureSerializer.type = SessionIdPeerFeatureSerializer 22 | 23 | } 24 | 25 | object SessionIdPeerFeature { 26 | 27 | val featureId: Id = 3: Byte 28 | 29 | } 30 | 31 | object SessionIdPeerFeatureSerializer extends ScorexSerializer[SessionIdPeerFeature] { 32 | 33 | override def serialize(obj: SessionIdPeerFeature, w: Writer): Unit = { 34 | w.putBytes(obj.networkMagic) 35 | w.putLong(obj.sessionId) 36 | } 37 | 38 | override def parse(r: Reader): SessionIdPeerFeature = { 39 | val networkMagic = r.getBytes(Message.MagicLength) 40 | val sessionId = r.getLong() 41 | SessionIdPeerFeature(networkMagic, sessionId) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/app/Version.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.app 2 | 3 | import scorex.core.serialization.{BytesSerializable, ScorexSerializer} 4 | import scorex.util.serialization._ 5 | 6 | /** 7 | * Version of p2p protocol. Node can only process messages of it's version or lower. 8 | */ 9 | case class Version(firstDigit: Byte, secondDigit: Byte, thirdDigit: Byte) extends BytesSerializable with Ordered[Version] { 10 | override type M = Version 11 | 12 | override def serializer: ScorexSerializer[Version] = ApplicationVersionSerializer 13 | 14 | override def compare(that: Version): Int = if (this.firstDigit != that.firstDigit) { 15 | this.firstDigit - that.firstDigit 16 | } else if (this.secondDigit != that.secondDigit) { 17 | this.secondDigit - that.secondDigit 18 | } else { 19 | this.thirdDigit - that.thirdDigit 20 | } 21 | } 22 | 23 | object Version { 24 | def apply(v: String): Version = { 25 | val splitted = v.split("\\.") 26 | Version(splitted(0).toByte, splitted(1).toByte, splitted(2).toByte) 27 | } 28 | 29 | val initial: Version = Version(0, 0, 1) 30 | val last: Version = Version(0, 0, 1) 31 | 32 | } 33 | 34 | object ApplicationVersionSerializer extends ScorexSerializer[Version] { 35 | val SerializedVersionLength: Int = 3 36 | 37 | 38 | override def serialize(obj: Version, w: Writer): Unit = { 39 | w.put(obj.firstDigit) 40 | w.put(obj.secondDigit) 41 | w.put(obj.thirdDigit) 42 | } 43 | 44 | override def parse(r: Reader): Version = { 45 | Version( 46 | r.getByte(), 47 | r.getByte(), 48 | r.getByte() 49 | ) 50 | } 51 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest] 17 | scala: [2.12.12] 18 | java: [adopt@1.8] 19 | runs-on: ${{ matrix.os }} 20 | env: 21 | HAS_SECRETS: ${{ secrets.SONATYPE_PASSWORD != '' }} 22 | steps: 23 | - name: Checkout current branch (full) 24 | uses: actions/checkout@v2 25 | with: 26 | fetch-depth: 0 27 | 28 | - name: Setup Java and Scala 29 | uses: olafurpg/setup-scala@v10 30 | with: 31 | java-version: ${{ matrix.java }} 32 | 33 | - name: Cache sbt 34 | uses: actions/cache@v2 35 | with: 36 | path: | 37 | ~/.sbt 38 | ~/.ivy2/cache 39 | ~/.coursier/cache/v1 40 | ~/.cache/coursier/v1 41 | ~/AppData/Local/Coursier/Cache/v1 42 | ~/Library/Caches/Coursier/v1 43 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 44 | 45 | - name: Run tests 46 | run: sbt "project examples" clean coverage test && sbt test 47 | 48 | - name: Publish a snapshot 49 | if: env.HAS_SECRETS == 'true' 50 | run: sbt ++${{ matrix.scala }} publish 51 | env: 52 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 53 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 54 | -------------------------------------------------------------------------------- /examples/src/test/scala/hybrid/StateGenerators.scala: -------------------------------------------------------------------------------- 1 | package hybrid 2 | 3 | import examples.commons.{Nonce, PublicKey25519NoncedBox, Value} 4 | import examples.hybrid.state.HBoxStoredState 5 | import io.iohk.iodb.LSMStore 6 | import org.scalacheck.Gen 7 | import scorex.core.transaction.box.proposition.PublicKey25519Proposition 8 | import scorex.core.transaction.state.{BoxStateChanges, Insertion, PrivateKey25519Companion} 9 | import scorex.testkit.generators.CoreGenerators 10 | 11 | import scala.util.Random 12 | 13 | trait StateGenerators extends StoreGenerators { this: HybridGenerators with CoreGenerators with StoreGenerators => 14 | 15 | private val valueSeed = 5000000 16 | 17 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial")) 18 | val stateGen: Gen[HBoxStoredState] = 19 | for { 20 | dir <- tempDirGen 21 | keepVersions <- Gen.chooseNum(5, 20) 22 | } yield { 23 | def randomBox(): PublicKey25519NoncedBox = { 24 | val value: Value = Value @@ (Random.nextInt(valueSeed) + valueSeed).toLong 25 | val nonce: Nonce = Nonce @@ Random.nextLong() 26 | val keyPair = privKey(value) 27 | PublicKey25519NoncedBox(keyPair._2, nonce, value) 28 | .ensuring(box => PrivateKey25519Companion.owns(keyPair._1, box)) 29 | } 30 | 31 | val store = new LSMStore(dir, keepVersions = keepVersions) 32 | val s0 = HBoxStoredState(store, versionTagGen.sample.get) 33 | val inserts = (1 to 10000) 34 | .map(_ => Insertion[PublicKey25519Proposition, PublicKey25519NoncedBox](randomBox())) 35 | s0.applyChanges(BoxStateChanges(inserts), versionTagGen.sample.get).get 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/transaction/proof/Signature25519.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.transaction.proof 2 | 3 | import scorex.util.serialization._ 4 | import scorex.core.serialization.ScorexSerializer 5 | import scorex.core.transaction.box.proposition.PublicKey25519Proposition 6 | import scorex.core.transaction.state.PrivateKey25519 7 | import scorex.core.utils.ScorexEncoding 8 | import scorex.crypto.signatures.{Curve25519, Signature} 9 | 10 | /** 11 | * @param signature 25519 signature 12 | */ 13 | case class Signature25519(signature: Signature) extends ProofOfKnowledge[PrivateKey25519, PublicKey25519Proposition] 14 | with ScorexEncoding { 15 | 16 | require(signature.isEmpty || signature.length == Curve25519.SignatureLength, 17 | s"${signature.length} != ${Curve25519.SignatureLength}") 18 | 19 | override def isValid(proposition: PublicKey25519Proposition, message: Array[Byte]): Boolean = 20 | Curve25519.verify(signature, message, proposition.pubKeyBytes) 21 | 22 | override type M = Signature25519 23 | 24 | override def serializer: ScorexSerializer[Signature25519] = Signature25519Serializer 25 | 26 | override def toString: String = s"Signature25519(${encoder.encode(signature)})" 27 | } 28 | 29 | object Signature25519Serializer extends ScorexSerializer[Signature25519] { 30 | 31 | override def serialize(obj: Signature25519, w: Writer): Unit = { 32 | w.putBytes(obj.signature) 33 | } 34 | 35 | override def parse(r: Reader): Signature25519 = { 36 | Signature25519(Signature @@ r.getBytes(Curve25519.SignatureLength)) 37 | } 38 | } 39 | 40 | object Signature25519 { 41 | lazy val SignatureSize = Curve25519.SignatureLength 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/scorex/core/utils/ScorexEncoder.scala: -------------------------------------------------------------------------------- 1 | package scorex.core.utils 2 | 3 | import scorex.core.VersionTag 4 | import scorex.util.ModifierId 5 | import scorex.util.encode.{Base16, BytesEncoder} 6 | 7 | import scala.util.Try 8 | 9 | class ScorexEncoder extends BytesEncoder { 10 | @inline 11 | override val Alphabet: String = Base16.Alphabet 12 | 13 | @inline 14 | override def encode(input: Array[Byte]): String = Base16.encode(input) 15 | 16 | @inline 17 | override def decode(input: String): Try[Array[Byte]] = Base16.decode(input) 18 | 19 | /** 20 | * This method might be useful and reimplemented, if encoding of ModifierId and VersionTag 21 | * is different form default bytes encoding, e.g. this method should be reimplemented together 22 | * with encode() and decode methods 23 | */ 24 | @inline 25 | def encode(input: String): String = input 26 | 27 | /** 28 | * This method might be useful and reimplemented, if encoding of ModifierId and VersionTag 29 | * is different form default bytes encoding, e.g. this method should be reimplemented together 30 | * with encode() and decode methods 31 | */ 32 | @inline 33 | def encodeVersion(input: VersionTag): String = input 34 | 35 | /** 36 | * This method might be useful and reimplemented, if encoding of ModifierId and VersionTag 37 | * is different form default bytes encoding, e.g. this method should be reimplemented together 38 | * with encode() and decode methods 39 | */ 40 | @inline 41 | def encodeId(input: ModifierId): String = input 42 | 43 | } 44 | 45 | object ScorexEncoder { 46 | val default: ScorexEncoder = new ScorexEncoder() 47 | } 48 | -------------------------------------------------------------------------------- /src/main/resources/swagger-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |