├── .github
├── settings.yml
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .travis.yml
├── CONTRIBUTING
├── LICENSE
├── README.md
├── build.sbt
├── ci
└── import_gpg.sh
├── doc
└── peer
│ ├── bibliography.bib
│ ├── compile.sh
│ ├── llncs.cls
│ ├── main.aux
│ ├── main.bbl
│ ├── main.blg
│ ├── main.out
│ ├── main.pdf
│ ├── main.tex
│ └── main.toc
├── examples
├── README.md
├── build.sbt
├── project
│ └── plugins.sbt
└── src
│ ├── main
│ ├── resources
│ │ ├── api
│ │ │ └── testApi.yaml
│ │ ├── reference.conf
│ │ ├── scripts
│ │ │ ├── ips.sh
│ │ │ ├── twinschainConfigGenerator.sh
│ │ │ ├── twinschainRebuild.sh
│ │ │ ├── twinschainRebuildAndStart.sh
│ │ │ ├── twinschainRerun.sh
│ │ │ └── twinschainStatus.sh
│ │ ├── settings.conf
│ │ ├── settings10.conf
│ │ ├── settings2.conf
│ │ ├── settings3.conf
│ │ ├── settings4.conf
│ │ ├── settings5.conf
│ │ ├── settings6.conf
│ │ ├── settings7.conf
│ │ ├── settings8.conf
│ │ └── settings9.conf
│ └── scala
│ │ └── examples
│ │ ├── commons
│ │ ├── FileLogger.scala
│ │ ├── PublicKey25519NoncedBox.scala
│ │ ├── SimpleBoxTransaction.scala
│ │ ├── SimpleBoxTransactionMemPool.scala
│ │ └── curvepos.scala
│ │ ├── hybrid
│ │ ├── HLocalInterface.scala
│ │ ├── HybridApp.scala
│ │ ├── HybridNodeViewHolder.scala
│ │ ├── README.md
│ │ ├── api
│ │ │ └── http
│ │ │ │ ├── DebugApiRoute.scala
│ │ │ │ ├── StatsApiRoute.scala
│ │ │ │ └── WalletApiRoute.scala
│ │ ├── blocks
│ │ │ ├── HybridBlock.scala
│ │ │ ├── PosBlock.scala
│ │ │ └── PowBlock.scala
│ │ ├── history
│ │ │ ├── HistoryStorage.scala
│ │ │ ├── HybridHistory.scala
│ │ │ └── HybridSyncInfo.scala
│ │ ├── mining
│ │ │ ├── HybridMiningSettings.scala
│ │ │ ├── PosForger.scala
│ │ │ └── PowMiner.scala
│ │ ├── simulations
│ │ │ └── PrivateChain.scala
│ │ ├── state
│ │ │ └── HBoxStoredState.scala
│ │ ├── util
│ │ │ ├── Cancellable.scala
│ │ │ └── FileFunctions.scala
│ │ ├── validation
│ │ │ ├── DifficultyBlockValidator.scala
│ │ │ ├── ParentBlockValidator.scala
│ │ │ └── SemanticBlockValidator.scala
│ │ └── wallet
│ │ │ ├── HBoxWallet.scala
│ │ │ └── SimpleBoxTransactionGenerator.scala
│ │ ├── spv
│ │ ├── Constants.scala
│ │ ├── Header.scala
│ │ ├── KLS16Proof.scala
│ │ ├── KMZProof.scala
│ │ ├── SpvAlgos.scala
│ │ └── simulation
│ │ │ ├── SPVSimulator.scala
│ │ │ └── SimulatorFuctions.scala
│ │ └── trimchain
│ │ ├── README.md
│ │ ├── core
│ │ ├── Algos.scala
│ │ ├── Constants.scala
│ │ ├── Ticket.scala
│ │ └── core.scala
│ │ ├── modifiers
│ │ ├── BlockHeader.scala
│ │ ├── TBlock.scala
│ │ ├── TModifier.scala
│ │ └── UtxoSnapshot.scala
│ │ ├── simulation
│ │ ├── InMemoryAuthenticatedUtxo.scala
│ │ ├── OneMinerSimulation.scala
│ │ ├── Simulators.scala
│ │ ├── SpaceSavingsCalculator.scala
│ │ └── ValidationSimulator.scala
│ │ └── utxo
│ │ └── PersistentAuthenticatedUtxo.scala
│ └── test
│ ├── resources
│ └── settings.conf
│ └── scala
│ ├── commons
│ └── ExamplesCommonGenerators.scala
│ ├── hybrid
│ ├── HistoryGenerators.scala
│ ├── HybridGenerators.scala
│ ├── HybridSanity.scala
│ ├── HybridTypes.scala
│ ├── ModifierGenerators.scala
│ ├── NodeViewHolderGenerators.scala
│ ├── NodeViewHolderSpec.scala
│ ├── NodeViewSynchronizerGenerators.scala
│ ├── NodeViewSynchronizerSpec.scala
│ ├── StateGenerators.scala
│ ├── StoreGenerators.scala
│ ├── history
│ │ ├── HybridHistorySpecification.scala
│ │ └── IODBSpecification.scala
│ ├── primitives
│ │ └── PrivateKey25519Suite.scala
│ ├── serialization
│ │ └── SerializationTests.scala
│ ├── state
│ │ ├── HBoxStoredStateSpecification.scala
│ │ └── SimpleBoxTransactionSpecification.scala
│ ├── transaction
│ │ └── TransactionSuite.scala
│ ├── validation
│ │ └── SemanticBlockValidatorSpecification.scala
│ └── wallet
│ │ └── HWalletSpecification.scala
│ ├── spv
│ ├── ChainTests.scala
│ ├── SPVGenerators.scala
│ └── serialization
│ │ └── SerializationTests.scala
│ └── trimchain
│ ├── TrimchainGenerators.scala
│ └── serialization
│ └── SerializationTests.scala
├── project
├── build.properties
└── plugins.sbt
├── release-notes.md
├── scalastyle-config.xml
├── src
├── main
│ ├── resources
│ │ ├── logback.xml
│ │ ├── reference.conf
│ │ └── swagger-ui
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── index.html
│ │ │ ├── oauth2-redirect.html
│ │ │ ├── swagger-ui-bundle.js
│ │ │ ├── swagger-ui-bundle.js.map
│ │ │ ├── swagger-ui-standalone-preset.js
│ │ │ ├── swagger-ui-standalone-preset.js.map
│ │ │ ├── swagger-ui.css
│ │ │ ├── swagger-ui.css.map
│ │ │ ├── swagger-ui.js
│ │ │ └── swagger-ui.js.map
│ └── scala
│ │ └── scorex
│ │ ├── ObjectGenerators.scala
│ │ ├── core
│ │ ├── ModifiersCache.scala
│ │ ├── NodeViewComponent.scala
│ │ ├── NodeViewHolder.scala
│ │ ├── NodeViewModifier.scala
│ │ ├── api
│ │ │ ├── client
│ │ │ │ └── ApiClient.scala
│ │ │ └── http
│ │ │ │ ├── ApiDirectives.scala
│ │ │ │ ├── ApiError.scala
│ │ │ │ ├── ApiErrorHandler.scala
│ │ │ │ ├── ApiRejectionHandler.scala
│ │ │ │ ├── ApiResponse.scala
│ │ │ │ ├── ApiRoute.scala
│ │ │ │ ├── ApiRouteWithFullView.scala
│ │ │ │ ├── ApiTry.scala
│ │ │ │ ├── CompositeHttpService.scala
│ │ │ │ ├── CorsHandler.scala
│ │ │ │ ├── NodeViewApiRoute.scala
│ │ │ │ ├── PeersApiRoute.scala
│ │ │ │ ├── UtilsApiRoute.scala
│ │ │ │ └── swagger
│ │ │ │ └── SwaggerConfigRoute.scala
│ │ ├── app
│ │ │ ├── Application.scala
│ │ │ ├── ScorexContext.scala
│ │ │ └── Version.scala
│ │ ├── block
│ │ │ ├── Block.scala
│ │ │ └── BlockValidator.scala
│ │ ├── consensus
│ │ │ ├── BlockChain.scala
│ │ │ ├── ConsensusSettings.scala
│ │ │ ├── ContainsModifiers.scala
│ │ │ ├── History.scala
│ │ │ ├── HistoryReader.scala
│ │ │ ├── ModifierSemanticValidity.scala
│ │ │ └── SyncInfo.scala
│ │ ├── core.scala
│ │ ├── network
│ │ │ ├── ConnectedPeer.scala
│ │ │ ├── ConnectionDescription.scala
│ │ │ ├── ConnectionDirection.scala
│ │ │ ├── ConnectionId.scala
│ │ │ ├── DeliveryTracker.scala
│ │ │ ├── Handshake.scala
│ │ │ ├── MaliciousBehaviorException.scala
│ │ │ ├── ModifiersStatus.scala
│ │ │ ├── NetworkController.scala
│ │ │ ├── NodeViewSynchronizer.scala
│ │ │ ├── PeerConnectionHandler.scala
│ │ │ ├── PeerFeature.scala
│ │ │ ├── PeerSpec.scala
│ │ │ ├── PeerSynchronizer.scala
│ │ │ ├── SendingStrategy.scala
│ │ │ ├── SyncTracker.scala
│ │ │ ├── Synchronizer.scala
│ │ │ ├── UPnP.scala
│ │ │ ├── message
│ │ │ │ ├── BasicMessagesRepo.scala
│ │ │ │ ├── Message.scala
│ │ │ │ ├── MessageSerializer.scala
│ │ │ │ └── MessageSpec.scala
│ │ │ └── peer
│ │ │ │ ├── InMemoryPeerDatabase.scala
│ │ │ │ ├── LocalAddressPeerFeature.scala
│ │ │ │ ├── PeerDatabase.scala
│ │ │ │ ├── PeerInfo.scala
│ │ │ │ ├── PeerManager.scala
│ │ │ │ ├── PenaltyType.scala
│ │ │ │ └── SessionIdPeerFeature.scala
│ │ ├── serialization
│ │ │ ├── BytesSerializable.scala
│ │ │ ├── ScorexSerializer.scala
│ │ │ └── SerializerRegistry.scala
│ │ ├── settings
│ │ │ ├── Settings.scala
│ │ │ └── SettingsReaders.scala
│ │ ├── transaction
│ │ │ ├── BoxTransaction.scala
│ │ │ ├── MemoryPool.scala
│ │ │ ├── MempoolReader.scala
│ │ │ ├── Transaction.scala
│ │ │ ├── account
│ │ │ │ └── PublicKeyNoncedBox.scala
│ │ │ ├── box
│ │ │ │ ├── Box.scala
│ │ │ │ ├── BoxUnlocker.scala
│ │ │ │ └── proposition
│ │ │ │ │ ├── Proposition.scala
│ │ │ │ │ └── PublicKey25519Proposition.scala
│ │ │ ├── proof
│ │ │ │ ├── Proof.scala
│ │ │ │ └── Signature25519.scala
│ │ │ ├── state
│ │ │ │ ├── BoxStateChanges.scala
│ │ │ │ ├── MinimalState.scala
│ │ │ │ ├── SecretHolder.scala
│ │ │ │ └── StateReader.scala
│ │ │ └── wallet
│ │ │ │ ├── BoxWallet.scala
│ │ │ │ ├── Vault.scala
│ │ │ │ └── VaultReader.scala
│ │ ├── utils
│ │ │ ├── ActorHelper.scala
│ │ │ ├── BlockTypeable.scala
│ │ │ ├── LocalTimeProvider.scala
│ │ │ ├── NetworkTime.scala
│ │ │ ├── NetworkUtils.scala
│ │ │ ├── ScorexEncoder.scala
│ │ │ ├── ScorexEncoding.scala
│ │ │ ├── SerializationConstants.scala
│ │ │ ├── TimeProvider.scala
│ │ │ └── utils.scala
│ │ └── validation
│ │ │ ├── ModifierError.scala
│ │ │ ├── ModifierValidator.scala
│ │ │ ├── ValidationResult.scala
│ │ │ └── ValidationSettings.scala
│ │ ├── mid
│ │ └── state
│ │ │ └── BoxMinimalState.scala
│ │ └── util
│ │ └── serialization
│ │ ├── VLQByteStringReader.scala
│ │ └── VLQByteStringWriter.scala
└── test
│ └── scala
│ └── scorex
│ ├── core
│ ├── DefaultModifiersCacheSpecification.scala
│ ├── api
│ │ └── http
│ │ │ ├── ApiResponseTest.scala
│ │ │ ├── PeersApiRouteSpec.scala
│ │ │ ├── Stubs.scala
│ │ │ └── UtilsApiRouteSpec.scala
│ ├── network
│ │ ├── HandshakeSpecification.scala
│ │ └── peer
│ │ │ ├── InMemoryPeerDatabaseSpec.scala
│ │ │ └── PeerManagerSpec.scala
│ ├── serialization
│ │ └── SerializerRegistrySpec.scala
│ ├── transaction
│ │ └── box
│ │ │ └── proposition
│ │ │ └── PublicKey25519PropositionSpecification.scala
│ └── validation
│ │ └── ValidationSpec.scala
│ ├── crypto
│ └── SigningFunctionsSpecification.scala
│ ├── network
│ ├── DeliveryTrackerSpecification.scala
│ ├── MessageSpecification.scala
│ ├── NetworkControllerSpec.scala
│ ├── NetworkTests.scala
│ └── PeerConnectionHandlerSpecification.scala
│ └── util
│ ├── ScorexLoggingSpec.scala
│ └── serialization
│ └── VQLByteStringReaderWriterSpecification.scala
└── testkit
├── README.md
├── build.sbt
├── project
└── plugins.sbt
└── src
└── main
└── scala
└── scorex
└── testkit
├── BlockchainPerformance.scala
├── BlockchainSanity.scala
├── SerializationTests.scala
├── TestkitHelpers.scala
├── generators
├── AllModifierProducers.scala
├── ArbitraryTransactionsCarryingModifierProducer.scala
├── CoreGenerators.scala
├── CustomModifierProducer.scala
├── SemanticallyInvalidModifierProducer.scala
├── SemanticallyValidModifierProducer.scala
├── SemanticallyValidTransactionsCarryingModifier.scala
├── SyntacticallyTargetedModifierProducer.scala
└── TotallyValidModifierProducer.scala
├── properties
├── HistoryTests.scala
├── NodeViewHolderTests.scala
├── NodeViewSynchronizerTests.scala
├── WalletSecretsTest.scala
├── mempool
│ ├── MemoryPoolTest.scala
│ ├── MempoolFilterPerformanceTest.scala
│ ├── MempoolRemovalTest.scala
│ └── MempoolTransactionsTest.scala
└── state
│ ├── StateApplicationTest.scala
│ ├── StateTests.scala
│ └── box
│ ├── BoxStateApplyChangesTest.scala
│ ├── BoxStateChangesGenerationTest.scala
│ ├── BoxStateRollbackTest.scala
│ └── BoxStateTests.scala
└── utils
├── AkkaFixture.scala
├── FileUtils.scala
└── NoShrink.scala
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | #
2 | # SPDX-License-Identifier: Apache-2.0
3 | #
4 |
5 | repository:
6 | name: Scorex
7 | archived: true
8 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/doc/peer/main.bbl:
--------------------------------------------------------------------------------
1 | \begin{thebibliography}{}
2 |
3 | \end{thebibliography}
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/doc/peer/main.out:
--------------------------------------------------------------------------------
1 | \BOOKMARK [0][-]{chapter.1}{A Flexible Peer Management Architecture \040for Blockchain Systems}{}% 1
2 |
--------------------------------------------------------------------------------
/doc/peer/main.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperledger-labs/Scorex/9e1cd58a5589a9062dd9974f1884cc640c1d51b3/doc/peer/main.pdf
--------------------------------------------------------------------------------
/doc/peer/main.toc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperledger-labs/Scorex/9e1cd58a5589a9062dd9974f1884cc640c1d51b3/doc/peer/main.toc
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Scorex Example: Twinscoin
2 |
3 |
4 | Currently, Scorex's distribution contains one example of blockchain system built on top of Scorex: **TwinsCoin**.
5 |
6 | Twinscoin is a hybrid Proof-of-Work(PoW) and Proof-of-Stake(PoS) cryptocurrency with
7 | 1:1 correspondence between PoW and PoS blocks. The main goal is to protect the hybrid
8 | chain against majority of hashing power being adversarial.
9 |
10 | The main characteristic of the implementation are:
11 |
12 | * The use of the [Twinscoin consensus protocol](https://eprint.iacr.org/2017/232.pdf);
13 |
14 | * A Bitcoin-like transaction model with a transaction having multiple inputs and outputs (with no [scripts](https://en.bitcoin.it/wiki/Script), however);
15 |
16 | * Persistence through the [IODB](https://github.com/input-output-hk/iodb) versioned key-value database.
17 |
18 |
19 | ### Execution
20 |
21 | To run, you need to have Java8 and [SBT (Scala Build Tool)](http://www.scala-sbt.org) installed.
22 |
23 | In the terminal, run `sbt`, and then type the following commands in the SBT shell:
24 |
25 | 1. `> project examples`
26 | 2. `> runMain examples.hybrid.HybridApp src/main/resources/settings.conf`
27 |
28 | This will create and run a node with the given settings. To create and run more nodes, repeat the steps above using the other setting files (e.g. `settings2.conf`, `settings3.conf`, ...) in the `src/main/resources/` folder.
29 |
30 |
31 | _Note to MacOS users:_ The 127.0.0.X loopback addresses (for X > 1) are disabled by default in MacOS. Therefore, the creation of nodes with `settingsX.conf` (for X > 1) fails. To prevent the failure, [enable the addresses](https://superuser.com/questions/458875/how-do-you-get-loopback-addresses-other-than-127-0-0-1-to-work-on-os-x?noredirect=1&lq=1).
32 |
33 |
34 | ### Web User Interface
35 |
36 | You can interact with the nodes you created through a web browser. For the node created with `settings.conf`, visit `localhost:9085`.
37 |
38 | TODO: Add instructions about how to perform a transaction through `wallet/transfer`.
39 |
40 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
--------------------------------------------------------------------------------
/examples/src/main/resources/reference.conf:
--------------------------------------------------------------------------------
1 | app {
2 | modifierIdSize = 32
3 | }
--------------------------------------------------------------------------------
/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")
--------------------------------------------------------------------------------
/examples/src/main/resources/scripts/twinschainConfigGenerator.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | my_dir="$(dirname "$0")"
4 | source "$my_dir/ips.sh"
5 |
6 | echo "Arguments: number of configs, data folder"
7 | limit=$(($1+21))
8 | for i in $(seq 21 $limit);
9 | do
10 | folder="$2/data-$i"
11 | port=`expr 19000 + $i`
12 | rpcPort=`expr 29000 + $i`
13 | original_string="genesis$i"
14 | seed="${original_string/0/o}"
15 | peers="\"52.40.210.252:9084\", \"52.88.43.127:9084\", \"35.167.200.197:9084\", \"35.163.27.226:9084\", \"35.167.74.210:9084\", \"54.70.0.50:9084\", \"52.89.211.42:9084\", \"35.167.43.13:9084\", \"35.167.184.105:9084\", \"35.165.178.93:9084\", \"54.191.32.214:9084\", \"52.26.217.230:9084\", \"52.24.190.196:9084\", \"34.209.224.39:9084\", \"52.25.11.49:9084\", \"52.41.61.115:9084\", \"35.163.92.164:9084\", \"35.161.164.5:9084\", \"52.40.158.125:9084\", \"52.24.217.224:9084\""
16 | conf="{\n \"p2p\": {\n \"name\": \"generatedNode$i\",\n \"bindAddress\": \"0.0.0.0\",\n \"upnp\": false,\n \"upnpGatewayTimeout\": 7000,\n \"upnpDiscoverTimeout\": 3000,\n \"port\": $port,\n \"knownPeers\": [$peers],\n \"addedMaxDelay\": 0,\n \"maxConnections\": 100\n },\n \"RparamX10\": 8,\n \"targetBlockDelayMillis\": 60000,\n \"walletDir\": \"$folder/wallet\",\n \"walletPassword\": \"cookies\",\n \"walletSeed\": \"$seed\",\n \"dataDir\": \"$folder/data\",\n \"logDir\": \"$folder/logs\",\n \"rpcPort\": $rpcPort,\n \"rpcAllowed\": [],\n \"agent\": \"generatedNode$i\",\n \"version\": [0, 0, 1],\n \"maxRollback\": 100,\n \"testnet\": true,\n \"offlineGeneration\": false,\n \"blockGenerationDelay\": 100,\n \"cors\": false\n}"
17 | printf "$conf" >> "settings-$i.json"
18 | done
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/commons/PublicKey25519NoncedBox.scala:
--------------------------------------------------------------------------------
1 | package examples.commons
2 |
3 | import io.circe.Encoder
4 | import io.circe.syntax._
5 | import scorex.core.serialization.ScorexSerializer
6 | import scorex.core.transaction.account.PublicKeyNoncedBox
7 | import scorex.core.transaction.box.proposition.{PublicKey25519Proposition, PublicKey25519PropositionSerializer}
8 | import scorex.core.utils.ScorexEncoding
9 | import scorex.util.encode.Base16
10 | import scorex.crypto.hash.Blake2b256
11 | import scorex.crypto.signatures.Curve25519
12 | import scorex.util.serialization.{Reader, Writer}
13 |
14 | case class PublicKey25519NoncedBox(override val proposition: PublicKey25519Proposition,
15 | override val nonce: Nonce,
16 | override val value: Value) extends PublicKeyNoncedBox[PublicKey25519Proposition] {
17 |
18 | override type M = PublicKey25519NoncedBox
19 |
20 | override def serializer: ScorexSerializer[PublicKey25519NoncedBox] = PublicKey25519NoncedBoxSerializer
21 |
22 | override def toString: String =
23 | s"PublicKey25519NoncedBox(id: ${Base16.encode(id)}, proposition: $proposition, nonce: $nonce, value: $value)"
24 | }
25 |
26 | object PublicKey25519NoncedBox extends ScorexEncoding {
27 | val BoxKeyLength: Int = Blake2b256.DigestSize
28 | val BoxLength: Int = Curve25519.KeyLength + 2 * 8
29 |
30 | implicit val publicKey25519NoncedBoxEncoder: Encoder[PublicKey25519NoncedBox] = (pknb: PublicKey25519NoncedBox) =>
31 | Map(
32 | "id" -> encoder.encode(pknb.id).asJson,
33 | "address" -> pknb.proposition.address.asJson,
34 | "publicKey" -> encoder.encode(pknb.proposition.pubKeyBytes).asJson,
35 | "nonce" -> pknb.nonce.toLong.asJson,
36 | "value" -> pknb.value.toLong.asJson
37 | ).asJson
38 | }
39 |
40 | object PublicKey25519NoncedBoxSerializer extends ScorexSerializer[PublicKey25519NoncedBox] {
41 |
42 |
43 | override def serialize(obj: PublicKey25519NoncedBox, w: Writer): Unit = {
44 | PublicKey25519PropositionSerializer.serialize(obj.proposition, w)
45 | w.putLong(obj.nonce)
46 | w.putULong(obj.value)
47 | }
48 |
49 | override def parse(r: Reader): PublicKey25519NoncedBox = {
50 | PublicKey25519NoncedBox(
51 | PublicKey25519PropositionSerializer.parse(r),
52 | Nonce @@ r.getLong(),
53 | Value @@ r.getULong()
54 | )
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/commons/SimpleBoxTransactionMemPool.scala:
--------------------------------------------------------------------------------
1 | package examples.commons
2 |
3 | import scorex.core.transaction.MemoryPool
4 | import scorex.util.ModifierId
5 |
6 | import scala.collection.concurrent.TrieMap
7 | import scala.util.{Success, Try}
8 |
9 |
10 | case class SimpleBoxTransactionMemPool(unconfirmed: TrieMap[ModifierId, SimpleBoxTransaction])
11 | extends MemoryPool[SimpleBoxTransaction, SimpleBoxTransactionMemPool] {
12 | override type NVCT = SimpleBoxTransactionMemPool
13 |
14 | //getters
15 | override def modifierById(id: ModifierId): Option[SimpleBoxTransaction] = unconfirmed.get(id)
16 |
17 | override def contains(id: ModifierId): Boolean = unconfirmed.contains(id)
18 |
19 | override def getAll(ids: Seq[ModifierId]): Seq[SimpleBoxTransaction] = ids.flatMap(getById)
20 |
21 | //modifiers
22 | override def put(tx: SimpleBoxTransaction): Try[SimpleBoxTransactionMemPool] = Success {
23 | unconfirmed.put(tx.id, tx)
24 | this
25 | }
26 |
27 | //todo
28 | override def put(txs: Iterable[SimpleBoxTransaction]): Try[SimpleBoxTransactionMemPool] = Success(putWithoutCheck(txs))
29 |
30 | override def putWithoutCheck(txs: Iterable[SimpleBoxTransaction]): SimpleBoxTransactionMemPool = {
31 | txs.foreach(tx => unconfirmed.put(tx.id, tx))
32 | this
33 | }
34 |
35 | override def remove(tx: SimpleBoxTransaction): SimpleBoxTransactionMemPool = {
36 | unconfirmed.remove(tx.id)
37 | this
38 | }
39 |
40 | override def take(limit: Int): Iterable[SimpleBoxTransaction] =
41 | unconfirmed.values.toSeq.sortBy(-_.fee).take(limit)
42 |
43 | override def filter(condition: SimpleBoxTransaction => Boolean): SimpleBoxTransactionMemPool = {
44 | unconfirmed.retain { (_, v) =>
45 | condition(v)
46 | }
47 | this
48 | }
49 |
50 | override def size: Int = unconfirmed.size
51 | }
52 |
53 |
54 | object SimpleBoxTransactionMemPool {
55 | lazy val emptyPool: SimpleBoxTransactionMemPool = SimpleBoxTransactionMemPool(TrieMap())
56 | }
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/hybrid/README.md:
--------------------------------------------------------------------------------
1 | Hybrid Proof-of-Work/Proof-of-Stake consensus protocol implementation
2 | ======================================================================
3 |
4 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/hybrid/api/http/StatsApiRoute.scala:
--------------------------------------------------------------------------------
1 | package examples.hybrid.api.http
2 |
3 | import akka.actor.{ActorRef, ActorRefFactory}
4 | import akka.http.scaladsl.server.Route
5 | import examples.commons.SimpleBoxTransactionMemPool
6 | import examples.hybrid.history.HybridHistory
7 | import examples.hybrid.state.HBoxStoredState
8 | import examples.hybrid.wallet.HBoxWallet
9 | import io.circe.syntax._
10 | import scorex.core.api.http.{ApiResponse, ApiRouteWithFullView, ApiTry}
11 | import scorex.core.settings.RESTApiSettings
12 | import scorex.core.utils.ScorexEncoding
13 | import scorex.util.ModifierId
14 |
15 | import scala.util.Try
16 |
17 | case class StatsApiRoute(override val settings: RESTApiSettings, nodeViewHolderRef: ActorRef)
18 | (implicit val context: ActorRefFactory)
19 | extends ApiRouteWithFullView[HybridHistory, HBoxStoredState, HBoxWallet, SimpleBoxTransactionMemPool]
20 | with ScorexEncoding {
21 |
22 | override val route: Route = (pathPrefix("stats")) {
23 | corsHandler(
24 | tail ~ meanDifficulty
25 | )
26 | }
27 |
28 | def tail: Route = (get & path("tail" / IntNumber)) { count =>
29 | withNodeView { view =>
30 | val lastBlockIds = view.history.lastBlockIds(view.history.bestBlock, count)
31 | val tail = lastBlockIds.map(id => encoder.encodeId(id).asJson)
32 | ApiResponse("count" -> count.asJson, "tail" -> tail.asJson)
33 | }
34 | }
35 |
36 | def meanDifficulty: Route = (get & path("meanDifficulty" / IntNumber / IntNumber)) { (start, end) =>
37 | withNodeView { view =>
38 | ApiTry {
39 | val count = (view.history.height - start).toInt
40 | val ids: Seq[ModifierId] = view.history.lastBlockIds(view.history.bestBlock, count).take(end - start)
41 | val posDiff = ids.flatMap(id => Try(view.history.storage.getPoSDifficulty(id)).toOption)
42 | val powDiff = ids.flatMap(id => Try(view.history.storage.getPoWDifficulty(Some(id))).toOption)
43 | ApiResponse(
44 | "posDiff" -> (posDiff.sum / posDiff.length).asJson,
45 | "powDiff" -> (powDiff.sum / powDiff.length).asJson
46 | )
47 | }
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/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/hybrid/history/HybridSyncInfo.scala:
--------------------------------------------------------------------------------
1 | package examples.hybrid.history
2 |
3 | import examples.hybrid.blocks.{PosBlock, PowBlock}
4 | import scorex.core.consensus.SyncInfo
5 | import scorex.core.network.message.SyncInfoMessageSpec
6 | import scorex.core.serialization.ScorexSerializer
7 | import scorex.core.{ModifierTypeId, NodeViewModifier}
8 | import scorex.util.serialization.{Reader, Writer}
9 | import scorex.util.{ModifierId, bytesToId, idToBytes}
10 |
11 | /**
12 | * Stores up to 50 last PoW & Pos blocks
13 | * Thus maximum message size is about 100 * 32 ~= 3.2 KB
14 | * TODO answer is never used
15 | */
16 | case class HybridSyncInfo(answer: Boolean,
17 | lastPowBlockIds: Seq[ModifierId],
18 | lastPosBlockId: ModifierId
19 | ) extends SyncInfo {
20 |
21 | import HybridSyncInfo.MaxLastPowBlocks
22 |
23 | require(lastPowBlockIds.size <= MaxLastPowBlocks)
24 |
25 | override def startingPoints: Seq[(ModifierTypeId, ModifierId)] =
26 | Seq(lastPowBlockIds.map(b => PowBlock.ModifierTypeId -> b) ++ Seq(PosBlock.ModifierTypeId -> lastPosBlockId)).flatten
27 |
28 |
29 | override type M = HybridSyncInfo
30 |
31 | override def serializer: ScorexSerializer[HybridSyncInfo] = HybridSyncInfoSerializer
32 | }
33 |
34 | object HybridSyncInfo {
35 | val MaxLastPowBlocks: Byte = 50 //don't make it more than 127 without changing serialization!
36 | }
37 |
38 | object HybridSyncInfoSerializer extends ScorexSerializer[HybridSyncInfo] {
39 |
40 | import HybridSyncInfo.MaxLastPowBlocks
41 |
42 | override def serialize(obj: HybridSyncInfo, w: Writer): Unit = {
43 | w.put(if (obj.answer) 1 else 0)
44 | w.put(obj.lastPowBlockIds.size.toByte)
45 |
46 | obj.lastPowBlockIds.foreach { b =>
47 | w.putBytes(idToBytes(b))
48 | }
49 | w.putBytes(idToBytes(obj.lastPosBlockId))
50 | }
51 |
52 | override def parse(r: Reader): HybridSyncInfo = {
53 | val answer: Boolean = r.getByte() == 1.toByte
54 | val lastPowBlockIdsSize = r.getByte()
55 |
56 | require(lastPowBlockIdsSize >= 0 && lastPowBlockIdsSize <= MaxLastPowBlocks)
57 | require(r.remaining == (lastPowBlockIdsSize + 1) * NodeViewModifier.ModifierIdSize)
58 |
59 | val lastPowBlockIds = (0 until lastPowBlockIdsSize).map { _ =>
60 | bytesToId(r.getBytes(NodeViewModifier.ModifierIdSize))
61 | }
62 |
63 | val lastPosBlockId = bytesToId(r.getBytes(NodeViewModifier.ModifierIdSize))
64 |
65 | HybridSyncInfo(answer, lastPowBlockIds, lastPosBlockId)
66 | }
67 | }
68 |
69 | object HybridSyncInfoMessageSpec extends SyncInfoMessageSpec[HybridSyncInfo](HybridSyncInfoSerializer)
70 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/hybrid/mining/HybridMiningSettings.scala:
--------------------------------------------------------------------------------
1 | package examples.hybrid.mining
2 |
3 | import java.io.File
4 |
5 | import com.typesafe.config.Config
6 | import net.ceedubs.ficus.Ficus._
7 | import net.ceedubs.ficus.readers.ArbitraryTypeReader._
8 | import net.ceedubs.ficus.readers.ValueReader
9 | import scorex.core.bytesToId
10 | import scorex.core.settings.ScorexSettings.readConfigFromPath
11 | import scorex.core.settings._
12 | import scorex.util.ScorexLogging
13 |
14 | import scala.concurrent.duration._
15 |
16 | case class HybridSettings(mining: HybridMiningSettings,
17 | walletSettings: WalletSettings,
18 | scorexSettings: ScorexSettings)
19 |
20 | case class WalletSettings(seed: String,
21 | password: String,
22 | walletDir: File)
23 |
24 | case class HybridMiningSettings(offlineGeneration: Boolean,
25 | targetBlockDelay: FiniteDuration,
26 | blockGenerationDelay: FiniteDuration,
27 | posAttachmentSize: Int,
28 | rParamX10: Int,
29 | initialDifficulty: BigInt) {
30 | lazy val MaxTarget = BigInt(1, Array.fill(32)(Byte.MinValue))
31 | lazy val GenesisParentId = bytesToId(Array.fill(32)(1: Byte))
32 | }
33 |
34 | object HybridSettings extends ScorexLogging with SettingsReaders {
35 | def read(userConfigPath: Option[String]): HybridSettings = {
36 | fromConfig(readConfigFromPath(userConfigPath, "scorex"))
37 | }
38 |
39 | implicit val networkSettingsValueReader: ValueReader[HybridSettings] =
40 | (cfg: Config, path: String) => fromConfig(cfg.getConfig(path))
41 |
42 | private def fromConfig(config: Config): HybridSettings = {
43 | log.info(config.toString)
44 | val walletSettings = config.as[WalletSettings]("scorex.wallet")
45 | val miningSettings = config.as[HybridMiningSettings]("scorex.miner")
46 | val scorexSettings = config.as[ScorexSettings]("scorex")
47 | HybridSettings(miningSettings, walletSettings, scorexSettings)
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/hybrid/validation/DifficultyBlockValidator.scala:
--------------------------------------------------------------------------------
1 | package examples.hybrid.validation
2 |
3 | import examples.hybrid.blocks.{HybridBlock, PosBlock, PowBlock}
4 | import examples.hybrid.history.HistoryStorage
5 | import examples.hybrid.mining.{HybridMiningSettings, PosForger}
6 | import scorex.core.block.BlockValidator
7 | import scorex.core.utils.ScorexEncoding
8 |
9 | import scala.util.Try
10 |
11 | class DifficultyBlockValidator(settings: HybridMiningSettings, storage: HistoryStorage)
12 | extends BlockValidator[HybridBlock] with ScorexEncoding {
13 |
14 | def validate(block: HybridBlock): Try[Unit] = block match {
15 | case b: PowBlock => checkPoWConsensusRules(b)
16 | case b: PosBlock => checkPoSConsensusRules(b, settings)
17 | }
18 |
19 | //PoW consensus rules checks, work/references
20 | //throws exception if anything wrong
21 | private def checkPoWConsensusRules(powBlock: PowBlock): Try[Unit] = Try {
22 | val powDifficulty = storage.getPoWDifficulty(Some(powBlock.prevPosId))
23 | //check work
24 | require(powBlock.correctWork(powDifficulty, settings),
25 | s"Work done is incorrect for block ${encoder.encodeId(powBlock.id)} and difficulty $powDifficulty")
26 |
27 | //some brothers work
28 | require(powBlock.brothers.forall(_.correctWork(powDifficulty, settings)))
29 |
30 | }
31 |
32 | //PoS consensus rules checks, throws exception if anything wrong
33 | private def checkPoSConsensusRules(posBlock: PosBlock, miningSettings: HybridMiningSettings): Try[Unit] = Try {
34 | if (!storage.isGenesis(posBlock)) {
35 | // TODO: review me - .get
36 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
37 | val parentPoW: PowBlock = storage.modifierById(posBlock.parentId).get.asInstanceOf[PowBlock]
38 | val hit = PosForger.hit(parentPoW)(posBlock.generatorBox)
39 | val posDifficulty = storage.getPoSDifficulty(parentPoW.prevPosId)
40 | val target = (miningSettings.MaxTarget / posDifficulty) * posBlock.generatorBox.value
41 | require(hit < target, s"$hit < $target failed, $posDifficulty, ")
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/trimchain/README.md:
--------------------------------------------------------------------------------
1 | TrimChain consensus protocol implementation
2 | ===========================================
3 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/trimchain/core/Ticket.scala:
--------------------------------------------------------------------------------
1 | package examples.trimchain.core
2 |
3 | import io.circe.Encoder
4 | import io.circe.syntax._
5 | import scorex.util.serialization.{Reader, Writer}
6 | import scorex.core.serialization.ScorexSerializer
7 | import scorex.core.utils.ScorexEncoding
8 | import scorex.crypto.authds.SerializedAdProof
9 | import scorex.crypto.signatures.Curve25519
10 |
11 | import scala.annotation.tailrec
12 | import scorex.util.Extensions._
13 |
14 | case class Ticket(minerKey: Array[Byte], partialProofs: Seq[SerializedAdProof]) {
15 |
16 | override def toString: String = this.asJson.noSpaces
17 | }
18 |
19 | object Ticket extends ScorexEncoding {
20 | implicit val ticketEncoder: Encoder[Ticket] = (t: Ticket) =>
21 | Map(
22 | "minerKey" -> encoder.encode(t.minerKey).asJson,
23 | "proofs" -> t.partialProofs.map(encoder.encode).asJson
24 | ).asJson
25 | }
26 |
27 | object TicketSerializer extends ScorexSerializer[Ticket] {
28 |
29 | val MinerKeySize: Int = Curve25519.KeyLength
30 |
31 | override def serialize(obj: Ticket, w: Writer): Unit = {
32 | w.putBytes(obj.minerKey)
33 | w.putShort(obj.partialProofs.length.toShortExact)
34 |
35 | obj.partialProofs.map { bytes =>
36 | require(bytes.length == bytes.length.toShort)
37 | w.putShort(bytes.length.toShort)
38 | w.putBytes(bytes)
39 | }
40 | }
41 |
42 | override def parse(r: Reader): Ticket = {
43 | val minerKey = r.getBytes(MinerKeySize)
44 | val proofNum = r.getShort()
45 |
46 | @tailrec
47 | def parseProofs(proofNum: Int, acc: Seq[SerializedAdProof] = Seq.empty): Seq[SerializedAdProof] = {
48 | if (proofNum > 0) {
49 | val proofSize = r.getShort()
50 | val proof = SerializedAdProof @@ r.getBytes(proofSize)
51 | parseProofs(proofNum - 1, proof +: acc)
52 | } else {
53 | acc
54 | }
55 | }
56 |
57 | val proofs = parseProofs(proofNum).reverse
58 | Ticket(minerKey, proofs)
59 | }
60 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/trimchain/modifiers/BlockHeader.scala:
--------------------------------------------------------------------------------
1 | package examples.trimchain.modifiers
2 |
3 | import examples.trimchain.core._
4 | import io.circe.Encoder
5 | import io.circe.syntax._
6 | import scorex.core.ModifierTypeId
7 | import scorex.util.serialization.{Reader, Writer}
8 | import scorex.core.serialization.ScorexSerializer
9 | import scorex.core.utils.ScorexEncoding
10 | import scorex.util.{ModifierId, bytesToId, idToBytes}
11 |
12 | //TODO compact proof of ticket in header
13 | case class BlockHeader(override val parentId: ModifierId,
14 | stateRoot: StateRoot,
15 | txRoot: TransactionsRoot,
16 | ticket: Ticket,
17 | powNonce: Long
18 | ) extends TModifier {
19 | override type M = BlockHeader
20 |
21 | override val modifierTypeId: ModifierTypeId = TModifier.Header
22 |
23 | override lazy val id: ModifierId = bytesToId(Constants.hashfn.hash(bytes))
24 |
25 | def correctWorkDone(difficulty: BigInt): Boolean = {
26 | val target = Constants.MaxTarget / difficulty
27 | BigInt(1, idToBytes(id)) < target
28 | }
29 |
30 | override lazy val serializer = BlockHeaderSerializer
31 | }
32 |
33 | object BlockHeader extends ScorexEncoding {
34 | implicit val blockHeaderEncoder: Encoder[BlockHeader] = (bh: BlockHeader) =>
35 | Map(
36 | "id" -> encoder.encodeId(bh.id).asJson,
37 | "parentId" -> encoder.encodeId(bh.parentId).asJson,
38 | "stateRoot" -> encoder.encode(bh.stateRoot).asJson,
39 | "txRoot" -> encoder.encode(bh.txRoot).asJson,
40 | "ticket" -> bh.ticket.asJson,
41 | "powNonce" -> bh.powNonce.asJson
42 | ).asJson
43 | }
44 |
45 | object BlockHeaderSerializer extends ScorexSerializer[BlockHeader] {
46 | private val ds = Constants.hashfn.DigestSize
47 |
48 | override def serialize(obj: BlockHeader, w: Writer): Unit = {
49 | w.putBytes(idToBytes(obj.parentId))
50 | w.putBytes(obj.stateRoot)
51 | w.putBytes(obj.txRoot)
52 | w.putLong(obj.powNonce)
53 | TicketSerializer.serialize(obj.ticket, w)
54 | }
55 |
56 | override def parse(r: Reader): BlockHeader = {
57 | val parentId = bytesToId(r.getBytes(ds))
58 | val stateRoot = StateRoot @@ r.getBytes(ds)
59 | val txRoot = TransactionsRoot @@ r.getBytes(ds)
60 | val powNonce = r.getLong()
61 | val ticket = TicketSerializer.parse(r)
62 | BlockHeader(parentId, stateRoot, txRoot, ticket, powNonce)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/trimchain/modifiers/TBlock.scala:
--------------------------------------------------------------------------------
1 | package examples.trimchain.modifiers
2 |
3 | import examples.commons.{SimpleBoxTransaction, SimpleBoxTransactionSerializer}
4 | import io.circe.Encoder
5 | import io.circe.syntax._
6 | import scorex.core.ModifierTypeId
7 | import scorex.core.block.Block
8 | import scorex.core.block.Block.{Timestamp, Version}
9 | import scorex.util.serialization.{Reader, Writer}
10 | import scorex.core.serialization.ScorexSerializer
11 | import scorex.util.ModifierId
12 |
13 | import scala.annotation.tailrec
14 |
15 | case class TBlock(header: BlockHeader, body: Seq[SimpleBoxTransaction], timestamp: Timestamp)
16 | extends TModifier with Block[SimpleBoxTransaction] {
17 |
18 | override def version: Version = 0: Version
19 |
20 | override def parentId: ModifierId = header.parentId
21 |
22 | override def transactions: Seq[SimpleBoxTransaction] = body
23 |
24 | override val modifierTypeId: ModifierTypeId = TModifier.Block
25 |
26 | override def id: ModifierId = header.id
27 |
28 | override type M = TBlock
29 |
30 | override def serializer: ScorexSerializer[TBlock] = TBlockSerializer
31 | }
32 |
33 | object TBlockSerializer extends ScorexSerializer[TBlock] {
34 |
35 | override def serialize(obj: TBlock, w: Writer): Unit = {
36 | w.putLong(obj.timestamp)
37 | val headerWriter = w.newWriter()
38 | BlockHeaderSerializer.serialize(obj.header, headerWriter)
39 | w.putShort(headerWriter.length().toShort)
40 | w.append(headerWriter)
41 |
42 | obj.body.foreach { tx =>
43 | val txw = w.newWriter()
44 | SimpleBoxTransactionSerializer.serialize(tx, txw)
45 | w.putShort(txw.length().toShort)
46 | w.append(txw)
47 | }
48 | }
49 |
50 | override def parse(r: Reader): TBlock = {
51 | val timestamp = r.getLong()
52 | val headerLength = r.getShort()
53 | val header = BlockHeaderSerializer.parse(r.newReader(r.getChunk(headerLength)))
54 |
55 | @tailrec
56 | def parseTxs(acc: Seq[SimpleBoxTransaction] = Seq()): Seq[SimpleBoxTransaction] = {
57 | if (r.remaining > 0) {
58 | val txLength = r.getShort()
59 | val tx = SimpleBoxTransactionSerializer.parse(r.newReader(r.getChunk(txLength)))
60 | parseTxs(tx +: acc)
61 | } else {
62 | acc
63 | }
64 | }
65 | val body = parseTxs().reverse
66 | TBlock(header, body, timestamp)
67 | }
68 | }
69 |
70 | object TBlock {
71 | implicit val tBlockEncoder: Encoder[TBlock] = (tb: TBlock) =>
72 | Map(
73 | "header" -> tb.header.asJson,
74 | "body" -> tb.body.map(_.asJson).asJson,
75 | "timestamp" -> tb.timestamp.asJson
76 | ).asJson
77 | }
78 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/trimchain/simulation/Simulators.scala:
--------------------------------------------------------------------------------
1 | package examples.trimchain.simulation
2 |
3 | import examples.commons.{PublicKey25519NoncedBox, SimpleBoxTransaction}
4 | import examples.trimchain.core.Constants._
5 | import examples.trimchain.core.{Algos, Constants, StateRoot, TransactionsRoot}
6 | import examples.trimchain.modifiers.TBlock
7 | import examples.trimchain.utxo.PersistentAuthenticatedUtxo
8 | import scorex.core._
9 | import scorex.core.transaction.state.PrivateKey25519Companion
10 |
11 | trait Simulators {
12 |
13 |
14 | val minerKeys = PrivateKey25519Companion.generateKeys(scorex.utils.Random.randomBytes(32))
15 | val minerPubKey = minerKeys._2
16 | val minerPrivKey = minerKeys._1
17 |
18 | val defaultBytes = Array.fill(32)(0: Byte)
19 | val defaultId = bytesToId(defaultBytes)
20 | val defaultVersion = bytesToVersion(defaultBytes)
21 |
22 | def generateBlock(txs: Seq[SimpleBoxTransaction],
23 | currentUtxo: InMemoryAuthenticatedUtxo,
24 | miningUtxos: IndexedSeq[InMemoryAuthenticatedUtxo]): (TBlock, Seq[PublicKey25519NoncedBox], InMemoryAuthenticatedUtxo) = {
25 | //todo: fix, hashchain instead of Merkle tree atm
26 | val txsHash = TransactionsRoot @@ hashfn(scorex.core.utils.concatBytes(txs.map(_.bytes)))
27 |
28 | val changes = PersistentAuthenticatedUtxo.changes(txs).get
29 | val updUtxo = currentUtxo.applyChanges(changes, bytesToVersion(scorex.utils.Random.randomBytes())).get
30 |
31 | // TODO: review me - .get.get
32 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
33 | val h = Algos.pow(defaultId, txsHash, StateRoot @@ currentUtxo.rootHash, minerPubKey.pubKeyBytes,
34 | miningUtxos, Constants.Difficulty, 10000).get.get
35 |
36 | val newRichBoxes = txs.flatMap(_.newBoxes).filter(_.value > 5)
37 | (TBlock(h, txs, System.currentTimeMillis()), newRichBoxes, updUtxo)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/trimchain/simulation/SpaceSavingsCalculator.scala:
--------------------------------------------------------------------------------
1 | package examples.trimchain.simulation
2 |
3 | import java.io.File
4 |
5 | import examples.commons.PublicKey25519NoncedBox
6 |
7 | object SpaceSavingsCalculator extends App {
8 |
9 | private val eta = 100
10 | private val start = 1000
11 | private val finish = 10000
12 |
13 | private val outSize = PublicKey25519NoncedBox.BoxLength
14 |
15 | private val file = new File("/home/pozharko/Code/papers/trimchain/results-20.csv")
16 |
17 | private val lines = scala.io.Source.fromFile(file).getLines().toIndexedSeq
18 |
19 | // println(lines.head)
20 |
21 | // TODO: fixme, What should we do if `lines` is empty?
22 | @SuppressWarnings(Array("org.wartremover.warts.TraversableOps"))
23 | private val data = lines.tail.take(finish).map(_.split(","))
24 |
25 | private val blockSizes = data.map(_.apply(8)).map(_.toLong)
26 |
27 | private val headerSizes = data.map(_.apply(5)).map(_.toLong)
28 |
29 | private val currentUtxoSizes = data.map(_.apply(2)).map(_.toLong * outSize)
30 |
31 | println(s"height,full,spv,light,mining,f/s,f/l,f/m")
32 | (start to finish).foreach{h =>
33 | val fullChain = blockSizes.take(h).sum
34 | val lightChain = headerSizes.take(h - eta).sum + blockSizes.slice(h - eta, h).sum
35 |
36 | val spvchain = headerSizes.take(h).sum
37 |
38 | val fullSet = fullChain + currentUtxoSizes(h - 1)
39 |
40 | val lightSet = lightChain + currentUtxoSizes(h - 1)
41 |
42 | val miningSet = lightSet + currentUtxoSizes(h - eta - 1)
43 |
44 | println(s"$h,$fullSet,$spvchain,$lightSet,$miningSet,${fullSet/spvchain.toDouble},${fullSet / lightSet.toDouble},${fullSet / miningSet.toDouble}")
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/examples/src/main/scala/examples/trimchain/simulation/ValidationSimulator.scala:
--------------------------------------------------------------------------------
1 | package examples.trimchain.simulation
2 |
3 | import com.google.common.primitives.{Ints, Longs}
4 | import examples.commons.{Nonce, PublicKey25519NoncedBox, Value}
5 | import examples.trimchain.core.Constants._
6 | import examples.trimchain.core.{Algos, Constants}
7 | import scorex.core.idToVersion
8 | import scorex.core.transaction.box.proposition.PublicKey25519Proposition
9 | import scorex.core.transaction.state.{BoxStateChanges, Insertion}
10 | import scorex.crypto.authds.ADDigest
11 |
12 | object ValidationSimulator extends App with Simulators {
13 |
14 | // NElementsInProof: 2, validation time: 0.000434440 seconds
15 | // NElementsInProof: 5, validation time: 0.000532366 seconds
16 | // NElementsInProof: 10, validation time: 0.000530379 seconds
17 | // NElementsInProof: 20, validation time: 0.000618186 seconds
18 |
19 | val StateSize: Int = 5000000
20 | val ValidationNum: Int = 10000
21 |
22 | val genesisBoxes = (1 to StateSize) map { i =>
23 | PublicKey25519NoncedBox(
24 | minerPubKey,
25 | Nonce @@ Longs.fromByteArray(hashfn(minerPubKey.pubKeyBytes ++ Ints.toByteArray(i)).take(8)),
26 | Value @@ 10000000000L
27 | )
28 | }
29 |
30 | val genesisChanges: BoxStateChanges[PublicKey25519Proposition, PublicKey25519NoncedBox] =
31 | BoxStateChanges(genesisBoxes.map(box => Insertion[PublicKey25519Proposition, PublicKey25519NoncedBox](box)))
32 |
33 | val genesisUtxo: InMemoryAuthenticatedUtxo = InMemoryAuthenticatedUtxo(genesisBoxes.size, None, idToVersion(defaultId))
34 | .applyChanges(genesisChanges, idToVersion(defaultId)).get
35 | val rootHash: ADDigest = genesisUtxo.rootHash
36 |
37 | var validationTime: Long = 0L
38 | (0 until ValidationNum) foreach { _ =>
39 | val block = generateBlock(Seq(), genesisUtxo, IndexedSeq(genesisUtxo))._1
40 | val start = System.nanoTime()
41 | Algos.validatePow(block.header, IndexedSeq(rootHash), Constants.Difficulty)
42 | println("!! " + (System.nanoTime() - start))
43 | validationTime += System.nanoTime() - start
44 | }
45 | println(s"One block validation takes ${validationTime / ValidationNum} nanos")
46 | }
47 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/examples/src/test/scala/commons/ExamplesCommonGenerators.scala:
--------------------------------------------------------------------------------
1 | package commons
2 |
3 | import examples.commons.{Nonce, SimpleBoxTransaction, Value}
4 | import org.scalacheck.Gen
5 | import scorex.core.transaction.box.proposition.PublicKey25519Proposition
6 | import scorex.core.transaction.state.PrivateKey25519
7 | import scorex.testkit.generators.CoreGenerators
8 |
9 | trait ExamplesCommonGenerators extends CoreGenerators {
10 | lazy val nonceGen: Gen[Nonce] = positiveLongGen.map(a => Nonce @@ a)
11 |
12 | lazy val valueGen: Gen[Value] = positiveLongGen.map(a => Value @@ a)
13 |
14 | lazy val pGen: Gen[(PublicKey25519Proposition, Value)] = for {
15 | prop <- propositionGen
16 | long <- valueGen
17 | } yield (prop, long)
18 |
19 | lazy val privGen: Gen[(PrivateKey25519, Nonce)] = for {
20 | prop <- key25519Gen.map(_._1)
21 | long <- nonceGen
22 | } yield (prop, long)
23 |
24 | lazy val simpleBoxTransactionGen: Gen[SimpleBoxTransaction] = for {
25 | fee <- positiveLongGen
26 | timestamp <- positiveLongGen
27 | from: IndexedSeq[(PrivateKey25519, Nonce)] <- smallInt.flatMap(i => Gen.listOfN(i + 1, privGen).map(_.toIndexedSeq))
28 | to: IndexedSeq[(PublicKey25519Proposition, Value)] <- smallInt.flatMap(i => Gen.listOfN(i, pGen).map(_.toIndexedSeq))
29 | } yield SimpleBoxTransaction(from, to, fee, timestamp)
30 |
31 | lazy val simpleBoxTransactionsGen: Gen[List[SimpleBoxTransaction]] = for {
32 | txs <- smallInt.flatMap(i => Gen.listOfN(i, simpleBoxTransactionGen))
33 | } yield txs
34 |
35 | def simpleBoxTransactionGenCustomMakeBoxes(toBoxes: IndexedSeq[(PublicKey25519Proposition, Value)]): Gen[SimpleBoxTransaction] = for {
36 | fee <- positiveLongGen
37 | timestamp <- positiveLongGen
38 | from: IndexedSeq[(PrivateKey25519, Nonce)] <- Gen.choose(1, 1).flatMap(i => Gen.listOfN(i + 1, privGen).map(_.toIndexedSeq))
39 | to = toBoxes
40 | } yield SimpleBoxTransaction(from, to, fee, timestamp)
41 |
42 | def simpleBoxTransactionGenCustomUseBoxes(fromBoxes: IndexedSeq[(PrivateKey25519, Nonce)]): Gen[SimpleBoxTransaction] = for {
43 | fee <- positiveLongGen
44 | timestamp <- positiveLongGen
45 | from = fromBoxes
46 | to: IndexedSeq[(PublicKey25519Proposition, Value)] <- Gen.choose(1, 1).flatMap(i => Gen.listOfN(i, pGen).map(_.toIndexedSeq))
47 | } yield SimpleBoxTransaction(from, to, fee, timestamp)
48 | }
49 |
--------------------------------------------------------------------------------
/examples/src/test/scala/hybrid/HistoryGenerators.scala:
--------------------------------------------------------------------------------
1 | package hybrid
2 |
3 | import examples.hybrid.blocks.PowBlock
4 | import examples.hybrid.history.{HistoryStorage, HybridHistory}
5 | import examples.hybrid.mining.HybridSettings
6 | import org.scalacheck.Gen
7 | import scorex.core.transaction.box.proposition.PublicKey25519Proposition
8 | import scorex.core.utils.NetworkTimeProvider
9 | import scorex.crypto.signatures.PublicKey
10 |
11 | import scala.concurrent.ExecutionContext.Implicits.global
12 |
13 | trait HistoryGenerators {
14 | this: StoreGenerators =>
15 |
16 | def settings: HybridSettings
17 |
18 | private val historyTimestamp = 1478164225796L
19 | private val historyNonce = -308545845552064644L
20 | private val historyBrothersCount = 0
21 | private val historyBrothersHash = Array.fill(32)(0: Byte)
22 | private val historyBrothers = Seq.empty
23 | private val historyProposition = PublicKey25519Proposition(PublicKey @@ scorex.utils.Random.randomBytes(32))
24 |
25 | private lazy val genesisBlock = PowBlock(
26 | settings.mining.GenesisParentId,
27 | settings.mining.GenesisParentId,
28 | historyTimestamp,
29 | historyNonce,
30 | historyBrothersCount,
31 | historyBrothersHash,
32 | historyProposition,
33 | historyBrothers)
34 |
35 | val historyGen: Gen[HybridHistory] = lsmStoreGen.map { blockStorage =>
36 | val storage = new HistoryStorage(blockStorage, settings.mining)
37 | //we don't care about validation here
38 | val validators = Seq()
39 | new HybridHistory(storage, settings.mining, validators, None, new NetworkTimeProvider(settings.scorexSettings.ntp))
40 | .append(genesisBlock).get._1
41 | .ensuring(_.modifierById(genesisBlock.id).isDefined)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/examples/src/test/scala/hybrid/NodeViewHolderGenerators.scala:
--------------------------------------------------------------------------------
1 | package hybrid
2 |
3 | import akka.actor.{ActorRef, ActorSystem, Props}
4 | import akka.testkit.TestProbe
5 | import examples.commons.SimpleBoxTransactionMemPool
6 | import examples.hybrid.HybridNodeViewHolder
7 | import examples.hybrid.wallet.HBoxWallet
8 | import io.iohk.iodb.ByteArrayWrapper
9 | import scorex.core._
10 | import scorex.core.utils.NetworkTimeProvider
11 |
12 | import scala.concurrent.ExecutionContext.Implicits.global
13 |
14 | @SuppressWarnings(Array("org.wartremover.warts.TraversableOps"))
15 | trait NodeViewHolderGenerators {
16 | this: ModifierGenerators with StateGenerators with HistoryGenerators with HybridTypes =>
17 |
18 | class NodeViewHolderForTests(h: HT, s: ST) extends HybridNodeViewHolder(settings, new NetworkTimeProvider(settings.scorexSettings.ntp)) {
19 |
20 | override protected def genesisState: (HIS, MS, VL, MP) = {
21 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
22 | val store = lsmStoreGen.sample.get
23 | val seed = Array.fill(10)(1: Byte)
24 | val gw = new HBoxWallet(seed, store)
25 | (h, s, gw, SimpleBoxTransactionMemPool.emptyPool)
26 | }
27 |
28 | override def restoreState(): Option[(HIS, MS, VL, MP)] = None
29 | }
30 |
31 | object NodeViewHolderForTests {
32 | def props(h: HT, s: ST): Props = Props(new NodeViewHolderForTests(h, s))
33 | }
34 |
35 | def nodeViewHolder(implicit system: ActorSystem): (ActorRef, TestProbe, PM, ST, HT) = {
36 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
37 | val h = historyGen.sample.get
38 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
39 | val sRaw = stateGen.sample.get
40 | val v = h.openSurfaceIds().last
41 | sRaw.store.update(ByteArrayWrapper(idToBytes(v)), Seq(), Seq())
42 | val s = sRaw.copy(version = idToVersion(v))
43 | val ref = system.actorOf(NodeViewHolderForTests.props(h, s))
44 | val m = totallyValidModifier(h, s)
45 | val eventListener = TestProbe()
46 | (ref, eventListener, m, s, h)
47 | }
48 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/src/test/scala/hybrid/transaction/TransactionSuite.scala:
--------------------------------------------------------------------------------
1 | package hybrid.transaction
2 |
3 | import examples.commons.SimpleBoxTransaction
4 | import hybrid.HybridGenerators
5 | import io.iohk.iodb.ByteArrayWrapper
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.PrivateKey25519Companion
10 |
11 |
12 | class TransactionSuite extends AnyPropSpec
13 | with ScalaCheckPropertyChecks
14 | with Matchers
15 | with HybridGenerators {
16 |
17 | property("SimpleBoxTransaction preservers inputs ids") {
18 | forAll(noncedBoxWithKeyListGen, noncedBoxWithKeyListGen) { case (boxesWithKeysIn, boxesWithKeysOut) =>
19 |
20 | val inputs = boxesWithKeysIn.map { case (box, k) => k -> box.nonce }.toIndexedSeq
21 |
22 | boxesWithKeysIn.foreach { case (box, k) =>
23 | PrivateKey25519Companion.owns(k, box) shouldBe true
24 | }
25 |
26 | val boxIds = boxesWithKeysIn.map(_._1.id).map(ByteArrayWrapper.apply)
27 |
28 | val to = boxesWithKeysOut.map { case (box, _) =>
29 | box.proposition -> box.value
30 | }.toIndexedSeq
31 |
32 | val tx: SimpleBoxTransaction = SimpleBoxTransaction(inputs, to, 0, 0)
33 |
34 | val outKeys = boxesWithKeysOut.map(_._2).map(_.publicKeyBytes).map(ByteArrayWrapper.apply)
35 |
36 | tx.newBoxes.foreach { newBox =>
37 | outKeys.contains(ByteArrayWrapper(newBox.proposition.pubKeyBytes)) shouldBe true
38 | }
39 |
40 | tx.boxIdsToOpen.map(ByteArrayWrapper.apply).forall { bid =>
41 | boxIds.contains(bid)
42 | } shouldBe true
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.8
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | // Comment to get more information during initialization
2 | logLevel := Level.Warn
3 |
4 | // The Typesafe repository
5 | resolvers += "Typesafe repository" at "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 |
--------------------------------------------------------------------------------
/release-notes.md:
--------------------------------------------------------------------------------
1 | 2.0.0-RC4
2 | ---------
3 | * *modifierCompanions* renamed to *modifierSerializers* in *NodeViewHolder*
4 |
5 | 2.0.0-RC3
6 | ---------
7 | * *MinimalState* interface simplification: *validate()* puled away from the basic trait
8 | * *maxRollback* field added to *MinimalState*
9 | * No *transactions* field with an optional value in *PersistentNodeViewModifier*,
10 | use *TransactionsCarryingPersistentNodeViewModifier* descendant for modifiers with transactions.
11 | * Non-exhaustive pattern-matching fix in *NodeViewholder.pmodModify()*
12 | * Simplification of type parameters in many classes around the whole codebase
13 | * *FastCryptographicHash* removed
14 | * Some obsolete code removed, such as *temp/mining* folder, *ScoreObserver* class
15 | * Scrypto 2.0.0
16 | * Using tagged types instead of *Array[Byte]*, *suppertagged* microframework is used for that
17 |
18 | 2.0.0-RC2
19 | ---------
20 | * *MinimalState* interface made minimal
21 | * protocolVersion in P2P Handshake
22 | * Scrypto 1.2.3
23 | * *BoxMinimalState* moved to *scorex.mid.state*
24 |
25 | 2.0.0-RC1
26 | ---------
27 | * Transaction interface simplified (*fee* & *timestamp* fields removed)
28 | * Scala 2.12
29 | * IODB 0.3.1
30 | * *reportInvalid()* in History
31 | * Issue #19 fixed
32 | * MapDB dependency removed
33 | * StateChanges reworked
34 | * TwinsCoin example improved
35 |
36 | 2.0.0-M4
37 | --------
38 |
39 | * IODB dependency upgraded to 0.2.+
40 | * TwinsChain example massively improved, README on it has been added
41 | (see "examples" folder)
42 | * akka-http dependency removed, Swagger updated
43 |
44 |
45 | 2.0.0-M3
46 | --------
47 |
48 | Serialization have been reworked throughout the core to be consistent
49 | (see the new Serializer interface).
50 |
51 | Handshake timeout implemented: (p2p/handshakeTimeout in settings.json)
52 | Agent name and version have been moved to settings.json
53 | ("agent" and "version" settings)
54 |
55 | Hybrid chain example got bugfixing and new tests.
56 |
57 |
58 | 2.0.0-M2
59 | --------
60 |
61 | * Wallet interface added
62 | * MvStore dependency removed
63 | * StoredBlockchain trait removed
64 | * ViewSynchronizer trait removed
65 | * Miner and MiningController are removed from the core
66 | * Maven artefact has been renamed from "scorex-basics" to "scorex-core"
67 | * blockFields method of a Block has been removed
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | scorex-errors.log
5 | true
6 |
7 | %date{MM/dd HH:mm:ss} %-5level[%.15thread] %logger{1} %msg%n
8 |
9 |
10 | WARN
11 |
12 |
13 |
14 |
15 | scorex.log
16 | true
17 |
18 | %date{MM/dd HH:mm:ss} %-5level[%.15thread] %logger{1} %msg%n
19 |
20 |
21 |
22 | scorex.%d{yyyy-MM-dd}.%i.log
23 |
24 | 100MB
25 | 60
26 | 20GB
27 |
28 |
29 |
30 |
31 | System.out
32 |
33 | INFO
34 |
35 |
36 | [%thread] >> [%-5level] %logger{36} >> %d{HH:mm:ss.SSS} %msg%n
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/main/resources/swagger-ui/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperledger-labs/Scorex/9e1cd58a5589a9062dd9974f1884cc640c1d51b3/src/main/resources/swagger-ui/favicon-16x16.png
--------------------------------------------------------------------------------
/src/main/resources/swagger-ui/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperledger-labs/Scorex/9e1cd58a5589a9062dd9974f1884cc640c1d51b3/src/main/resources/swagger-ui/favicon-32x32.png
--------------------------------------------------------------------------------
/src/main/resources/swagger-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Swagger UI
7 |
8 |
9 |
10 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/main/resources/swagger-ui/oauth2-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
68 |
--------------------------------------------------------------------------------
/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/NodeViewModifier.scala:
--------------------------------------------------------------------------------
1 | package scorex.core
2 |
3 | import com.typesafe.config.ConfigFactory
4 | import scorex.core.serialization.BytesSerializable
5 | import scorex.core.transaction.Transaction
6 | import scorex.core.utils.ScorexEncoding
7 |
8 | import scala.util.Try
9 |
10 | sealed trait NodeViewModifier extends BytesSerializable with ScorexEncoding {
11 | self =>
12 |
13 | val modifierTypeId: ModifierTypeId
14 |
15 | //todo: check statically or dynamically output size
16 | def id: scorex.util.ModifierId
17 |
18 | def encodedId: String = encoder.encodeId(id)
19 |
20 | override def equals(obj: scala.Any): Boolean = obj match {
21 | case that: NodeViewModifier => (that.id == id) && (that.modifierTypeId == modifierTypeId)
22 | case _ => false
23 | }
24 | }
25 |
26 | trait EphemerealNodeViewModifier extends NodeViewModifier
27 |
28 | /**
29 | * It is supposed that all the modifiers (offchain transactions, blocks, blockheaders etc)
30 | * have identifiers of the some length fixed with the ModifierIdSize constant
31 | */
32 | object NodeViewModifier {
33 | private val DefaultIdSize: Byte = 32 // in bytes
34 |
35 | val ModifierIdSize: Int = Try(ConfigFactory.load().getConfig("app").getInt("modifierIdSize")).getOrElse(DefaultIdSize)
36 | }
37 |
38 | /**
39 | * Persistent node view modifier is a part of replicated log of state transformations.
40 | * The log should be deterministic and ordered to get a deterministic state after its
41 | * application to the genesis state and thus every modifier should have parent - modifier,
42 | * which application should precede this modifier application
43 | */
44 | trait PersistentNodeViewModifier extends NodeViewModifier {
45 | /**
46 | * Id modifier, which should be applied to the node view before this modifier
47 | */
48 | def parentId: scorex.util.ModifierId
49 | }
50 |
51 |
52 | trait TransactionsCarryingPersistentNodeViewModifier[TX <: Transaction]
53 | extends PersistentNodeViewModifier {
54 |
55 | def transactions: Seq[TX]
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/scala/scorex/core/api/client/ApiClient.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.api.client
2 |
3 | import java.net.{HttpURLConnection, URL}
4 |
5 | import scorex.core.settings.{RESTApiSettings, ScorexSettings}
6 |
7 | import scala.io.{Source, StdIn}
8 | import scala.util.{Failure, Success, Try}
9 |
10 |
11 | class ApiClient(settings: RESTApiSettings) {
12 | private val OkHttpCode = 200
13 |
14 | def executeCommand(command: String): String = {
15 | if (command.equals("help")) {
16 | " \n Type quit to stop."
17 | } else Try {
18 | val args = command.split(" ")
19 | val method = args.head.toUpperCase
20 | val path = args(1)
21 |
22 | val content = if (method.equals("POST")) {
23 | command.substring((method + " " + path + " ").length())
24 | } else ""
25 |
26 | // fixme: magic string "http://127.0.0.1:" should come from settings.
27 | // fixme: Shouldn' we use the address from the bind address instead of 127.0.0.1?
28 | val url = new URL("http://127.0.0.1:" + settings.bindAddress.getPort + "/" + path)
29 | val connection = url.openConnection().asInstanceOf[HttpURLConnection]
30 | connection.setRequestMethod(method)
31 |
32 | if (method.equals("POST")) {
33 | connection.setDoOutput(true)
34 | connection.getOutputStream.write(content.getBytes)
35 | connection.getOutputStream.flush()
36 | connection.getOutputStream.close()
37 | }
38 |
39 | val stream = connection.getResponseCode match {
40 | case OkHttpCode => connection.getInputStream
41 | case _ => connection.getErrorStream
42 | }
43 |
44 | val result = Source.fromInputStream(stream).mkString("")
45 | Try(result)
46 | }.flatten match {
47 | case Success(result) => result
48 | case Failure(e) =>
49 | s"Problem occurred $e! \n Type help to get a list of commands."
50 | }
51 | }
52 | }
53 |
54 | object ApiClient extends App {
55 | private val settingsFilename = args.headOption.getOrElse("settings.conf")
56 | private val settings = ScorexSettings.read(Some(settingsFilename))
57 | private val apiClient = new ApiClient(settings.restApi)
58 |
59 | println("Welcome to the Scorex command-line client...")
60 | Iterator.continually(StdIn.readLine()).takeWhile(!_.equals("quit")).foreach { command =>
61 | println(s"[$command RESULT] " + apiClient.executeCommand(command))
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/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/api/http/ApiError.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.api.http
2 |
3 | import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCode, StatusCodes}
4 | import akka.http.scaladsl.server.{Directives, Route}
5 | import io.circe.syntax._
6 |
7 | import scala.language.implicitConversions
8 |
9 | case class ApiError(statusCode: StatusCode, reason: String = "") {
10 |
11 | def apply(detail: String): Route = complete(detail)
12 |
13 | def defaultRoute: Route = complete()
14 |
15 | def complete(detail: String = ""): Route = {
16 | val nonEmptyReason = if (reason.isEmpty) statusCode.reason else reason
17 | val detailOpt = if (detail.isEmpty) None else Some(detail)
18 | val response = Map(
19 | "error" -> statusCode.intValue.asJson,
20 | "reason" -> nonEmptyReason.asJson,
21 | "detail" -> detailOpt.asJson
22 | ).asJson
23 | val entity = HttpEntity(ContentTypes.`application/json`, response.spaces2)
24 | Directives.complete(statusCode.intValue() -> entity)
25 | }
26 | }
27 |
28 | object ApiError {
29 |
30 | def apply(s: String): Route = InternalError(s)
31 | def apply(e: Throwable): Route = InternalError(safeMessage(e))
32 | def apply(causes: Seq[Throwable]): Route = InternalError(mkString(causes))
33 | def mkString(causes: Seq[Throwable]): String = causes.map(safeMessage).mkString(", ")
34 | private def safeMessage(e: Throwable): String = Option(e.getMessage).getOrElse(e.toString)
35 |
36 | implicit def toRoute(error: ApiError): Route = error.defaultRoute
37 |
38 | object InternalError extends ApiError(StatusCodes.InternalServerError, "internal.error")
39 | object InvalidJson extends ApiError(StatusCodes.BadRequest, "invalid.json")
40 | object BadRequest extends ApiError(StatusCodes.BadRequest, "bad.request")
41 | object ApiKeyNotValid extends ApiError(StatusCodes.Forbidden, "invalid.api-key")
42 | object NotExists extends ApiError(StatusCodes.NotFound, "not-found")
43 | object Forbidden extends ApiError(StatusCodes.Forbidden, "forbidden")
44 | }
45 |
--------------------------------------------------------------------------------
/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/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/api/http/ApiResponse.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.api.http
2 |
3 | import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCode, StatusCodes}
4 | import akka.http.scaladsl.server.{Directives, Route}
5 | import io.circe.syntax._
6 | import io.circe.{Encoder, Json}
7 |
8 | import scala.concurrent.Future
9 | import scala.language.implicitConversions
10 |
11 | /** Bunch of methods to wrap json result to route with the given status code, completing it.
12 | * When receiving `Null` json, then complete route with `404 not found` status code.
13 | */
14 | class ApiResponse(statusCode: StatusCode) {
15 |
16 | def apply(result: Json): Route = withJson(result)
17 | def apply[R: Encoder](result: R): Route = withJson(result)
18 | def apply[R: Encoder](result: Future[R]): Route = Directives.onSuccess(result)(withJson)
19 | def apply(result: Future[Json]): Route = Directives.onSuccess(result)(withJson)
20 |
21 | def defaultRoute: Route = withString(defaultMessage)
22 | def defaultMessage: String = statusCode.reason
23 |
24 | def withString(s: String): Route = complete(s.asJson)
25 |
26 | def withJson[R](result: R)(implicit encoder: Encoder[R]): Route = complete(encoder(result))
27 |
28 | def complete(result: Json): Route = {
29 | if (result.isNull) {
30 | ApiError.NotExists
31 | } else {
32 | val httpEntity = HttpEntity(ContentTypes.`application/json`, result.spaces2)
33 | Directives.complete(statusCode.intValue() -> httpEntity)
34 | }
35 | }
36 | }
37 |
38 | object ApiResponse {
39 | implicit def toRoute(jsonRoute: ApiResponse): Route = jsonRoute.defaultRoute
40 |
41 | def apply[R: Encoder](result: R): Route = OK(result)
42 | def apply[R: Encoder](result: Future[R]): Route = OK(result)
43 | def apply(result: Either[Throwable, Json]): Route = result.fold(ApiError.apply, OK.apply)
44 | def apply(keyValues: (String, Json)*): Route = apply(Map(keyValues: _*))
45 | def apply(keyValue: (String, String)): Route = apply(Map(keyValue))
46 |
47 | object OK extends ApiResponse(StatusCodes.OK)
48 | }
49 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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 | }
--------------------------------------------------------------------------------
/src/main/scala/scorex/core/block/Block.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.block
2 |
3 | import scorex.core.block.Block.{Timestamp, Version}
4 | import scorex.core.consensus.History
5 | import scorex.core.serialization.ScorexSerializer
6 | import scorex.core.transaction._
7 | import scorex.core.transaction.box.proposition.Proposition
8 | import scorex.core.{NodeViewModifier, TransactionsCarryingPersistentNodeViewModifier}
9 | import scorex.util.ModifierId
10 |
11 | /**
12 | * A block is an atomic piece of data network participates are agreed on.
13 | *
14 | * A block has:
15 | * - transactional data: a sequence of transactions, where a transaction is an atomic state update.
16 | * Some metadata is possible as well(transactions Merkle tree root, state Merkle tree root etc).
17 | *
18 | * - consensus data to check whether block was generated by a right party in a right way. E.g.
19 | * "baseTarget" & "generatorSignature" fields in the Nxt block structure, nonce & difficulty in the
20 | * Bitcoin block structure.
21 | *
22 | * - a signature(s) of a block generator(s)
23 | *
24 | * - additional data: block structure version no, timestamp etc
25 | */
26 |
27 | trait Block[TX <: Transaction]
28 | extends TransactionsCarryingPersistentNodeViewModifier[TX] {
29 |
30 | def version: Version
31 |
32 | def timestamp: Timestamp
33 | }
34 |
35 | object Block {
36 | type BlockId = ModifierId
37 | type Timestamp = Long
38 | type Version = Byte
39 |
40 | val BlockIdLength: Int = NodeViewModifier.ModifierIdSize
41 |
42 | }
43 |
44 | trait BlockCompanion[P <: Proposition, TX <: Transaction, B <: Block[TX]]
45 | extends ScorexSerializer[B] {
46 |
47 | def isValid(block: B): Boolean
48 |
49 | /**
50 | * Get block producers(miners/forgers). Usually one miner produces a block, but in some proposals not
51 | * (see e.g. Proof-of-Activity paper of Bentov et al. http://eprint.iacr.org/2014/452.pdf)
52 | *
53 | * @return blocks' producers
54 | */
55 | def producers(block: B, history: History[B, _, _]): Seq[P]
56 | }
57 |
--------------------------------------------------------------------------------
/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/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/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/consensus/HistoryReader.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.consensus
2 |
3 | import scorex.core.{NodeViewComponent, PersistentNodeViewModifier}
4 | import scorex.util.ModifierId
5 |
6 | import scala.util.Try
7 |
8 |
9 | trait HistoryReader[PM <: PersistentNodeViewModifier, SI <: SyncInfo] extends NodeViewComponent
10 | with ContainsModifiers[PM] {
11 |
12 | import History._
13 |
14 | /**
15 | * Is there's no history, even genesis block
16 | */
17 | def isEmpty: Boolean
18 |
19 | /**
20 | * Whether a modifier could be applied to the history
21 | *
22 | * @param modifier - modifier to apply
23 | * @return `Success` if modifier can be applied, `Failure(ModifierError)` if can not
24 | */
25 | def applicableTry(modifier: PM): Try[Unit]
26 |
27 | /**
28 | * Return semantic validity status of modifier with id == modifierId
29 | *
30 | * @param modifierId - modifier id to check
31 | * @return
32 | */
33 | def isSemanticallyValid(modifierId: ModifierId): ModifierSemanticValidity
34 |
35 | //todo: output should be ID | Seq[ID]
36 | def openSurfaceIds(): Seq[ModifierId]
37 |
38 | /**
39 | * Ids of modifiers, that node with info should download and apply to synchronize
40 | */
41 | def continuationIds(info: SI, size: Int): ModifierIds
42 |
43 | /**
44 | * Information about our node synchronization status. Other node should be able to compare it's view with ours by
45 | * this syncInfo message and calculate modifiers missed by our node.
46 | *
47 | * @return
48 | */
49 | def syncInfo: SI
50 |
51 | /**
52 | * Whether another's node syncinfo shows that another node is ahead or behind ours
53 | *
54 | * @param other other's node sync info
55 | * @return Equal if nodes have the same history, Younger if another node is behind, Older if a new node is ahead
56 | */
57 | def compare(other: SI): HistoryComparisonResult
58 | }
59 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/main/scala/scorex/core/core.scala:
--------------------------------------------------------------------------------
1 | package scorex
2 |
3 | import scorex.core.network.message.InvData
4 | import scorex.core.utils.ScorexEncoder
5 | import scorex.util.encode.Base16
6 | import supertagged.TaggedType
7 |
8 | package object core {
9 |
10 | //TODO implement ModifierTypeId as a trait
11 | object ModifierTypeId extends TaggedType[Byte]
12 |
13 | @deprecated("use `scorex.util.ModifierId`", "")
14 | type ModifierId = scorex.util.ModifierId.Type
15 |
16 | @deprecated("use `scorex.util.ModifierId`", "")
17 | val ModifierId: scorex.util.ModifierId.type = scorex.util.ModifierId
18 |
19 | object VersionTag extends TaggedType[String]
20 |
21 | type ModifierTypeId = ModifierTypeId.Type
22 |
23 | type VersionTag = VersionTag.Type
24 |
25 | def idsToString(ids: Seq[(ModifierTypeId, util.ModifierId)])(implicit enc: ScorexEncoder): String = {
26 | List(ids.headOption, ids.lastOption)
27 | .flatten
28 | .map { case (typeId, id) => s"($typeId,${enc.encodeId(id)})" }
29 | .mkString("[", "..", "]")
30 | }
31 |
32 | def idsToString(modifierType: ModifierTypeId, ids: Seq[util.ModifierId])(implicit encoder: ScorexEncoder): String = {
33 | idsToString(ids.map(id => (modifierType, id)))
34 | }
35 |
36 | def idsToString(invData: InvData)(implicit encoder: ScorexEncoder): String = idsToString(invData.typeId, invData.ids)
37 |
38 | def bytesToId: Array[Byte] => util.ModifierId = scorex.util.bytesToId
39 |
40 | def idToBytes: util.ModifierId => Array[Byte] = scorex.util.idToBytes
41 |
42 | def bytesToVersion(bytes: Array[Byte]): VersionTag = VersionTag @@ Base16.encode(bytes)
43 |
44 | def versionToBytes(id: VersionTag): Array[Byte] = Base16.decode(id).get
45 |
46 | def versionToId(version: VersionTag): util.ModifierId = util.ModifierId @@ version
47 |
48 | def idToVersion(id: util.ModifierId): VersionTag = VersionTag @@ id
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/main/scala/scorex/core/network/Synchronizer.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.network
2 |
3 | import scorex.core.network.message.MessageSpec
4 | import scorex.util.ScorexLogging
5 |
6 | import scala.util.{Failure, Success}
7 |
8 | trait Synchronizer extends ScorexLogging {
9 |
10 | // these are the case statements for identifying the message handlers
11 | protected val msgHandlers: PartialFunction[(MessageSpec[_], _, ConnectedPeer), Unit]
12 |
13 | /**
14 | * This method will attempt to parse a message from a remote peer into it class representation and use
15 | * the defined message handlers for processing the message
16 | *
17 | * @param spec the message specification (basically a header informing of the message type)
18 | * @param msgBytes a ByteString of the message data that must be parsed
19 | * @param source the remote peer that sent the message
20 | */
21 | protected def parseAndHandle(spec: MessageSpec[Any], msgBytes: Array[Byte], source: ConnectedPeer): Unit = {
22 | // attempt to parse the message
23 | spec.parseBytesTry(msgBytes) match {
24 | // if a message could be parsed, match the type of content found and ensure a handler is defined
25 | case Success(content) =>
26 | val parsedMsg = (spec, content, source)
27 | if (msgHandlers.isDefinedAt(parsedMsg)) {
28 | msgHandlers.apply(parsedMsg)
29 | } else {
30 | log.error(s"Function handler not found for the parsed message: $parsedMsg")
31 | }
32 |
33 | // if a message could not be parsed, penalize the remote peer
34 | case Failure(e) =>
35 | log.error(s"Failed to deserialize data from $source: ", e)
36 | penalizeMaliciousPeer(source)
37 | }
38 | }
39 |
40 | /**
41 | * Handles how a peer that sent un-parsable data should be handled
42 | *
43 | * @param peer peer that sent the offending message
44 | */
45 | protected def penalizeMaliciousPeer(peer: ConnectedPeer): Unit
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/scala/scorex/core/network/message/Message.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.network.message
2 |
3 |
4 | import akka.actor.DeadLetterSuppression
5 | import scorex.core.network.ConnectedPeer
6 | import scala.util.{Success, Try}
7 |
8 |
9 | /**
10 | * Wrapper for a network message, whether come from external peer or generated locally
11 | *
12 | * @param spec - message specification
13 | * @param input - message being wrapped, whether in byte-array form (if from outside),
14 | * or structured data (if formed locally)
15 | * @param source - source peer, if the message is from outside
16 | * @tparam Content - message data type
17 | */
18 | case class Message[Content](spec: MessageSpec[Content],
19 | input: Either[Array[Byte], Content],
20 | source: Option[ConnectedPeer])
21 | extends DeadLetterSuppression {
22 |
23 | import Message._
24 |
25 | /**
26 | * Message data bytes
27 | */
28 | lazy val dataBytes: Array[Byte] = input match {
29 | case Left(db) => db
30 | case Right(d) => spec.toBytes(d)
31 | }
32 |
33 | /**
34 | * Structured message content
35 | */
36 | lazy val data: Try[Content] = input match {
37 | case Left(db) => spec.parseBytesTry(db)
38 | case Right(d) => Success(d)
39 | }
40 |
41 | lazy val dataLength: Int = dataBytes.length
42 |
43 | /**
44 | * @return serialized message length in bytes
45 | */
46 | def messageLength: Int = {
47 | if (dataLength > 0) {
48 | HeaderLength + ChecksumLength + dataLength
49 | } else {
50 | HeaderLength
51 | }
52 | }
53 |
54 | }
55 |
56 | object Message {
57 | type MessageCode = Byte
58 |
59 | val MagicLength: Int = 4
60 |
61 | val ChecksumLength: Int = 4
62 |
63 | val HeaderLength: Int = MagicLength + 5
64 | }
65 |
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/main/scala/scorex/core/utils/NetworkTime.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.utils
2 |
3 | import java.net.InetAddress
4 | import java.util.concurrent.atomic.AtomicLong
5 |
6 | import org.apache.commons.net.ntp.NTPUDPClient
7 |
8 | import scala.concurrent.duration._
9 | import scala.concurrent.{ExecutionContext, Future}
10 | import scala.util.{Failure, Success}
11 |
12 | object NetworkTime {
13 | def localWithOffset(offset: Long): Long = System.currentTimeMillis() + offset
14 | type Offset = Long
15 | }
16 |
17 | case class NetworkTimeProviderSettings(server: String, updateEvery: FiniteDuration, timeout: FiniteDuration)
18 |
19 | class NetworkTimeProvider(ntpSettings: NetworkTimeProviderSettings)(implicit ec: ExecutionContext)
20 | extends TimeProvider with scorex.util.ScorexLogging {
21 |
22 | private val lastUpdate = new AtomicLong(0)
23 | private var offset = new AtomicLong(0)
24 | private val client = new NTPUDPClient()
25 | client.setDefaultTimeout(ntpSettings.timeout.toMillis.toInt)
26 | client.open()
27 |
28 | override def time(): TimeProvider.Time = {
29 | checkUpdateRequired()
30 | NetworkTime.localWithOffset(offset.get())
31 | }
32 |
33 | private def updateOffset(): Future[NetworkTime.Offset] = Future {
34 | val info = client.getTime(InetAddress.getByName(ntpSettings.server))
35 | info.computeDetails()
36 | info.getOffset
37 | }
38 |
39 | private def checkUpdateRequired(): Unit = {
40 | val time = NetworkTime.localWithOffset(offset.get())
41 | // set lastUpdate to current time so other threads won't start to update it
42 | val lu = lastUpdate.getAndSet(time)
43 | if (time > lu + ntpSettings.updateEvery.toMillis) {
44 | // time to update offset
45 | updateOffset().onComplete {
46 | case Success(newOffset) =>
47 | offset.set(newOffset)
48 | log.info("New offset adjusted: " + offset)
49 | lastUpdate.set(time)
50 | case Failure(e) =>
51 | log.warn("Problems with NTP: ", e)
52 | lastUpdate.compareAndSet(time, lu)
53 | }
54 | } else {
55 | // No update required. Set lastUpdate back to it's initial value
56 | lastUpdate.compareAndSet(time, lu)
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/utils.scala:
--------------------------------------------------------------------------------
1 | package scorex.core
2 |
3 | import java.security.SecureRandom
4 |
5 | import scala.annotation.tailrec
6 | import scala.concurrent.duration._
7 | import scala.util.{Failure, Success, Try}
8 |
9 | package object utils {
10 |
11 | @deprecated("Use scorex.util.ScorexLogging instead.", "scorex-util 0.1.0")
12 | type ScorexLogging = scorex.util.ScorexLogging
13 |
14 | /**
15 | * @param block - function to profile
16 | * @return - execution time in seconds and function result
17 | */
18 | def profile[R](block: => R): (Float, R) = {
19 | val t0 = System.nanoTime()
20 | val result = block // call-by-name
21 | val t1 = System.nanoTime()
22 | ((t1 - t0).toFloat / 1000000000, result)
23 | }
24 |
25 | def toTry(b: Boolean, msg: String): Try[Unit] = b match {
26 | case true => Success(Unit)
27 | case false => Failure(new Exception(msg))
28 | }
29 |
30 | @tailrec
31 | final def untilTimeout[T](timeout: FiniteDuration,
32 | delay: FiniteDuration = 100.milliseconds)(fn: => T): T = {
33 | Try {
34 | fn
35 | } match {
36 | case Success(x) => x
37 | case _ if timeout > delay =>
38 | Thread.sleep(delay.toMillis)
39 | untilTimeout(timeout - delay, delay)(fn)
40 | case Failure(e) => throw e
41 | }
42 | }
43 |
44 | def randomBytes(howMany: Int): Array[Byte] = {
45 | val r = new Array[Byte](howMany)
46 | new SecureRandom().nextBytes(r) //overrides r
47 | r
48 | }
49 |
50 | def concatBytes(seq: Traversable[Array[Byte]]): Array[Byte] = {
51 | val length: Int = seq.map(_.length).sum
52 | val result: Array[Byte] = new Array[Byte](length)
53 | var pos: Int = 0
54 | seq.foreach { array =>
55 | System.arraycopy(array, 0, result, pos, array.length)
56 | pos += array.length
57 | }
58 | result
59 | }
60 |
61 | def concatFixLengthBytes(seq: Traversable[Array[Byte]]): Array[Byte] = seq.headOption match {
62 | case None => Array[Byte]()
63 | case Some(head) => concatFixLengthBytes(seq, head.length)
64 | }
65 |
66 |
67 | def concatFixLengthBytes(seq: Traversable[Array[Byte]], length: Int): Array[Byte] = {
68 | val result: Array[Byte] = new Array[Byte](seq.toSeq.length * length)
69 | var index = 0
70 | seq.foreach { s =>
71 | Array.copy(s, 0, result, index, length)
72 | index += length
73 | }
74 | result
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/scala/scorex/core/validation/ModifierError.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.validation
2 |
3 | import scala.util.control.NoStackTrace
4 |
5 | /** Base trait for errors that were occurred during NodeView Modifier validation
6 | */
7 | trait ModifierError {
8 | def message: String
9 | def isFatal: Boolean
10 | def toThrowable: Throwable
11 |
12 | def info: String = {
13 | val fatality = if (isFatal) "fatally" else "recoverably"
14 | s"Modifier Validation failed $fatality: $message"
15 | }
16 | }
17 |
18 | /** Permanent modifier error that could not be recovered in future even after any history updates
19 | */
20 | @SuppressWarnings(Array("org.wartremover.warts.Null"))
21 | class MalformedModifierError(val message: String, cause: Option[Throwable] = None)
22 | extends Exception(message, cause.orNull) with ModifierError {
23 | def isFatal: Boolean = true
24 | def toThrowable: Throwable = this
25 | }
26 |
27 | /** Temporary modifier error that may be recovered in future after some history updates.
28 | * When an instance is created, the stack trace is not collected which makes this exception lightweight.
29 | */
30 | @SuppressWarnings(Array("org.wartremover.warts.Null"))
31 | class RecoverableModifierError(val message: String, cause: Option[Throwable] = None)
32 | extends Exception(message, cause.orNull) with ModifierError with NoStackTrace {
33 | def isFatal: Boolean = false
34 | def toThrowable: Throwable = this
35 | }
36 |
37 |
38 | /** Composite error class that can hold more than one modifier error inside. This was not made a `ModifierError` instance
39 | * intentionally to prevent nesting `MultipleErrors` to `MultipleErrors`
40 | */
41 | @SuppressWarnings(Array("org.wartremover.warts.Null"))
42 | case class MultipleErrors(errors: Seq[ModifierError])
43 | extends Exception(errors.mkString(" | "), errors.headOption.map(_.toThrowable).orNull) {
44 | def isFatal: Boolean = errors.exists(_.isFatal)
45 | }
46 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/main/scala/scorex/util/serialization/VLQByteStringReader.scala:
--------------------------------------------------------------------------------
1 | package scorex.util.serialization
2 |
3 | import java.nio.ByteOrder
4 |
5 | import akka.util.ByteString
6 |
7 | class VLQByteStringReader(byteString: ByteString) extends VLQReader {
8 |
9 | type CH = ByteString
10 |
11 | private var it = byteString.iterator
12 | private var _position = 0
13 | private var _mark = 0
14 | private implicit val byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN
15 |
16 | @inline
17 | override def newReader(chunk: ByteString): Reader.Aux[CH] = {
18 | new VLQByteStringReader(chunk)
19 | }
20 |
21 | /**
22 | * Get a byte at current position without advancing the position.
23 | *
24 | * @return byte at current position
25 | */
26 | @inline
27 | override def peekByte(): Byte = byteString(position)
28 |
29 | @inline
30 | override def getByte(): Byte = {
31 | incPosition()
32 | it.getByte
33 | }
34 |
35 | @inline
36 | override def getBytes(size: Int): Array[Byte] = {
37 | require(size <= remaining, s"Not enough bytes in the buffer: $size")
38 | incPosition(size)
39 | it.getBytes(size)
40 | }
41 |
42 | @inline
43 | override def getChunk(size: Int): ByteString = {
44 | it.getByteString(size)
45 | }
46 |
47 | @inline
48 | override def mark(): this.type = {
49 | _mark = _position
50 | this
51 | }
52 |
53 | @inline
54 | override def consumed: Int = _position - _mark
55 |
56 | @inline
57 | override def position: Int = _position
58 |
59 | @inline
60 | override def position_=(p: Int): Unit = {
61 | _position = p
62 | it = byteString.iterator.drop(p)
63 | }
64 |
65 | @inline
66 | override def remaining: Int = it.len
67 |
68 | @inline
69 | private def incPosition(): Unit = {
70 | _position += 1
71 | }
72 |
73 | @inline
74 | private def incPosition(size: Int): Unit = {
75 | _position += size
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/test/scala/scorex/core/api/http/UtilsApiRouteSpec.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.api.http
2 |
3 | import java.net.InetSocketAddress
4 |
5 | import akka.http.scaladsl.model.{ContentTypes, StatusCodes, HttpEntity}
6 | import akka.http.scaladsl.server
7 | import akka.http.scaladsl.testkit.{ScalatestRouteTest, RouteTestTimeout}
8 | import akka.testkit.TestDuration
9 | import io.circe.syntax._
10 | import org.scalatest.flatspec.AnyFlatSpec
11 | import org.scalatest.matchers.should.Matchers
12 | import scorex.core.settings.RESTApiSettings
13 |
14 | import scala.concurrent.duration._
15 | import scala.language.postfixOps
16 |
17 | class UtilsApiRouteSpec extends AnyFlatSpec
18 | with Matchers
19 | with ScalatestRouteTest
20 | with Stubs {
21 |
22 | implicit val timeout: RouteTestTimeout = RouteTestTimeout(15.seconds dilated)
23 |
24 | val addr: InetSocketAddress = new InetSocketAddress("localhost", 8080)
25 | val restApiSettings: RESTApiSettings = RESTApiSettings(addr, None, None, 10 seconds)
26 | val prefix: String = "/utils"
27 | val routes: server.Route = UtilsApiRoute(restApiSettings).route
28 |
29 | it should "send random seeds" in {
30 | Get(prefix + "/seed") ~> routes ~> check {
31 | status shouldBe StatusCodes.OK
32 | responseAs[String] should not be empty
33 | }
34 |
35 | Get(prefix + "/seed/32") ~> routes ~> check {
36 | status shouldBe StatusCodes.OK
37 | responseAs[String] should not be empty
38 | }
39 |
40 | Get(prefix + "/seed/64") ~> routes ~> check {
41 | status shouldBe StatusCodes.OK
42 | responseAs[String] should not be empty
43 | }
44 | }
45 |
46 | it should "hash string with blake2b" in {
47 | val msg = HttpEntity("hash_me".asJson.toString).withContentType(ContentTypes.`application/json`)
48 | Post(prefix + "/hash/blake2b", msg) ~> routes ~> check {
49 | status shouldBe StatusCodes.OK
50 | responseAs[String] should not be empty
51 | responseAs[String] should not be msg
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/scala/scorex/core/network/HandshakeSpecification.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.network
2 |
3 | import java.net.InetSocketAddress
4 |
5 | import org.scalacheck.Gen
6 | import org.scalatest.matchers.should.Matchers
7 | import org.scalatest.propspec.AnyPropSpec
8 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
9 | import scorex.ObjectGenerators
10 | import scorex.core.app.Version
11 | import scorex.core.network.message.HandshakeSpec
12 |
13 |
14 | class HandshakeSpecification extends AnyPropSpec
15 | with ScalaCheckPropertyChecks
16 | with Matchers
17 | with ObjectGenerators {
18 |
19 | private val featSerializers = Map(FullNodePeerFeature.featureId -> FullNodePeerFeature.serializer)
20 | private val noSerializers: PeerFeature.Serializers = Map()
21 |
22 | property("handshake should remain the same after serialization/deserialization") {
23 | val serializersGen = Gen.oneOf(featSerializers, noSerializers)
24 | val featuresGen: Gen[Seq[PeerFeature]] = Gen.oneOf(Seq(FullNodePeerFeature), Seq[PeerFeature]())
25 |
26 | forAll(Gen.alphaStr, appVersionGen, Gen.alphaStr, Gen.option(inetSocketAddressGen), Gen.posNum[Long], serializersGen) {
27 | (appName: String,
28 | av: Version,
29 | nodeName: String,
30 | isaOpt: Option[InetSocketAddress],
31 | time: Long,
32 | serializers: PeerFeature.Serializers) =>
33 |
34 | whenever(appName.nonEmpty) {
35 | val feats: Seq[PeerFeature] = featuresGen.sample.getOrElse(Seq())
36 |
37 | val handshakeSerializer = new HandshakeSpec(serializers, Int.MaxValue)
38 |
39 | val h = Handshake(PeerSpec(appName, av, nodeName, isaOpt, feats), time)
40 | val hr = handshakeSerializer.parseBytes(handshakeSerializer.toBytes(h))
41 | hr.peerSpec.agentName should be(h.peerSpec.agentName)
42 | hr.peerSpec.protocolVersion should be(h.peerSpec.protocolVersion)
43 | hr.peerSpec.declaredAddress should be(h.peerSpec.declaredAddress)
44 | if (serializers.nonEmpty) hr.peerSpec.features shouldBe h.peerSpec.features else hr.peerSpec.features.isEmpty shouldBe true
45 | hr.time should be(h.time)
46 | }
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/test/scala/scorex/core/network/peer/PeerManagerSpec.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.network.peer
2 |
3 | import java.net.InetSocketAddress
4 |
5 | import akka.actor.{ActorRef, ActorSystem}
6 | import akka.testkit.TestProbe
7 | import scorex.core.app.ScorexContext
8 | import scorex.network.NetworkTests
9 |
10 |
11 | class PeerManagerSpec extends NetworkTests {
12 |
13 | import scorex.core.network.peer.PeerManager.ReceivableMessages.{AddOrUpdatePeer, GetAllPeers}
14 |
15 | type Data = Map[InetSocketAddress, PeerInfo]
16 | private val DefaultPort = 27017
17 |
18 | it should "ignore adding self as a peer" in {
19 | implicit val system = ActorSystem()
20 | val p = TestProbe("p")(system)
21 | implicit val defaultSender: ActorRef = p.testActor
22 |
23 |
24 | val selfAddress = settings.network.bindAddress
25 | val scorexContext = ScorexContext(Seq.empty, Seq.empty, None, timeProvider, Some(selfAddress))
26 | val peerManager = PeerManagerRef(settings, scorexContext)(system)
27 | val peerInfo = getPeerInfo(selfAddress)
28 |
29 | peerManager ! AddOrUpdatePeer(peerInfo)
30 | peerManager ! GetAllPeers
31 | val data = p.expectMsgClass(classOf[Data])
32 |
33 | data.keySet should not contain selfAddress
34 | system.terminate()
35 | }
36 |
37 | it should "added peer be returned in GetAllPeers" in {
38 | implicit val system = ActorSystem()
39 | val p = TestProbe("p")(system)
40 | implicit val defaultSender: ActorRef = p.testActor
41 |
42 | val scorexContext = ScorexContext(Seq.empty, Seq.empty, None, timeProvider, None)
43 | val peerManager = PeerManagerRef(settings, scorexContext)(system)
44 | val peerAddress = new InetSocketAddress("1.1.1.1", DefaultPort)
45 | val peerInfo = getPeerInfo(peerAddress)
46 |
47 | peerManager ! AddOrUpdatePeer(peerInfo)
48 | peerManager ! GetAllPeers
49 |
50 | val data = p.expectMsgClass(classOf[Data])
51 | data.keySet should contain(peerAddress)
52 | system.terminate()
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/scala/scorex/core/serialization/SerializerRegistrySpec.scala:
--------------------------------------------------------------------------------
1 | package scorex.core.serialization
2 |
3 | import io.circe.Encoder
4 | import io.circe.syntax._
5 | import org.scalatest.flatspec.AnyFlatSpec
6 | import org.scalatest.matchers.should.Matchers
7 | import scorex.core.serialization.SerializerRegistry.SerializerRecord
8 |
9 | import scala.reflect.ClassTag
10 | import scala.util.{Random, Try}
11 |
12 | class SerializerRegistrySpec extends AnyFlatSpec with Matchers {
13 |
14 | sealed trait Creature
15 | sealed case class Human(name: String, country: String) extends Creature
16 | sealed case class Animal(family: String, legs: Int) extends Creature
17 |
18 | val humanEnc: Encoder[Human] = (h: Human) =>
19 | Map(
20 | "name" -> h.name.asJson,
21 | "country" -> h.country.asJson
22 | ).asJson
23 |
24 | val animalEnc: Encoder[Animal] = (a: Animal) =>
25 | Map(
26 | "family" -> a.family.asJson,
27 | "legs" -> a.legs.asJson
28 | ).asJson
29 |
30 |
31 | "`toJson`" should "be able to generate json at runtime when encoders are registered" in {
32 |
33 | val reg = SerializerRegistry(Seq(SerializerRecord(humanEnc), SerializerRecord(animalEnc)))
34 |
35 | val c: Creature = randomCreature
36 |
37 | val clazz = ClassTag(c.getClass).runtimeClass
38 |
39 | val result = reg.toJson(clazz, c)
40 | result.isRight shouldBe true
41 | result.right.get shouldBe Try(humanEnc.apply(c.asInstanceOf[Human])).getOrElse(animalEnc.apply(c.asInstanceOf[Animal]))
42 | }
43 |
44 |
45 | "`toJson`" should "generate an exception when encoders are not registered" in {
46 |
47 | val reg = SerializerRegistry(Seq(SerializerRecord(humanEnc)))
48 |
49 | val c: Creature = Animal("big cats", 4)
50 |
51 | val clazz = ClassTag(c.getClass).runtimeClass
52 |
53 | val result = reg.toJson(clazz, c)
54 | result.isRight shouldBe false
55 | }
56 |
57 | private def randomCreature: Creature = {
58 | if (Random.nextInt() % 2 == 0) Animal("big cats", 4)
59 | else Human("Kim", "North Korea")
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/test/scala/scorex/crypto/SigningFunctionsSpecification.scala:
--------------------------------------------------------------------------------
1 | package scorex.crypto
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.{PrivateKey25519Serializer, PrivateKey25519Companion}
7 |
8 |
9 | class SigningFunctionsSpecification extends AnyPropSpec
10 | with ScalaCheckPropertyChecks
11 | with Matchers {
12 |
13 | property("PrivateKey25519Companion generates valid keypair") {
14 | forAll() { (seed1: Array[Byte], message1: Array[Byte], seed2: Array[Byte], message2: Array[Byte]) =>
15 | whenever(!seed1.sameElements(seed2) && !message1.sameElements(message2)) {
16 | val priv = PrivateKey25519Companion.generateKeys(seed1)._1
17 | val priv2 = PrivateKey25519Companion.generateKeys(seed2)._1
18 | val sig = PrivateKey25519Companion.sign(priv, message1)
19 | sig.isValid(priv.publicImage, message1) shouldBe true
20 |
21 | sig.isValid(priv.publicImage, message2) shouldBe false
22 | sig.isValid(priv2.publicImage, message1) shouldBe false
23 | }
24 | }
25 | }
26 |
27 | property("PrivateKey25519Companion serialization") {
28 | forAll() { (seed: Array[Byte], message: Array[Byte]) =>
29 | val priv = PrivateKey25519Companion.generateKeys(seed)._1
30 | val parsed = PrivateKey25519Serializer.parseByteString(PrivateKey25519Serializer.toByteString(priv))
31 |
32 | parsed.publicImage.address shouldBe priv.publicImage.address
33 | PrivateKey25519Companion.sign(parsed, message)
34 | .isValid(priv.publicImage, message) shouldBe true
35 | }
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/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/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/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/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/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
--------------------------------------------------------------------------------
/testkit/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/testkit/src/main/scala/scorex/testkit/BlockchainSanity.scala:
--------------------------------------------------------------------------------
1 | package scorex.testkit
2 |
3 | import scorex.core.{PersistentNodeViewModifier, TransactionsCarryingPersistentNodeViewModifier}
4 | import scorex.core.consensus.{History, SyncInfo}
5 | import scorex.core.transaction.box.Box
6 | import scorex.core.transaction.box.proposition.Proposition
7 | import scorex.core.transaction.{BoxTransaction, MemoryPool}
8 | import scorex.mid.state.BoxMinimalState
9 | import scorex.testkit.generators.AllModifierProducers
10 | import scorex.testkit.properties._
11 | import scorex.testkit.properties.mempool.{MempoolFilterPerformanceTest, MempoolRemovalTest, MempoolTransactionsTest}
12 | import scorex.testkit.properties.state.StateApplicationTest
13 | import scorex.testkit.properties.state.box.{BoxStateApplyChangesTest, BoxStateChangesGenerationTest, BoxStateRollbackTest}
14 |
15 | /**
16 | * The idea of this class is to get some generators and test some situations, common for all blockchains
17 | */
18 | trait BlockchainSanity[P <: Proposition,
19 | TX <: BoxTransaction[P, B],
20 | PM <: PersistentNodeViewModifier,
21 | CTM <: PM with TransactionsCarryingPersistentNodeViewModifier[TX],
22 | SI <: SyncInfo,
23 | B <: Box[P],
24 | MPool <: MemoryPool[TX, MPool],
25 | ST <: BoxMinimalState[P, B, TX, PM, ST],
26 | HT <: History[PM, SI, HT]]
27 | extends
28 | BoxStateChangesGenerationTest[P, TX, PM, B, ST]
29 | with StateApplicationTest[PM, ST]
30 | with HistoryTests[TX, PM, SI, HT]
31 | with BoxStateApplyChangesTest[P, TX, PM, B, ST]
32 | with WalletSecretsTest[P, TX, PM]
33 | with BoxStateRollbackTest[P, TX, PM, CTM, B, ST]
34 | with MempoolTransactionsTest[TX, MPool]
35 | with MempoolFilterPerformanceTest[TX, MPool]
36 | with MempoolRemovalTest[TX, MPool, PM, CTM, HT, SI]
37 | with AllModifierProducers[TX, MPool, PM, CTM, ST, SI, HT]
38 | with NodeViewHolderTests[TX, PM, ST, SI, HT, MPool]
39 | with NodeViewSynchronizerTests[TX, PM, ST, SI, HT, MPool] {
40 | }
41 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/src/main/scala/scorex/testkit/properties/mempool/MempoolFilterPerformanceTest.scala:
--------------------------------------------------------------------------------
1 | package scorex.testkit.properties.mempool
2 |
3 | import java.security.MessageDigest
4 |
5 | import org.scalatest.matchers.should.Matchers
6 | import org.scalatest.propspec.AnyPropSpec
7 | import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
8 | import scorex.core.transaction.{MemoryPool, Transaction}
9 | import scorex.core.utils._
10 |
11 | trait MempoolFilterPerformanceTest[TX <: Transaction, MPool <: MemoryPool[TX, MPool]]
12 | extends AnyPropSpec
13 | with ScalaCheckPropertyChecks
14 | with Matchers
15 | with MemoryPoolTest[TX, MPool] {
16 |
17 | var initializedMempool: Option[MPool] = None
18 |
19 | val thresholdInHashes = 500000
20 |
21 | private val HeatJVMHashesCount = 1000000 //to heat up JVM, just in case it is cold
22 |
23 | val thresholdSecs: Double = {
24 | //heat up
25 | (1 to HeatJVMHashesCount).foreach(i => MessageDigest.getInstance("SHA-256").digest(("dummy" + i).getBytes()))
26 |
27 | val t0 = System.currentTimeMillis()
28 | (1 to thresholdInHashes).foreach(i => MessageDigest.getInstance("SHA-256").digest(("dummy" + i).getBytes()))
29 | val t = System.currentTimeMillis()
30 | (t - t0) / 1000.0
31 | }
32 |
33 | property("Mempool should be able to store a lot of transactions") {
34 | var m: MPool = memPool
35 | (0 until 1000) foreach { _ =>
36 | forAll(transactionGenerator) { tx: TX =>
37 | m = m.put(tx).get
38 | }
39 | }
40 | m.size should be > 1000
41 | initializedMempool = Some(m)
42 | }
43 |
44 | property("Mempool filter of non-existing transaction should be fast") {
45 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
46 | val m = initializedMempool.get
47 | forAll(transactionGenerator) { tx: TX =>
48 | val (time, _) = profile(m.filter(Seq(tx)))
49 | assert(time < thresholdSecs)
50 | }
51 | }
52 |
53 | property("Mempool filter of existing transaction should be fast") {
54 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
55 | var m = initializedMempool.get
56 | forAll(transactionGenerator) { tx: TX =>
57 | m = m.put(tx).get
58 | val (time, _) = profile(m.filter(Seq(tx)))
59 | assert(time < thresholdSecs)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/testkit/src/main/scala/scorex/testkit/properties/mempool/MempoolRemovalTest.scala:
--------------------------------------------------------------------------------
1 | package scorex.testkit.properties.mempool
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, TransactionsCarryingPersistentNodeViewModifier}
8 | import scorex.core.consensus.{History, SyncInfo}
9 | import scorex.core.transaction.{MemoryPool, Transaction}
10 | import scorex.testkit.TestkitHelpers
11 | import scorex.testkit.generators.ArbitraryTransactionsCarryingModifierProducer
12 | import scorex.util.ScorexLogging
13 |
14 | trait MempoolRemovalTest[
15 | TX <: Transaction,
16 | MPool <: MemoryPool[TX, MPool],
17 | PM <: PersistentNodeViewModifier,
18 | CTM <: PM with TransactionsCarryingPersistentNodeViewModifier[TX],
19 | HT <: History[PM, SI, HT],
20 | SI <: SyncInfo] extends AnyPropSpec
21 | with ScalaCheckPropertyChecks
22 | with Matchers
23 | with ScorexLogging
24 | with TestkitHelpers
25 | with MemoryPoolTest[TX, MPool]
26 | with ArbitraryTransactionsCarryingModifierProducer[TX, MPool, PM, CTM] {
27 |
28 | val historyGen: Gen[HT]
29 |
30 | //todo: this test doesn't check anything. It should be reworked as a test for node view holder
31 | property("Transactions once added to block should be removed from Mempool") {
32 | val min = 1
33 | val max = 10
34 | forAll(Gen.choose(min, max)) { noOfTransactionsFromMempool: Int =>
35 | var m: MPool = memPool
36 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
37 | var h: HT = historyGen.sample.get
38 | forAll(transactionGenerator) { tx: TX =>
39 | m = m.put(tx).get
40 | }
41 | // var prevMempoolSize = m.size
42 | val b = modifierWithTransactions(Some(m), None)
43 | //todo: fix (m.size + b.transactions.get.size) shouldEqual prevMempoolSize
44 | }
45 | }
46 | }
47 |
48 |
49 |
--------------------------------------------------------------------------------
/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/properties/state/box/BoxStateApplyChangesTest.scala:
--------------------------------------------------------------------------------
1 | package scorex.testkit.properties.state.box
2 |
3 | import org.scalacheck.Gen
4 | import scorex.core._
5 | import scorex.core.transaction.BoxTransaction
6 | import scorex.core.transaction.box.Box
7 | import scorex.core.transaction.box.proposition.Proposition
8 | import scorex.core.transaction.state.BoxStateChanges
9 | import scorex.mid.state.BoxMinimalState
10 |
11 | import scala.util.Random
12 |
13 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
14 | trait BoxStateApplyChangesTest[P <: Proposition,
15 | TX <: BoxTransaction[P, B],
16 | PM <: PersistentNodeViewModifier,
17 | B <: Box[P],
18 | ST <: BoxMinimalState[P, B, TX, PM, ST]] extends BoxStateTests[P, B, TX, PM, ST] {
19 |
20 | def stateChangesGenerator(state: ST): Gen[BoxStateChanges[P, B]]
21 |
22 | property("BoxMinimalState should be able to add and remove boxes") {
23 | forAll(stateGen, minSuccessful(2)) { state =>
24 | val changes = stateChangesGenerator(state).sample.get
25 | changes.toAppend.foreach { insertion =>
26 | state.closedBox(insertion.box.id).isDefined shouldBe false
27 | }
28 | changes.toRemove.foreach { removal =>
29 | state.closedBox(removal.boxId).isDefined shouldBe true
30 | }
31 | val newVersion = bytesToVersion(Array.fill(32)(Random.nextInt(Byte.MaxValue).toByte))
32 | val newState = state.applyChanges(changes, newVersion).get
33 | changes.toAppend.foreach { insertion =>
34 | newState.closedBox(insertion.box.id).isDefined shouldBe true
35 | }
36 | changes.toRemove.foreach { removal =>
37 | newState.closedBox(removal.boxId).isDefined shouldBe false
38 | }
39 | }
40 |
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/testkit/src/main/scala/scorex/testkit/properties/state/box/BoxStateChangesGenerationTest.scala:
--------------------------------------------------------------------------------
1 | package scorex.testkit.properties.state.box
2 |
3 | import scorex.core.{PersistentNodeViewModifier, VersionTag, idToVersion}
4 | import scorex.core.transaction.BoxTransaction
5 | import scorex.core.transaction.box.Box
6 | import scorex.core.transaction.box.proposition.Proposition
7 | import scorex.core.transaction.state.Insertion
8 | import scorex.mid.state.BoxMinimalState
9 | import scorex.testkit.TestkitHelpers
10 | import scorex.testkit.generators.SemanticallyValidModifierProducer
11 |
12 | trait BoxStateChangesGenerationTest[P <: Proposition,
13 | TX <: BoxTransaction[P, B],
14 | PM <: PersistentNodeViewModifier,
15 | B <: Box[P],
16 | ST <: BoxMinimalState[P, B, TX, PM, ST]]
17 | extends BoxStateTests[P, B, TX, PM, ST]
18 | with TestkitHelpers
19 | with SemanticallyValidModifierProducer[PM, ST] {
20 |
21 |
22 | property("State should be able to generate changes from block and apply them") {
23 | check(checksToMake) { _ =>
24 | @SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
25 | val state1 = stateGen.sample.get
26 | val block = semanticallyValidModifier(state1)
27 | val blockChanges = state1.changes(block).get
28 |
29 | blockChanges.toAppend.foreach { case Insertion(b) =>
30 | state1.closedBox(b.id) shouldBe None
31 | }
32 |
33 | blockChanges.toRemove.foreach { r =>
34 | state1.closedBox(r.boxId).isDefined shouldBe true
35 | }
36 |
37 | val state2 = state1.applyChanges(blockChanges, idToVersion(block.id)).get
38 |
39 | blockChanges.toAppend.foreach { case Insertion(b) =>
40 | state2.closedBox(b.id) shouldBe Some(b)
41 | }
42 | blockChanges.toRemove.foreach { r =>
43 | state2.closedBox(r.boxId).isDefined shouldBe false
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/testkit/src/main/scala/scorex/testkit/utils/FileUtils.scala:
--------------------------------------------------------------------------------
1 | package scorex.testkit.utils
2 |
3 | import java.nio.file.Path
4 |
5 | import org.scalacheck.Gen
6 |
7 | trait FileUtils {
8 |
9 | protected val randomPrefixLength = 10
10 |
11 | val basePath: Path = java.nio.file.Files.createTempDirectory(s"scorex-${System.nanoTime()}")
12 |
13 | sys.addShutdownHook {
14 | remove(basePath)
15 | }
16 |
17 | def createTempFile: java.io.File = {
18 | val dir = createTempDir
19 | val prefix = scala.util.Random.alphanumeric.take(randomPrefixLength).mkString
20 | val suffix = scala.util.Random.alphanumeric.take(randomPrefixLength).mkString
21 | val file = java.nio.file.Files.createTempFile(dir.toPath, prefix, suffix).toFile
22 | file.deleteOnExit()
23 | file
24 | }
25 |
26 | def createTempDir: java.io.File = {
27 | val rndString = scala.util.Random.alphanumeric.take(randomPrefixLength).mkString
28 | createTempDirForPrefix(rndString)
29 | }
30 |
31 | def tempDirGen: Gen[java.io.File] = Gen.listOfN(randomPrefixLength, Gen.alphaNumChar).map { p =>
32 | val prefix = p.mkString("")
33 | createTempDirForPrefix(prefix)
34 | }
35 |
36 | /**
37 | * Recursively remove all the files and directories in `root`
38 | */
39 | def remove(root: Path): Unit = {
40 |
41 | @SuppressWarnings(Array("org.wartremover.warts.Recursion"))
42 | def deleteRecursive(dir: java.io.File): Unit = {
43 | for (file <- dir.listFiles) {
44 | if (file.isDirectory){
45 | deleteRecursive(file)
46 | }
47 | file.delete()
48 | }
49 | }
50 |
51 | deleteRecursive(root.toFile)
52 | }
53 |
54 | private def createTempDirForPrefix(prefix: String): java.io.File = {
55 | val file = java.nio.file.Files.createTempDirectory(basePath, prefix).toFile
56 | file.deleteOnExit()
57 | file
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------