├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .sbtopts
├── .scalafmt.conf
├── LICENSE
├── README.md
├── build.sbt
├── dex-it-common
├── build.sbt
└── src
│ └── main
│ └── scala
│ └── com
│ └── wavesplatform
│ └── dex
│ └── it
│ ├── api
│ ├── AsyncEnrichedApi.scala
│ ├── BaseContainersKit.scala
│ ├── EnrichedResponse.scala
│ ├── HasKafka.scala
│ ├── HasRedis.scala
│ ├── HasToxiProxy.scala
│ ├── MultipleVersions.scala
│ ├── RawHttpChecks.scala
│ ├── Transformations.scala
│ ├── dex
│ │ ├── AsyncEnrichedDexApi.scala
│ │ ├── DexApi.scala
│ │ ├── DexApiSyntax.scala
│ │ └── HasDex.scala
│ ├── node
│ │ ├── AsyncEnrichedNodeApi.scala
│ │ ├── ConnectReq.scala
│ │ ├── HasWavesNode.scala
│ │ ├── NodeApi.scala
│ │ ├── NodeApiExtensions.scala
│ │ ├── NodeApiSyntax.scala
│ │ └── RollbackReq.scala
│ ├── responses
│ │ ├── dex
│ │ │ └── MatcherError.scala
│ │ └── node
│ │ │ ├── ActivationStatusResponse.scala
│ │ │ ├── AssetBalanceResponse.scala
│ │ │ ├── AssetsBalancesResponse.scala
│ │ │ ├── ConnectedPeersResponse.scala
│ │ │ ├── ErrorResponse.scala
│ │ │ ├── HeightResponse.scala
│ │ │ ├── NftAsset.scala
│ │ │ ├── WavesBalanceResponse.scala
│ │ │ └── package.scala
│ └── websockets
│ │ ├── HasJwt.scala
│ │ ├── HasWebSockets.scala
│ │ └── WsMessageOps.scala
│ ├── cache
│ └── CachedData.scala
│ ├── collections
│ └── Implicits.scala
│ ├── config
│ ├── GenesisConfig.scala
│ ├── GenesisConfigGenerator.scala
│ ├── PredefinedAccounts.scala
│ ├── PredefinedAssets.scala
│ └── genesis
│ │ ├── Block.scala
│ │ ├── GenesisSettings.scala
│ │ ├── GenesisTransaction.scala
│ │ ├── GenesisTransactionSettings.scala
│ │ ├── NxtLikeConsensusBlockData.scala
│ │ └── SignerData.scala
│ ├── docker
│ ├── BaseContainer.scala
│ ├── ConfigurableToxicProxyContainer.scala
│ ├── DexContainer.scala
│ ├── MountableFileOps.scala
│ ├── PortBindingKeeper.scala
│ ├── WavesNodeContainer.scala
│ └── package.scala
│ ├── fp
│ ├── CanExtract.scala
│ ├── CanExtractInstances.scala
│ ├── CanRepeat.scala
│ ├── CanWait.scala
│ ├── RepeatRequestOptions.scala
│ └── package.scala
│ ├── json
│ └── package.scala
│ ├── matchers
│ ├── FailWith.scala
│ └── ItMatchers.scala
│ ├── metamask
│ └── EthTransactionsHelper.scala
│ ├── resources
│ └── package.scala
│ ├── sttp
│ └── LoggingSttpBackend.scala
│ ├── test
│ ├── InformativeTestStart.scala
│ ├── NoStackTraceCancelAfterFailure.scala
│ └── Scripts.scala
│ ├── time
│ └── GlobalTimer.scala
│ └── waves
│ ├── Implicits.scala
│ └── MkWavesEntities.scala
├── dex-it
├── build.sbt
└── src
│ └── test
│ ├── container
│ └── start-matcher-server-it.sh
│ ├── java
│ └── com
│ │ └── wavesplatform
│ │ └── it
│ │ └── tags
│ │ ├── DexItExternalKafkaRequired.java
│ │ ├── DexMultipleVersions.java
│ │ ├── NetworkTests.java
│ │ └── SmokeTests.java
│ ├── resources
│ ├── dex-servers
│ │ ├── dex-1.conf
│ │ ├── dex-2.conf
│ │ ├── dex-base.conf
│ │ ├── jul.properties
│ │ └── logback-container.xml
│ ├── genesis.conf
│ ├── logback-test.xml
│ ├── nodes
│ │ ├── jul.properties
│ │ ├── logback-container.xml
│ │ ├── waves-1.conf
│ │ ├── waves-2.conf
│ │ └── waves-base.conf
│ └── testcontainers.properties
│ └── scala
│ └── com
│ └── wavesplatform
│ └── it
│ ├── MatcherSuiteBase.scala
│ ├── WsSuiteBase.scala
│ ├── api
│ ├── ApiExtensions.scala
│ ├── MatcherCommand.scala
│ └── MatcherState.scala
│ ├── async
│ └── CorrectStatusAfterPlaceTestSuite.scala
│ ├── config
│ └── DexTestConfig.scala
│ ├── it.scala
│ ├── matcher
│ ├── api
│ │ └── http
│ │ │ ├── ApiKeyHeaderChecks.scala
│ │ │ ├── balances
│ │ │ ├── GetReservedBalanceByPKSpec.scala
│ │ │ └── GetTradableBalanceByAssetPairAndAddressSpec.scala
│ │ │ ├── cancel
│ │ │ ├── CancelAllOrdersWithSigSpec.scala
│ │ │ ├── CancelOneOrAllInPairOrdersWithSigSpec.scala
│ │ │ ├── CancelOrderByIdSpec.scala
│ │ │ └── CancelOrdersByAddressAndIdsSpec.scala
│ │ │ ├── debug
│ │ │ ├── GetAddressStateSpec.scala
│ │ │ ├── GetAllSnapshotOffsetsSpec.scala
│ │ │ ├── GetCurrentOffsetSpec.scala
│ │ │ ├── GetLastOffsetSpec.scala
│ │ │ ├── GetMatcherConfigSpec.scala
│ │ │ ├── GetMatcherStatusSpec.scala
│ │ │ ├── GetOldestSnapshotOffsetSpec.scala
│ │ │ └── SaveSnapshotsSpec.scala
│ │ │ ├── history
│ │ │ ├── DeleteOrderFromHistoryByIdSpec.scala
│ │ │ ├── GetOrderHistoryByAddressWithKeySpec.scala
│ │ │ ├── GetOrderHistoryByAssetPairAndPKWithSigSpec.scala
│ │ │ └── GetOrderHistoryByPKWithSigSpec.scala
│ │ │ ├── http.scala
│ │ │ ├── info
│ │ │ ├── GetMatcherPKInBase58Spec.scala
│ │ │ └── GetMatcherPublicSettingsSpec.scala
│ │ │ ├── markets
│ │ │ ├── CalculateFeeSpec.scala
│ │ │ ├── CancelAllOrdersInOrderBookWithKeySpec.scala
│ │ │ ├── DeleteOrderBookWithKeySpec.scala
│ │ │ ├── GetOrderBookRestrictionsSpec.scala
│ │ │ ├── GetOrderBookSpec.scala
│ │ │ ├── GetOrderBookStatusSpec.scala
│ │ │ └── GetOrderBooksSpec.scala
│ │ │ ├── place
│ │ │ ├── PlaceLimitOrderSpec.scala
│ │ │ ├── PlaceMarketOrderSpec.scala
│ │ │ └── PlaceOrderBaseSpec.scala
│ │ │ ├── rates
│ │ │ ├── DeleteAssetRateSpec.scala
│ │ │ ├── GetAssetRatesSpec.scala
│ │ │ └── UpsertAssetRateSpec.scala
│ │ │ ├── status
│ │ │ ├── GetOrderStatusByAddressAndIdWithKeySpec.scala
│ │ │ ├── GetOrderStatusByPKAndIdWithSigSpec.scala
│ │ │ └── OrderStatusByAssetPairAndIdSpec.scala
│ │ │ ├── swagger
│ │ │ └── SwaggerSpec.scala
│ │ │ └── transactions
│ │ │ └── GetTransactionsByOrderIdSpec.scala
│ └── bugs
│ │ ├── Dex1052BugSpec.scala
│ │ └── Dex1086BugSpec.scala
│ └── sync
│ ├── AutoCancelOrderTestSuite.scala
│ ├── BlacklistedTradingTestSuite.scala
│ ├── BouncingBalancesTestSuite.scala
│ ├── BroadcastUntilConfirmedTestSuite.scala
│ ├── CancelOrderTestSuite.scala
│ ├── CheckAddressTestSuite.scala
│ ├── DeleteOrderBookKafkaTestSuite.scala
│ ├── DisableProducerTestSuite.scala
│ ├── MarketOrderTestSuite.scala
│ ├── MarketStatusRecoveryTestSuite.scala
│ ├── MatcherMassOrdersTestSuite.scala
│ ├── MatcherRecoveryTestSuite.scala
│ ├── MatcherRestartTestSuite.scala
│ ├── MatcherTestSuite.scala
│ ├── MatcherTickerTestSuite.scala
│ ├── MatchingRulesTestSuite.scala
│ ├── MultipleMatchersTestSuite.scala
│ ├── OrderBookSnapshotsTestSuite.scala
│ ├── OrderBookTestSuite.scala
│ ├── OrderDeviationsTestSuite.scala
│ ├── OrderExecutedDataInProofsTestSuite.scala
│ ├── OrderHistoryTestSuite.scala
│ ├── OrderRestrictionsTestSuite.scala
│ ├── OrderV3TestSuite.scala
│ ├── OrderV4TestSuite.scala
│ ├── PostgresHistoryDatabaseTestSuite.scala
│ ├── RatesTestSuite.scala
│ ├── ReBroadcastUntilConfirmedTestSuite.scala
│ ├── ReBroadcastingFailedByScriptTxsTestSuite.scala
│ ├── RedisTestSuite.scala
│ ├── RestOrderLimitTestSuite.scala
│ ├── RoundingIssuesTestSuite.scala
│ ├── SeveralPartialOrdersTestSuite.scala
│ ├── TradeBalanceAndRoundingTestSuite.scala
│ ├── TradersTestSuite.scala
│ ├── TradingMarketsTestSuite.scala
│ ├── api
│ ├── GetOrderBookTestSuite.scala
│ └── ws
│ │ ├── WsAddressStreamRealTimeTestSuite.scala
│ │ ├── WsAddressStreamTestSuite.scala
│ │ ├── WsConnectionTestSuite.scala
│ │ ├── WsImaginaryTransactionsTestSuite.scala
│ │ ├── WsInternalStreamTestSuite.scala
│ │ ├── WsOrderBookStreamTestSuite.scala
│ │ ├── WsPingPongBaseSuite.scala
│ │ ├── WsPingPongExternalTestSuite.scala
│ │ ├── WsPingPongInternalTestSuite.scala
│ │ └── WsRatesUpdatesTestSuite.scala
│ ├── compat
│ ├── BackwardCompatSuiteBase.scala
│ ├── DatabaseBackwardCompatTestSuite.scala
│ └── OrderBookBackwardCompatTestSuite.scala
│ ├── kafka
│ └── issues
│ │ ├── DeserializationIssuesTestSuite.scala
│ │ └── NetworkAndQueueIssuesTestSuite.scala
│ ├── metamask
│ └── EthereumTransactionTestSuite.scala
│ ├── networking
│ ├── DexClientFaultToleranceTestSuite.scala
│ ├── MultipleMatchersOrderCancelTestSuite.scala
│ └── NetworkIssuesTestSuite.scala
│ ├── orders
│ ├── DEX1570Bug.scala
│ ├── MakerTakerFeeTestSuite.scala
│ ├── OrderCompositeFeeTestSuite.scala
│ ├── OrderDynamicFeeTestSuite.scala
│ ├── OrderDynamicFeeWithZeroFeeAccountsTestSuite.scala
│ ├── OrderFeeBaseTestSuite.scala
│ ├── OrderFeeDiscountTestSuite.scala
│ ├── OrderFixedFeeTestSuite.scala
│ ├── OrderPercentFeeAmountTestSuite.scala
│ └── OrderPercentFeeSpendingTestSuite.scala
│ ├── smartcontracts
│ ├── AllowedBlockchainStateAccountsTestSuite.scala
│ ├── ExtraFeeTestSuite.scala
│ ├── OrderTypeTestSuite.scala
│ ├── OrdersFromScriptedAccTestSuite.scala
│ ├── OrdersFromScriptedAssetTestSuite.scala
│ └── ProofAndAssetPairTestSuite.scala
│ └── txts
│ ├── ExistingOrderBookTxTsTest.scala
│ ├── MultipleMatchersTxTsTestSuite.scala
│ └── NewOrderBookTxTsTest.scala
├── dex-jmh
├── build.sbt
└── src
│ └── test
│ └── scala
│ └── com
│ └── wavesplatform
│ └── dex
│ └── model
│ ├── address
│ └── AddressActorStartingBenchmark.scala
│ ├── orderbook
│ ├── OrderBookAddBenchmark.scala
│ └── OrderBookCancelBenchmark.scala
│ └── state
│ └── OrderBookBenchmarkState.scala
├── dex-load
├── build.sbt
└── src
│ └── main
│ ├── resources
│ ├── devnet.conf
│ ├── logback.xml
│ ├── reinstallExtension.sh
│ ├── reinstallMatcher.sh
│ ├── reinstallNode.sh
│ └── runLoadTest.sh
│ └── scala
│ └── com
│ └── wavesplatform
│ └── dex
│ └── load
│ ├── GatlingFeeder.scala
│ ├── RequestDeleter.scala
│ ├── TankGenerator.scala
│ ├── WavesDexLoadCli.scala
│ ├── WsAccumulateChanges.scala
│ ├── request
│ ├── Request.scala
│ ├── RequestTag.scala
│ └── RequestType.scala
│ ├── utils
│ ├── Settings.scala
│ └── package.scala
│ └── ws
│ ├── WsCollectChangesClient.scala
│ └── WsConnection.scala
├── dex-pb
├── build.sbt
└── src
│ └── main
│ └── protobuf
│ └── dex_order.proto
├── dex-test-common
├── build.sbt
└── src
│ └── main
│ └── scala
│ └── com
│ └── wavesplatform
│ └── dex
│ ├── Implicits.scala
│ ├── NoShrink.scala
│ ├── asset
│ └── DoubleOps.scala
│ ├── gen
│ └── package.scala
│ ├── test
│ └── matchers
│ │ ├── DiffMatcherWithImplicits.scala
│ │ └── ProduceError.scala
│ └── waves
│ └── WavesFeeConstants.scala
├── dex-ws-load
├── build.sbt
├── project
│ ├── build.properties
│ └── plugins.sbt
└── src
│ └── test
│ ├── resources
│ └── logback.xml
│ └── scala
│ └── load
│ └── DexSimulation.scala
├── dex
├── README_DOCKER.md
├── build.sbt
└── src
│ ├── main
│ ├── container
│ │ ├── dex.conf
│ │ └── start-matcher-server.sh
│ ├── resources
│ │ ├── application.conf
│ │ └── order-history
│ │ │ └── order-history-ddl.sql
│ └── scala
│ │ ├── com
│ │ └── wavesplatform
│ │ │ └── dex
│ │ │ ├── Application.scala
│ │ │ ├── actors
│ │ │ ├── ActorSystemOps.scala
│ │ │ ├── AskActor.scala
│ │ │ ├── OrderBookAskAdapter.scala
│ │ │ ├── OrderBookDirectoryActor.scala
│ │ │ ├── RootActorSystem.scala
│ │ │ ├── SnapshotsState.scala
│ │ │ ├── WatchDistributedCompletionActor.scala
│ │ │ ├── address
│ │ │ │ ├── AddressActor.scala
│ │ │ │ ├── AddressBalance.scala
│ │ │ │ ├── AddressDirectoryActor.scala
│ │ │ │ ├── BalancesFormatter.scala
│ │ │ │ └── BatchOrderCancelActor.scala
│ │ │ ├── events
│ │ │ │ └── OrderEventsCoordinatorActor.scala
│ │ │ ├── orderbook
│ │ │ │ ├── AggregatedOrderBookActor.scala
│ │ │ │ ├── OrderBookActor.scala
│ │ │ │ └── OrderBookSnapshotStoreActor.scala
│ │ │ ├── package.scala
│ │ │ └── tx
│ │ │ │ ├── ExchangeTransactionBroadcastActor.scala
│ │ │ │ └── WriteExchangeTransactionActor.scala
│ │ │ ├── api
│ │ │ ├── http
│ │ │ │ ├── ApiMarshallers.scala
│ │ │ │ ├── CompositeHttpService.scala
│ │ │ │ ├── HasStatusBarrier.scala
│ │ │ │ ├── MetricHttpFlow.scala
│ │ │ │ ├── OrderBookHttpInfo.scala
│ │ │ │ ├── SwaggerDocService.scala
│ │ │ │ ├── directives
│ │ │ │ │ ├── HttpKamonDirectives.scala
│ │ │ │ │ └── ProtectDirective.scala
│ │ │ │ ├── entities
│ │ │ │ │ ├── HttpAddressCheck.scala
│ │ │ │ │ ├── HttpAddressState.scala
│ │ │ │ │ ├── HttpAssetInfo.scala
│ │ │ │ │ ├── HttpCalculateFeeRequest.scala
│ │ │ │ │ ├── HttpCalculatedFeeResponse.scala
│ │ │ │ │ ├── HttpError.scala
│ │ │ │ │ ├── HttpMarketDataWithMeta.scala
│ │ │ │ │ ├── HttpMatcherPublicSettings.scala
│ │ │ │ │ ├── HttpMatchingRules.scala
│ │ │ │ │ ├── HttpMessage.scala
│ │ │ │ │ ├── HttpOrderBook.scala
│ │ │ │ │ ├── HttpOrderBookHistoryItem.scala
│ │ │ │ │ ├── HttpOrderBookInfo.scala
│ │ │ │ │ ├── HttpOrderBookStatus.scala
│ │ │ │ │ ├── HttpOrderFeeMode.scala
│ │ │ │ │ ├── HttpOrderRestrictions.scala
│ │ │ │ │ ├── HttpOrderStatus.scala
│ │ │ │ │ ├── HttpSuccessfulBatchCancel.scala
│ │ │ │ │ ├── HttpSuccessfulCancel.scala
│ │ │ │ │ ├── HttpSuccessfulDeleteHistory.scala
│ │ │ │ │ ├── HttpSuccessfulPlace.scala
│ │ │ │ │ ├── HttpSuccessfulSingleCancel.scala
│ │ │ │ │ ├── HttpSystemStatus.scala
│ │ │ │ │ ├── HttpTradingMarkets.scala
│ │ │ │ │ ├── HttpV0LevelAgg.scala
│ │ │ │ │ ├── HttpV0OrderBook.scala
│ │ │ │ │ ├── HttpV1LevelAgg.scala
│ │ │ │ │ ├── HttpV1OrderBook.scala
│ │ │ │ │ ├── HttpWebSocketCloseFilter.scala
│ │ │ │ │ ├── HttpWebSocketConnections.scala
│ │ │ │ │ ├── MatcherResponse.scala
│ │ │ │ │ └── package.scala
│ │ │ │ ├── headers
│ │ │ │ │ ├── CustomContentTypes.scala
│ │ │ │ │ ├── CustomMediaTypes.scala
│ │ │ │ │ ├── MatcherHttpServer.scala
│ │ │ │ │ ├── `X-Api-Key`.scala
│ │ │ │ │ └── `X-User-Public-Key`.scala
│ │ │ │ ├── json
│ │ │ │ │ └── CustomJson.scala
│ │ │ │ ├── protocol
│ │ │ │ │ └── HttpCancelOrder.scala
│ │ │ │ └── routes
│ │ │ │ │ ├── v0
│ │ │ │ │ ├── BalancesRoute.scala
│ │ │ │ │ ├── CancelRoute.scala
│ │ │ │ │ ├── DebugRoute.scala
│ │ │ │ │ ├── HistoryRoute.scala
│ │ │ │ │ ├── MarketsRoute.scala
│ │ │ │ │ ├── MatcherInfoRoute.scala
│ │ │ │ │ ├── PlaceRoute.scala
│ │ │ │ │ ├── RatesRoute.scala
│ │ │ │ │ └── TransactionsRoute.scala
│ │ │ │ │ └── v1
│ │ │ │ │ └── OrderBookRoute.scala
│ │ │ ├── routes
│ │ │ │ ├── ApiRoute.scala
│ │ │ │ ├── AuthRoute.scala
│ │ │ │ └── PathMatchers.scala
│ │ │ └── ws
│ │ │ │ ├── actors
│ │ │ │ ├── WsExternalClientDirectoryActor.scala
│ │ │ │ ├── WsExternalClientHandlerActor.scala
│ │ │ │ ├── WsHealthCheckSettings.scala
│ │ │ │ ├── WsInternalBroadcastActor.scala
│ │ │ │ └── WsInternalClientHandlerActor.scala
│ │ │ │ ├── connection
│ │ │ │ ├── TestWsHandlerActor.scala
│ │ │ │ ├── WsConnection.scala
│ │ │ │ └── WsConnectionOps.scala
│ │ │ │ ├── entities
│ │ │ │ ├── WsAddressFlag.scala
│ │ │ │ ├── WsAssetInfo.scala
│ │ │ │ ├── WsBalances.scala
│ │ │ │ ├── WsFullOrder.scala
│ │ │ │ ├── WsLastTrade.scala
│ │ │ │ ├── WsMatchTransactionInfo.scala
│ │ │ │ ├── WsOrder.scala
│ │ │ │ ├── WsOrderBookSettings.scala
│ │ │ │ └── WsTxsData.scala
│ │ │ │ ├── headers
│ │ │ │ ├── `X-Error-Code`.scala
│ │ │ │ └── `X-Error-Message`.scala
│ │ │ │ ├── package.scala
│ │ │ │ ├── protocol
│ │ │ │ ├── WsAddressChanges.scala
│ │ │ │ ├── WsAddressSubscribe.scala
│ │ │ │ ├── WsClientMessage.scala
│ │ │ │ ├── WsError.scala
│ │ │ │ ├── WsInitial.scala
│ │ │ │ ├── WsMessage.scala
│ │ │ │ ├── WsOrderBookChanges.scala
│ │ │ │ ├── WsOrderBookSubscribe.scala
│ │ │ │ ├── WsOrdersUpdate.scala
│ │ │ │ ├── WsPingOrPong.scala
│ │ │ │ ├── WsRatesUpdates.scala
│ │ │ │ ├── WsRatesUpdatesSubscribe.scala
│ │ │ │ ├── WsServerMessage.scala
│ │ │ │ └── WsUnsubscribe.scala
│ │ │ │ ├── routes
│ │ │ │ └── MatcherWebSocketRoute.scala
│ │ │ │ └── state
│ │ │ │ ├── WsAddressState.scala
│ │ │ │ └── WsOrderBookState.scala
│ │ │ ├── app
│ │ │ ├── ApplicationStopReason.scala
│ │ │ ├── MatcherStatus.scala
│ │ │ └── package.scala
│ │ │ ├── auth
│ │ │ └── JwtUtils.scala
│ │ │ ├── caches
│ │ │ ├── DropOldestFixedBuffer2.scala
│ │ │ ├── MatchingRulesCache.scala
│ │ │ ├── OrderFeeSettingsCache.scala
│ │ │ └── RateCache.scala
│ │ │ ├── cli
│ │ │ ├── Actions.scala
│ │ │ ├── ScoptImplicits.scala
│ │ │ ├── WavesDexCli.scala
│ │ │ └── package.scala
│ │ │ ├── codecs
│ │ │ ├── ByteBufferCodecs.scala
│ │ │ └── OrderBookSideSnapshotCodecs.scala
│ │ │ ├── collections
│ │ │ ├── NegativeMap.scala
│ │ │ ├── NonNegativeMap.scala
│ │ │ ├── NonPositiveMap.scala
│ │ │ ├── PositiveMap.scala
│ │ │ └── SafeMapOps.scala
│ │ │ ├── crypto
│ │ │ └── Enigma.scala
│ │ │ ├── db
│ │ │ ├── AccountStorage.scala
│ │ │ ├── AssetPairsDb.scala
│ │ │ ├── AssetsCache.scala
│ │ │ ├── AssetsDb.scala
│ │ │ ├── AssetsReadOnlyDb.scala
│ │ │ ├── DbKeys.scala
│ │ │ ├── ExchangeTxStorage.scala
│ │ │ ├── LocalQueueStore.scala
│ │ │ ├── OrderBookSnapshotDb.scala
│ │ │ ├── OrderDb.scala
│ │ │ ├── RateDb.scala
│ │ │ ├── SecuredFileStorage.scala
│ │ │ ├── leveldb
│ │ │ │ ├── Key.scala
│ │ │ │ ├── KeyHelpers.scala
│ │ │ │ ├── LevelDb.scala
│ │ │ │ ├── LevelDbFactory.scala
│ │ │ │ ├── ReadOnlyDb.scala
│ │ │ │ ├── ReadWriteDb.scala
│ │ │ │ ├── jna
│ │ │ │ │ ├── LevelDBJNADB.scala
│ │ │ │ │ └── LevelDBJNADBFactory.scala
│ │ │ │ └── package.scala
│ │ │ └── package.scala
│ │ │ ├── doc
│ │ │ └── MatcherErrorDoc.scala
│ │ │ ├── effect
│ │ │ ├── Implicits.scala
│ │ │ └── package.scala
│ │ │ ├── error
│ │ │ ├── ErrorFormatterContext.scala
│ │ │ ├── Implicits.scala
│ │ │ └── MatcherError.scala
│ │ │ ├── exceptions
│ │ │ └── BinaryMessagesNotSupportedException.scala
│ │ │ ├── grpc
│ │ │ └── integration
│ │ │ │ └── clients
│ │ │ │ └── MatcherExtensionAssetsCachingClient.scala
│ │ │ ├── history
│ │ │ ├── DBRecords.scala
│ │ │ ├── HasPostgresJdbcContext.scala
│ │ │ ├── HistoryMessagesBatchSenderActor.scala
│ │ │ └── HistoryRouterActor.scala
│ │ │ ├── json
│ │ │ ├── Implicits.scala
│ │ │ └── package.scala
│ │ │ ├── logs
│ │ │ └── SystemInformationReporter.scala
│ │ │ ├── meta
│ │ │ ├── DescendantSamples.scala
│ │ │ ├── Sample.scala
│ │ │ └── package.scala
│ │ │ ├── metrics
│ │ │ ├── LevelDBStats.scala
│ │ │ └── package.scala
│ │ │ ├── model
│ │ │ ├── AcceptedOrderType.scala
│ │ │ ├── AssetPairBuilder.scala
│ │ │ ├── ExchangeTransactionCreator.scala
│ │ │ ├── ExecutionParamsInProofs.scala
│ │ │ ├── Fee.scala
│ │ │ ├── Implicits.scala
│ │ │ ├── LastTrade.scala
│ │ │ ├── LevelAmounts.scala
│ │ │ ├── MatchTimestamp.scala
│ │ │ ├── MatcherModel.scala
│ │ │ ├── OrderBook.scala
│ │ │ ├── OrderBookAggregatedSnapshot.scala
│ │ │ ├── OrderBookSnapshot.scala
│ │ │ ├── OrderInfo.scala
│ │ │ ├── OrderValidator.scala
│ │ │ ├── ValidationStages.scala
│ │ │ └── package.scala
│ │ │ ├── queue
│ │ │ ├── KafkaMatcherQueue.scala
│ │ │ ├── LocalMatcherQueue.scala
│ │ │ ├── MatcherQueue.scala
│ │ │ ├── RepeatableRequests.scala
│ │ │ ├── ValidatedCommand.scala
│ │ │ ├── ValidatedCommandPbConversions.scala
│ │ │ └── ValidatedCommandWithMeta.scala
│ │ │ ├── redis
│ │ │ ├── RedisClient.scala
│ │ │ ├── RedisStream.scala
│ │ │ └── actors
│ │ │ │ └── RedisInternalClientHandlerActor.scala
│ │ │ ├── settings
│ │ │ ├── AssetType.scala
│ │ │ ├── CliSettings.scala
│ │ │ ├── DenormalizedMatchingRule.scala
│ │ │ ├── DeviationsSettings.scala
│ │ │ ├── EventsQueueSettings.scala
│ │ │ ├── GRPCSettings.scala
│ │ │ ├── LpAccountsSettings.scala
│ │ │ ├── MatcherSettings.scala
│ │ │ ├── MatchingRule.scala
│ │ │ ├── OrderFeeSettings.scala
│ │ │ ├── OrderHistorySettings.scala
│ │ │ ├── OrderRestrictionsSettings.scala
│ │ │ ├── PassExecutionParamsSettings.scala
│ │ │ ├── PostgresConnection.scala
│ │ │ ├── RedisInternalClientHandlerActorSettings.scala
│ │ │ ├── RedisSettings.scala
│ │ │ ├── RestAPISettings.scala
│ │ │ ├── SubscriptionsSettings.scala
│ │ │ ├── WaitingOffsetToolSettings.scala
│ │ │ ├── WebSocketSettings.scala
│ │ │ ├── package.scala
│ │ │ └── utils
│ │ │ │ ├── ConfigFieldValidate.scala
│ │ │ │ ├── ConfigListValidate.scala
│ │ │ │ ├── ConfigOps.scala
│ │ │ │ ├── ConfigReaderOps.scala
│ │ │ │ ├── ConfigReaders.scala
│ │ │ │ ├── RawFailureReason.scala
│ │ │ │ ├── WrappedDescendantHint.scala
│ │ │ │ ├── rules.scala
│ │ │ │ └── validationOf.scala
│ │ │ ├── time
│ │ │ ├── NTP.scala
│ │ │ └── Time.scala
│ │ │ └── tool
│ │ │ ├── Checker.scala
│ │ │ ├── ComparisonTool.scala
│ │ │ ├── ConfigChecker.scala
│ │ │ ├── LocaleUtils.scala
│ │ │ ├── OnComplete.scala
│ │ │ ├── PrettyPrinter.scala
│ │ │ ├── Using.scala
│ │ │ ├── WaitOffsetTool.scala
│ │ │ └── connectors
│ │ │ ├── AuthServiceRestConnector.scala
│ │ │ ├── Connector.scala
│ │ │ ├── DexExtensionGrpcConnector.scala
│ │ │ ├── DexRestConnector.scala
│ │ │ ├── DexWsConnector.scala
│ │ │ ├── NodeRestConnector.scala
│ │ │ ├── RestConnector.scala
│ │ │ └── SuperConnector.scala
│ │ └── kamon
│ │ └── instrumentation
│ │ └── logback
│ │ └── tools
│ │ ├── CompactSpanIDConverter.scala
│ │ └── CompactTraceIDConverter.scala
│ ├── package
│ ├── debian
│ │ ├── postinst
│ │ ├── postrm
│ │ ├── preinst
│ │ └── prerm
│ ├── doc
│ │ ├── README.md
│ │ ├── logback.xml
│ │ └── main.conf
│ ├── systemd.service
│ └── upstart.conf
│ └── test
│ ├── resources
│ ├── application.conf
│ ├── logback-test.xml
│ └── logback-verbose.xml
│ └── scala
│ ├── com
│ └── wavesplatform
│ │ └── dex
│ │ ├── MatcherSpecBase.scala
│ │ ├── actors
│ │ ├── AskActorSpec.scala
│ │ ├── Generators.scala
│ │ ├── HasOecInteraction.scala
│ │ ├── MatcherSpec.scala
│ │ ├── OrderBookDirectoryActorSpecification.scala
│ │ ├── SnapshotStateSpecification.scala
│ │ ├── StashContextPropagationSpec.scala
│ │ ├── WatchDistributedCompletionActorSpecification.scala
│ │ ├── address
│ │ │ ├── AddressActorSpecification.scala
│ │ │ └── AddressBalanceSpec.scala
│ │ ├── events
│ │ │ └── OrderEventsCoordinatorActorSpec.scala
│ │ ├── orderbook
│ │ │ ├── AggregatedOrderBookActorSpec.scala
│ │ │ └── OrderBookActorSpecification.scala
│ │ └── tx
│ │ │ └── ExchangeTransactionBroadcastActorSpecification.scala
│ │ ├── api
│ │ ├── RouteSpec.scala
│ │ ├── http
│ │ │ ├── OrderBookHttpInfoSpec.scala
│ │ │ ├── entities
│ │ │ │ ├── HttpBalanceSpec.scala
│ │ │ │ ├── HttpErrorSpec.scala
│ │ │ │ ├── HttpMatcherPublicSettingsSpec.scala
│ │ │ │ ├── HttpMessageSpec.scala
│ │ │ │ ├── HttpOrderBookHistoryItemSpec.scala
│ │ │ │ ├── HttpOrderBookInfoSpec.scala
│ │ │ │ ├── HttpOrderBookStatusSpec.scala
│ │ │ │ ├── HttpOrderFeeModeSpec.scala
│ │ │ │ ├── HttpRatesSpec.scala
│ │ │ │ ├── HttpSnapshotOffsetsSpec.scala
│ │ │ │ ├── HttpSuccessfulBatchCancelSpec.scala
│ │ │ │ ├── HttpSuccessfulPlaceSpec.scala
│ │ │ │ ├── HttpSuccessfulSingleCancelSpec.scala
│ │ │ │ ├── HttpSystemStatusSpec.scala
│ │ │ │ ├── HttpTradingMarketsSpec.scala
│ │ │ │ ├── HttpV0OrderBookSpec.scala
│ │ │ │ └── HttpV1OrderBookSpec.scala
│ │ │ └── routes
│ │ │ │ ├── MatcherApiRouteSpec.scala
│ │ │ │ ├── MatcherWebSocketRouteSpec.scala
│ │ │ │ └── v1OrderBookRouteSpec.scala
│ │ └── ws
│ │ │ ├── ActorsWebSocketInteractionsSpecification.scala
│ │ │ ├── HasJwt.scala
│ │ │ ├── WsAddressSubscribeSerializationSpec.scala
│ │ │ ├── WsMessagesSerdeSpecification.scala
│ │ │ └── actors
│ │ │ └── WsExternalClientHandlerActorSpec.scala
│ │ ├── caches
│ │ ├── DropOldestFixedBuffer2Spec.scala
│ │ ├── OrderFeeSettingsCacheSpecification.scala
│ │ └── RateCacheSpecification.scala
│ │ ├── db
│ │ ├── AssetPairsDbSpec.scala
│ │ ├── AssetsDbSpec.scala
│ │ ├── EmptyOrderDb.scala
│ │ ├── OrderBookSnapshotDbSpec.scala
│ │ ├── RateDbSpecification.scala
│ │ ├── TestAssetPairDb.scala
│ │ ├── TestOrderBookSnapshotDb.scala
│ │ ├── TestOrderDb.scala
│ │ ├── TestRateDb.scala
│ │ ├── WaitingOrderDb.scala
│ │ └── WithDb.scala
│ │ ├── doc
│ │ └── MatcherErrorDocSpec.scala
│ │ ├── error
│ │ └── MatcherErrorSpec.scala
│ │ ├── fixtures
│ │ └── RestartableActor.scala
│ │ ├── fp
│ │ └── MapImplicitsSpec.scala
│ │ ├── gen
│ │ ├── AssetDescriptionGen.scala
│ │ └── OrderBookGen.scala
│ │ ├── history
│ │ └── HistoryRouterActorSpecification.scala
│ │ ├── matching
│ │ └── ReservedBalanceSpecification.scala
│ │ ├── model
│ │ ├── AssetPairBuilderSpec.scala
│ │ ├── DenormalizedMatchingRulesSpecification.scala
│ │ ├── EventSpecification.scala
│ │ ├── ExchangeTransactionCreatorSpecification.scala
│ │ ├── FeeSpecification.scala
│ │ ├── OrderBookSpec.scala
│ │ ├── OrderBookTestSuite.scala
│ │ ├── OrderDbSpec.scala
│ │ ├── OrderHistoryBalanceSpecification.scala
│ │ ├── OrderHistoryStub.scala
│ │ ├── OrderInfoSpec.scala
│ │ └── OrderValidatorSpecification.scala
│ │ ├── queue
│ │ └── ValidatedCommandSerializationSpec.scala
│ │ ├── settings
│ │ ├── BaseSettingsSpecification.scala
│ │ ├── MatcherFeeSettingsSpecification.scala
│ │ ├── MatcherSettingsSpecification.scala
│ │ ├── MatchingRulesSpecification.scala
│ │ ├── OrderRestrictionSpecification.scala
│ │ └── utils
│ │ │ └── ConfigOpsSpecification.scala
│ │ ├── test
│ │ └── WavesEntitiesGen.scala
│ │ ├── time
│ │ ├── SystemTime.scala
│ │ └── TestTime.scala
│ │ ├── tool
│ │ ├── AssetCleanerCliSpec.scala
│ │ ├── ConfigCheckerCliSpec.scala
│ │ └── WaitOffsetToolSpec.scala
│ │ └── util
│ │ ├── Implicits.scala
│ │ └── TestHelpers.scala
│ └── tools
│ └── KafkaTopicInfo.scala
├── docs
├── README.md
├── gen-docs.sh
├── glossary.md
├── images
│ ├── cancels.mmd
│ ├── cancels.svg
│ ├── m-dep.dot
│ ├── m-dep.svg
│ ├── oa-balance-affect.dot
│ ├── oa-balance-affect.svg
│ ├── places.mmd
│ ├── places.svg
│ ├── wni-chain.dot
│ ├── wni-chain.svg
│ ├── wni-chains-diff.svg
│ ├── wni-ext.dot
│ ├── wni-ext.svg
│ ├── wni-not-resolved-fork.dot
│ ├── wni-not-resolved-fork.svg
│ ├── wni-resolved-fork.dot
│ ├── wni-resolved-fork.svg
│ ├── wni-state-machine.dot
│ ├── wni-state-machine.svg
│ ├── wni-streams.dot
│ └── wni-streams.svg
├── modules.md
├── order-auto-canceling.md
├── places-and-cancels.md
└── waves-node-interaction.md
├── project
├── CommonSettings.scala
├── Dependencies.scala
├── ExtensionKeys.scala
├── ExtensionPackaging.scala
├── Hashes.scala
├── ImageVersionPlugin.scala
├── ItCachedArtifactsPlugin.scala
├── ItTestPlugin.scala
├── MatcherIOUtils.scala
├── Network.scala
├── ReleasePlugin.scala
├── RewriteSwaggerConfigPlugin.scala
├── RunApplicationSettings.scala
├── VersionSourcePlugin.scala
├── WavesNodeArtifactsPlugin.scala
├── build.properties
└── plugins.sbt
├── scalastyle-config.xml
├── waves-ext
├── build.sbt
├── lib
│ └── .gitignore
└── src
│ ├── main
│ ├── resources
│ │ └── application.conf
│ └── scala
│ │ └── com
│ │ └── wavesplatform
│ │ └── dex
│ │ ├── collections
│ │ └── Implicits.scala
│ │ └── grpc
│ │ └── integration
│ │ ├── DEXExtension.scala
│ │ ├── dto
│ │ └── BriefAssetDescription.scala
│ │ ├── error
│ │ └── package.scala
│ │ ├── protobuf
│ │ ├── GRPCErrors.scala
│ │ ├── PbToWavesConversions.scala
│ │ ├── WavesToPbConversions.scala
│ │ └── package.scala
│ │ ├── services
│ │ └── WavesBlockchainApiGrpcService.scala
│ │ ├── smart
│ │ └── MatcherScriptRunner.scala
│ │ └── utx
│ │ └── UtxState.scala
│ └── test
│ └── scala
│ └── com
│ └── wavesplatform
│ └── dex
│ ├── WavesExtSuiteBase.scala
│ ├── grpc
│ └── integration
│ │ └── protobuf
│ │ └── ConversionsSuite.scala
│ ├── smart
│ └── MatcherScriptRunnerSpecification.scala
│ └── test
│ └── WavesEntitiesGen.scala
├── waves-grpc
├── build.sbt
└── src
│ └── main
│ └── protobuf
│ └── waves_blockchain_api.proto
├── waves-integration-it
├── build.sbt
└── src
│ └── test
│ ├── container
│ └── start-matcher-node-it.sh
│ ├── resources
│ ├── genesis.conf
│ ├── jul.properties
│ ├── logback-test.xml
│ ├── nodes
│ │ ├── jul.properties
│ │ ├── logback-container.xml
│ │ ├── waves-1.conf
│ │ └── waves-base.conf
│ └── testcontainers.properties
│ └── scala
│ └── com
│ └── wavesplatform
│ └── dex
│ └── grpc
│ └── integration
│ ├── ExchangeTransactionMinimumFeeTestSuite.scala
│ ├── IntegrationSuiteBase.scala
│ ├── TransactionsOrderingTestSuite.scala
│ └── clients
│ ├── BlockchainUpdatesClientTestSuite.scala
│ └── combined
│ └── CombinedWavesBlockchainClientTestSuite.scala
├── waves-integration
├── build.sbt
└── src
│ ├── main
│ └── scala
│ │ ├── akka
│ │ └── actor
│ │ │ └── typed
│ │ │ ├── internal
│ │ │ └── StashBufferImplWithCtxPropagation.scala
│ │ │ └── scaladsl
│ │ │ └── BehaviorsImplicits.scala
│ │ └── com
│ │ └── wavesplatform
│ │ └── dex
│ │ ├── actors
│ │ └── CustomLoggerBehaviorInterceptor.scala
│ │ ├── collections
│ │ ├── FifoSet.scala
│ │ ├── ListOps.scala
│ │ ├── MapOps.scala
│ │ ├── OrdersRangeMap.scala
│ │ └── VectorOps.scala
│ │ ├── domain
│ │ ├── account
│ │ │ ├── Address.scala
│ │ │ ├── AddressOrAlias.scala
│ │ │ ├── AddressScheme.scala
│ │ │ ├── Alias.scala
│ │ │ ├── KeyPair.scala
│ │ │ ├── PrivateKey.scala
│ │ │ ├── PublicKey.scala
│ │ │ └── package.scala
│ │ ├── asset
│ │ │ ├── Asset.scala
│ │ │ └── AssetPair.scala
│ │ ├── bytes
│ │ │ ├── ByteStr.scala
│ │ │ ├── codec
│ │ │ │ ├── Base58.scala
│ │ │ │ ├── Base64.scala
│ │ │ │ ├── BaseXXEncDec.scala
│ │ │ │ ├── FastBase58.scala
│ │ │ │ └── StdBase58.scala
│ │ │ └── deser
│ │ │ │ ├── EntityParser.scala
│ │ │ │ └── package.scala
│ │ ├── crypto
│ │ │ ├── Authorized.scala
│ │ │ ├── Proofs.scala
│ │ │ ├── Proven.scala
│ │ │ ├── Verifier.scala
│ │ │ └── package.scala
│ │ ├── error
│ │ │ └── ValidationError.scala
│ │ ├── feature
│ │ │ └── BlockhainFeature.scala
│ │ ├── model
│ │ │ └── package.scala
│ │ ├── order
│ │ │ ├── EthOrders.scala
│ │ │ ├── Order.scala
│ │ │ ├── OrderAuthentication.scala
│ │ │ ├── OrderJson.scala
│ │ │ ├── OrderOps.scala
│ │ │ ├── OrderType.scala
│ │ │ ├── OrderV1.scala
│ │ │ ├── OrderV2.scala
│ │ │ ├── OrderV3.scala
│ │ │ └── OrderV4.scala
│ │ ├── serialization
│ │ │ └── ByteAndJsonSerializable.scala
│ │ ├── state
│ │ │ ├── LeaseBalance.scala
│ │ │ ├── Portfolio.scala
│ │ │ └── package.scala
│ │ ├── transaction
│ │ │ ├── ExchangeTransaction.scala
│ │ │ ├── ExchangeTransactionParser.scala
│ │ │ ├── ExchangeTransactionResult.scala
│ │ │ ├── ExchangeTransactionV1.scala
│ │ │ ├── ExchangeTransactionV2.scala
│ │ │ └── ExchangeTransactionV3.scala
│ │ ├── utils
│ │ │ ├── PBUtils.scala
│ │ │ ├── ScorexLogging.scala
│ │ │ └── package.scala
│ │ └── validation
│ │ │ └── Validation.scala
│ │ ├── fp
│ │ ├── MapImplicits.scala
│ │ ├── MayBeEmpty.scala
│ │ └── PartialFunctionOps.scala
│ │ ├── grpc
│ │ ├── integration
│ │ │ ├── caches
│ │ │ │ ├── AssetDescriptionsCache.scala
│ │ │ │ ├── BalancesCache.scala
│ │ │ │ ├── BlockchainCache.scala
│ │ │ │ └── FeaturesCache.scala
│ │ │ ├── clients
│ │ │ │ ├── BroadcastResult.scala
│ │ │ │ ├── CheckedBroadcastResult.scala
│ │ │ │ ├── ControlledStream.scala
│ │ │ │ ├── RunScriptResult.scala
│ │ │ │ ├── WavesBlockchainClient.scala
│ │ │ │ ├── blockchainupdates
│ │ │ │ │ ├── BlockchainUpdatesClient.scala
│ │ │ │ │ ├── BlockchainUpdatesControlledStream.scala
│ │ │ │ │ ├── BlockchainUpdatesConversions.scala
│ │ │ │ │ └── GrpcBlockchainUpdatesControlledStream.scala
│ │ │ │ ├── combined
│ │ │ │ │ ├── AkkaCombinedStream.scala
│ │ │ │ │ ├── CombinedStream.scala
│ │ │ │ │ ├── CombinedStreamActor.scala
│ │ │ │ │ ├── CombinedWavesBlockchainClient.scala
│ │ │ │ │ └── NoOpCombinedStream.scala
│ │ │ │ ├── domain
│ │ │ │ │ ├── AddressBalanceUpdates.scala
│ │ │ │ │ ├── BlockRef.scala
│ │ │ │ │ ├── BlockchainBalance.scala
│ │ │ │ │ ├── BlockchainStatus.scala
│ │ │ │ │ ├── DiffIndex.scala
│ │ │ │ │ ├── StatusTransitions.scala
│ │ │ │ │ ├── StatusUpdate.scala
│ │ │ │ │ ├── TransactionWithChanges.scala
│ │ │ │ │ ├── UtxUpdate.scala
│ │ │ │ │ ├── WavesBlock.scala
│ │ │ │ │ ├── WavesChain.scala
│ │ │ │ │ ├── WavesFork.scala
│ │ │ │ │ ├── WavesNodeEvent.scala
│ │ │ │ │ ├── WavesNodeUpdates.scala
│ │ │ │ │ └── portfolio
│ │ │ │ │ │ ├── DefaultPessimisticPortfolios.scala
│ │ │ │ │ │ ├── Implicits.scala
│ │ │ │ │ │ ├── LookAheadPessimisticPortfolios.scala
│ │ │ │ │ │ ├── PessimisticPortfolios.scala
│ │ │ │ │ │ ├── PessimisticTransaction.scala
│ │ │ │ │ │ ├── SynchronizedPessimisticPortfolios.scala
│ │ │ │ │ │ └── package.scala
│ │ │ │ └── matcherext
│ │ │ │ │ ├── GrpcUtxEventsControlledStream.scala
│ │ │ │ │ ├── MatcherExtensionCachingClient.scala
│ │ │ │ │ ├── MatcherExtensionClient.scala
│ │ │ │ │ ├── MatcherExtensionGrpcAsyncClient.scala
│ │ │ │ │ ├── UtxEventConversions.scala
│ │ │ │ │ └── UtxEventsControlledStream.scala
│ │ │ ├── dto
│ │ │ │ └── BriefAssetDescription.scala
│ │ │ ├── effect
│ │ │ │ └── Implicits.scala
│ │ │ ├── exceptions
│ │ │ │ ├── UnexpectedConnectionException.scala
│ │ │ │ └── WavesNodeConnectionLostException.scala
│ │ │ ├── protobuf
│ │ │ │ ├── DexToPbConversions.scala
│ │ │ │ └── PbToDexConversions.scala
│ │ │ ├── settings
│ │ │ │ ├── GrpcClientSettings.scala
│ │ │ │ └── WavesBlockchainClientSettings.scala
│ │ │ └── tool
│ │ │ │ └── RestartableManagedChannel.scala
│ │ └── observers
│ │ │ ├── ClosingObserver.scala
│ │ │ └── IntegrationObserver.scala
│ │ ├── meta
│ │ └── package.scala
│ │ ├── remote
│ │ └── Delay.scala
│ │ └── tool
│ │ └── KamonTraceUtils.scala
│ └── test
│ ├── resources
│ ├── logback-test.xml
│ └── logback-verbose.xml
│ └── scala
│ └── com
│ └── wavesplatform
│ └── dex
│ ├── NoShrink.scala
│ ├── WavesIntegrationSuiteBase.scala
│ ├── collections
│ ├── ListOfMapOpsSpec.scala
│ └── OrdersRangeMapSpec.scala
│ ├── domain
│ └── order
│ │ ├── EthOrdersSpec.scala
│ │ └── OrderJsonSpec.scala
│ ├── grpc
│ ├── integration
│ │ ├── caches
│ │ │ └── BlockchainCacheSpecification.scala
│ │ ├── clients
│ │ │ ├── combined
│ │ │ │ ├── AkkaCombinedStreamTestSuite.scala
│ │ │ │ └── CombinedStreamActorTestSuite.scala
│ │ │ └── domain
│ │ │ │ ├── StatusTransitionsTestSuite.scala
│ │ │ │ ├── WavesChainTestSuite.scala
│ │ │ │ ├── WavesForkTestSuite.scala
│ │ │ │ └── portfolio
│ │ │ │ ├── DefaultPessimisticPortfoliosTestSuite.scala
│ │ │ │ ├── LookAheadPessimisticPortfoliosTestSuite.scala
│ │ │ │ ├── PBEntitiesGen.scala
│ │ │ │ ├── PessimisticPortfoliosTestSuiteBase.scala
│ │ │ │ └── StateUpdateOpsTestSuite.scala
│ │ └── tool
│ │ │ └── RestartableManagedChannelSuite.scala
│ └── observers
│ │ └── IntegrationObserverSpec.scala
│ └── test
│ └── matchers
│ └── ProduceError.scala
└── wavesNode.sbt
/.gitattributes:
--------------------------------------------------------------------------------
1 | src/main/resources/swagger-ui/* linguist-vendored
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug-fix
6 | assignees: koloale
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. Debian/Ubuntu/iOS]
28 | - Browser/JVM [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: feature
6 | assignees: koloale
7 |
8 | ---
9 |
10 | **Аbstract**
11 | Is your feature request related to a problem? Please describe.
12 |
13 | **Motivation and Purposes**
14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...].
15 |
16 | ** Specification**
17 | A clear and concise description of what you want to happen. Describe alternatives you've considered
18 |
19 | **Backwards Compatibility**
20 | Can your proposition affect any existing features?
21 |
22 | **Examples and Implementation**
23 | Examples of implementation in other projects?
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE/Editor files
2 | .idea/
3 | .ensime/
4 | .ensime_cache/
5 | .metals/
6 | .bsp/
7 | matcher.iml
8 | .bloop/
9 | project/metals.sbt
10 | project/project/
11 |
12 | # scala build folders and files
13 | target
14 | *.tgz
15 | *.gz
16 | *.jar
17 | !third-party/yourkit/yjp-controller-api-redist.jar
18 |
19 | # other dependencies
20 | .node_modules
21 |
22 | # vim
23 | *.*~
24 |
25 | # dotfiles
26 | .dockerignore
27 | .editorconfig
28 | .DS_Store
29 |
30 | # logs
31 | *.log
32 |
33 | # local settings
34 | localsettings.json
35 | local.conf
36 | waves_logback.xml
37 |
38 | # Text files
39 | *.txt
40 | *.ods
41 | *.csv
42 | *.dat
43 |
44 | # standalone docker
45 | Dockerfile
46 |
47 | # logs
48 | *.log
49 |
50 | native
51 |
52 | allure-results
53 |
54 | data
55 |
--------------------------------------------------------------------------------
/.sbtopts:
--------------------------------------------------------------------------------
1 | -J-server
2 | -J-Xss256m
3 | -J-Xmx2G
4 | -J-XX:MetaspaceSize=256m
5 | -J-XX:MaxMetaspaceExpansion=0
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 WavesPlatform
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/dex-it-common/build.sbt:
--------------------------------------------------------------------------------
1 | description := "Shared classes for integration tests of DEX and waves-integration"
2 |
3 | libraryDependencies ++= Dependencies.Module.dexItCommon
4 |
5 | scalacOptions += "-P:silencer:globalFilters=^magnolia: using fallback derivation.*$" // https://github.com/softwaremill/diffx#customization
6 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/node/ConnectReq.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.node
2 |
3 | import play.api.libs.json.{Format, Json}
4 |
5 | case class ConnectReq(host: String, port: Int)
6 |
7 | object ConnectReq {
8 | implicit val connectFormat: Format[ConnectReq] = Json.format
9 | }
10 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/node/NodeApiExtensions.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.node
2 |
3 | import cats.Id
4 | import com.wavesplatform.transactions.Transaction
5 |
6 | trait NodeApiExtensions {
7 |
8 | this: HasWavesNode =>
9 |
10 | protected def broadcast(tx: Transaction): Unit = wavesNode1.api broadcast tx
11 |
12 | protected def broadcastAndAwait(txs: Transaction*): Unit = broadcastAndAwait(wavesNode1.api, txs: _*)
13 |
14 | protected def broadcastAndAwait(wavesNodeApi: NodeApi[Id], txs: Transaction*): Unit = {
15 | txs.foreach(wavesNodeApi.broadcast)
16 | txs.foreach(wavesNodeApi.waitForTransaction)
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/node/RollbackReq.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.node
2 |
3 | import play.api.libs.json.{Format, Json}
4 |
5 | case class RollbackReq(rollbackTo: Int, returnTransactionsToUtx: Boolean)
6 |
7 | object RollbackReq {
8 | implicit val rollbackFormat: Format[RollbackReq] = Json.format
9 | }
10 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/responses/node/AssetBalanceResponse.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.responses.node
2 |
3 | import play.api.libs.json.{Format, Json}
4 |
5 | case class AssetBalanceResponse(address: String, assetId: String, balance: Long)
6 |
7 | object AssetBalanceResponse {
8 | implicit val assetBalanceFormat: Format[AssetBalanceResponse] = Json.format
9 | }
10 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/responses/node/AssetsBalancesResponse.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.responses.node
2 |
3 | import com.wavesplatform.dex.it.api.responses.node.AssetsBalancesResponse.AssetBalance
4 | import play.api.libs.json.{Format, JsObject, Json}
5 |
6 | case class AssetsBalancesResponse(address: String, balances: Seq[AssetBalance])
7 |
8 | object AssetsBalancesResponse {
9 | implicit val format: Format[AssetsBalancesResponse] = Json.format[AssetsBalancesResponse]
10 |
11 | case class AssetBalance(
12 | assetId: String,
13 | balance: Long,
14 | reissuable: Boolean,
15 | minSponsoredAssetFee: Option[Long],
16 | sponsorBalance: Option[Long],
17 | quantity: Long,
18 | issueTransaction: Option[JsObject]
19 | )
20 |
21 | object AssetBalance {
22 | implicit val format: Format[AssetBalance] = Json.format[AssetBalance]
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/responses/node/ConnectedPeersResponse.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.responses.node
2 |
3 | import com.wavesplatform.dex.it.api.responses.node.ConnectedPeersResponse.PeerInfo
4 | import play.api.libs.json.{Format, Json}
5 |
6 | case class ConnectedPeersResponse(peers: List[PeerInfo])
7 |
8 | object ConnectedPeersResponse {
9 | implicit val format: Format[ConnectedPeersResponse] = Json.format[ConnectedPeersResponse]
10 |
11 | case class PeerInfo(
12 | address: String,
13 | declaredAddress: String,
14 | peerName: String,
15 | peerNonce: Long,
16 | applicationName: String,
17 | applicationVersion: String
18 | )
19 |
20 | object PeerInfo {
21 | implicit val format: Format[PeerInfo] = Json.format[PeerInfo]
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/responses/node/ErrorResponse.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.responses.node
2 |
3 | import play.api.libs.json.{Format, Json}
4 |
5 | case class ErrorResponse(error: Int, message: String)
6 |
7 | object ErrorResponse {
8 | implicit val format: Format[ErrorResponse] = Json.format[ErrorResponse]
9 | }
10 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/responses/node/HeightResponse.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.responses.node
2 |
3 | import play.api.libs.json.{Format, Json}
4 |
5 | case class HeightResponse(height: Int)
6 |
7 | object HeightResponse {
8 | implicit val format: Format[HeightResponse] = Json.format[HeightResponse]
9 | }
10 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/responses/node/NftAsset.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.responses.node
2 |
3 | import play.api.libs.json.{Format, Json}
4 |
5 | case class NftAsset(
6 | assetId: String,
7 | issueHeight: Long,
8 | issueTimestamp: Long,
9 | issuer: String,
10 | name: String,
11 | description: String,
12 | decimals: Long,
13 | quantity: Long,
14 | scripted: Boolean,
15 | minSponsoredAssetFee: Option[Long],
16 | originTransactionId: Option[String]
17 | )
18 |
19 | object NftAsset {
20 | implicit val format: Format[NftAsset] = Json.format[NftAsset]
21 | }
22 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/responses/node/WavesBalanceResponse.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.responses.node
2 |
3 | import play.api.libs.json.{Format, Json}
4 |
5 | case class WavesBalanceResponse(address: String, confirmations: Int, balance: Long)
6 |
7 | object WavesBalanceResponse {
8 | implicit val balanceFormat: Format[WavesBalanceResponse] = Json.format
9 | }
10 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/api/responses/node/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.api.responses
2 |
3 | import com.wavesplatform.transactions.Transaction
4 | import play.api.libs.json.{JsSuccess, Reads}
5 |
6 | package object node {
7 |
8 | implicit val transactionReads: Reads[Transaction] = Reads { js =>
9 | JsSuccess(Transaction.fromJson(js.toString()))
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/cache/CachedData.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.cache
2 |
3 | class CachedData[T <: AnyRef](getData: => T) {
4 |
5 | @volatile private var cached = Option.empty[T]
6 |
7 | def get(): T = synchronized {
8 | cached match {
9 | case Some(x) => x
10 | case None =>
11 | val r = getData
12 | cached = Some(r)
13 | r
14 | }
15 | }
16 |
17 | def invalidate(): Unit = synchronized {
18 | cached = None
19 | }
20 |
21 | }
22 |
23 | object CachedData {
24 | def apply[T <: AnyRef](getData: => T): CachedData[T] = new CachedData[T](getData)
25 | }
26 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/collections/Implicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.collections
2 |
3 | object Implicits {
4 |
5 | implicit final class ListOps[T](val self: List[T]) extends AnyVal {
6 | def prependIf(cond: Boolean)(item: => T): List[T] = if (cond) item :: self else self
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/config/GenesisConfig.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.config
2 |
3 | import com.typesafe.config.{Config, ConfigFactory}
4 | import com.wavesplatform.dex.domain.account.AddressScheme
5 |
6 | object GenesisConfig {
7 | val generatorConfig: Config = ConfigFactory.parseResources("genesis.conf")
8 | val config: Config = GenesisConfigGenerator.generate(generatorConfig)
9 |
10 | val chainId = config.getString("waves.blockchain.custom.address-scheme-character").head.toByte
11 |
12 | def setupAddressScheme(): Unit =
13 | if (AddressScheme.current.chainId != chainId)
14 | AddressScheme.current = new AddressScheme {
15 | override val chainId: Byte = GenesisConfig.chainId
16 | }
17 |
18 | setupAddressScheme()
19 | }
20 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/config/genesis/GenesisSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.config.genesis
2 |
3 | import com.wavesplatform.dex.domain.bytes.ByteStr
4 |
5 | import scala.concurrent.duration.FiniteDuration
6 |
7 | case class GenesisSettings(
8 | blockTimestamp: Long,
9 | timestamp: Long,
10 | initialBalance: Long,
11 | signature: Option[ByteStr],
12 | transactions: Seq[GenesisTransactionSettings],
13 | initialBaseTarget: Long,
14 | averageBlockDelay: FiniteDuration
15 | )
16 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/config/genesis/GenesisTransactionSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.config.genesis
2 |
3 | case class GenesisTransactionSettings(recipient: String, amount: Long)
4 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/config/genesis/NxtLikeConsensusBlockData.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.config.genesis
2 |
3 | import com.wavesplatform.dex.domain.bytes.ByteStr
4 |
5 | case class NxtLikeConsensusBlockData(baseTarget: Long, generationSignature: ByteStr)
6 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/config/genesis/SignerData.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.config.genesis
2 |
3 | import com.wavesplatform.dex.domain.account.PublicKey
4 | import com.wavesplatform.dex.domain.bytes.ByteStr
5 |
6 | case class SignerData(generator: PublicKey, signature: ByteStr)
7 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/docker/MountableFileOps.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.docker
2 |
3 | import java.nio.charset.StandardCharsets
4 | import java.nio.file.Files
5 |
6 | import mouse.any._
7 | import org.testcontainers.utility.MountableFile
8 |
9 | // Can't create an implicit for Java's MountableFile.type
10 | object MountableFileOps {
11 |
12 | def fromContent(content: String): MountableFile = MountableFile.forHostPath {
13 | Files.createTempFile("dex-it", "").unsafeTap(Files.write(_, content getBytes StandardCharsets.UTF_8))
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/docker/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it
2 |
3 | import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy
4 | import org.testcontainers.utility.TestcontainersConfiguration
5 |
6 | package object docker {
7 | val apiKey = "integration-test-rest-api"
8 | val ignoreWaitStrategy: AbstractWaitStrategy = () => ()
9 |
10 | TestcontainersConfiguration.getInstance().updateUserConfig("checks.disable", "true")
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/fp/CanExtract.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.fp
2 |
3 | trait CanExtract[F[_]] {
4 | def extract[ErrorT, ResultT](f: => F[Either[ErrorT, ResultT]]): F[ResultT]
5 | }
6 |
7 | object CanExtract extends CanExtractInstances
8 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/fp/CanExtractInstances.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.fp
2 |
3 | import cats.{Id, MonadError}
4 |
5 | trait CanExtractInstances extends {
6 |
7 | implicit def monadError[F[_]](implicit M: MonadError[F, Throwable]): CanExtract[F] = new CanExtract[F] {
8 |
9 | override def extract[ErrorT, ResultT](f: => F[Either[ErrorT, ResultT]]): F[ResultT] = M.flatMap(f) {
10 | case Left(e) => M.raiseError(new RuntimeException(s"Can't extract: $e"))
11 | case Right(r) => M.pure(r)
12 | }
13 |
14 | }
15 |
16 | implicit val id: CanExtract[Id] = new CanExtract[Id] {
17 |
18 | override def extract[ErrorT, ResultT](f: => Id[Either[ErrorT, ResultT]]): Id[ResultT] = f match {
19 | case Left(e) => throw new RuntimeException(s"Can't extract: $e")
20 | case Right(r) => r
21 | }
22 |
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/fp/CanWait.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.fp
2 |
3 | import cats.Id
4 | import com.wavesplatform.dex.it.time.GlobalTimer
5 | import com.wavesplatform.dex.it.time.GlobalTimer.TimerOpsImplicits
6 |
7 | import scala.concurrent.Future
8 | import scala.concurrent.duration.FiniteDuration
9 | import scala.util.{Success, Try}
10 |
11 | trait CanWait[F[_]] {
12 | def wait(duration: FiniteDuration): F[Unit]
13 | }
14 |
15 | object CanWait {
16 |
17 | implicit val future: CanWait[Future] = (duration: FiniteDuration) => GlobalTimer.instance.sleep(duration)
18 |
19 | implicit val tryCanWait: CanWait[Try] = { (duration: FiniteDuration) =>
20 | Thread.sleep(duration.toMillis)
21 | Success(())
22 | }
23 |
24 | implicit val idCanWait: CanWait[Id] = (duration: FiniteDuration) => Thread.sleep(duration.toMillis)
25 |
26 | def apply[F[_]](implicit W: CanWait[F]): CanWait[F] = W
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/fp/RepeatRequestOptions.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.fp
2 |
3 | import scala.concurrent.duration.{DurationInt, FiniteDuration}
4 |
5 | case class RepeatRequestOptions(delayBetweenRequests: FiniteDuration, maxAttempts: Int) {
6 | def decreaseAttempts: RepeatRequestOptions = copy(maxAttempts = maxAttempts - 1)
7 | }
8 |
9 | object RepeatRequestOptions {
10 | val default = RepeatRequestOptions(1.second, 60)
11 | }
12 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/fp/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it
2 |
3 | import cats.arrow.FunctionK
4 | import cats.tagless.FunctorK
5 | import cats.{~>, Id, MonadError}
6 |
7 | import scala.util.Try
8 |
9 | package object fp {
10 |
11 | type ThrowableMonadError[F[_]] = MonadError[F, Throwable]
12 |
13 | implicit val tryToId: Try ~> Id = new FunctionK[Try, Id] {
14 | def apply[A](fa: Try[A]): Id[A] = fa.get
15 | }
16 |
17 | def sync[F[_[_]]](x: F[Try])(implicit functorK: FunctorK[F]): F[Id] = functorK.mapK(x)(tryToId)
18 | }
19 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/resources/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it
2 |
3 | import java.io.FileNotFoundException
4 |
5 | import scala.io.Source
6 | import scala.util.Try
7 |
8 | package object resources {
9 |
10 | def getRawContentFromResource(fileName: String): String =
11 | Try(Source fromResource fileName).getOrElse(throw new FileNotFoundException(s"Resource '$fileName'")).mkString
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/test/NoStackTraceCancelAfterFailure.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.test
2 |
3 | import org.scalatest._
4 |
5 | trait NoStackTraceCancelAfterFailure extends CancelAfterFailure { this: TestSuite =>
6 |
7 | abstract override def withFixture(test: NoArgTest): Outcome = super.withFixture(test) match {
8 | case x: Canceled =>
9 | x.exception.setStackTrace(Array.empty)
10 | x
11 | case x => x
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/dex-it-common/src/main/scala/com/wavesplatform/dex/it/time/GlobalTimer.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.it.time
2 |
3 | import io.netty.util.{HashedWheelTimer, Timer}
4 |
5 | import scala.concurrent.duration.FiniteDuration
6 | import scala.concurrent.{Future, Promise}
7 | import scala.util.control.NonFatal
8 |
9 | object GlobalTimer {
10 |
11 | val instance: Timer = new HashedWheelTimer()
12 |
13 | sys.addShutdownHook {
14 | instance.stop()
15 | }
16 |
17 | implicit class TimerOpsImplicits(val timer: Timer) extends AnyVal {
18 |
19 | def schedule[A](f: => Future[A], delay: FiniteDuration): Future[A] = {
20 | val p = Promise[A]()
21 | try timer.newTimeout(_ => p.completeWith(f), delay.length, delay.unit)
22 | catch {
23 | case NonFatal(e) => p.failure(e)
24 | }
25 | p.future
26 | }
27 |
28 | def sleep(term: FiniteDuration): Future[Unit] = schedule(Future.successful(()), term)
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/dex-it/src/test/container/start-matcher-server-it.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | trap 'kill -TERM $PID' TERM INT
4 |
5 | echo "Starting process..." >> ${DETAILED_LOG_PATH}
6 | echo "Config file: ${WAVES_DEX_CONFIGPATH}" >> ${DETAILED_LOG_PATH}
7 | echo "Options: ${WAVES_DEX_OPTS}" >> ${DETAILED_LOG_PATH}
8 |
9 | # Remove sun.zip.disableMemoryMapping after migration to JRE 9+
10 | # See https://bugs.openjdk.java.net/browse/JDK-8175192
11 | # Also see nice table about &>> https://askubuntu.com/a/731237
12 | /usr/share/waves-dex/bin/waves-dex \
13 | -Dsun.zip.disableMemoryMapping=true \
14 | ${WAVES_DEX_OPTS} \
15 | -main com.wavesplatform.dex.Application -- ${WAVES_DEX_CONFIGPATH} &>> ${DETAILED_LOG_PATH} &
16 |
17 | PID=$!
18 | echo "PID: ${PID}" >> ${DETAILED_LOG_PATH}
19 | wait ${PID}
20 |
21 | trap - TERM INT
22 | wait ${PID}
23 | EXIT_STATUS=$?
24 | echo "Exit status: ${EXIT_STATUS}" >> ${DETAILED_LOG_PATH}
25 | exit ${EXIT_STATUS}
26 |
--------------------------------------------------------------------------------
/dex-it/src/test/java/com/wavesplatform/it/tags/DexItExternalKafkaRequired.java:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.tags;
2 |
3 | import org.scalatest.TagAnnotation;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | @TagAnnotation
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target({ElementType.TYPE})
13 | public @interface DexItExternalKafkaRequired {
14 | }
15 |
--------------------------------------------------------------------------------
/dex-it/src/test/java/com/wavesplatform/it/tags/DexMultipleVersions.java:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.tags;
2 |
3 | import org.scalatest.TagAnnotation;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | @TagAnnotation
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target({ElementType.TYPE})
13 | public @interface DexMultipleVersions {
14 | }
15 |
--------------------------------------------------------------------------------
/dex-it/src/test/java/com/wavesplatform/it/tags/NetworkTests.java:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.tags;
2 |
3 | import org.scalatest.TagAnnotation;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | @TagAnnotation
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target({ElementType.TYPE})
13 | public @interface NetworkTests {
14 | }
15 |
--------------------------------------------------------------------------------
/dex-it/src/test/java/com/wavesplatform/it/tags/SmokeTests.java:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.tags;
2 |
3 | import org.scalatest.TagAnnotation;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | @TagAnnotation
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target({ElementType.TYPE})
13 | public @interface SmokeTests {
14 | }
15 |
--------------------------------------------------------------------------------
/dex-it/src/test/resources/dex-servers/dex-1.conf:
--------------------------------------------------------------------------------
1 | # Base configuration for DEX servers for dex-it tests. It is assumed that this configuration does not change
2 | include "dex-base.conf"
3 |
4 | waves.dex {
5 | id = "matcher-1"
6 | # For different DEX servers connected to the same topic should be different groups
7 | events-queue.kafka.group = 1
8 | }
9 |
10 | # Configuration for the waves.dex.events-queue section (kafka settings) are built in runtime
11 | include "run.conf"
12 |
13 | # Highest priority configurations specified in tests (suiteInitialDexConfig)
14 | include "suite.conf"
--------------------------------------------------------------------------------
/dex-it/src/test/resources/dex-servers/dex-2.conf:
--------------------------------------------------------------------------------
1 | # Base configuration for DEX servers for dex-it tests. It is assumed that this configuration does not change
2 | include "dex-base.conf"
3 |
4 | waves.dex {
5 | id = "matcher-2"
6 | # For different DEX servers connected to the same topic should be different groups
7 | events-queue.kafka.group = 2
8 | }
9 |
10 | # Configuration for the waves.dex.events-queue section (kafka settings) are built in runtime
11 | include "run.conf"
12 |
13 | # Highest priority configurations specified in tests (suiteInitialDexConfig)
14 | include "suite.conf"
15 |
--------------------------------------------------------------------------------
/dex-it/src/test/resources/dex-servers/jul.properties:
--------------------------------------------------------------------------------
1 | # java.util.logging properties
2 | # To pass logs to SLF4J
3 | handlers=org.slf4j.bridge.SLF4JBridgeHandler
4 | # To enable gRPC logging. See levels in java.util.logging.Level
5 | io.grpc.level=WARNING
6 |
--------------------------------------------------------------------------------
/dex-it/src/test/resources/nodes/jul.properties:
--------------------------------------------------------------------------------
1 | # java.util.logging properties
2 | # To pass logs to SLF4J
3 | handlers=org.slf4j.bridge.SLF4JBridgeHandler
4 | # To enable gRPC logging. See levels in java.util.logging.Level
5 | io.grpc.level=WARNING
6 |
--------------------------------------------------------------------------------
/dex-it/src/test/resources/nodes/waves-1.conf:
--------------------------------------------------------------------------------
1 | # Base configuration for waves nodes for dex-it tests. It is assumed that this configuration does not change
2 | include "waves-base.conf"
3 |
4 | # Node name to send during handshake
5 | waves.network.node-name = "node01"
6 |
7 | # Configuration of the waves.blockchain.custom.genesis/address-scheme-character sections
8 | # generated by GenesisConfigGenerator from dex-it/src/test/resources/genesis.conf
9 | include "run.conf"
10 |
11 | # Highest priority configurations specified in tests (suiteInitialWavesNodeConfig)
12 | include "suite.conf"
--------------------------------------------------------------------------------
/dex-it/src/test/resources/nodes/waves-2.conf:
--------------------------------------------------------------------------------
1 | # Base configuration for waves nodes for dex-it tests. It is assumed that this configuration does not change
2 | include "waves-base.conf"
3 |
4 | # Node name to send during handshake
5 | waves.network.node-name = "node02"
6 |
7 | # Configuration of the waves.blockchain.custom.genesis/address-scheme-character sections
8 | # generated by GenesisConfigGenerator from dex-it/src/test/resources/genesis.conf
9 | include "run.conf"
10 |
11 | # Highest priority configurations specified in tests (suiteInitialWavesNodeConfig)
12 | include "suite.conf"
--------------------------------------------------------------------------------
/dex-it/src/test/resources/testcontainers.properties:
--------------------------------------------------------------------------------
1 | transport.type=httpclient5
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/api/MatcherCommand.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.api
2 |
3 | import com.wavesplatform.dex.domain.account.KeyPair
4 | import com.wavesplatform.dex.domain.order.Order
5 | import com.wavesplatform.dex.it.docker.DexContainer
6 |
7 | sealed trait MatcherCommand extends Product with Serializable
8 |
9 | object MatcherCommand {
10 | case class Place(dex: DexContainer, order: Order, isMarket: Boolean = false) extends MatcherCommand
11 | case class Cancel(dex: DexContainer, owner: KeyPair, order: Order) extends MatcherCommand
12 | }
13 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/api/MatcherState.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.api
2 |
3 | import com.wavesplatform.dex.api.http.entities._
4 | import com.wavesplatform.dex.domain.account.KeyPair
5 | import com.wavesplatform.dex.domain.asset.AssetPair
6 |
7 | case class MatcherState(
8 | offset: HttpOffset,
9 | snapshots: HttpSnapshotOffsets,
10 | orderBooks: Map[AssetPair, (HttpV0OrderBook, HttpOrderBookStatus)],
11 | orderStatuses: Map[String, HttpOrderStatus],
12 | orderTransactionIds: Map[String, Set[String]],
13 | reservedBalances: Map[KeyPair, HttpBalance],
14 | orderHistory: Map[KeyPair, Map[AssetPair, Seq[HttpOrderBookHistoryItem]]]
15 | ) {
16 |
17 | override def toString: String =
18 | s"""MatcherState(
19 | | offset=$offset,
20 | | snapshots=$snapshots,
21 | | orderBooks=$orderBooks,
22 | | orderStatuses=$orderStatuses,
23 | | orderTransactionIds=$orderTransactionIds,
24 | | reservedBalances=$reservedBalances,
25 | | orderHistory=$orderHistory
26 | |)""".stripMargin
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/matcher/api/http/ApiKeyHeaderChecks.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.matcher.api.http
2 |
3 | import com.wavesplatform.dex.it.api.{EnrichedResponse, RawHttpChecks}
4 | import org.scalatest.freespec.AnyFreeSpec
5 |
6 | trait ApiKeyHeaderChecks extends AnyFreeSpec with RawHttpChecks {
7 |
8 | val incorrectApiKeyHeader = Map("X-API-KEY" -> "incorrect")
9 |
10 | def shouldReturnErrorWithoutApiKeyHeader[ErrorT, EntityT](r: => EnrichedResponse[ErrorT, EntityT]) =
11 | "should return an error without X-API-KEY" in validateAuthorizationError(r)
12 |
13 | def shouldReturnErrorWithIncorrectApiKeyValue[ErrorT, EntityT](r: => EnrichedResponse[ErrorT, EntityT]) =
14 | "should return an error with incorrect X-API-KEY" in validateAuthorizationError(r)
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/matcher/api/http/debug/GetAddressStateSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.matcher.api.http.debug
2 |
3 | import com.wavesplatform.dex.domain.account.KeyPair.toAddress
4 | import com.wavesplatform.it.MatcherSuiteBase
5 | import com.wavesplatform.it.matcher.api.http.ApiKeyHeaderChecks
6 |
7 | class GetAddressStateSpec extends MatcherSuiteBase with ApiKeyHeaderChecks {
8 |
9 | "GET /matcher/debug/address/{address}" - {
10 | "should return correct state" in {
11 | validate200Json(dex1.rawApi.getAddressState(alice))
12 | }
13 |
14 | shouldReturnErrorWithoutApiKeyHeader(dex1.rawApi.getAddressState(alice, Map.empty))
15 |
16 | shouldReturnErrorWithIncorrectApiKeyValue(dex1.rawApi.getAddressState(alice, incorrectApiKeyHeader))
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/matcher/api/http/debug/GetMatcherConfigSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.matcher.api.http.debug
2 |
3 | import com.wavesplatform.dex.settings.utils.ConfigOps.ConfigOps
4 | import com.wavesplatform.it.MatcherSuiteBase
5 | import com.wavesplatform.it.matcher.api.http.ApiKeyHeaderChecks
6 |
7 | class GetMatcherConfigSpec extends MatcherSuiteBase with ApiKeyHeaderChecks {
8 |
9 | override protected def beforeAll(): Unit = {
10 | wavesNode1.start()
11 | dex1.start()
12 | }
13 |
14 | "GET /matcher/debug/config" - {
15 |
16 | "should return correct config" in {
17 | val config = validate200Hocon(dex1.rawApi.getMatcherConfig).rendered
18 |
19 | Set("user", "pass", "seed", "private", "java", "sun", "api").foreach(config should not contain _)
20 | }
21 |
22 | shouldReturnErrorWithoutApiKeyHeader(dex1.rawApi.getMatcherConfig(Map.empty))
23 |
24 | shouldReturnErrorWithIncorrectApiKeyValue(dex1.rawApi.getMatcherConfig(incorrectApiKeyHeader))
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/matcher/api/http/debug/GetMatcherStatusSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.matcher.api.http.debug
2 |
3 | import com.wavesplatform.dex.app.MatcherStatus
4 | import com.wavesplatform.dex.grpc.integration.clients.combined.CombinedStream
5 | import com.wavesplatform.it.MatcherSuiteBase
6 | import com.wavesplatform.it.matcher.api.http.ApiKeyHeaderChecks
7 |
8 | class GetMatcherStatusSpec extends MatcherSuiteBase with ApiKeyHeaderChecks {
9 |
10 | "GET /matcher/debug/status" - {
11 | "should return matcher status" in {
12 | eventually {
13 | val httpSystemStatus = validate200Json(dex1.rawApi.getMatcherStatus)
14 | httpSystemStatus.blockchain shouldBe a[CombinedStream.Status.Working]
15 | httpSystemStatus.service shouldBe MatcherStatus.Working
16 | }
17 | }
18 |
19 | shouldReturnErrorWithoutApiKeyHeader(dex1.rawApi.getMatcherStatus(Map.empty))
20 |
21 | shouldReturnErrorWithIncorrectApiKeyValue(dex1.rawApi.getMatcherStatus(incorrectApiKeyHeader))
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/matcher/api/http/http.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.matcher.api
2 |
3 | import com.wavesplatform.dex.api.http.entities.HttpOrderBookHistoryItem
4 | import com.wavesplatform.dex.asset.DoubleOps.NumericOps
5 | import com.wavesplatform.dex.domain.order.Order
6 | import com.wavesplatform.dex.model.{AcceptedOrderType, OrderStatus}
7 |
8 | package object http {
9 |
10 | def toHttpOrderBookHistoryItem(
11 | order: Order,
12 | status: OrderStatus,
13 | fee: Long = 0.003.waves,
14 | acceptedOrderType: AcceptedOrderType = AcceptedOrderType.Limit,
15 | avgWeighedPrice: Long = 0,
16 | totalExecutedPriceAssets: Long = 0
17 | ): HttpOrderBookHistoryItem =
18 | HttpOrderBookHistoryItem(
19 | order.id(),
20 | order.orderType,
21 | acceptedOrderType,
22 | order.amount,
23 | status.filledAmount,
24 | order.price,
25 | fee,
26 | status.filledFee,
27 | order.feeAsset,
28 | order.timestamp,
29 | status.name,
30 | order.assetPair,
31 | avgWeighedPrice,
32 | order.version,
33 | totalExecutedPriceAssets
34 | )
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/matcher/api/http/info/GetMatcherPKInBase58Spec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.matcher.api.http.info
2 |
3 | import com.wavesplatform.dex.it.api.RawHttpChecks
4 | import com.wavesplatform.it.MatcherSuiteBase
5 |
6 | class GetMatcherPKInBase58Spec extends MatcherSuiteBase with RawHttpChecks {
7 |
8 | override protected def beforeAll(): Unit = {
9 | wavesNode1.start()
10 | broadcastAndAwait(IssueUsdTx)
11 | dex1.start()
12 | }
13 |
14 | "GET /matcher" - {
15 | "should return correct public key of matcher in base58" in {
16 | validate200Json(dex1.rawApi.getMatcherPKInBase58) should be(matcher.publicKey.toString)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/matcher/api/http/swagger/SwaggerSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.matcher.api.http.swagger
2 |
3 | import com.wavesplatform.dex.Version.VersionTuple
4 | import com.wavesplatform.dex.it.api.RawHttpChecks
5 | import com.wavesplatform.it.MatcherSuiteBase
6 | import com.wavesplatform.it.tags.DexMultipleVersions
7 |
8 | @DexMultipleVersions
9 | class SwaggerSpec extends MatcherSuiteBase with RawHttpChecks {
10 |
11 | "GET /api-docs/swagger.json " - {
12 |
13 | "should return correct version in json schema" in {
14 | val version = (validate200Json(dex1.rawApi.apiDocs()) \ "info" \ "version").as[String]
15 | version should startWith(s"${VersionTuple._1}.${VersionTuple._2}.${VersionTuple._3}")
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/sync/MarketStatusRecoveryTestSuite.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.sync
2 |
3 | import com.typesafe.config.{Config, ConfigFactory}
4 | import com.wavesplatform.it.api.MatcherState
5 |
6 | class MarketStatusRecoveryTestSuite extends MatcherRecoveryTestSuite {
7 |
8 | // To create a snapshot for each event at least for one order book
9 | override protected def dexInitialSuiteConfig: Config =
10 | ConfigFactory.parseString("waves.dex.snapshots-interval = 2").withFallback(super.dexInitialSuiteConfig)
11 |
12 | override protected def cleanState(state: MatcherState): MatcherState = state.copy(snapshots = Map.empty)
13 | }
14 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/sync/TradingMarketsTestSuite.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.sync
2 |
3 | import com.wavesplatform.dex.domain.order.OrderType.BUY
4 | import com.wavesplatform.it.MatcherSuiteBase
5 |
6 | class TradingMarketsTestSuite extends MatcherSuiteBase {
7 | val (amount, price) = (1000L, 1000000000L)
8 |
9 | override protected def beforeAll(): Unit = {
10 | super.beforeAll()
11 | broadcastAndAwait(IssueWctTx)
12 | }
13 |
14 | "When some orders were placed and matcher was restarted" - {
15 | "Trading markets have info about all asset pairs" in {
16 | placeAndAwaitAtDex(mkOrder(alice, wctWavesPair, BUY, amount, price))
17 |
18 | dex1.restart()
19 |
20 | val markets = dex1.api.getOrderBooks.markets
21 | markets.size shouldBe 1
22 | markets.head.amountAssetName shouldNot be("Unknown")
23 | markets.head.priceAssetName shouldNot be("Unknown")
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/sync/api/ws/WsPingPongInternalTestSuite.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.sync.api.ws
2 |
3 | import com.typesafe.config.{Config, ConfigFactory}
4 |
5 | class WsPingPongInternalTestSuite extends WsPingPongBaseSuite {
6 |
7 | override protected lazy val wsStreamUri = getWsInternalStreamUri(dex1)
8 |
9 | override protected val dexInitialSuiteConfig: Config = ConfigFactory
10 | .parseString(
11 | s"""waves.dex.web-sockets.internal-client-handler.health-check = {
12 | | ping-interval = $pingInterval
13 | | pong-timeout = $pongTimeout
14 | |}""".stripMargin
15 | )
16 | .withFallback(jwtPublicKeyConfig)
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/dex-it/src/test/scala/com/wavesplatform/it/sync/orders/OrderFeeBaseTestSuite.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.it.sync.orders
2 |
3 | import com.wavesplatform.it.MatcherSuiteBase
4 | import com.wavesplatform.dex.domain.asset.Asset
5 |
6 | class OrderFeeBaseTestSuite extends MatcherSuiteBase {
7 | val percentFee = 14
8 |
9 | val price = 1.2.usd
10 | val fullyAmountWaves = 15.waves
11 | val partiallyAmountWaves = 9.waves
12 | val fullyAmountUsd = 18.usd
13 | val minimalFee = 2.52.usd
14 | val partialMinimalFee = 1.512.usd
15 | val partiallyAmountUsd = 10.8.usd
16 | val tooLowFee = 2.51.usd
17 | val tooHighFee = 18.01.usd
18 | val minimalFeeWaves = 2.1.waves
19 | val partialMinimalFeeWaves = 1.26.waves
20 | val tooLowFeeWaves = 2.09.waves
21 | val tooHighFeeWaves = 15.00001.waves
22 | val partiallyFeeWaves = 1.26.waves
23 | val percentMinFeeInWaves = 0.003.waves
24 | val usdRate = 10
25 | val wctRate = 15
26 | val discount = 50.0
27 |
28 | def upsertAssetRate(pairs: (Asset.IssuedAsset, Double)*): Unit = pairs.foreach {
29 | case (asset, rate) => withClue(s"$asset")(dex1.api.upsertAssetRate(asset, rate))
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/dex-jmh/build.sbt:
--------------------------------------------------------------------------------
1 | enablePlugins(JmhPlugin)
2 |
3 | Compile / run / fork := true
4 |
5 | // https://github.com/ktoso/sbt-jmh#adding-to-your-project
6 | inConfig(Jmh)(
7 | Seq(
8 | sourceDirectory := (Test / sourceDirectory).value,
9 | classDirectory := (Test / classDirectory).value,
10 | dependencyClasspath := (Test / dependencyClasspath).value,
11 | // rewire tasks, so that 'jmh:run' automatically invokes 'jmh:compile' (otherwise a clean 'jmh:run' would fail)
12 | compile := compile.dependsOn(Test / compile).value,
13 | run := run.dependsOn(compile).evaluated
14 | )
15 | )
16 |
--------------------------------------------------------------------------------
/dex-load/src/main/resources/devnet.conf:
--------------------------------------------------------------------------------
1 | waves.dex.load {
2 | chain-id = "D"
3 | rich-account = "create genesis wallet devnet-1"
4 | dex-rest-api-key = "integration-test-rest-api"
5 | matcher-seed = "matcher"
6 | matcher-public-key = ${?MATCHER_PUBLIC_KEY}
7 |
8 | assets {
9 | quantity = 9000000000000000000
10 | issue-fee = 100000000
11 | pairs-count = 10
12 | count = 10
13 | }
14 |
15 | defaults {
16 | matcher-fee = 300000
17 | minimal-order-amount = 10000
18 | minimal-order-price = 10000
19 | pairs-file = "pairs.txt"
20 | max-orders-per-account = 400
21 | waves-per-account = 100000000
22 | mass-transfer-fee = 100000
23 | mass-transfer-multiplier = 50000
24 | }
25 |
26 | distribution {
27 | order-book-by-pair = 0.65
28 | order-status = 0.15
29 | tradable-balance = 0.101
30 | place-order = 0.033
31 | order-book-by-pair-and-key = 0.033
32 | cancel-order = 0.033
33 | }
34 |
35 | hosts {
36 | node = ${?NODE}
37 | matcher = ${?MATCHER}
38 | shooted = ${?AIM}
39 | }
40 | }
41 |
42 | include "local.conf"
43 |
--------------------------------------------------------------------------------
/dex-load/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/dex-load/src/main/resources/reinstallExtension.sh:
--------------------------------------------------------------------------------
1 | systemctl stop waves-devnet.service || true
2 | dpkg -P waves-dex-extension-devnet || true
3 | dpkg -i /home/buildagent-matcher/waves-dex-extension-devnet*.deb
4 | dpkg -i /home/buildagent-matcher/waves-grpc-server-devnet*.deb
5 | systemctl start waves-devnet
6 | rm -rf /home/buildagent-matcher/*
7 |
--------------------------------------------------------------------------------
/dex-load/src/main/resources/reinstallMatcher.sh:
--------------------------------------------------------------------------------
1 | systemctl stop waves-dex.service || true
2 | dpkg -P waves-dex || true
3 | dpkg -i /home/buildagent-matcher/waves-dex*.deb
4 | systemctl start waves-dex
5 | rm -rf /home/buildagent-matcher/*
6 | iptables -I INPUT -p tcp -m tcp --dport 6886 -j ACCEPT
7 |
--------------------------------------------------------------------------------
/dex-load/src/main/resources/reinstallNode.sh:
--------------------------------------------------------------------------------
1 | systemctl stop waves-devnet.service || true
2 | dpkg -P waves-dex-extension-devnet || true
3 | dpkg -P waves-devnet || true
4 | dpkg -i /home/buildagent-matcher/waves-devnet*.deb
5 | systemctl start waves-devnet
6 | rm -rf /home/buildagent-matcher/*
7 | iptables -I INPUT -p tcp -m tcp --dport 443 -j ACCEPT
8 | iptables -I INPUT -p tcp -m tcp --dport 6887 -j ACCEPT
9 | iptables -I INPUT -p tcp -m tcp --dport 6869 -j ACCEPT
10 |
--------------------------------------------------------------------------------
/dex-load/src/main/resources/runLoadTest.sh:
--------------------------------------------------------------------------------
1 | sed -i "6s/.*/ ammofile: $(ls | grep requests)/" /home/yatank/loadtest/dexload.yaml
2 | sed -i "23s/.*/ job_name: $1/" /home/yatank/loadtest/dexload.yaml
3 | mv $(ls | grep requests) /home/yatank/loadtest/
4 | rm -rf /home/yatank/loadtest/logs/*
5 | echo "The performance test has been launched, but we made the decision not to print its stdout. Keep patience, it will be finished near $(date -d '+ 9 minutes')"
6 | if [ ! "$(docker ps -q -f name=dexload)" ]; then
7 | if [ "$(docker ps -aq -f status=exited -f name=dexload)" ]; then
8 | docker rm dexload
9 | fi
10 | docker run -v /home/yatank/loadtest:/var/loadtest -v /home/ngadiyak/.ssh:/root/.ssh --rm --net host -it --entrypoint /bin/bash -d --name dexload direvius/yandex-tank:latest
11 | fi
12 | docker exec -i dexload yandex-tank -c dexload.yaml > /dev/null
13 | echo "The performance has been finished"
14 | echo "$(cat /home/yatank/loadtest/logs/lunapark/$(sudo ls /home/yatank/loadtest/logs/lunapark/)/finish_status.yaml | grep -Po -m 1 https://overload.yandex.net/[0-9]+)"
15 |
--------------------------------------------------------------------------------
/dex-load/src/main/scala/com/wavesplatform/dex/load/request/RequestTag.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.load.request
2 |
3 | object RequestTag extends Enumeration {
4 | type RequestTag = Value
5 | val TRADABLE_BALANCE, ORDER_BOOK_BY_PAIR, ORDER_BOOK_BY_PAIR_AND_KEY, ORDER_STATUS, CANCEL, PLACE, MASS_TRANSFER = Value
6 | }
7 |
--------------------------------------------------------------------------------
/dex-load/src/main/scala/com/wavesplatform/dex/load/request/RequestType.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.load.request
2 |
3 | object RequestType extends Enumeration {
4 | type RequestType = Value
5 | val GET, POST = Value
6 | }
7 |
--------------------------------------------------------------------------------
/dex-load/src/main/scala/com/wavesplatform/dex/load/utils/Settings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.load.utils
2 |
3 | case class Asset(quantity: Long, issueFee: Long, count: Int, pairsCount: Int)
4 |
5 | case class Defaults(
6 | minimalOrderAmount: Long,
7 | minimalOrderPrice: Long,
8 | matcherFee: Long,
9 | pairsFile: String,
10 | maxOrdersPerAccount: Int,
11 | wavesPerAccount: Long,
12 | massTransferFee: Long,
13 | massTransferMultiplier: Long
14 | )
15 |
16 | case class Hosts(node: String, matcher: String, shooted: String)
17 |
18 | case class Distribution(
19 | orderBookByPair: Double,
20 | orderStatus: Double,
21 | orderBookByPairAndKey: Double,
22 | tradableBalance: Double,
23 | placeOrder: Double,
24 | cancelOrder: Double
25 | )
26 |
27 | case class Settings(
28 | chainId: String,
29 | richAccount: String,
30 | dexRestApiKey: String,
31 | matcherPublicKey: String,
32 | matcherSeed: String,
33 | assets: Asset,
34 | distribution: Distribution,
35 | defaults: Defaults,
36 | hosts: Hosts
37 | )
38 |
--------------------------------------------------------------------------------
/dex-pb/build.sbt:
--------------------------------------------------------------------------------
1 | description := "Proto files and generated entities for DEX-KAFKA interaction"
2 |
3 | libraryDependencies ++= Dependencies.Module.dexPb
4 |
5 | scalacOptions += "-P:silencer:pathFilters=FileOptions;Endpoint;MetricDescriptor"
6 |
7 | // Use protocGenerate to generate it manually
8 | inConfig(Compile)(
9 | Seq(
10 | PB.deleteTargetDirectory := false,
11 | PB.protoSources += PB.externalIncludePath.value,
12 | PB.targets += scalapb.gen(flatPackage = true) -> sourceManaged.value / "scalapb"
13 | )
14 | )
15 |
--------------------------------------------------------------------------------
/dex-test-common/build.sbt:
--------------------------------------------------------------------------------
1 | description := "Shared classes for unit tests of DEX and waves-integration"
2 |
3 | libraryDependencies ++= Dependencies.Module.dexTestCommon
4 |
--------------------------------------------------------------------------------
/dex-test-common/src/main/scala/com/wavesplatform/dex/Implicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import org.scalatest.concurrent.PatienceConfiguration.Timeout
4 |
5 | import scala.concurrent.duration.Duration
6 | import scala.util.Using.Releasable
7 |
8 | object Implicits {
9 |
10 | implicit def durationToScalatestTimeout(d: Duration): Timeout = Timeout(d)
11 |
12 | implicit def releasable[T <: AutoCloseable]: Releasable[Seq[T]] = seq => seq.foreach(v => v.close())
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/dex-test-common/src/main/scala/com/wavesplatform/dex/NoShrink.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import org.scalacheck.Shrink
4 |
5 | trait NoShrink {
6 | implicit def noShrink[A]: Shrink[A] = Shrink.withLazyList(_ => LazyList.empty)
7 | }
8 |
--------------------------------------------------------------------------------
/dex-test-common/src/main/scala/com/wavesplatform/dex/asset/DoubleOps.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.asset
2 |
3 | import com.wavesplatform.dex.domain.model.Normalization
4 |
5 | trait DoubleOps {
6 |
7 | implicit final class NumericOps[A](val value: A)(implicit num: Numeric[A]) {
8 |
9 | private def normalize(assetDecimals: Int): Long = Normalization.normalizeAmountAndFee(num.toDouble(value), assetDecimals)
10 |
11 | val waves, eth, btc, asset8: Long = normalize(8)
12 | val usd, wct: Long = normalize(2)
13 | val usdn: Long = normalize(6)
14 | }
15 |
16 | }
17 |
18 | object DoubleOps extends DoubleOps
19 |
--------------------------------------------------------------------------------
/dex-test-common/src/main/scala/com/wavesplatform/dex/gen/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import org.scalacheck.{Arbitrary, Gen}
4 |
5 | package object gen {
6 | val bytes32gen: Gen[Array[Byte]] = byteArrayGen(32)
7 | val issuedAssetIdGen: Gen[Array[Byte]] = bytes32gen
8 | def byteArrayGen(length: Int): Gen[Array[Byte]] = Gen.containerOfN[Array, Byte](length, Arbitrary.arbitrary[Byte])
9 | }
10 |
--------------------------------------------------------------------------------
/dex-ws-load/build.sbt:
--------------------------------------------------------------------------------
1 | enablePlugins(GatlingPlugin)
2 |
3 | scalaVersion := "2.12.10"
4 |
5 | scalacOptions := Seq(
6 | "-encoding",
7 | "UTF-8",
8 | "-target:jvm-1.8",
9 | "-deprecation",
10 | "-feature",
11 | "-unchecked",
12 | "-language:implicitConversions",
13 | "-language:postfixOps"
14 | )
15 |
16 | javacOptions := Seq("-Xmx=1024", "-Xms=4096")
17 |
18 | libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "3.3.1" % "test,it"
19 | libraryDependencies += "io.gatling" % "gatling-test-framework" % "3.3.1" % "test,it"
20 | libraryDependencies += "org.scalactic" % "scalactic_2.12" % "3.0.8"
21 | libraryDependencies += "org.scalaj" % "scalaj-http_2.11" % "2.3.0"
22 |
--------------------------------------------------------------------------------
/dex-ws-load/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.3.8
2 |
--------------------------------------------------------------------------------
/dex-ws-load/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("io.gatling" % "gatling-sbt" % "3.1.0")
2 |
--------------------------------------------------------------------------------
/dex-ws-load/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx
7 |
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/dex/src/main/container/dex.conf:
--------------------------------------------------------------------------------
1 | waves.dex {
2 | # Mainnet is the network by default
3 | address-scheme-character = "W"
4 | # REST API is available from the outside
5 | rest-api {
6 | # Bind address
7 | address = "0.0.0.0"
8 | # Bind port
9 | port = 6886
10 | }
11 | # Default path for account.dat file
12 | account-storage.encrypted-file.path = ${waves.dex.root-directory}"/account.dat"
13 | }
14 |
15 | include "/var/lib/waves-dex/config/local.conf"
16 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/actors/ActorSystemOps.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.actors
2 |
3 | import java.util.concurrent.TimeoutException
4 |
5 | import akka.actor.ActorSystem
6 |
7 | import scala.concurrent.duration.FiniteDuration
8 | import scala.concurrent.{Future, Promise}
9 |
10 | object ActorSystemOps {
11 |
12 | implicit final class ImplicitOps(val self: ActorSystem) extends AnyVal {
13 |
14 | def timeout(after: FiniteDuration): Future[Nothing] = {
15 | val failure = Promise[Nothing]()
16 | self.scheduler.scheduleOnce(after)(failure.failure(new TimeoutException))(self.dispatcher)
17 | failure.future
18 | }
19 |
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/actors/address/BalancesFormatter.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.actors.address
2 |
3 | import com.wavesplatform.dex.domain.asset.Asset
4 | import com.wavesplatform.dex.grpc.integration.clients.domain.AddressBalanceUpdates
5 |
6 | object BalancesFormatter {
7 |
8 | def format(xs: Map[Asset, Long]): String =
9 | xs.toList.sortBy(_._1.maybeBase58Repr)
10 | .map { case (asset, v) => s"$v ${asset.maybeBase58Repr.fold("🔷")(_.take(5))}" }
11 | .mkString("{", ", ", "}")
12 |
13 | def format(xs: AddressBalanceUpdates): String = s"r=${format(xs.regular)}, l=${xs.outgoingLeasing}, p=${format(xs.pessimisticCorrection)}"
14 | }
15 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/actors/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | package object actors {
4 | case object TimedOut
5 | }
6 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/actors/tx/WriteExchangeTransactionActor.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.actors.tx
2 |
3 | import akka.actor.{Actor, Props}
4 | import com.wavesplatform.dex.db.ExchangeTxStorage
5 | import com.wavesplatform.dex.domain.utils.ScorexLogging
6 | import com.wavesplatform.dex.model.Events._
7 |
8 | import scala.concurrent.Future
9 | import scala.util.Failure
10 |
11 | class WriteExchangeTransactionActor(storage: ExchangeTxStorage[Future]) extends Actor with ScorexLogging {
12 |
13 | import context.dispatcher
14 |
15 | // TODO DEX-1081 Batching
16 | override def receive: Receive = {
17 | case ExchangeTransactionCreated(tx) =>
18 | log.trace(s"Appending ${tx.id()} to orders [${tx.buyOrder.idStr()}, ${tx.sellOrder.idStr()}]")
19 | storage.put(tx).onComplete {
20 | case Failure(e) => log.warn(s"Can't write ${tx.id}", e)
21 | case _ =>
22 | }
23 | }
24 |
25 | }
26 |
27 | object WriteExchangeTransactionActor {
28 | val name: String = "WriteExchangeTransactionActor"
29 | def props(storage: ExchangeTxStorage[Future]): Props = Props(new WriteExchangeTransactionActor(storage))
30 | }
31 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/HasStatusBarrier.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http
2 |
3 | import akka.http.scaladsl.server.Directive0
4 | import com.wavesplatform.dex.api.http.entities.{DuringShutdown, DuringStart}
5 | import com.wavesplatform.dex.api.routes.ApiRoute
6 | import com.wavesplatform.dex.app.MatcherStatus
7 |
8 | trait HasStatusBarrier { this: ApiRoute =>
9 |
10 | def matcherStatus: () => MatcherStatus
11 |
12 | def matcherStatusBarrier: Directive0 = matcherStatus() match {
13 | case MatcherStatus.Working => pass
14 | case MatcherStatus.Starting => complete(DuringStart)
15 | case MatcherStatus.Stopping => complete(DuringShutdown)
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/MetricHttpFlow.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http
2 |
3 | import akka.NotUsed
4 | import akka.http.scaladsl.model.{HttpRequest, HttpResponse}
5 | import akka.http.scaladsl.server.Route
6 | import akka.actor.ActorSystem
7 | import akka.stream.scaladsl.Flow
8 | import com.wavesplatform.dex.api.http.directives.HttpKamonDirectives.requestMetricsAttributeKey
9 |
10 | object MetricHttpFlow {
11 |
12 | def metricFlow(combinedRoute: Route)(implicit as: ActorSystem): Flow[HttpRequest, HttpResponse, NotUsed] =
13 | Flow[HttpRequest].via(combinedRoute).via(metricFlow())
14 |
15 | def metricFlow(): Flow[HttpResponse, HttpResponse, NotUsed] = Flow[HttpResponse].wireTap { response =>
16 | response.attribute(requestMetricsAttributeKey).foreach { metrics =>
17 | val status = response.status.intValue()
18 | metrics.startedTimer.withTag("status", status).stop()
19 | metrics.counter.withTag("status", status).increment()
20 | }
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpAddressCheck.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import io.swagger.annotations.ApiModelProperty
4 | import play.api.libs.json.{Format, Json}
5 |
6 | case class HttpAddressCheck(
7 | @ApiModelProperty matcher: Boolean,
8 | @ApiModelProperty blockchain: Boolean
9 | )
10 |
11 | object HttpAddressCheck {
12 |
13 | implicit val httpAddressCheckFormat: Format[HttpAddressCheck] = Json.format
14 | }
15 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpAssetInfo.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.actors.OrderBookDirectoryActor.AssetInfo
4 | import io.swagger.annotations.ApiModelProperty
5 | import play.api.libs.json.{Format, Json}
6 |
7 | case class HttpAssetInfo(@ApiModelProperty(example = "8") decimals: Int)
8 |
9 | object HttpAssetInfo {
10 | implicit val httpAssetInfoFormat: Format[HttpAssetInfo] = Json.format[HttpAssetInfo]
11 | def fromAssetInfo(ai: AssetInfo): HttpAssetInfo = HttpAssetInfo(ai.decimals)
12 | }
13 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpCalculateFeeRequest.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.domain.order.OrderType
4 | import io.swagger.annotations.{ApiModel, ApiModelProperty}
5 | import play.api.libs.json.{Json, OFormat}
6 |
7 | @ApiModel(value = "HttpCalculateFeeRequest")
8 | final case class HttpCalculateFeeRequest(
9 | @ApiModelProperty(
10 | value = "Order type (sell or buy)",
11 | dataType = "string",
12 | example = "sell",
13 | required = true
14 | )
15 | orderType: OrderType,
16 | amount: Long,
17 | price: Long
18 | )
19 |
20 | object HttpCalculateFeeRequest {
21 | implicit val formats: OFormat[HttpCalculateFeeRequest] = Json.format[HttpCalculateFeeRequest]
22 | }
23 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpError.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.error.MatcherError
4 | import play.api.libs.json.{Format, JsObject, Json}
5 |
6 | case class HttpError(
7 | error: Int,
8 | message: String,
9 | template: String,
10 | params: Option[JsObject] = None,
11 | status: String, // @deprecated(message = "This field is unnecessary", since = "1.2.0")
12 | success: Boolean = false
13 | )
14 |
15 | object HttpError {
16 |
17 | implicit val httpErrorFormat: Format[HttpError] = Json.format
18 |
19 | def apply(error: Int, message: String, template: String, params: JsObject, status: String): HttpError = new HttpError(
20 | error = error,
21 | message = message,
22 | template = template,
23 | params = Some(params),
24 | status = status
25 | )
26 |
27 | def from(x: MatcherError, status: String): HttpError = HttpError(
28 | error = x.code,
29 | message = x.message.text,
30 | template = x.message.template,
31 | params = if (x.message.params == JsObject.empty) None else Some(x.message.params),
32 | status = status
33 | )
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpMatchingRules.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.json
4 | import io.swagger.annotations.ApiModelProperty
5 | import play.api.libs.json.{Format, Json, OFormat}
6 |
7 | case class HttpMatchingRules(@ApiModelProperty(dataType = "string", example = "0.0001") tickSize: Double)
8 |
9 | object HttpMatchingRules {
10 | implicit val doubleFormat: Format[Double] = json.stringAsDoubleFormat
11 | implicit val httpMatchingRulesFormat: OFormat[HttpMatchingRules] = Json.format[HttpMatchingRules]
12 | }
13 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpMessage.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import io.swagger.annotations.ApiModelProperty
4 | import play.api.libs.json.{Json, OFormat}
5 |
6 | case class HttpMessage(@ApiModelProperty() message: String) extends AnyVal
7 |
8 | object HttpMessage {
9 | implicit val httpMessageFormat: OFormat[HttpMessage] = Json.format
10 | }
11 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpOrderBookInfo.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import io.swagger.annotations.ApiModelProperty
4 | import play.api.libs.json.{Json, OFormat}
5 |
6 | case class HttpOrderBookInfo(
7 | @ApiModelProperty(value = "Restrictions of orders' amount and price")
8 | restrictions: Option[HttpOrderRestrictions],
9 | @ApiModelProperty(value = "Matching rules, tick size in particular", required = true)
10 | matchingRules: HttpMatchingRules
11 | )
12 |
13 | object HttpOrderBookInfo {
14 | implicit val httpOrderBookInfoFormat: OFormat[HttpOrderBookInfo] = Json.format[HttpOrderBookInfo]
15 | }
16 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpSuccessfulCancel.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import io.swagger.annotations.{ApiModel, ApiModelProperty}
4 |
5 | @ApiModel(
6 | description = "Successful cancellation message. Can be one of: HttpSuccessfulSingleCancel, HttpSuccessfulBatchCancel",
7 | subTypes = Array(
8 | classOf[HttpSuccessfulSingleCancel],
9 | classOf[HttpSuccessfulBatchCancel]
10 | )
11 | )
12 | class HttpSuccessfulCancel {
13 |
14 | @ApiModelProperty(value = "Success flag")
15 | val success: Boolean = HttpSuccessfulCancel.success
16 |
17 | @ApiModelProperty(allowableValues = "OrderCanceled, BatchCancelCompleted")
18 | val status: String = HttpSuccessfulCancel.status // @deprecated(message = "This field is unnecessary", since = "1.2.0")
19 | }
20 |
21 | object HttpSuccessfulCancel {
22 | val success: Boolean = true
23 | val status: String = "OrderCanceled"
24 | }
25 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpSuccessfulPlace.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.domain.order.{Order, OrderJson}
4 | import io.swagger.annotations.ApiModelProperty
5 | import play.api.libs.json.{Format, Json, OFormat}
6 |
7 | case class HttpSuccessfulPlace(
8 | @ApiModelProperty(value = "Order", dataType = "com.wavesplatform.dex.domain.order.OrderV4") message: Order,
9 | @ApiModelProperty(value = "Success flag") success: Boolean = true,
10 | @ApiModelProperty(value = "Status", example = "OrderAccepted") status: String =
11 | "OrderAccepted" // @deprecated(message = "This field is unnecessary", since = "1.2.0")
12 | )
13 |
14 | object HttpSuccessfulPlace {
15 | implicit private val orderFormat: Format[Order] = OrderJson.orderFormat // TODO
16 | implicit val httpSuccessfulPlaceFormat: OFormat[HttpSuccessfulPlace] = Json.format
17 | }
18 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpSuccessfulSingleCancel.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.domain.order.Order
4 | import io.swagger.annotations.{ApiModel, ApiModelProperty}
5 | import play.api.libs.json._
6 |
7 | @ApiModel(description = "Cancel of the single order", parent = classOf[HttpSuccessfulCancel])
8 | case class HttpSuccessfulSingleCancel(
9 | @ApiModelProperty(
10 | value = "Base58 encoded Order ID",
11 | dataType = "string",
12 | example = "7VEr4T9icqopHWLawGAZ7AQiJbjAcnzXn65ekYvbpwnN"
13 | ) orderId: Order.Id,
14 | @ApiModelProperty(value = "Success flag")
15 | override val success: Boolean = HttpSuccessfulCancel.success,
16 | @ApiModelProperty(
17 | value = "Status",
18 | example = "OrderCanceled",
19 | required = false
20 | ) override val status: String = "OrderCanceled"
21 | ) extends HttpSuccessfulCancel
22 |
23 | object HttpSuccessfulSingleCancel {
24 | implicit val httpSuccessfulCancelFormat: OFormat[HttpSuccessfulSingleCancel] = Json.format[HttpSuccessfulSingleCancel]
25 | }
26 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpSystemStatus.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.app.MatcherStatus
4 | import com.wavesplatform.dex.grpc.integration.clients.combined.CombinedStream
5 | import io.swagger.annotations.ApiModelProperty
6 | import play.api.libs.json.{Format, Json}
7 |
8 | case class HttpSystemStatus(
9 | @ApiModelProperty() service: MatcherStatus,
10 | @ApiModelProperty() blockchain: CombinedStream.Status
11 | )
12 |
13 | object HttpSystemStatus {
14 |
15 | implicit val HttpSystemStatusFormat: Format[HttpSystemStatus] = Json.format
16 |
17 | def apply(service: MatcherStatus, blockchain: CombinedStream.Status) = new HttpSystemStatus(service, blockchain)
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpTradingMarkets.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.domain.account.PublicKey
4 | import io.swagger.annotations.ApiModelProperty
5 | import play.api.libs.json.{Json, OFormat}
6 |
7 | case class HttpTradingMarkets(
8 | @ApiModelProperty(
9 | value = "Base58 encoded Matcher Public Key",
10 | dataType = "string",
11 | example = "HBqhfdFASRQ5eBBpu2y6c6KKi1az6bMx8v1JxX4iW1Q8"
12 | ) matcherPublicKey: PublicKey,
13 | @ApiModelProperty(
14 | value = "Market data with meta information",
15 | dataType = "[Lcom.wavesplatform.dex.api.http.entities.HttpMarketDataWithMeta;"
16 | ) markets: Seq[HttpMarketDataWithMeta]
17 | )
18 |
19 | object HttpTradingMarkets {
20 | implicit val httpTradingMarketsFormat: OFormat[HttpTradingMarkets] = Json.format[HttpTradingMarkets]
21 | }
22 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpV0LevelAgg.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.model.LevelAgg
4 | import io.swagger.annotations.ApiModelProperty
5 | import play.api.libs.json.{Json, Reads}
6 |
7 | case class HttpV0LevelAgg(@ApiModelProperty(example = "83187648950") amount: Long, @ApiModelProperty(example = "12079") price: Long)
8 |
9 | object HttpV0LevelAgg {
10 | implicit val httpV0LevelAggReads: Reads[HttpV0LevelAgg] = Json.reads
11 | def fromLevelAgg(la: LevelAgg): HttpV0LevelAgg = HttpV0LevelAgg(la.amount, la.price)
12 | }
13 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpV0OrderBook.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import java.nio.charset.StandardCharsets
4 |
5 | import akka.http.scaladsl.model.{HttpEntity, HttpResponse}
6 | import com.wavesplatform.dex.domain.asset.AssetPair
7 | import io.swagger.annotations.ApiModelProperty
8 | import play.api.libs.json.{Json, Reads}
9 |
10 | case class HttpV0OrderBook(
11 | @ApiModelProperty(value = "Timestamp of the last Order Book update") timestamp: Long,
12 | @ApiModelProperty(value = "Corresponding Asset Pair") pair: AssetPair,
13 | @ApiModelProperty(value = "List of aggregated bid levels") bids: List[HttpV0LevelAgg],
14 | @ApiModelProperty(value = "List of aggregated ask levels") asks: List[HttpV0LevelAgg]
15 | )
16 |
17 | object HttpV0OrderBook {
18 |
19 | implicit val httpV0OrderBookReads: Reads[HttpV0OrderBook] = Json.reads
20 |
21 | def fromHttpResponse(response: HttpResponse): HttpV0OrderBook =
22 | Json.parse(response.entity.asInstanceOf[HttpEntity.Strict].getData().decodeString(StandardCharsets.UTF_8)).as[HttpV0OrderBook]
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpWebSocketCloseFilter.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import io.swagger.annotations.ApiModelProperty
4 | import play.api.libs.json.{Json, OFormat}
5 |
6 | case class HttpWebSocketCloseFilter(@ApiModelProperty() oldest: Int) extends AnyVal
7 |
8 | object HttpWebSocketCloseFilter {
9 | implicit val httpWebSocketCloseFilterFormat: OFormat[HttpWebSocketCloseFilter] = Json.format
10 | }
11 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/entities/HttpWebSocketConnections.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import io.swagger.annotations.ApiModelProperty
4 | import play.api.libs.json.{Json, OFormat}
5 |
6 | case class HttpWebSocketConnections(@ApiModelProperty() connections: Int, @ApiModelProperty() clientAndOs: Map[String, Int])
7 |
8 | object HttpWebSocketConnections {
9 | implicit val httpWebSocketConnectionsFormat: OFormat[HttpWebSocketConnections] = Json.format
10 | }
11 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/headers/CustomContentTypes.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.headers
2 |
3 | import akka.http.scaladsl.model.ContentType
4 |
5 | object CustomContentTypes {
6 | val `application/hocon` = ContentType(CustomMediaTypes.`application/hocon`)
7 | }
8 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/headers/CustomMediaTypes.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.headers
2 |
3 | import akka.http.scaladsl.model.{HttpCharsets, MediaType}
4 |
5 | object CustomMediaTypes {
6 | val `application/hocon` = MediaType.customWithFixedCharset("application", "hocon", HttpCharsets.`UTF-8`)
7 | }
8 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/headers/MatcherHttpServer.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.headers
2 |
3 | import akka.http.scaladsl.model.headers._
4 |
5 | import scala.util.Try
6 |
7 | object MatcherHttpServer extends ModeledCustomHeaderCompanion[MatcherHttpServer] {
8 | final override val name: String = "matcher-http-server"
9 | override def parse(value: String): Try[MatcherHttpServer] = Try(new MatcherHttpServer(value))
10 | }
11 |
12 | final class MatcherHttpServer(val value: String) extends ModeledCustomHeader[MatcherHttpServer] {
13 | override def companion: MatcherHttpServer.type = MatcherHttpServer
14 | override def renderInRequests: Boolean = false
15 | override def renderInResponses: Boolean = true
16 | }
17 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/http/headers/`X-User-Public-Key`.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.headers
2 |
3 | import akka.http.scaladsl.model.headers._
4 |
5 | import scala.util.Try
6 |
7 | object `X-User-Public-Key` extends ModeledCustomHeaderCompanion[`X-User-Public-Key`] {
8 | final val headerName = "X-User-Public-Key"
9 | final override val name: String = headerName
10 |
11 | override def parse(value: String): Try[`X-User-Public-Key`] = Try(new `X-User-Public-Key`(value))
12 | }
13 |
14 | final class `X-User-Public-Key`(val value: String) extends ModeledCustomHeader[`X-User-Public-Key`] {
15 | override def companion: `X-User-Public-Key`.type = `X-User-Public-Key`
16 | override def renderInRequests: Boolean = true
17 | override def renderInResponses: Boolean = false
18 | }
19 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/actors/WsHealthCheckSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.actors
2 |
3 | import scala.concurrent.duration.FiniteDuration
4 |
5 | case class WsHealthCheckSettings(pingInterval: FiniteDuration, pongTimeout: FiniteDuration)
6 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/connection/WsConnectionOps.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.connection
2 |
3 | import com.wavesplatform.dex.api.ws.entities.{WsBalances, WsOrder}
4 | import com.wavesplatform.dex.api.ws.protocol.{WsAddressChanges, WsPingOrPong, WsServerMessage}
5 | import com.wavesplatform.dex.domain.asset.Asset
6 |
7 | import scala.reflect.ClassTag
8 |
9 | trait WsConnectionOps {
10 |
11 | implicit final class Ops(self: WsConnection) {
12 | def collectMessages[T <: WsServerMessage: ClassTag]: List[T] = self.messages.collect { case x: T => x }
13 | def pings: List[WsPingOrPong] = collectMessages[WsPingOrPong]
14 | def addressStateChanges: List[WsAddressChanges] = collectMessages[WsAddressChanges]
15 | def balanceChanges: List[Map[Asset, WsBalances]] = addressStateChanges.map(_.balances).filter(_.nonEmpty)
16 | def orderChanges: List[WsOrder] = addressStateChanges.flatMap(_.orders)
17 | }
18 |
19 | }
20 |
21 | object WsConnectionOps extends WsConnectionOps
22 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/entities/WsAddressFlag.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.entities
2 |
3 | import play.api.libs.json.{Format, Reads, Writes}
4 |
5 | sealed abstract class WsAddressFlag(key: String) extends Product with Serializable {
6 | val value: String = key
7 | }
8 |
9 | object WsAddressFlag {
10 | case object ExcludeNft extends WsAddressFlag("-nft")
11 | case object ImaginaryTxs extends WsAddressFlag("+imaginary-txs")
12 |
13 | implicit val format: Format[WsAddressFlag] = Format(
14 | Reads.StringReads.map {
15 | case "-nft" => ExcludeNft
16 | case "+imaginary-txs" => ImaginaryTxs
17 | case x => throw new IllegalArgumentException(s"Can't parse '$x' as WsAddressBalancesFilter")
18 | },
19 | Writes.StringWrites.contramap(_.value)
20 | )
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/entities/WsAssetInfo.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.entities
2 |
3 | case class WsAssetInfo(balances: WsBalances, isNft: Boolean)
4 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/entities/WsBalances.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.entities
2 |
3 | import com.wavesplatform.dex.api.ws.doubleAsStringFormat
4 | import play.api.libs.json._
5 |
6 | case class WsBalances(tradable: Double, reserved: Double)
7 |
8 | object WsBalances {
9 |
10 | implicit val reads: Reads[WsBalances] =
11 | Reads.Tuple2R[Double, Double](doubleAsStringFormat, doubleAsStringFormat).map { case (t, r) => WsBalances(t, r) }
12 |
13 | implicit val writes: Writes[WsBalances] =
14 | Writes.Tuple2W[Double, Double](doubleAsStringFormat, doubleAsStringFormat).contramap(wsb => wsb.tradable -> wsb.reserved)
15 |
16 | implicit val wsBalancesFormat: Format[WsBalances] = Format(reads, writes)
17 | }
18 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/entities/WsLastTrade.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.entities
2 |
3 | import com.wavesplatform.dex.api.ws.doubleAsStringFormat
4 | import com.wavesplatform.dex.domain.order.OrderType
5 | import play.api.libs.json._
6 |
7 | import scala.collection.IndexedSeq
8 |
9 | case class WsLastTrade(price: Double, amount: Double, side: OrderType)
10 |
11 | object WsLastTrade {
12 |
13 | implicit private val doubleFormat: Format[Double] = doubleAsStringFormat
14 |
15 | implicit val wsLastTradeFormat: Format[WsLastTrade] = Format(
16 | fjs = Reads {
17 | case JsArray(IndexedSeq(price, amount, orderType)) =>
18 | for {
19 | price <- price.validate[Double]
20 | amount <- amount.validate[Double]
21 | side <- orderType.validate[OrderType]
22 | } yield WsLastTrade(price, amount, side)
23 | case x => JsError(JsPath, s"Can't read WsLastTrade from ${x.getClass.getName}")
24 | },
25 | tjs = Writes { x =>
26 | Json.arr(x.price, x.amount, x.side)
27 | }
28 | )
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/entities/WsTxsData.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.entities
2 |
3 | import com.wavesplatform.dex.domain.order.Order
4 | import com.wavesplatform.dex.domain.transaction.ExchangeTransaction
5 | import com.wavesplatform.dex.json._
6 | import play.api.libs.functional.syntax._
7 | import play.api.libs.json._
8 |
9 | final case class WsTxsData(
10 | txsData: Map[ExchangeTransaction.Id, Seq[Order.Id]],
11 | removedTxs: Set[ExchangeTransaction.Id]
12 | )
13 |
14 | object WsTxsData {
15 |
16 | implicit val formats: Format[WsTxsData] = (
17 | (__ \ "+").formatNullableWithDefault[Map[ExchangeTransaction.Id, Seq[Order.Id]]](None) and
18 | (__ \ "-").formatNullableWithDefault[Set[ExchangeTransaction.Id]](None)
19 | )(
20 | (txsData, removedTxs) => WsTxsData(txsData.getOrElse(Map.empty), removedTxs.getOrElse(Set.empty)),
21 | wsTxsData => (Option(wsTxsData.txsData).filter(_.nonEmpty), Option(wsTxsData.removedTxs).filter(_.nonEmpty))
22 | )
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/headers/`X-Error-Code`.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.headers
2 |
3 | import akka.http.scaladsl.model.headers._
4 |
5 | import scala.util.Try
6 |
7 | object `X-Error-Code` extends ModeledCustomHeaderCompanion[`X-Error-Code`] {
8 | final override val name: String = "X-Error-Code"
9 | override def parse(value: String): Try[`X-Error-Code`] = Try(new `X-Error-Code`(value))
10 | }
11 |
12 | final class `X-Error-Code`(val value: String) extends ModeledCustomHeader[`X-Error-Code`] {
13 | override def companion: `X-Error-Code`.type = `X-Error-Code`
14 | override def renderInRequests: Boolean = false
15 | override def renderInResponses: Boolean = true
16 | }
17 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/headers/`X-Error-Message`.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.headers
2 |
3 | import akka.http.scaladsl.model.headers._
4 |
5 | import scala.util.Try
6 |
7 | object `X-Error-Message` extends ModeledCustomHeaderCompanion[`X-Error-Message`] {
8 | final override val name: String = "X-Error-Message"
9 | override def parse(value: String): Try[`X-Error-Message`] = Try(new `X-Error-Message`(value))
10 | }
11 |
12 | final class `X-Error-Message`(val value: String) extends ModeledCustomHeader[`X-Error-Message`] {
13 | override def companion: `X-Error-Message`.type = `X-Error-Message`
14 | override def renderInRequests: Boolean = false
15 | override def renderInResponses: Boolean = true
16 | }
17 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api
2 |
3 | import play.api.libs.json._
4 |
5 | package object ws {
6 |
7 | val doubleAsStringFormat: Format[Double] = Format(
8 | fjs = Reads {
9 | case JsString(x) =>
10 | try JsSuccess(x.toDouble)
11 | catch {
12 | case _: NumberFormatException => JsError(JsPath, "Invalid number")
13 | }
14 |
15 | case x => JsError(JsPath, s"Can't read Double from ${x.getClass.getName}")
16 | },
17 | tjs = Writes(x => JsString(x.toString))
18 | )
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/protocol/WsError.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.protocol
2 |
3 | import cats.syntax.option._
4 | import com.wavesplatform.dex.error.MatcherError
5 | import play.api.libs.functional.syntax._
6 | import play.api.libs.json.{Format, _}
7 |
8 | case class WsError(timestamp: Long, code: Int, message: String) extends WsServerMessage {
9 | override def tpe: String = WsError.tpe
10 | }
11 |
12 | object WsError {
13 | val tpe = "e"
14 |
15 | def from(error: MatcherError, timestamp: Long): WsError = WsError(
16 | timestamp = timestamp,
17 | code = error.code,
18 | message = error.message.text
19 | )
20 |
21 | def wsUnapply(arg: WsError): Option[(String, Long, Int, String)] = (arg.tpe, arg.timestamp, arg.code, arg.message).some
22 |
23 | implicit val wsErrorFormat: Format[WsError] = (
24 | (__ \ "T").format[String] and
25 | (__ \ "_").format[Long] and
26 | (__ \ "c").format[Int] and
27 | (__ \ "m").format[String]
28 | )(
29 | (_, timestamp, code, message) => WsError(timestamp, code, message),
30 | unlift(WsError.wsUnapply)
31 | )
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/protocol/WsInitial.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.protocol
2 |
3 | import cats.syntax.option._
4 | import play.api.libs.functional.syntax._
5 | import play.api.libs.json.{Format, _}
6 |
7 | case class WsInitial(connectionId: String, timestamp: Long) extends WsServerMessage {
8 | override def tpe: String = WsInitial.tpe
9 | }
10 |
11 | object WsInitial {
12 |
13 | val tpe = "i"
14 |
15 | def wsUnapply(arg: WsInitial): Option[(String, Long, String)] = (arg.tpe, arg.timestamp, arg.connectionId).some
16 |
17 | implicit val wsInitialFormat: Format[WsInitial] = (
18 | (__ \ "T").format[String] and
19 | (__ \ "_").format[Long] and
20 | (__ \ "i").format[String]
21 | )(
22 | (_, timestamp, connectionId) => WsInitial(connectionId, timestamp),
23 | unlift(WsInitial.wsUnapply)
24 | )
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/protocol/WsMessage.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.protocol
2 |
3 | import akka.http.scaladsl.model.ws.TextMessage
4 | import play.api.libs.json.{Json, Writes}
5 |
6 | trait WsMessage {
7 | def tpe: String
8 | }
9 |
10 | object WsMessage {
11 | def toStrictTextMessage[T <: WsMessage: Writes](x: T): TextMessage.Strict = TextMessage.Strict(Json.toJson(x).toString)
12 | }
13 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/protocol/WsOrderBookSubscribe.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.protocol
2 |
3 | import cats.syntax.option._
4 | import com.wavesplatform.dex.domain.asset.AssetPair
5 | import play.api.libs.functional.syntax._
6 | import play.api.libs.json._
7 |
8 | final case class WsOrderBookSubscribe(key: AssetPair, depth: Int) extends WsClientMessage {
9 | override val tpe: String = WsOrderBookSubscribe.tpe
10 | }
11 |
12 | object WsOrderBookSubscribe {
13 |
14 | val tpe = "obs"
15 |
16 | def wsUnapply(arg: WsOrderBookSubscribe): Option[(String, AssetPair, Int)] = (arg.tpe, arg.key, arg.depth).some
17 |
18 | implicit val wsOrderBookSubscribeFormat: Format[WsOrderBookSubscribe] = (
19 | (__ \ "T").format[String] and
20 | (__ \ "S").format[AssetPair](AssetPair.assetPairKeyAsStringFormat) and
21 | (__ \ "d").format[Int]
22 | )(
23 | (_, key, depth) => WsOrderBookSubscribe(key, depth),
24 | unlift(WsOrderBookSubscribe.wsUnapply)
25 | )
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/protocol/WsPingOrPong.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.protocol
2 |
3 | import cats.syntax.option._
4 | import play.api.libs.functional.syntax._
5 | import play.api.libs.json._
6 |
7 | final case class WsPingOrPong(timestamp: Long) extends WsClientMessage with WsServerMessage {
8 | override val tpe: String = WsPingOrPong.tpe
9 | }
10 |
11 | object WsPingOrPong {
12 |
13 | val tpe = "pp"
14 |
15 | def wsUnapply(arg: WsPingOrPong): Option[(String, Long)] = (arg.tpe, arg.timestamp).some
16 |
17 | implicit val wsPingOrPongFormat: Format[WsPingOrPong] = (
18 | (__ \ "T").format[String] and
19 | (__ \ "_").format[Long]
20 | )(
21 | (_, ts) => WsPingOrPong(ts),
22 | unlift(WsPingOrPong.wsUnapply)
23 | )
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/api/ws/protocol/WsRatesUpdatesSubscribe.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws.protocol
2 |
3 | import play.api.libs.functional.syntax._
4 | import play.api.libs.json._
5 |
6 | case class WsRatesUpdatesSubscribe(id: String = "ru") extends WsClientMessage {
7 | override def tpe: String = WsRatesUpdatesSubscribe.tpe
8 | }
9 |
10 | object WsRatesUpdatesSubscribe {
11 |
12 | val tpe = "rus"
13 | val subscribeId = "ru"
14 |
15 | implicit val wsRatesUpdatesSubscribeFormat: Format[WsRatesUpdatesSubscribe] = (
16 | (__ \ "T").format[String] and
17 | (__ \ "S").format[String]
18 | )(
19 | (_, id) => WsRatesUpdatesSubscribe(id),
20 | unlift(s => Option(tpe -> s.id))
21 | )
22 |
23 | def validateId(wsRatesUpdatesSubscribe: WsRatesUpdatesSubscribe): JsResult[WsRatesUpdatesSubscribe] =
24 | if (wsRatesUpdatesSubscribe.id == subscribeId) JsSuccess(wsRatesUpdatesSubscribe)
25 | else JsError(JsPath \ "S", s"Unexpected subscribe id, got: ${wsRatesUpdatesSubscribe.id}, but expected $subscribeId")
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/app/MatcherStatus.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.app
2 |
3 | import com.wavesplatform.dex.meta.getSimpleName
4 | import play.api.libs.json.{Format, Reads, Writes}
5 |
6 | sealed abstract class MatcherStatus extends Product with Serializable {
7 | val name: String = getSimpleName(this)
8 | }
9 |
10 | object MatcherStatus {
11 | case object Starting extends MatcherStatus
12 | case object Working extends MatcherStatus
13 | case object Stopping extends MatcherStatus
14 |
15 | val All = List(Starting, Stopping, Working)
16 |
17 | implicit val format: Format[MatcherStatus] = Format(
18 | Reads.StringReads.map { x =>
19 | All.find(_.name == x) match {
20 | case Some(r) => r
21 | case None => throw new IllegalArgumentException(s"Can't parse '$x' as MatcherStatus")
22 | }
23 | },
24 | Writes.StringWrites.contramap(_.name)
25 | )
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/app/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import com.wavesplatform.dex.domain.utils.ScorexLogging
4 |
5 | package object app extends ScorexLogging {
6 |
7 | def forceStopApplication(reason: ApplicationStopReason): Unit = {
8 | log.error(s"The force stop was called: ${reason.getMessage}")
9 | System.exit(reason.code)
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/caches/DropOldestFixedBuffer2.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.caches
2 |
3 | private[caches] case class DropOldestFixedBuffer2(prev: Double, latest: Double) {
4 | val min: Double = math.min(prev, latest)
5 | val max: Double = math.max(prev, latest)
6 | def append(e: Double): DropOldestFixedBuffer2 = DropOldestFixedBuffer2(latest, e)
7 | }
8 |
9 | private[caches] object DropOldestFixedBuffer2 {
10 | def apply(init: Double): DropOldestFixedBuffer2 = DropOldestFixedBuffer2(init, init)
11 | }
12 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/caches/OrderFeeSettingsCache.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.caches
2 |
3 | import com.wavesplatform.dex.settings.OrderFeeSettings
4 |
5 | import scala.collection.immutable.TreeMap
6 |
7 | class OrderFeeSettingsCache(orderFeeSettingsMap: Map[Long, OrderFeeSettings]) {
8 |
9 | private val allOrderFeeSettings = {
10 | if (orderFeeSettingsMap.isEmpty) throw new IllegalArgumentException("Order fee settings should contain at least 1 value!")
11 | TreeMap.empty[Long, OrderFeeSettings] ++ orderFeeSettingsMap
12 | }
13 |
14 | def getSettingsForOffset(offset: Long): OrderFeeSettings =
15 | allOrderFeeSettings
16 | .takeWhile { case (o, _) => o <= offset }
17 | .lastOption
18 | .map(_._2)
19 | .getOrElse(throw new IllegalStateException(s"Order fee settings are not set for offset $offset"))
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/cli/ScoptImplicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.cli
2 |
3 | import scala.concurrent.duration.{Duration, FiniteDuration}
4 |
5 | trait ScoptImplicits {
6 |
7 | implicit val finiteDurationScoptRead: scopt.Read[FiniteDuration] = scopt.Read.reads { x =>
8 | Duration(x) match {
9 | case x: FiniteDuration => x
10 | case _ => throw new IllegalArgumentException("'" + x + "' is not a finite duration.")
11 | }
12 | }
13 |
14 | implicit val byteScoptRead: scopt.Read[Byte] = scopt.Read.reads { x =>
15 | x.toByteOption match {
16 | case Some(x: Byte) => x
17 | case _ => throw new IllegalArgumentException("'" + x + "' is not a byte.")
18 | }
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/cli/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import cats.syntax.either._
4 | import cats.syntax.flatMap._
5 | import cats.syntax.functor._
6 | import cats.{Applicative, Monad}
7 |
8 | package object cli {
9 | type ErrorOr[A] = Either[String, A]
10 |
11 | def lift[A](a: A): ErrorOr[A] = a.asRight
12 |
13 | val success: ErrorOr[Unit] = lift(())
14 |
15 | def log[F[_]: Applicative](log: String, indent: Option[Int] = None): F[Unit] = Applicative[F].pure {
16 | // It's fine
17 | print(indent.foldLeft(log) { (r, indent) =>
18 | r + " " * (indent - log.length)
19 | })
20 | }
21 |
22 | def wrapByLogs[F[_]: Monad, A](begin: String, end: String = "Done\n", indent: Option[Int] = None)(f: => F[A]): F[A] =
23 | for {
24 | _ <- log(begin, indent)
25 | result <- f
26 | _ <- log(end)
27 | } yield result
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/collections/NegativeMap.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.collections
2 |
3 | import cats.instances.map._
4 | import cats.syntax.semigroup._
5 | import cats.{Monoid, Semigroup}
6 |
7 | sealed abstract case class NegativeMap[K, V] private (xs: Map[K, V]) extends SafeMapOps[K, V, NegativeMap, NegativeMap[K, V]] {
8 | override protected val factory = NegativeMap
9 | }
10 |
11 | object NegativeMap extends SafeMapOpsFactory[NegativeMap] {
12 |
13 | implicit def negativeMapMonoid[K, V: Semigroup]: Monoid[NegativeMap[K, V]] = new Monoid[NegativeMap[K, V]] {
14 | override def empty: NegativeMap[K, V] = safeMk(Map.empty)
15 | override def combine(x: NegativeMap[K, V], y: NegativeMap[K, V]): NegativeMap[K, V] = safeMk(x.xs |+| y.xs)
16 | }
17 |
18 | override protected[collections] def safeMk[K, V](xs: Map[K, V]): NegativeMap[K, V] = new NegativeMap(xs) {}
19 | override protected def isValid[V](v: V)(implicit n: Numeric[V]): Boolean = n.lt(v, n.zero) // v < 0
20 | }
21 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/collections/NonNegativeMap.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.collections
2 |
3 | sealed abstract case class NonNegativeMap[K, V] private (xs: Map[K, V]) extends SafeMapOps[K, V, NonNegativeMap, NonNegativeMap[K, V]] {
4 | override protected val factory = NonNegativeMap
5 | }
6 |
7 | object NonNegativeMap extends SafeMapOpsFactory[NonNegativeMap] {
8 | override protected[collections] def safeMk[K, V](xs: Map[K, V]): NonNegativeMap[K, V] = new NonNegativeMap(xs) {}
9 | override protected def isValid[V](v: V)(implicit n: Numeric[V]): Boolean = n.gteq(v, n.zero) // v >= 0
10 | }
11 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/collections/NonPositiveMap.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.collections
2 |
3 | sealed abstract case class NonPositiveMap[K, V] private (xs: Map[K, V]) extends SafeMapOps[K, V, NonPositiveMap, NonPositiveMap[K, V]] {
4 | override protected val factory = NonPositiveMap
5 | }
6 |
7 | object NonPositiveMap extends SafeMapOpsFactory[NonPositiveMap] {
8 | override protected[collections] def safeMk[K, V](xs: Map[K, V]): NonPositiveMap[K, V] = new NonPositiveMap(xs) {}
9 | override protected def isValid[V](v: V)(implicit n: Numeric[V]): Boolean = n.lteq(v, n.zero) // v <= 0
10 | }
11 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/collections/PositiveMap.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.collections
2 |
3 | import cats.instances.map._
4 | import cats.syntax.semigroup._
5 | import cats.{Monoid, Semigroup}
6 |
7 | sealed abstract case class PositiveMap[K, V] private (xs: Map[K, V]) extends SafeMapOps[K, V, PositiveMap, PositiveMap[K, V]] {
8 | override protected val factory = PositiveMap
9 | }
10 |
11 | object PositiveMap extends SafeMapOpsFactory[PositiveMap] {
12 |
13 | implicit def positiveMapMonoid[K, V: Semigroup]: Monoid[PositiveMap[K, V]] = new Monoid[PositiveMap[K, V]] {
14 | override def empty: PositiveMap[K, V] = safeMk(Map.empty)
15 | override def combine(x: PositiveMap[K, V], y: PositiveMap[K, V]): PositiveMap[K, V] = safeMk(x.xs |+| y.xs)
16 | }
17 |
18 | override protected[collections] def safeMk[K, V](xs: Map[K, V]): PositiveMap[K, V] = new PositiveMap(xs) {}
19 | override protected def isValid[V](v: V)(implicit n: Numeric[V]): Boolean = n.gt(v, n.zero) // v > 0
20 | }
21 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/db/AssetsDb.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.db
2 |
3 | import com.wavesplatform.dex.db.leveldb.LevelDb
4 | import com.wavesplatform.dex.domain.asset.Asset.IssuedAsset
5 | import com.wavesplatform.dex.grpc.integration.dto.BriefAssetDescription
6 | import com.wavesplatform.dex.meta.getSimpleName
7 | import com.wavesplatform.dex.tool.OnComplete
8 |
9 | trait AssetsDb[F[_]] extends AssetsReadOnlyDb[F] {
10 | def put(asset: IssuedAsset, item: BriefAssetDescription): F[Unit]
11 | }
12 |
13 | object AssetsDb {
14 |
15 | private val cls = getSimpleName(this)
16 |
17 | def levelDb[F[_]: OnComplete](levelDb: LevelDb[F]): AssetsDb[F] = new AssetsDb[F] {
18 |
19 | override def put(asset: IssuedAsset, record: BriefAssetDescription): F[Unit] =
20 | measureDb(cls, "put") {
21 | levelDb.put(DbKeys.asset(asset), Some(record))
22 | }
23 |
24 | override def get(asset: IssuedAsset): F[Option[BriefAssetDescription]] =
25 | measureDb(cls, "get") {
26 | levelDb.get(DbKeys.asset(asset))
27 | }
28 |
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/db/leveldb/KeyHelpers.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.db.leveldb
2 |
3 | import java.nio.ByteBuffer
4 |
5 | import com.google.common.primitives.{Ints, Longs, Shorts}
6 |
7 | object KeyHelpers {
8 |
9 | def hBytes(prefix: Short, height: Int, bytes: Array[Byte]): Array[Byte] =
10 | ByteBuffer.allocate(6 + bytes.length).putShort(prefix).putInt(height).put(bytes).array()
11 |
12 | def bytes(prefix: Short, bytes: Array[Byte]): Array[Byte] =
13 | ByteBuffer.allocate(2 + bytes.length).putShort(prefix).put(bytes).array()
14 |
15 | def intKey(name: String, prefix: Short, default: Int = 0): Key[Int] =
16 | Key(name, Shorts.toByteArray(prefix), Option(_).fold(default)(Ints.fromByteArray), Ints.toByteArray)
17 |
18 | def longKey(name: String, prefix: Short, default: Long = 0): Key[Long] =
19 | Key(name, Longs.toByteArray(prefix), Option(_).fold(default)(Longs.fromByteArray), Longs.toByteArray)
20 |
21 | def bytesSeqNr(name: String, prefix: Short, b: Array[Byte], default: Int = 0): Key[Int] =
22 | Key(name, bytes(prefix, b), Option(_).fold(default)(Ints.fromByteArray), Ints.toByteArray)
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/db/leveldb/ReadWriteDb.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.db.leveldb
2 |
3 | import com.wavesplatform.dex.metrics.LevelDBStats
4 | import com.wavesplatform.dex.metrics.LevelDBStats.DbHistogramExt
5 | import org.iq80.leveldb.{DB, ReadOptions, WriteBatch}
6 |
7 | class ReadWriteDb(db: DB, readOptions: ReadOptions, batch: WriteBatch) extends ReadOnlyDb(db, readOptions) {
8 |
9 | def put[V](key: Key[V], value: V): Unit = {
10 | val bytes = key.encode(value)
11 | LevelDBStats.write.recordTagged(key, bytes)
12 | batch.put(key.keyBytes, bytes)
13 | }
14 |
15 | /** Because of how leveldb batches work, you can increment a specific value only once! */
16 | def inc(key: Key[Int]): Int = {
17 | val newValue = get(key) + 1
18 | put(key, newValue)
19 | newValue
20 | }
21 |
22 | def delete[V](key: Key[V]): Unit = batch.delete(key.keyBytes)
23 | def delete(key: Array[Byte]): Unit = batch.delete(key)
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/db/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import com.wavesplatform.dex.tool.OnComplete
4 | import kamon.Kamon
5 | import kamon.tag.TagSet
6 |
7 | package object db {
8 |
9 | private val timer = Kamon.timer("matcher.leveldb")
10 |
11 | def measureDb[F[_]: OnComplete, T](cls: String, method: String)(block: => F[T]): F[T] = {
12 | val startedTimer = mkTimer(cls, method).start()
13 | val startedBlock = block
14 | OnComplete[F].onComplete(startedBlock)(_ => startedTimer.stop())
15 | startedBlock
16 | }
17 |
18 | private def mkTimer(cls: String, method: String) =
19 | timer.withTags(TagSet.from(Map(
20 | "cls" -> cls,
21 | "method" -> method
22 | )))
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/effect/Implicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.effect
2 |
3 | import scala.collection.immutable.Queue
4 | import scala.concurrent.{ExecutionContext, Future}
5 | import scala.util.{Success, Try}
6 |
7 | object Implicits {
8 |
9 | implicit final class FutureOps[T](val self: Future[T]) extends AnyVal {
10 | def safe(implicit ec: ExecutionContext): Future[Try[T]] = self.transform(x => Success(x))
11 | }
12 |
13 | implicit final class FutureCompanionOps(val self: Future.type) extends AnyVal {
14 |
15 | def inSeries[A, B](xs: Iterable[A])(f: A => Future[B])(implicit ec: ExecutionContext): Future[Queue[B]] =
16 | xs.foldLeft(Future.successful(Queue.empty[B])) {
17 | case (r, x) =>
18 | for {
19 | xs <- r
20 | b <- f(x)
21 | } yield xs.enqueue(b)
22 | }
23 |
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/effect/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import cats.data.EitherT
4 | import cats.instances.future.catsStdInstancesForFuture
5 | import cats.syntax.either.catsSyntaxEitherId
6 | import com.wavesplatform.dex.error.MatcherError
7 |
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | package object effect {
11 |
12 | type FutureResult[T] = EitherT[Future, MatcherError, T]
13 |
14 | def successAsync: FutureResult[Unit] = liftValueAsync(())
15 | def liftValueAsync[T](value: T): FutureResult[T] = EitherT(Future.successful(value.asRight[MatcherError]))
16 | def liftErrorAsync[T](error: MatcherError): FutureResult[T] = EitherT(Future.successful(error.asLeft[T]))
17 | def liftFutureAsync[T](x: Future[T])(implicit ex: ExecutionContext): FutureResult[T] = EitherT.right[MatcherError](x)
18 | }
19 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/error/ErrorFormatterContext.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.error
2 |
3 | import cats.syntax.option._
4 | import com.wavesplatform.dex.domain.asset.Asset
5 | import scala.util.Try
6 |
7 | @FunctionalInterface
8 | trait ErrorFormatterContext {
9 | def assetDecimals(asset: Asset): Option[Int]
10 |
11 | def unsafeAssetDecimals(asset: Asset): Int = assetDecimals(asset) match {
12 | case Some(value) => value
13 | case None => throw new RuntimeException(s"Can't find asset: $asset")
14 | }
15 |
16 | }
17 |
18 | object ErrorFormatterContext {
19 |
20 | def from(f: Asset => Int): ErrorFormatterContext = new ErrorFormatterContext {
21 | override def assetDecimals(asset: Asset): Option[Int] = Try(f(asset)).fold(_ => None, _.some)
22 | override def unsafeAssetDecimals(asset: Asset): Int = f(asset)
23 | }
24 |
25 | def fromOptional(f: Asset => Option[Int]): ErrorFormatterContext = (asset: Asset) => f(asset)
26 | def from(xs: PartialFunction[Asset, Int]): ErrorFormatterContext = fromOptional(xs.lift)
27 | }
28 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/exceptions/BinaryMessagesNotSupportedException.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.exceptions
2 |
3 | import scala.util.control.NoStackTrace
4 |
5 | final case class BinaryMessagesNotSupportedException(message: String = "Binary messages are not supported")
6 | extends Exception(message)
7 | with NoStackTrace
8 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/history/DBRecords.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.history
2 |
3 | import java.time.LocalDateTime
4 |
5 | import com.wavesplatform.dex.model.Events.EventReason
6 |
7 | object DBRecords {
8 |
9 | sealed trait Record
10 |
11 | case class OrderRecord(
12 | id: String,
13 | tpe: Byte,
14 | senderAddress: String,
15 | senderPublicKey: String,
16 | amountAssetId: String,
17 | priceAssetId: String,
18 | feeAssetId: String,
19 | side: Byte,
20 | price: BigDecimal,
21 | amount: BigDecimal,
22 | timestamp: LocalDateTime,
23 | expiration: LocalDateTime,
24 | fee: BigDecimal,
25 | created: LocalDateTime,
26 | closedAt: Option[LocalDateTime]
27 | ) extends Record
28 |
29 | case class EventRecord(
30 | orderId: String,
31 | eventType: Byte,
32 | timestamp: LocalDateTime,
33 | price: BigDecimal,
34 | filled: BigDecimal,
35 | totalFilled: BigDecimal,
36 | feeFilled: BigDecimal,
37 | feeTotalFilled: BigDecimal,
38 | status: Byte,
39 | reason: EventReason
40 | ) extends Record
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/json/Implicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.json
2 |
3 | import com.wavesplatform.dex.fp.MayBeEmpty
4 | import play.api.libs.functional.syntax._
5 | import play.api.libs.json._
6 |
7 | object Implicits {
8 |
9 | implicit final class JsPathOps(val self: JsPath) extends AnyVal {
10 |
11 | def formatMayBeEmpty[T](implicit f: Format[T], mayBeEmpty: MayBeEmpty[T]): OFormat[T] =
12 | self
13 | .formatNullable[T]
14 | .inmap[T](
15 | _.fold(mayBeEmpty.empty)(identity),
16 | Option(_).filterNot(mayBeEmpty.isEmpty)
17 | )
18 |
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/meta/DescendantSamples.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.meta
2 |
3 | import shapeless._
4 |
5 | class DescendantSamples[Base] {
6 | trait Helper[A <: Coproduct] { def descendants: List[Base] }
7 |
8 | implicit def caseCNil: Helper[CNil] = new Helper[CNil] {
9 | def descendants: List[Base] = Nil
10 | }
11 |
12 | implicit def caseCCons[A <: Base, T <: Coproduct](implicit rec: Helper[T], ct: Sample[A]): Helper[A :+: T] =
13 | new Helper[A :+: T] {
14 | def descendants: List[Base] = ct.sample :: rec.descendants
15 | }
16 |
17 | def run[C <: Coproduct](implicit g: Generic.Aux[Base, C], s: Helper[C]): List[Base] = s.descendants
18 | }
19 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/meta/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | package object meta {
4 | def getSimpleName(x: Any): String = x.getClass.getName.replaceAll(".*?(\\w+)\\$?$", "$1")
5 | }
6 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/metrics/LevelDBStats.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.metrics
2 |
3 | import com.wavesplatform.dex.db.leveldb.Key
4 | import kamon.Kamon
5 | import kamon.metric.{MeasurementUnit, Metric}
6 |
7 | object LevelDBStats {
8 |
9 | implicit class DbHistogramExt(val h: Metric.Histogram) {
10 | def recordTagged(key: Key[_], value: Array[Byte]): Unit = recordTagged(key.name, value)
11 | def recordTagged(tag: String, value: Array[Byte]): Unit = h.withTag("key", tag).record(Option(value).map(_.length.toLong).getOrElse(0))
12 | def recordTagged(tag: String, totalBytes: Long): Unit = h.withTag("key", tag).record(totalBytes)
13 | }
14 |
15 | val read: Metric.Histogram = Kamon.histogram("dex.db.read", MeasurementUnit.information.bytes)
16 | val write: Metric.Histogram = Kamon.histogram("dex.db.write", MeasurementUnit.information.bytes)
17 | }
18 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/metrics/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import kamon.metric.Timer
4 |
5 | import scala.concurrent.{ExecutionContext, Future}
6 |
7 | package object metrics {
8 |
9 | implicit final class TimerExt(private val timer: Timer) extends AnyVal {
10 |
11 | def measureWithFilter[T](f: => T)(filter: T => Boolean): T = {
12 | val startedTimer = timer.start()
13 | val result = f
14 | if (filter(result)) startedTimer.stop()
15 | result
16 | }
17 |
18 | def measure[T](f: => T): T = measureWithFilter(f)(_ => true)
19 |
20 | def measureFuture[T](f: => Future[T])(implicit ec: ExecutionContext): Future[T] = {
21 | val startedTimer = timer.start()
22 | val future = f
23 | future.onComplete(_ => startedTimer.stop())
24 | future
25 | }
26 |
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/model/AcceptedOrderType.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.model
2 |
3 | import play.api.libs.json._
4 |
5 | // TODO Rename OrderType to OrderSide and AcceptedOrderType to OrderType in master after DEX-526
6 | sealed trait AcceptedOrderType
7 |
8 | object AcceptedOrderType {
9 |
10 | case object Limit extends AcceptedOrderType
11 | case object Market extends AcceptedOrderType
12 |
13 | implicit val acceptedOrderTypeFormat: Format[AcceptedOrderType] = Format(
14 | Reads {
15 | case JsString(value) =>
16 | value match {
17 | case "limit" => JsSuccess(AcceptedOrderType.Limit)
18 | case "market" => JsSuccess(AcceptedOrderType.Market)
19 | case x => JsError(s"Unknown order type: $x")
20 | }
21 | case x => JsError(s"Can't parse '$x' as AcceptedOrderType")
22 | },
23 | Writes {
24 | case Limit => JsString("limit")
25 | case Market => JsString("market")
26 | }
27 | )
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/model/Implicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.model
2 |
3 | import com.wavesplatform.dex.domain.asset.AssetPair
4 |
5 | import scala.util.{Failure, Try}
6 |
7 | object Implicits {
8 |
9 | implicit final class AssetPairOps(val self: AssetPair.type) extends AnyVal {
10 |
11 | def fromString(s: String): Try[AssetPair] = Try(s.split("-")).flatMap {
12 | case Array(amtAssetStr, prcAssetStr) => AssetPair.createAssetPair(amtAssetStr, prcAssetStr)
13 | case xs => Failure(new Exception(s"$s (incorrect assets count, expected 2 but got ${xs.size}: ${xs.mkString(", ")})"))
14 | }
15 |
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/model/LastTrade.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.model
2 |
3 | import java.nio.ByteBuffer
4 |
5 | import com.google.common.primitives.Longs
6 | import com.wavesplatform.dex.domain.order.OrderType
7 |
8 | import scala.collection.mutable
9 |
10 | case class LastTrade(price: Long, amount: Long, side: OrderType) {
11 | override def toString: String = s"LastTrade(p=$price, a=$amount, $side)"
12 | }
13 |
14 | object LastTrade {
15 |
16 | def serialize(dest: mutable.ArrayBuilder[Byte], x: LastTrade): Unit = {
17 | dest ++= Longs.toByteArray(x.price)
18 | dest ++= Longs.toByteArray(x.amount)
19 | dest ++= x.side.bytes
20 | }
21 |
22 | def fromBytes(bb: ByteBuffer): LastTrade = LastTrade(bb.getLong, bb.getLong, OrderType(bb.get))
23 | }
24 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/model/MatchTimestamp.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.model
2 |
3 | object MatchTimestamp {
4 |
5 | def getMatchTimestamp(startOffset: Long)(currentOffset: Long)(takerOrderAddedTs: Long, orderBookTs: Long): Long =
6 | if (currentOffset < startOffset) takerOrderAddedTs
7 | else math.max(takerOrderAddedTs, orderBookTs)
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/model/OrderBookAggregatedSnapshot.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.model
2 |
3 | case class OrderBookAggregatedSnapshot(bids: Seq[LevelAgg] = Seq.empty, asks: Seq[LevelAgg] = Seq.empty) {
4 | def getSideFor(acceptedOrder: AcceptedOrder): Seq[LevelAgg] = if (acceptedOrder.isBuyOrder) bids else asks
5 | def getCounterSideFor(acceptedOrder: AcceptedOrder): Seq[LevelAgg] = if (acceptedOrder.isBuyOrder) asks else bids
6 | }
7 |
8 | object OrderBookAggregatedSnapshot {
9 | val empty: OrderBookAggregatedSnapshot = OrderBookAggregatedSnapshot()
10 | }
11 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/queue/RepeatableRequests.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.queue
2 |
3 | import java.util.concurrent.TimeoutException
4 |
5 | import com.wavesplatform.dex.domain.utils.ScorexLogging
6 |
7 | import scala.concurrent.duration.Deadline
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | class RepeatableRequests(queue: MatcherQueue, deadline: Deadline)(implicit ec: ExecutionContext) extends ScorexLogging {
11 | def firstOffset: Future[ValidatedCommandWithMeta.Offset] = getOffset("first", deadline, queue.firstOffset)
12 | def lastOffset: Future[ValidatedCommandWithMeta.Offset] = getOffset("last", deadline, queue.lastOffset)
13 |
14 | private def getOffset[T](which: String, deadline: Deadline, get: => Future[T]): Future[T] = get.recoverWith {
15 | case e: TimeoutException =>
16 | log.warn(s"During receiving $which offset", e)
17 | if (deadline.isOverdue()) Future.failed(new TimeoutException(s"Can't get the $which offset from queue"))
18 | else getOffset(which, deadline, get)
19 |
20 | case e: Throwable =>
21 | log.error(s"Can't catch ${e.getClass.getName}", e)
22 | throw e
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/queue/ValidatedCommandWithMeta.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.queue
2 |
3 | import com.google.common.primitives.Longs
4 |
5 | case class ValidatedCommandWithMeta(offset: ValidatedCommandWithMeta.Offset, timestamp: Long, command: ValidatedCommand) {
6 |
7 | override def toString: String =
8 | s"ValidatedCommandWithMeta(offset=$offset, ts=$timestamp, ${command.toString})"
9 |
10 | }
11 |
12 | object ValidatedCommandWithMeta {
13 |
14 | type Offset = Long
15 |
16 | def toBytes(x: ValidatedCommandWithMeta): Array[Byte] =
17 | Longs.toByteArray(x.offset) ++ Longs.toByteArray(x.timestamp) ++ ValidatedCommand.toBytes(x.command)
18 |
19 | def fromBytes(xs: Array[Byte]): ValidatedCommandWithMeta = ValidatedCommandWithMeta(
20 | offset = Longs.fromByteArray(xs.take(8)),
21 | timestamp = Longs.fromByteArray(xs.slice(8, 16)),
22 | command = ValidatedCommand.fromBytes(xs.drop(16))
23 | )
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/redis/RedisStream.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.redis
2 |
3 | import kamon.instrumentation.executor.ExecutorInstrumentation
4 | import org.redisson.api.{RStream, StreamMessageId}
5 |
6 | import java.util
7 | import java.util.concurrent.Executors
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | final class RedisStream[K, V](innerStream: RStream[K, V]) {
11 |
12 | private val stex =
13 | ExecutionContext.fromExecutorService(
14 | ExecutorInstrumentation.instrument(Executors.newFixedThreadPool(1), "redis-ordered-stream")
15 | )
16 |
17 | def addOrderedAsync(key: K, value: V): Future[StreamMessageId] =
18 | Future {
19 | innerStream.add(key, value)
20 | }(stex)
21 |
22 | def createReadGroup(groupName: String): Unit = innerStream.createGroup(groupName, StreamMessageId.ALL)
23 | def removeReadGroup(groupName: String): Unit = innerStream.removeGroup(groupName)
24 |
25 | def read(groupName: String): util.Map[StreamMessageId, util.Map[K, V]] =
26 | innerStream.readGroup(groupName, s"consumer-${System.currentTimeMillis()}")
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/AssetType.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import cats.implicits.catsSyntaxEitherId
4 | import com.wavesplatform.dex.settings.utils.RawFailureReason
5 | import enumeratum.{Enum, EnumEntry, PlayLowercaseJsonEnum}
6 | import pureconfig.ConfigReader
7 | import pureconfig.error.FailureReason
8 |
9 | sealed trait AssetType extends EnumEntry
10 |
11 | object AssetType extends Enum[AssetType] with PlayLowercaseJsonEnum[AssetType] {
12 | override val values = findValues
13 |
14 | case object Amount extends AssetType
15 | case object Price extends AssetType
16 | case object Spending extends AssetType
17 | case object Receiving extends AssetType
18 |
19 | implicit val assetTypeConfigReader = ConfigReader.fromString { x =>
20 | lowerCaseNamesToValuesMap
21 | .get(x)
22 | .fold[Either[FailureReason, AssetType]](
23 | RawFailureReason(s"Unknown asset type: '$x', valid are: ${lowerCaseNamesToValuesMap.values.mkString(", ")}").asLeft
24 | )(_.asRight)
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/DeviationsSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import com.wavesplatform.dex.settings.utils.ConfigReaderOps.Implicits
4 | import com.wavesplatform.dex.settings.utils.{rules, validationOf}
5 | import pureconfig.generic.semiauto
6 |
7 | /** Represents market order restrictions. Field values are in percents */
8 | case class DeviationsSettings(enable: Boolean, maxPriceProfit: Double, maxPriceLoss: Double, maxFeeDeviation: Double)
9 |
10 | object DeviationsSettings {
11 |
12 | implicit val deviationsConfigReader = semiauto
13 | .deriveReader[DeviationsSettings]
14 | .validatedField(
15 | validationOf.field[DeviationsSettings, "maxPriceProfit"].mk(x => rules.gt0(x.maxPriceProfit)),
16 | validationOf.field[DeviationsSettings, "maxPriceLoss"].mk(x => rules.gt0(x.maxPriceLoss)),
17 | validationOf.field[DeviationsSettings, "maxFeeDeviation"].mk(x => rules.gt0(x.maxFeeDeviation))
18 | )
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/EventsQueueSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import com.wavesplatform.dex.queue.{KafkaMatcherQueue, LocalMatcherQueue}
4 | import com.wavesplatform.dex.settings.EventsQueueSettings.CircuitBreakerSettings
5 |
6 | import scala.concurrent.duration.FiniteDuration
7 |
8 | case class EventsQueueSettings(
9 | `type`: String,
10 | local: LocalMatcherQueue.Settings,
11 | kafka: KafkaMatcherQueue.Settings,
12 | circuitBreaker: CircuitBreakerSettings
13 | )
14 |
15 | object EventsQueueSettings {
16 | case class CircuitBreakerSettings(maxFailures: Int, callTimeout: FiniteDuration, resetTimeout: FiniteDuration)
17 | }
18 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/GRPCSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | final case class GRPCSettings(host: String, port: Int)
4 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/LpAccountsSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import com.wavesplatform.crypto.base.Base58
4 | import com.wavesplatform.dex.domain.account.{Address, PublicKey}
5 |
6 | import java.io._
7 | import scala.jdk.CollectionConverters._
8 |
9 | case class LpAccountsSettings(filePath: String) {
10 |
11 | lazy val publicKeys: Set[PublicKey] = {
12 | val stream = new FileInputStream(filePath)
13 | try {
14 | val streamReader = new InputStreamReader(stream)
15 | val bufferedReader = new BufferedReader(streamReader)
16 | bufferedReader.lines
17 | .filter(_.nonEmpty)
18 | .map[PublicKey](line => PublicKey(Base58.decode(line)))
19 | .iterator
20 | .asScala
21 | .toSet
22 | } finally stream.close()
23 | }
24 |
25 | lazy val addresses: Set[Address] = publicKeys.map(_.toAddress)
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/MatchingRule.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import com.wavesplatform.dex.domain.model.Denormalization
4 | import com.wavesplatform.dex.queue.ValidatedCommandWithMeta
5 |
6 | /** Normalized representation of the matching rule */
7 | case class MatchingRule(startOffset: ValidatedCommandWithMeta.Offset, tickSize: Long) {
8 |
9 | def denormalize(amountAssetDecimals: Int, priceAssetDecimals: Int): DenormalizedMatchingRule =
10 | DenormalizedMatchingRule(
11 | startOffset,
12 | Denormalization.denormalizePrice(tickSize, amountAssetDecimals, priceAssetDecimals)
13 | )
14 |
15 | }
16 |
17 | object MatchingRule {
18 | val DefaultTickSize: Long = 1
19 | val DefaultRule: MatchingRule = MatchingRule(0L, DefaultTickSize)
20 | }
21 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/PassExecutionParamsSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | final case class PassExecutionParamsSettings(sinceOffset: Long)
4 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/PostgresConnection.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import com.typesafe.config.{Config, ConfigFactory, ConfigValueFactory}
4 |
5 | case class PostgresConnection(
6 | serverName: String,
7 | portNumber: Int,
8 | database: String,
9 | user: String,
10 | password: String,
11 | dataSourceClassName: String,
12 | applicationName: String
13 | ) {
14 |
15 | def getQuillContextConfig: Config =
16 | ConfigFactory
17 | .empty()
18 | .withValue("dataSource.serverName", ConfigValueFactory.fromAnyRef(serverName))
19 | .withValue("dataSource.portNumber", ConfigValueFactory.fromAnyRef(portNumber))
20 | .withValue("dataSource.databaseName", ConfigValueFactory.fromAnyRef(database))
21 | .withValue("dataSource.user", ConfigValueFactory.fromAnyRef(user))
22 | .withValue("dataSource.password", ConfigValueFactory.fromAnyRef(password))
23 | .withValue("dataSourceClassName", ConfigValueFactory.fromAnyRef(dataSourceClassName))
24 | .withValue("dataSource.ApplicationName", ConfigValueFactory.fromAnyRef(applicationName))
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/RedisInternalClientHandlerActorSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | final case class RedisInternalClientHandlerActorSettings(
4 | enabled: Boolean,
5 | streamName: String,
6 | key: String
7 | )
8 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/RedisSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | final case class RedisSettings(
4 | address: String,
5 | username: String,
6 | password: String,
7 | nettyThreads: Int,
8 | threads: Int,
9 | retryAttempts: Int,
10 | retryInterval: Int,
11 | keepAlive: Boolean,
12 | pingConnectionInterval: Int,
13 | connectionPoolSize: Int,
14 | connectionMinimumIdleSize: Int
15 | )
16 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/RestAPISettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | case class RestAPISettings(address: String, port: Int, apiKeyHashes: List[String], cors: Boolean, apiKeyDifferentHost: Boolean)
4 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/SubscriptionsSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import com.wavesplatform.dex.settings.SubscriptionsSettings.default
4 | import com.wavesplatform.dex.settings.utils.ConfigReaderOps.Implicits
5 | import com.wavesplatform.dex.settings.utils.{rules, validationOf}
6 | import pureconfig.generic.semiauto
7 |
8 | final case class SubscriptionsSettings(maxOrderBookNumber: Int = default.maxOrderBookNumber, maxAddressNumber: Int = default.maxAddressNumber)
9 |
10 | object SubscriptionsSettings {
11 |
12 | val default = SubscriptionsSettings(10, 10)
13 |
14 | implicit val subscriptionsConfigReader = semiauto
15 | .deriveReader[SubscriptionsSettings]
16 | .validatedField(
17 | validationOf.field[SubscriptionsSettings, "maxOrderBookNumber"].mk(x => rules.gt0(x.maxOrderBookNumber)),
18 | validationOf.field[SubscriptionsSettings, "maxAddressNumber"].mk(x => rules.gt0(x.maxAddressNumber))
19 | )
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/WaitingOffsetToolSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import scala.concurrent.duration._
4 |
5 | final case class WaitingOffsetToolSettings(
6 | queueProcessingTimeout: FiniteDuration,
7 | maxWaitingTime: FiniteDuration,
8 | checkInterval: FiniteDuration
9 | )
10 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/WebSocketSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings
2 |
3 | import com.wavesplatform.dex.api.ws.actors.{WsExternalClientHandlerActor, WsInternalBroadcastActor, WsInternalClientHandlerActor}
4 |
5 | final case class WebSocketSettings(
6 | externalClientHandler: WsExternalClientHandlerActor.Settings,
7 | internalBroadcast: WsInternalBroadcastActor.Settings,
8 | internalClientHandler: WsInternalClientHandlerActor.Settings
9 | )
10 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/utils/ConfigFieldValidate.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings.utils
2 |
3 | import pureconfig.ConfigObjectCursor
4 | import pureconfig.error.ConfigReaderFailure
5 | import pureconfig.generic.ProductHint
6 | import shapeless.ops.hlist.Selector
7 | import shapeless.ops.record.Keys
8 | import shapeless.tag.Tagged
9 | import shapeless.{HList, LabelledGeneric}
10 |
11 | class ConfigFieldValidate[ObjectT, FieldName] {
12 |
13 | def mk[L <: HList, KeyList <: HList](f: ObjectT => Option[String])(implicit
14 | ev: LabelledGeneric.Aux[ObjectT, L],
15 | ev2: Keys.Aux[L, KeyList],
16 | ev3: Selector[KeyList, Symbol with Tagged[FieldName]]
17 | ): (ObjectT, ConfigObjectCursor, ProductHint[ObjectT]) => Option[ConfigReaderFailure] = {
18 | val keys: KeyList = Keys[ev.Repr].apply()
19 | val fieldName = keys.select[Symbol with Tagged[FieldName]].name
20 |
21 | (obj: ObjectT, c: ConfigObjectCursor, hint: ProductHint[ObjectT]) =>
22 | f(obj).map(RawFailureReason).map(hint.from(c, fieldName).cursor.failureFor)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/utils/ConfigListValidate.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings.utils
2 |
3 | import pureconfig.ConfigListCursor
4 | import pureconfig.error.ConfigReaderFailure
5 | import pureconfig.generic.ProductHint
6 |
7 | class ConfigListValidate[ListT] {
8 |
9 | def mk(f: ListT => Option[String]): (ListT, ConfigListCursor, ProductHint[ListT]) => Option[ConfigReaderFailure] =
10 | (obj: ListT, c: ConfigListCursor, _: ProductHint[ListT]) => f(obj).map(RawFailureReason).map(c.failureFor)
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/utils/ConfigReaders.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings.utils
2 |
3 | import com.wavesplatform.dex.domain.account.PublicKey
4 | import com.wavesplatform.dex.domain.asset.Asset
5 | import com.wavesplatform.dex.domain.bytes.ByteStr
6 | import com.wavesplatform.dex.settings.MatcherSettings.assetPairKeyParser
7 | import pureconfig.ConfigReader
8 | import pureconfig.error.CannotConvert
9 | import sttp.model.Uri
10 |
11 | trait ConfigReaders {
12 | val byteStr58ConfigReader = ConfigReader.fromStringTry(ByteStr.decodeBase58)
13 | val byteStr64ConfigReader = ConfigReader.fromStringTry(ByteStr.decodeBase64)
14 |
15 | implicit val assetConfigReader = ConfigReader.fromStringOpt(Asset.fromString)
16 | implicit val issuedAssetConfigReader = byteStr58ConfigReader.map(Asset.IssuedAsset(_))
17 |
18 | implicit val assetPairConfigReader = ConfigReader.fromString(assetPairKeyParser)
19 |
20 | implicit val publicKeyReader = byteStr58ConfigReader.map(PublicKey(_))
21 |
22 | implicit val uriReader = ConfigReader.fromString(x => Uri.parse(x).left.map(e => CannotConvert(x, "Uri", e)))
23 | }
24 |
25 | object ConfigReaders extends ConfigReaders
26 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/utils/RawFailureReason.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings.utils
2 |
3 | import pureconfig.error.FailureReason
4 |
5 | final case class RawFailureReason(description: String) extends FailureReason
6 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/settings/utils/validationOf.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings.utils
2 |
3 | object validationOf {
4 | def field[A, F]: ConfigFieldValidate[A, F] = new ConfigFieldValidate[A, F]
5 | def list[L]: ConfigListValidate[L] = new ConfigListValidate[L]
6 | }
7 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/time/Time.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.time
2 |
3 | trait Time {
4 | def correctedTime(): Long
5 | def getTimestamp(): Long
6 | }
7 |
8 | object Time {
9 |
10 | val system: Time = new Time {
11 | override def correctedTime(): Long = System.currentTimeMillis()
12 | override def getTimestamp(): Long = System.currentTimeMillis()
13 | }
14 |
15 | val zero: Time = new Time {
16 | override def correctedTime(): Long = 0L
17 | override def getTimestamp(): Long = 0L
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/tool/LocaleUtils.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.tool
2 |
3 | import java.text.DecimalFormatSymbols
4 | import java.util.{Locale => JLocale}
5 |
6 | object LocaleUtils {
7 |
8 | val symbols = new DecimalFormatSymbols(JLocale.US)
9 | }
10 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/tool/OnComplete.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.tool
2 |
3 | import cats.Id
4 |
5 | import scala.concurrent.{ExecutionContext, Future}
6 | import scala.util.{Success, Try}
7 |
8 | trait OnComplete[F[_]] {
9 |
10 | def onComplete[A, B](fa: F[A])(f: Try[A] => B): Unit
11 |
12 | }
13 |
14 | object OnComplete {
15 |
16 | def apply[F[_]: OnComplete[*[_]]]: OnComplete[F] = implicitly
17 |
18 | implicit val deriveOnCompleteForFuture: OnComplete[Future] = new OnComplete[Future] {
19 |
20 | override def onComplete[A, B](fa: Future[A])(f: Try[A] => B): Unit =
21 | fa.onComplete(f)(ExecutionContext.parasitic)
22 |
23 | }
24 |
25 | implicit val deriveOnCompleteForId: OnComplete[Id] = new OnComplete[Id] {
26 |
27 | override def onComplete[A, B](fa: Id[A])(f: Try[A] => B): Unit =
28 | f(Success(fa))
29 |
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/tool/PrettyPrinter.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.tool
2 |
3 | import cats.implicits._
4 | import com.wavesplatform.dex.cli
5 | import com.wavesplatform.dex.cli.ErrorOr
6 |
7 | object PrettyPrinter {
8 |
9 | def prettyPrintUnusedProperties(unusedProperties: Seq[String], indent: Option[Int] = None): ErrorOr[Unit] =
10 | if (unusedProperties.nonEmpty)
11 | for {
12 | _ <- cli.log[ErrorOr](s"\nWarning! Found ${unusedProperties.size} potentially unused properties.", indent)
13 | _ <- cli.log[ErrorOr]("\nUnused matcher properties found in waves.dex:\n", indent)
14 | } yield unusedProperties.foreach(p => cli.log[ErrorOr](s"$p\n", indent))
15 | else
16 | cli.log[ErrorOr]("No unused properties in waves.dex found!\n", indent)
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/dex/src/main/scala/com/wavesplatform/dex/tool/Using.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.tool
2 |
3 | import scala.util.{Using => ScalaUsing}
4 |
5 | object Using {
6 |
7 | implicit final class UsingManagerOps(val manager: ScalaUsing.Manager.type) extends AnyVal {
8 |
9 | def unsafe[A](f: ScalaUsing.Manager => A): A =
10 | manager.apply(f).get
11 |
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/dex/src/main/scala/kamon/instrumentation/logback/tools/CompactTraceIDConverter.scala:
--------------------------------------------------------------------------------
1 | package kamon.instrumentation.logback.tools
2 |
3 | import ch.qos.logback.classic.pattern.ClassicConverter
4 | import ch.qos.logback.classic.spi.ILoggingEvent
5 | import kamon.Kamon
6 | import kamon.trace.Identifier
7 |
8 | class CompactTraceIDConverter extends ClassicConverter {
9 |
10 | override def convert(event: ILoggingEvent): String = {
11 | val currentSpan = Kamon.currentSpan()
12 | val traceID = currentSpan.trace.id
13 |
14 | if (traceID == Identifier.Empty) ""
15 | else s"[${traceID.string}] "
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/dex/src/package/debian/postrm:
--------------------------------------------------------------------------------
1 | ${{header}}
2 | ${{detect-loader}}
3 |
4 | if [ "$1" = purge ]; then
5 | rm -rf /var/lib/${{app_name}}/data
6 | rm -rf /var/log/${{app_name}}
7 |
8 | userdel ${{app_name}} >/dev/null 2>&1 || true
9 | groupdel ${{app_name}} >/dev/null 2>&1 || true
10 | fi
11 |
12 | exit 0
13 |
--------------------------------------------------------------------------------
/dex/src/package/debian/preinst:
--------------------------------------------------------------------------------
1 | ${{header}}
2 | ${{loader-functions}}
3 |
4 | if [ "$1" = upgrade ]; then
5 | echo Upgrading ...
6 | fi
7 |
8 | exit 0
9 |
--------------------------------------------------------------------------------
/dex/src/package/debian/prerm:
--------------------------------------------------------------------------------
1 | ${{header}}
2 | ${{loader-functions}}
3 | ${{detect-loader}}
4 |
5 | if [ "$1" = remove ]; then
6 | if is_systemd; then
7 | stopService ${{app_name}} || true
8 | elif is_upstart; then
9 | service ${{app_name}} stop || true
10 | fi
11 | fi
12 |
13 | exit 0
14 |
--------------------------------------------------------------------------------
/dex/src/package/doc/README.md:
--------------------------------------------------------------------------------
1 | This directory contains a documentation and examples of configs.
--------------------------------------------------------------------------------
/dex/src/package/doc/main.conf:
--------------------------------------------------------------------------------
1 | # An example of DEX server config
2 | # All options: https://github.com/wavesplatform/matcher/blob/master/dex/src/main/resources/application.conf
3 | waves.dex {
4 | root-directory = "/var/lib/waves-dex"
5 |
6 | address-scheme-character = "W" # T - testnet, D - devnet, S - stagenet
7 |
8 | account-storage {
9 | type = "encrypted-file"
10 |
11 | encrypted-file {
12 | path = ${waves.dex.root-directory}"/account.dat"
13 | # password = "password-for-file"
14 | }
15 | }
16 |
17 | rest-api {
18 | # address = "0.0.0.0" # uncomment this line to accept connections from any host
19 | port = 6886
20 | api-key-hash = ""
21 | }
22 |
23 | waves-blockchain-client {
24 | # Client for com.wavesplatform.dex.grpc.integration.DEXExtension
25 | # grpc.target = "127.0.0.1:6887" # Replace host and port. 6887 is a default port.
26 |
27 | # Client for com.wavesplatform.events.BlockchainUpdates
28 | # blockchain-updates-grpc.target = "127.0.0.1:6881" # Replace host and port. 6881 is a default port.
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/dex/src/package/systemd.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=${{descr}}
3 | Requires=${{start_facilities}}
4 |
5 | [Service]
6 | Type=simple
7 | EnvironmentFile=${{env_config}}
8 | ExecStart=/usr/share/${{app_name}}/bin/${{exec}} \
9 | -Dlogback.configurationFile=/etc/${{app_name}}/logback.xml \
10 | -Dlogback.file.directory=/var/log/${{app_name}} \
11 | -- /etc/${{app_name}}/main.conf
12 | Restart=always
13 | RestartSec=${{retryTimeout}}
14 | RestartPreventExitStatus=10 12 16 18 74
15 | SuccessExitStatus=143
16 | User=${{daemon_user}}
17 | PermissionsStartOnly=true
18 | TimeoutStopSec=300
19 | LimitNOFILE=65536
20 |
21 | [Install]
22 | WantedBy=multi-user.target
--------------------------------------------------------------------------------
/dex/src/package/upstart.conf:
--------------------------------------------------------------------------------
1 | description "Waves DEX server"
2 | author "wavesplatform.com"
3 |
4 | kill timeout 5
5 |
6 | start on runlevel [2345]
7 | stop on runlevel [016]
8 |
9 | normal exit 0 38
10 |
11 | respawn
12 | respawn limit 0 60
13 |
14 | chdir /usr/share/${{app_name}}
15 |
16 | script
17 | exec sudo -u ${{app_name}} bin/${{exec}} \
18 | -Dlogback.configurationFile=/etc/${{app_name}}/logback.xml \
19 | -Dlogback.file.directory=/var/log/${{app_name}} \
20 | -- /etc/${{app_name}}/main.conf
21 | end script
22 |
--------------------------------------------------------------------------------
/dex/src/test/resources/application.conf:
--------------------------------------------------------------------------------
1 | waves.matcher.publicKey = "TestMatcherPubKey"
2 |
3 | waves.dex {
4 | address-scheme-character = T
5 |
6 | account-storage {
7 | type = "in-mem"
8 | in-mem.seed-in-base-64 = "3yZe7d"
9 | }
10 |
11 | lp-accounts.file-path = "/lp/accounts"
12 | }
13 |
14 | akka {
15 | loglevel = "OFF"
16 | # log-config-on-start = on
17 |
18 | actor {
19 | guardian-supervisor-strategy = "akka.actor.DefaultSupervisorStrategy"
20 | debug {
21 | lifecycle = off
22 | autoreceive = off
23 | receive = off
24 | }
25 | }
26 |
27 | test {
28 | default-timeout = 5s
29 | expect-no-message-default = 200ms
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/dex/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/dex/src/test/resources/logback-verbose.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %date{ISO8601,UTC} %-5level [%.15thread] %logger{26} - %msg%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/actors/HasOecInteraction.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.actors
2 |
3 | import akka.testkit.TestProbe
4 | import cats.data.NonEmptyList
5 | import com.wavesplatform.dex.actors.events.OrderEventsCoordinatorActor.Command.Process
6 | import com.wavesplatform.dex.model.Events
7 | import org.scalatest.Suite
8 |
9 | import scala.reflect.ClassTag
10 |
11 | trait HasOecInteraction {
12 | this: Suite =>
13 |
14 | implicit final class TestProbeOps(val self: TestProbe) {
15 |
16 | def expectOecProcess[T <: Events.Event](implicit ct: ClassTag[T]): NonEmptyList[T] =
17 | NonEmptyList.fromListUnsafe(self.expectMsgType[Process].events.toList).map {
18 | case x: T => x
19 | case ev => fail(s"Expected ${ct.runtimeClass.getName}, given: $ev")
20 | }
21 |
22 | def expectFirstOec[T <: Events.Event](implicit ct: ClassTag[T]): T =
23 | self.expectMsgType[Process].events.toList match {
24 | case (head: T) :: _ => head
25 | case events => fail(s"Expected ${ct.runtimeClass.getName}, given: $events")
26 | }
27 |
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/api/RouteSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api
2 |
3 | import akka.http.scaladsl.testkit._
4 | import org.scalatest.freespec.AnyFreeSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | abstract class RouteSpec(basePath: String) extends AnyFreeSpec with ScalatestRouteTest with Matchers {
8 | protected def routePath(suffix: String) = s"$basePath$suffix"
9 | }
10 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/api/http/entities/HttpBalanceSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.domain.asset.Asset.{IssuedAsset, Waves}
4 | import com.wavesplatform.dex.domain.bytes.codec.Base58
5 | import com.wavesplatform.dex.test.matchers.DiffMatcherWithImplicits
6 | import org.scalatest.freespec.AnyFreeSpec
7 | import org.scalatest.matchers.should.Matchers
8 | import play.api.libs.json.Json
9 |
10 | class HttpBalanceSpec extends AnyFreeSpec with Matchers with DiffMatcherWithImplicits {
11 |
12 | private val json =
13 | """{
14 | | "WAVES" : 100,
15 | | "2gCPcEnoZa9LtZzZPFK9fJf7aWzvdBJUABayd1Zj5qFh" : 300
16 | |}""".stripMargin
17 |
18 | private val issuedAsset = IssuedAsset(Base58.decode("2gCPcEnoZa9LtZzZPFK9fJf7aWzvdBJUABayd1Zj5qFh"))
19 |
20 | private val balance =
21 | Map(
22 | Waves -> 100L,
23 | issuedAsset -> 300L
24 | )
25 |
26 | "backward JSON compatibility" - {
27 | "deserialization" in {
28 | Json.parse(json).as[HttpBalance] should matchTo(balance)
29 | }
30 |
31 | "serialization" in {
32 | Json.prettyPrint(Json.toJson(balance)) should matchTo(json)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/api/http/entities/HttpMessageSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.test.matchers.DiffMatcherWithImplicits
4 | import org.scalatest.freespec.AnyFreeSpec
5 | import org.scalatest.matchers.should.Matchers
6 | import play.api.libs.json.Json
7 |
8 | class HttpMessageSpec extends AnyFreeSpec with Matchers with DiffMatcherWithImplicits {
9 |
10 | private val json = """{"message":"test text"}"""
11 | private val message = HttpMessage("test text")
12 |
13 | "backward JSON compatibility" - {
14 | "deserialization" in {
15 | Json.parse(json).as[HttpMessage] should matchTo(message)
16 | }
17 |
18 | "serialization" in {
19 | Json.stringify(Json.toJson(message)) should matchTo(json)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/api/http/entities/HttpRatesSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.domain.asset.Asset.{IssuedAsset, Waves}
4 | import com.wavesplatform.dex.domain.bytes.codec.Base58
5 | import com.wavesplatform.dex.test.matchers.DiffMatcherWithImplicits
6 | import org.scalatest.freespec.AnyFreeSpec
7 | import org.scalatest.matchers.should.Matchers
8 | import play.api.libs.json.Json
9 |
10 | class HttpRatesSpec extends AnyFreeSpec with Matchers with DiffMatcherWithImplicits {
11 |
12 | private val json =
13 | """{
14 | | "WAVES" : 1,
15 | | "2gCPcEnoZa9LtZzZPFK9fJf7aWzvdBJUABayd1Zj5qFh" : 3
16 | |}""".stripMargin
17 |
18 | private val issuedAsset = IssuedAsset(Base58.decode("2gCPcEnoZa9LtZzZPFK9fJf7aWzvdBJUABayd1Zj5qFh"))
19 |
20 | private val rates =
21 | Map(
22 | Waves -> 1d,
23 | issuedAsset -> 3d
24 | )
25 |
26 | "backward JSON compatibility" - {
27 | "deserialization" in {
28 | Json.parse(json).as[HttpRates] should matchTo(rates)
29 | }
30 |
31 | "serialization" in {
32 | Json.prettyPrint(Json.toJson(rates)) should matchTo(json)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/api/http/entities/HttpSuccessfulSingleCancelSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.http.entities
2 |
3 | import com.wavesplatform.dex.domain.bytes.ByteStr
4 | import com.wavesplatform.dex.test.matchers.DiffMatcherWithImplicits
5 | import org.scalatest.freespec.AnyFreeSpec
6 | import org.scalatest.matchers.should.Matchers
7 | import play.api.libs.json.Json
8 |
9 | class HttpSuccessfulSingleCancelSpec extends AnyFreeSpec with Matchers with DiffMatcherWithImplicits {
10 |
11 | private val json =
12 | """{
13 | | "orderId" : "CijYneWqeJwtYLQvP3T6nRNueTFSmB977ULUDBxPZJNH",
14 | | "success" : true,
15 | | "status" : "OrderCanceled"
16 | |}""".stripMargin
17 |
18 | private val message = HttpSuccessfulSingleCancel(orderId = ByteStr.decodeBase58("CijYneWqeJwtYLQvP3T6nRNueTFSmB977ULUDBxPZJNH").get)
19 |
20 | "backward JSON compatibility" - {
21 | "deserialization" in {
22 | Json.parse(json).as[HttpSuccessfulSingleCancel] should matchTo(message)
23 | }
24 |
25 | "serialization" in {
26 | Json.prettyPrint(Json.toJson(message)) should matchTo(json)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/api/ws/HasJwt.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.api.ws
2 |
3 | import java.security
4 | import java.security.KeyPairGenerator
5 |
6 | import com.wavesplatform.dex.api.ws.protocol.WsAddressSubscribe.JwtPayload
7 | import com.wavesplatform.dex.auth.JwtUtils
8 | import play.api.libs.json.{JsObject, Json}
9 |
10 | trait HasJwt extends JwtUtils {
11 |
12 | protected val authServiceKeyPair: security.KeyPair = {
13 | val kpg = KeyPairGenerator.getInstance("RSA")
14 | kpg.initialize(1024)
15 | kpg.generateKeyPair()
16 | }
17 |
18 | protected def mkJwt(payload: JwtPayload): String = mkJwt(authServiceKeyPair, Json.toJsObject(payload))
19 | protected def mkJwt(payload: JsObject): String = mkJwt(authServiceKeyPair, payload)
20 | }
21 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/caches/DropOldestFixedBuffer2Spec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.caches
2 |
3 | import com.wavesplatform.dex.NoShrink
4 | import org.scalacheck.{Arbitrary, Gen}
5 | import org.scalatest.freespec.AnyFreeSpecLike
6 | import org.scalatest.matchers.should.Matchers
7 | import org.scalatestplus.scalacheck.{ScalaCheckPropertyChecks => PropertyChecks}
8 |
9 | class DropOldestFixedBuffer2Spec extends AnyFreeSpecLike with Matchers with PropertyChecks with NoShrink {
10 | private val doubleGen = Arbitrary.arbDouble.arbitrary
11 | private val testGen = Gen.zip(doubleGen, Gen.listOf(doubleGen))
12 |
13 | "DropOldestFixedBuffer2" - {
14 | "min" - {
15 | "selects the minimal value between two last" in forAll(testGen) { case (init, xs) =>
16 | val buff = xs.foldLeft(DropOldestFixedBuffer2(init))(_.append(_))
17 | val lastTwo = (init :: xs).takeRight(2)
18 | buff.min shouldBe lastTwo.min
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/db/AssetsDbSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.db
2 |
3 | import cats.Id
4 | import cats.syntax.option._
5 | import com.wavesplatform.dex.NoShrink
6 | import com.wavesplatform.dex.gen.AssetDescriptionGen
7 | import org.scalacheck.Gen
8 | import org.scalatest.freespec.AnyFreeSpec
9 | import org.scalatest.matchers.should.Matchers
10 | import org.scalatestplus.scalacheck.{ScalaCheckPropertyChecks => PropertyChecks}
11 |
12 | class AssetsDbSpec extends AnyFreeSpec with Matchers with AssetDescriptionGen with WithDb with PropertyChecks with NoShrink {
13 |
14 | "AssetsDb.levelDb implementation" - {
15 | "stores and reads all assets" in forAll(Gen.mapOf(assertDescriptionGen)) { assets =>
16 | test { adb: AssetsDb[Id] =>
17 | assets.foreach(Function.tupled(adb.put))
18 | assets.foreach {
19 | case (asset, desc) =>
20 | adb.get(asset) should matchTo(desc.some)
21 | }
22 | }
23 | }
24 |
25 | }
26 |
27 | private def test(f: AssetsDb[Id] => Any): Any = tempLevelDb(db => f(AssetsDb.levelDb(db)))
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/db/TestAssetPairDb.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.db
2 |
3 | import cats.Applicative
4 | import cats.syntax.applicative._
5 | import cats.syntax.functor._
6 | import cats.instances.future._
7 | import com.wavesplatform.dex.domain.asset.AssetPair
8 |
9 | import java.util.concurrent.ConcurrentHashMap
10 | import scala.concurrent.{ExecutionContext, Future}
11 | import scala.jdk.CollectionConverters.IteratorHasAsScala
12 |
13 | class TestAssetPairDb[F[_]: Applicative] private () extends AssetPairsDb[F] {
14 |
15 | private val storage = ConcurrentHashMap.newKeySet[AssetPair]()
16 |
17 | override def add(pair: AssetPair): F[Unit] = storage.add(pair).pure[F].void
18 | override def remove(pair: AssetPair): F[Unit] = storage.remove(pair).pure[F].void
19 | override def all(): F[Set[AssetPair]] = storage.iterator().asScala.toSet.pure[F]
20 |
21 | override def contains(pair: AssetPair): F[Boolean] = storage.contains(pair).pure[F]
22 | }
23 |
24 | object TestAssetPairDb {
25 |
26 | def apply(): TestAssetPairDb[Future] = {
27 | implicit val ec: ExecutionContext = ExecutionContext.fromExecutor((command: Runnable) => command.run())
28 | new TestAssetPairDb()
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/db/TestRateDb.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.db
2 |
3 | import cats.Applicative
4 | import cats.syntax.applicative._
5 | import cats.syntax.functor._
6 | import cats.instances.future._
7 | import com.wavesplatform.dex.domain.asset.Asset
8 | import com.wavesplatform.dex.domain.asset.Asset.IssuedAsset
9 |
10 | import scala.collection.concurrent.TrieMap
11 | import scala.concurrent.{ExecutionContext, Future}
12 |
13 | class TestRateDb[F[_]: Applicative] private () extends RateDb[F] {
14 |
15 | private val rates = TrieMap.empty[IssuedAsset, Double]
16 |
17 | override def upsertRate(asset: Asset.IssuedAsset, value: Double): F[Unit] = (rates += asset -> value).pure[F].void
18 |
19 | override def getAllRates: F[Map[Asset.IssuedAsset, Double]] = rates.toMap.pure[F]
20 |
21 | override def deleteRate(asset: Asset.IssuedAsset): F[Unit] = (rates -= asset).pure[F].void
22 |
23 | }
24 |
25 | object TestRateDb {
26 |
27 | def apply(): TestRateDb[Future] = {
28 | implicit val ec: ExecutionContext = ExecutionContext.fromExecutor((command: Runnable) => command.run())
29 | new TestRateDb[Future]()
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/doc/MatcherErrorDocSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.doc
2 |
3 | import com.wavesplatform.dex.meta.getSimpleName
4 | import org.scalatest.freespec.AnyFreeSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class MatcherErrorDocSpec extends AnyFreeSpec with Matchers {
8 | "MatcherErrorDoc" - {
9 | "should not contain two equal error codes" in {
10 | val samples = MatcherErrorDoc.errorSamples.run.sortBy(_.code)
11 | samples.zip(samples.tail).foreach {
12 | case (e1, e2) =>
13 | withClue(s"${getSimpleName(e1)} and ${getSimpleName(e2)}") {
14 | e1.code shouldNot be(e2.code)
15 | }
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/fixtures/RestartableActor.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.fixtures
2 |
3 | import akka.actor.Actor
4 | import com.wavesplatform.dex.fixtures.RestartableActor.{RestartActor, RestartActorException}
5 |
6 | trait RestartableActor extends Actor {
7 |
8 | override def unhandled(message: Any): Unit = {
9 | message match {
10 | case RestartActor => throw RestartActorException
11 | case _ =>
12 | }
13 | super.unhandled(message)
14 | }
15 |
16 | }
17 |
18 | object RestartableActor {
19 | case object RestartActor
20 |
21 | private object RestartActorException extends Exception("Planned restart")
22 | }
23 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/fp/MapImplicitsSpec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.fp
2 |
3 | import cats.instances.long.catsKernelStdGroupForLong
4 | import cats.syntax.group._
5 | import com.wavesplatform.dex.NoShrink
6 | import org.scalacheck.{Arbitrary, Gen}
7 | import org.scalatest.matchers.should.Matchers
8 | import org.scalatest.propspec.AnyPropSpec
9 | import org.scalatestplus.scalacheck.{ScalaCheckPropertyChecks => PropertyChecks}
10 |
11 | class MapImplicitsSpec extends AnyPropSpec with PropertyChecks with Matchers with NoShrink {
12 |
13 | private val mapGen = Gen.mapOf[Int, Long] {
14 | for {
15 | k <- Arbitrary.arbInt.arbitrary
16 | v <- Arbitrary.arbLong.arbitrary
17 | } yield (k, v)
18 | }
19 |
20 | property("cleaningGroup.combine - returns a map without empty values") {
21 | import MapImplicits.cleaningGroup
22 | forAll(mapGen, mapGen) {
23 | case (a, b) =>
24 | val r = (a |+| b).filter { case (_, v) => v == 0 }
25 | r shouldBe empty
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/gen/AssetDescriptionGen.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.gen
2 |
3 | import com.wavesplatform.dex.MatcherSpecBase
4 | import com.wavesplatform.dex.domain.asset.Asset
5 | import com.wavesplatform.dex.grpc.integration.dto.BriefAssetDescription
6 | import org.scalacheck.{Arbitrary, Gen}
7 | import org.scalatest.Suite
8 |
9 | trait AssetDescriptionGen extends MatcherSpecBase { _: Suite =>
10 |
11 | protected def assertDescriptionsGen(n: Int): Gen[Map[Asset.IssuedAsset, BriefAssetDescription]] =
12 | Gen.containerOfN[Seq, (Asset.IssuedAsset, BriefAssetDescription)](n, assertDescriptionGen).map(_.toMap)
13 |
14 | protected val assertDescriptionGen: Gen[(Asset.IssuedAsset, BriefAssetDescription)] = for {
15 | asset <- issuedAssetGen(1.toByte)
16 | name <- Arbitrary.arbString.arbitrary
17 | decimals <- Gen.choose(0, 8)
18 | hasScript <- Arbitrary.arbBool.arbitrary
19 | isNft <- Gen.oneOf(true, false)
20 | } yield (asset, BriefAssetDescription(name, decimals, hasScript, isNft))
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/settings/utils/ConfigOpsSpecification.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.settings.utils
2 |
3 | import com.typesafe.config.{Config, ConfigFactory}
4 | import com.wavesplatform.dex.MatcherSpecBase
5 | import com.wavesplatform.dex.settings.toConfigOps
6 | import org.scalatest.matchers.should.Matchers
7 | import org.scalatest.wordspec.AnyWordSpecLike
8 |
9 | class ConfigOpsSpecification extends AnyWordSpecLike with Matchers with MatcherSpecBase {
10 |
11 | val config: Config = ConfigFactory.parseString(
12 | s"""
13 | |waves.dex {
14 | | id = "matcher-1"
15 | | user = "test-user",
16 | | private {
17 | | seed = "test-seed",
18 | | password = "test-password",
19 | | seed58 = "test"
20 | | }
21 | |}""".stripMargin
22 | )
23 |
24 | "ConfigOps" should {
25 |
26 | "correctly filter keys" in {
27 | val filtered = config.withoutKeys(Set("seed"))
28 |
29 | filtered.getString("waves.dex.user") should be("test-user")
30 | filtered.getString("waves.dex.private.password") should be("test-password")
31 | filtered.getObject("waves.dex.private") should have size 1
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/time/SystemTime.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.time
2 |
3 | import org.scalatest.{BeforeAndAfterAll, Suite}
4 |
5 | trait SystemTime extends BeforeAndAfterAll { _: Suite =>
6 |
7 | protected val time = Time.system
8 | protected val zeroTime = Time.zero
9 |
10 | protected def ntpNow: Long = time.getTimestamp()
11 |
12 | override protected def afterAll(): Unit =
13 | super.afterAll()
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/time/TestTime.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.time
2 |
3 | import scala.concurrent.duration.FiniteDuration
4 |
5 | class TestTime(var t: Long = System.currentTimeMillis()) extends Time {
6 |
7 | def setTime(tt: Long): this.type = {
8 | t = tt
9 | this
10 | }
11 |
12 | def advance(d: FiniteDuration): this.type = {
13 | t += d.toMillis
14 | this
15 | }
16 |
17 | override def correctedTime(): Long = t
18 |
19 | override def getTimestamp(): Long = {
20 | t += 1
21 | t
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/util/Implicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.util
2 |
3 | import monix.execution.{Ack, Cancelable}
4 | import monix.reactive.observers.Subscriber
5 | import monix.reactive.subjects.Subject
6 |
7 | import scala.concurrent.Future
8 |
9 | object Implicits {
10 |
11 | implicit final class SubjectOps(val self: Subject.type) extends AnyVal {
12 |
13 | def empty[T]: Subject[T, T] = new Subject[T, T] {
14 | override def size: Int = 0
15 | override def unsafeSubscribeFn(subscriber: Subscriber[T]): Cancelable = Cancelable.empty
16 | override def onNext(elem: T): Future[Ack] = Future.successful(Ack.Stop)
17 | override def onError(ex: Throwable): Unit = {}
18 | override def onComplete(): Unit = {}
19 | }
20 |
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/dex/src/test/scala/com/wavesplatform/dex/util/TestHelpers.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.util
2 |
3 | import java.io.IOException
4 | import java.nio.file.attribute.BasicFileAttributes
5 | import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}
6 |
7 | object TestHelpers {
8 |
9 | def deleteRecursively(path: Path): Unit = Files.walkFileTree(
10 | path,
11 | new SimpleFileVisitor[Path] {
12 |
13 | override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult =
14 | Option(exc).fold {
15 | Files.delete(dir)
16 | FileVisitResult.CONTINUE
17 | }(throw _)
18 |
19 | override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = {
20 | Files.delete(file)
21 | FileVisitResult.CONTINUE
22 | }
23 |
24 | }
25 | )
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Internals
2 |
3 | This documentation contains a high-level overview of how Matcher works.
4 |
5 | 1. [Modules](./modules.md)
6 | 2. [How Matcher interacts with the Waves Node](./waves-node-interaction.md)
7 |
--------------------------------------------------------------------------------
/docs/gen-docs.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | IMAGES_DIR="$( cd "$( dirname "$0" )" && pwd )/images"
3 | # Can't run a loop or other script using entrypoint: a rights issue
4 | # Also see https://dreampuf.github.io/GraphvizOnline/ for an online graphviz editor
5 | cd ${IMAGES_DIR}
6 | for f in *.dot
7 | do
8 | echo "Converting $f..."
9 | docker run --rm --volume ${IMAGES_DIR}:/app --workdir=/app webuni/graphviz dot -Tsvg "$f" > $(echo $f | sed s/dot/svg/)
10 | done
11 |
12 | for f in *.mmd
13 | do
14 | echo "Converting $f..."
15 | # Online editor: https://mermaid-js.github.io/mermaid-live-editor/
16 | # https://github.com/mermaid-js/mermaid-cli#use-docker
17 | docker run --rm --volume ${IMAGES_DIR}:/data minlag/mermaid-cli:8.10.1 -i "$f" -o $(echo $f | sed s/mmd/svg/)
18 | done
19 |
--------------------------------------------------------------------------------
/docs/images/m-dep.dot:
--------------------------------------------------------------------------------
1 | // @formatter:off
2 | digraph G {
3 | graph [label = "Modules"]
4 | edge [fontsize = 11, arrowsize = 0.5]
5 |
6 | dtc [label="dex-test-common"]
7 | dex
8 | dic [label="dex-it-common"]
9 | di [label="dex-it"]
10 | dl [label="dex-load"]
11 | wg [label="waves-grpc"]
12 | we [label="waves-ext"]
13 | wi [label="waves-integration"]
14 | wit [label="waves-integration-it"]
15 | dj [label="dex-jmh"]
16 |
17 | dtc -> wi
18 |
19 | dex -> wi
20 | dex -> dtc[taillabel="test->compile", labeldistance=2, labelangle=0]
21 |
22 | dic -> dex[taillabel="compile;runtime->provided", minlen = 4, labeldistance = 5, labelangle=-40]
23 | dic -> dtc
24 |
25 | di -> dex[headlabel="compile;test->test", labeldistance = 10, labelangle = 20]
26 | di -> wit,dic
27 |
28 | dl -> dex,dic
29 |
30 | we -> wg
31 | we -> dtc[label="test->compile"]
32 |
33 | wi -> wg
34 |
35 | wit -> wi,dic
36 |
37 | dj -> dex[taillabel="compile;test->test", minlen = 2, labelangle=10]
38 | }
39 |
--------------------------------------------------------------------------------
/docs/images/oa-balance-affect.dot:
--------------------------------------------------------------------------------
1 | // @formatter:off
2 | digraph G {
3 | graph [label="How can transactions affect it"]
4 | node [shape=rect]
5 | edge [fontsize=11, arrowsize=0.5]
6 |
7 | o[label="observed transaction"]
8 | f[label="failed transaction"]
9 | u[label="unconfirmed transaction"]
10 | c[label="confirmed transaction"]
11 |
12 | w[label="wait to be known"]
13 | k[label="is known?", shape=diamond]
14 | kf[label="is known?", shape=diamond]
15 |
16 | cs[label="compensate spendings for audit balance"]
17 | r[label="remove from a list"]
18 |
19 | o:s->f:n,u:n,c:n[minlen=2]
20 |
21 | f:s->kf:n[minlen=2]
22 | kf:w->w:n[taillabel="no", minlen=2]
23 | kf:e->r:n[minlen=2]
24 |
25 | u:s,c:s->k:n[minlen=2]
26 |
27 | k:w->r:n[minlen=2]
28 | k:e->cs:n[taillabel="no",minlen=2]
29 | }
30 |
--------------------------------------------------------------------------------
/docs/images/wni-chain.dot:
--------------------------------------------------------------------------------
1 | // @formatter:off
2 | digraph NodeChain {
3 | graph [label="Chain", newrank="true", rankdir=RL, compound=true]
4 | node [shape="circle"]
5 | edge [fontsize=11, arrowsize=0.5]
6 |
7 | MB2 [label=<µB2>, style="filled", color="#FFE5D9"]
8 | MB1 [label=<µB1>, style="filled", color="#FFE5D9"]
9 |
10 | B1000 [label=1000>, style="filled", color="#F4ACB7"]
11 | B999 [label=999>, style="filled", color="#F4ACB7"]
12 | B998 [label=998>, style="filled", color="#F4ACB7"]
13 | Betc [label="...", style="filled", color="#F4ACB7"]
14 | B901 [label=901>, style="filled", color="#F4ACB7"]
15 | B900 [label=900>, style="filled", color="#F4ACB7"]
16 | B899 [label="dropped", style="dashed"]
17 |
18 | MB2 -> MB1 -> B1000 -> B999 -> B998 -> Betc -> B901 -> B900 -> B899
19 | }
20 |
--------------------------------------------------------------------------------
/docs/images/wni-state-machine.dot:
--------------------------------------------------------------------------------
1 | // @formatter:off
2 | digraph WniStateMachine {
3 | graph [label="State Machine"]
4 | edge [fontsize=11, arrowsize=0.5]
5 |
6 | N:n -> N:n [label="Appended ok"]
7 | N:w -> N:w [label="Utx*"]
8 | N:e -> TRB:n [headlabel="RolledBack", labeldistance=5, labelangle=-4]
9 | N:e -> TRB:ne [headlabel="Appended invalid", minlen=3, labeldistance=8, labelangle=8]
10 | TRB:nw -> N:se [headlabel=1>, headtooltip="Same addresses and assets affected", labeldistance=4, labelangle=-43, fontcolor="#FF66B3"]
11 | TRB:w -> TRB:w [headlabel="Utx*", labeldistance=2, labelangle=-90]
12 | TRB:e -> TRB:e [label="Appended ok"]
13 | TRB:se -> TRB:se [label="RolledBack"]
14 | TRB -> TRS [headlabel="Appended ok", labeldistance=5, labelangle=-35]
15 | TRS -> N [headlabel="DataReceived", minlen=5, labeldistance=9, labelangle=31]
16 |
17 | N [label="Normal", style="filled", color="#084B83", fontcolor="#F0F6F6"]
18 | TRB [label="TransientRollback", style="filled", color="#42BFDD", fontcolor="#084B83"]
19 | TRS [label="TransientResolving", style="filled", color="#BBE6E4", fontcolor="#084B83"]
20 | }
21 |
--------------------------------------------------------------------------------
/docs/modules.md:
--------------------------------------------------------------------------------
1 | # Modules
2 |
3 | 
--------------------------------------------------------------------------------
/docs/places-and-cancels.md:
--------------------------------------------------------------------------------
1 | # How Matcher places and cancels orders by clients requests
2 |
3 | The high level diagrams of how it works.
4 |
5 | ## On matcher which accepts requests
6 |
7 | ### Places
8 |
9 | 
10 |
11 | ### Cancels
12 |
13 | Works similarly:
14 | 
15 |
16 | ## On other matchers
17 |
18 | There is possible a configuration with multiple matchers.
19 | On a secondary matcher processing looks same, but it starts from `Application`.
20 |
--------------------------------------------------------------------------------
/project/CommonSettings.scala:
--------------------------------------------------------------------------------
1 | import sbt.Keys._
2 | import sbt._
3 |
4 | object CommonSettings extends AutoPlugin {
5 | object autoImport extends CommonKeys
6 | import autoImport._
7 |
8 | override def trigger: PluginTrigger = allRequirements
9 |
10 | // These options doesn't work for ScalaJS
11 | override def projectSettings: Seq[Def.Setting[_]] = Seq(
12 | packageSource := sourceDirectory.value / "package"
13 | )
14 |
15 | }
16 |
17 | trait CommonKeys {
18 | val network = SettingKey[NodeNetwork]("node-network", "The network for artifacts") // "network" is already defined
19 | val packageSource = settingKey[File]("Additional files for DEB")
20 | }
21 |
--------------------------------------------------------------------------------
/project/ExtensionKeys.scala:
--------------------------------------------------------------------------------
1 | import sbt.Keys.Classpath
2 | import sbt._
3 |
4 | trait ExtensionKeys {
5 |
6 | val classpathOrdering = taskKey[Seq[(File, String)]](
7 | "The order of the classpath used at runtime for the bat/bash scripts."
8 | )
9 |
10 | val projectDependencyArtifacts = taskKey[Classpath](
11 | "The set of exported artifacts from our dependent projects."
12 | )
13 |
14 | val providedArtifacts = taskKey[Classpath](
15 | "The set of exported artifacts from our dependent projects."
16 | )
17 |
18 | val classpath = taskKey[Seq[String]](
19 | "A list of relative filenames (to the lib/ folder in the distribution) of what to include on the classpath."
20 | )
21 |
22 | val nodePackageName = settingKey[String]("Node deb package name")
23 | }
24 |
--------------------------------------------------------------------------------
/project/Hashes.scala:
--------------------------------------------------------------------------------
1 | import java.io.InputStream
2 |
3 | object Hashes {
4 |
5 | def mk(algorithm: String, stream: InputStream): Array[Byte] = {
6 | import java.security.{DigestInputStream, MessageDigest}
7 | val digest = MessageDigest.getInstance(algorithm)
8 | try {
9 | val dis = new DigestInputStream(stream, digest)
10 | val buffer = new Array[Byte](8192)
11 | while (dis.read(buffer) >= 0) {}
12 | dis.close()
13 | digest.digest
14 | } finally stream.close()
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/project/MatcherIOUtils.scala:
--------------------------------------------------------------------------------
1 | import java.io.{BufferedInputStream, FileInputStream, FileOutputStream}
2 |
3 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
4 | import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
5 | import org.apache.commons.io.IOUtils
6 | import sbt._
7 |
8 | object MatcherIOUtils {
9 |
10 | def decompressTgz(tarFile: File, dest: File): Unit = {
11 | val tarIn = new TarArchiveInputStream(
12 | new GzipCompressorInputStream(
13 | new BufferedInputStream(
14 | new FileInputStream(tarFile)
15 | )
16 | )
17 | )
18 |
19 | try {
20 | dest.mkdirs()
21 | Iterator
22 | .continually(tarIn.getNextEntry)
23 | .takeWhile(_ != null)
24 | .foreach { entry =>
25 | val name = entry.getName
26 | val path = dest / name
27 |
28 | if (entry.isDirectory) path.mkdirs()
29 | else if (!path.isFile) IOUtils.copy(tarIn, new FileOutputStream(path)) // isFile to check existence
30 | }
31 | } finally tarIn.close()
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/project/Network.scala:
--------------------------------------------------------------------------------
1 | sealed abstract class NodeNetwork(val suffix: String) {
2 | lazy val packageSuffix = if (suffix == Mainnet.suffix) "" else "-" + suffix
3 | override val toString = suffix
4 | }
5 |
6 | object NodeNetwork {
7 |
8 | def apply(v: Option[String]): NodeNetwork = v match {
9 | case Some(Testnet.suffix) => Testnet
10 | case Some(Stagenet.suffix) => Stagenet
11 | case Some(Devnet.suffix) => Devnet
12 | case _ => Mainnet
13 | }
14 |
15 | val All: List[NodeNetwork] = List(Mainnet, Testnet, Stagenet, Devnet)
16 | }
17 |
18 | object Mainnet extends NodeNetwork("mainnet")
19 | object Testnet extends NodeNetwork("testnet")
20 | object Stagenet extends NodeNetwork("stagenet")
21 | object Devnet extends NodeNetwork("devnet")
22 |
--------------------------------------------------------------------------------
/project/RunApplicationSettings.scala:
--------------------------------------------------------------------------------
1 | import sbt.Keys._
2 | import sbt._
3 |
4 | object RunApplicationSettings extends AutoPlugin {
5 |
6 | override def projectSettings: Seq[Def.Setting[_]] = inConfig(Compile)(
7 | Seq(
8 | mainClass := Some("com.wavesplatform.Application"),
9 | discoveredMainClasses := (Compile / mainClass).value.toSeq,
10 | run / fork := true
11 | )
12 | )
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.5.3
2 |
--------------------------------------------------------------------------------
/waves-ext/lib/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/waves-ext/src/main/resources/application.conf:
--------------------------------------------------------------------------------
1 | waves.extensions += "com.wavesplatform.events.BlockchainUpdates"
2 | waves.extensions += "com.wavesplatform.dex.grpc.integration.DEXExtension"
3 |
4 | waves {
5 |
6 | dex {
7 | # gRPC integration settings for Waves Node
8 | grpc.integration {
9 | # Extension's host
10 | host = localhost
11 | # Extension's port
12 | port = 6887
13 | }
14 |
15 | # Path to file with liquidity pool accounts encoded in base58
16 | lp-accounts.file-path = "/path/to/lp/accounts"
17 |
18 | order-script-validation {
19 | # Public keys of accounts in base58 that are allowed accessing blockchain state
20 | allowed-blockchain-state-accounts = []
21 | }
22 | }
23 |
24 | blockchain-updates.min-keep-alive = 2s
25 |
26 | }
27 |
28 | akka.actor.waves-dex-grpc-scheduler {
29 | type = "Dispatcher"
30 | executor = "thread-pool-executor"
31 | thread-pool-executor.fixed-pool-size = 8
32 | throughput = 10
33 | }
34 |
--------------------------------------------------------------------------------
/waves-ext/src/main/scala/com/wavesplatform/dex/collections/Implicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.collections
2 |
3 | // TODO DEX-994
4 | object Implicits {
5 |
6 | implicit final class ListOps[T](val self: List[T]) extends AnyVal {
7 | def prependIf(cond: Boolean)(item: => T): List[T] = if (cond) item :: self else self
8 | }
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/waves-ext/src/main/scala/com/wavesplatform/dex/grpc/integration/dto/BriefAssetDescription.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.dto
2 |
3 | import com.wavesplatform.transaction.assets.exchange.AssetPair
4 |
5 | case class BriefAssetDescription(name: String, decimals: Int, hasScript: Boolean, isNft: Boolean)
6 |
7 | object BriefAssetDescription {
8 |
9 | val wavesDescription: BriefAssetDescription =
10 | BriefAssetDescription(
11 | name = AssetPair.WavesName,
12 | decimals = 8,
13 | hasScript = false,
14 | isNft = false
15 | )
16 |
17 | val someWavesDescription: Option[BriefAssetDescription] = Option(wavesDescription)
18 | }
19 |
--------------------------------------------------------------------------------
/waves-ext/src/main/scala/com/wavesplatform/dex/grpc/integration/protobuf/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration
2 |
3 | import com.wavesplatform.lang.ValidationError
4 |
5 | package object protobuf {
6 |
7 | implicit class EitherVEExt[T](e: Either[ValidationError, T]) {
8 | def explicitGetErr(): T = e.fold(e => throw GRPCErrors.toStatusException(e), identity)
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/waves-ext/src/test/scala/com/wavesplatform/dex/WavesExtSuiteBase.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import io.qameta.allure.scalatest.AllureScalatestContext
4 | import org.scalatest.freespec.AnyFreeSpecLike
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | trait WavesExtSuiteBase extends AnyFreeSpecLike with Matchers with AllureScalatestContext
8 |
--------------------------------------------------------------------------------
/waves-ext/src/test/scala/com/wavesplatform/dex/grpc/integration/protobuf/ConversionsSuite.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.protobuf
2 |
3 | import com.wavesplatform.dex.grpc.integration.protobuf.PbToWavesConversions._
4 | import com.wavesplatform.dex.grpc.integration.protobuf.WavesToPbConversions._
5 | import com.wavesplatform.dex.test.WavesEntitiesGen
6 | import com.wavesplatform.dex.{NoShrink, WavesExtSuiteBase}
7 | import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
8 |
9 | class ConversionsSuite extends WavesExtSuiteBase with ScalaCheckDrivenPropertyChecks with WavesEntitiesGen with NoShrink {
10 | "order" in forAll(orderAndSenderGen()) { orderAndSender =>
11 | val (order, _) = orderAndSender
12 | order.toPB.toVanilla shouldBe order
13 | }
14 |
15 | "exchangeTx" in forAll(exchangeTransactionGen) { tx =>
16 | tx.toPB.toVanilla.explicitGetErr() shouldBe tx
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/waves-grpc/build.sbt:
--------------------------------------------------------------------------------
1 | description := "Proto files and generated gRPC entities and servers for DEX-Node interaction"
2 |
3 | libraryDependencies ++= Dependencies.Module.wavesGrpc
4 |
5 | // Google's descriptor.proto contains a deprecated field:
6 | // optional bool java_generate_equals_and_hash = 20 [deprecated=true];
7 | // When scalac compiles a generated class, it warns about a deprecated field.
8 | scalacOptions += "-P:silencer:pathFilters=FileOptions;TransactionsApiGrpc;InvokeScriptResult;Endpoint;MetricDescriptor"
9 |
10 | // Use protocGenerate to generate it manually
11 | inConfig(Compile)(
12 | Seq(
13 | PB.deleteTargetDirectory := false,
14 | PB.protoSources += PB.externalIncludePath.value,
15 | PB.targets += scalapb.gen(flatPackage = true) -> sourceManaged.value / "scalapb"
16 | )
17 | )
18 |
--------------------------------------------------------------------------------
/waves-integration-it/src/test/container/start-matcher-node-it.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Overriding to be able to switch configs
3 |
4 | trap 'kill -TERM $PID' TERM INT
5 |
6 | echo "Starting process..." >> ${DETAILED_LOG_PATH}
7 | echo Config file: ${WAVES_NODE_CONFIGPATH} >> ${DETAILED_LOG_PATH}
8 | echo Options: ${WAVES_OPTS} >> ${DETAILED_LOG_PATH}
9 |
10 | java ${WAVES_OPTS} -cp "/usr/share/waves/lib/plugins/*:/usr/share/waves/lib/*" com.wavesplatform.Application ${WAVES_NODE_CONFIGPATH} &>> ${DETAILED_LOG_PATH} &
11 |
12 | PID=$!
13 | echo "PID: ${PID}" >> ${DETAILED_LOG_PATH}
14 | wait ${PID}
15 |
16 | trap - TERM INT
17 | wait ${PID}
18 | EXIT_STATUS=$?
19 | echo "Exit status: ${EXIT_STATUS}" >> ${DETAILED_LOG_PATH}
20 |
--------------------------------------------------------------------------------
/waves-integration-it/src/test/resources/jul.properties:
--------------------------------------------------------------------------------
1 | # java.util.logging properties
2 | # To pass logs to SLF4J
3 | handlers=org.slf4j.bridge.SLF4JBridgeHandler
4 | # To enable gRPC logging. See levels in java.util.logging.Level
5 | io.grpc.level=FINE
6 |
--------------------------------------------------------------------------------
/waves-integration-it/src/test/resources/nodes/jul.properties:
--------------------------------------------------------------------------------
1 | # java.util.logging properties
2 | # To pass logs to SLF4J
3 | handlers=org.slf4j.bridge.SLF4JBridgeHandler
4 | # To enable gRPC logging. See levels in java.util.logging.Level
5 | io.grpc.level=FINE
6 |
--------------------------------------------------------------------------------
/waves-integration-it/src/test/resources/nodes/waves-1.conf:
--------------------------------------------------------------------------------
1 | # Base configuration for waves nodes for waves-integration-it tests. It is assumed that this configuration does not change
2 | include "waves-base.conf"
3 |
4 | # Node name to send during handshake
5 | waves.network.node-name = "node01"
6 |
7 | # Configuration of the waves.blockchain.custom.genesis/address-scheme-character sections
8 | # generated by GenesisConfigGenerator from waves-integration-it/src/test/resources/genesis.conf
9 | include "run.conf"
10 |
11 | # Highest priority configurations specified in tests (suiteInitialWavesNodeConfig)
12 | include "suite.conf"
--------------------------------------------------------------------------------
/waves-integration-it/src/test/resources/testcontainers.properties:
--------------------------------------------------------------------------------
1 | transport.type=httpclient5
--------------------------------------------------------------------------------
/waves-integration/build.sbt:
--------------------------------------------------------------------------------
1 | description := "DEX domain entities, cryptography and blockchain client for DEX-Node interaction"
2 |
3 | libraryDependencies ++= Dependencies.Module.wavesIntegration
4 |
5 | // Google's descriptor.proto contains a deprecated field:
6 | // optional bool java_generate_equals_and_hash = 20 [deprecated=true];
7 | // When scalac compiles a generated class, it warns about a deprecated field.
8 | scalacOptions += "-P:silencer:pathFilters=FileOptions;TransactionsApiGrpc;InvokeScriptResult;Endpoint;MetricDescriptor"
9 |
10 | inConfig(Compile)(
11 | Seq(
12 | PB.deleteTargetDirectory := false,
13 | PB.protoSources += PB.externalIncludePath.value,
14 | PB.targets += scalapb.gen(flatPackage = true) -> sourceManaged.value / "scalapb"
15 | )
16 | )
17 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/akka/actor/typed/scaladsl/BehaviorsImplicits.scala:
--------------------------------------------------------------------------------
1 | package akka.actor.typed.scaladsl
2 |
3 | import akka.actor.typed.Behavior
4 | import akka.actor.typed.internal.{BehaviorImpl, StashBufferImplWithCtxPropagation}
5 |
6 | object BehaviorsImplicits {
7 |
8 | implicit class BehaviorsOps(val behaviors: Behaviors.type) extends AnyVal {
9 |
10 | def stashWithCtxPropagation[T](capacity: Int)(factory: StashBuffer[T] => Behavior[T]): Behavior[T] =
11 | BehaviorImpl.DeferredBehavior { ctx =>
12 | val stash = StashBufferImplWithCtxPropagation[T](ctx, capacity)
13 | factory(stash)
14 | }
15 |
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/collections/FifoSet.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.collections
2 |
3 | import scala.collection.mutable
4 |
5 | sealed trait FifoSet[T] {
6 |
7 | def contains(x: T): Boolean
8 |
9 | /**
10 | * @return added?
11 | */
12 | def append(x: T): Boolean
13 |
14 | def remove(x: T): Boolean
15 |
16 | def clear(): Unit
17 | }
18 |
19 | // DEX-1044
20 | private case class LimitedFifoSet[T] private (elements: mutable.LinkedHashSet[T], capacity: Int) extends FifoSet[T] {
21 |
22 | override def contains(id: T): Boolean = elements.contains(id)
23 |
24 | override def append(id: T): Boolean =
25 | if (contains(id)) false
26 | else {
27 | elements.add(id)
28 | if (elements.size > capacity) elements.headOption.map(elements.remove)
29 | true
30 | }
31 |
32 | override def remove(id: T): Boolean =
33 | elements.remove(id)
34 |
35 | override def clear(): Unit =
36 | elements.clear()
37 |
38 | }
39 |
40 | object FifoSet {
41 |
42 | def limited[T](capacity: Int): FifoSet[T] = {
43 | require(capacity >= 0)
44 | LimitedFifoSet[T](new mutable.LinkedHashSet[T], capacity)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/collections/MapOps.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.collections
2 |
3 | // TODO DEX-994
4 | object MapOps {
5 |
6 | implicit final class Ops[K, V](val self: Map[K, V]) extends AnyVal {
7 |
8 | def deepCombine(update: Iterable[(K, V)])(combine: (V, V) => V): Map[K, V] =
9 | update.foldLeft(self) {
10 | case (r, (k, updateV)) =>
11 | val updatedV = r.get(k).fold(updateV)(combine(_, updateV))
12 | r.updated(k, updatedV)
13 | }
14 |
15 | }
16 |
17 | implicit final class Ops2D[K1, K2, V](val self: Map[K1, Map[K2, V]]) extends AnyVal {
18 | def deepReplace(update: Iterable[(K1, Map[K2, V])]): Map[K1, Map[K2, V]] = self.deepCombine(update)(_ ++ _)
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/collections/VectorOps.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.collections
2 |
3 | import scala.annotation.tailrec
4 |
5 | // TODO DEX-994, tests
6 | object VectorOps {
7 |
8 | implicit final class Ops[T](val self: Vector[T]) extends AnyVal {
9 |
10 | /**
11 | * @return (pxs, rest xs), where: pxs is reversed and for each x in pxs p(x) == true
12 | */
13 | def splitOnCondReversed(p: T => Boolean): (List[T], Vector[T]) = {
14 | @tailrec def loop(rest: Vector[T], accDropped: List[T]): (List[T], Vector[T]) = rest.headOption match {
15 | case None => (accDropped, rest)
16 | case Some(x) =>
17 | if (p(x)) loop(rest.tail, x :: accDropped)
18 | else (accDropped, rest)
19 | }
20 |
21 | loop(self, List.empty)
22 | }
23 |
24 | def splitOnCond(p: T => Boolean): (List[T], Vector[T]) = {
25 | val (t, f) = splitOnCondReversed(p)
26 | (t.reverse, f)
27 | }
28 |
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/account/AddressScheme.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.account
2 |
3 | abstract class AddressScheme {
4 | val chainId: Byte
5 | override def toString: String = s"AddressScheme($chainId)"
6 | }
7 |
8 | object AddressScheme {
9 | @volatile var current: AddressScheme = DefaultAddressScheme
10 | }
11 |
12 | object DefaultAddressScheme extends AddressScheme {
13 | val chainId: Byte = 'T'.toByte
14 | }
15 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/account/PrivateKey.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.account
2 |
3 | import com.wavesplatform.dex.domain.bytes.ByteStr
4 | import play.api.libs.json.{Format, Writes}
5 | import supertagged._
6 |
7 | object PrivateKey extends TaggedType[ByteStr] {
8 |
9 | def apply(privateKey: ByteStr): PrivateKey = privateKey @@ PrivateKey
10 | def apply(privateKey: Array[Byte]): PrivateKey = apply(ByteStr(privateKey))
11 |
12 | def unapply(arg: Array[Byte]): Option[PrivateKey] = Some(apply(arg))
13 |
14 | implicit lazy val jsonFormat: Format[PrivateKey] = Format[PrivateKey](
15 | ByteStr.byteStrFormat.map(this.apply),
16 | Writes(pk => ByteStr.byteStrFormat.writes(pk))
17 | )
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/account/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain
2 |
3 | package object account {
4 | type PublicKey = PublicKey.Type
5 | type PrivateKey = PrivateKey.Type
6 | }
7 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/bytes/codec/Base58.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.bytes.codec
2 |
3 | import scala.util.control.NonFatal
4 |
5 | object Base58 extends BaseXXEncDec {
6 |
7 | private[this] val useSlowBase58: Boolean = sys.props.get("waves.use-slow-base58").exists(s => s.toLowerCase == "true" || s == "1")
8 |
9 | override val defaultDecodeLimit: Int = 192
10 |
11 | override def encode(array: Array[Byte]): String =
12 | if (useSlowBase58) StdBase58.encode(array)
13 | else
14 | try FastBase58.encode(array)
15 | catch {
16 | case NonFatal(_) => StdBase58.encode(array)
17 | }
18 |
19 | override def decode(str: String): Array[Byte] =
20 | if (useSlowBase58) StdBase58.decode(str)
21 | else
22 | try FastBase58.decode(str)
23 | catch {
24 | case NonFatal(_) => StdBase58.decode(str)
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/bytes/codec/Base64.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.bytes.codec
2 |
3 | object Base64 extends BaseXXEncDec {
4 |
5 | val Prefix = "base64:"
6 | override val defaultDecodeLimit: Int = 1024 * 1024 * 1024 // 1 MB
7 |
8 | override def encode(input: Array[Byte]): String = {
9 | val encoder = java.util.Base64.getEncoder
10 | val encodedBytes = encoder.encode(input)
11 | new String(encodedBytes)
12 | }
13 |
14 | override def decode(input: String): Array[Byte] = {
15 | val decoder = java.util.Base64.getDecoder
16 | val encodedStr = if (input.startsWith(Prefix)) input.substring(Prefix.length) else input
17 | decoder.decode(encodedStr)
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/bytes/codec/BaseXXEncDec.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.bytes.codec
2 |
3 | import scala.util.Try
4 |
5 | trait BaseXXEncDec {
6 |
7 | def defaultDecodeLimit: Int
8 |
9 | def encode(array: Array[Byte]): String
10 | def decode(str: String): Array[Byte]
11 |
12 | def tryDecode(str: String): Try[Array[Byte]] = Try(this.decode(str))
13 |
14 | def tryDecodeWithLimit(str: String, limit: Int = defaultDecodeLimit): Try[Array[Byte]] =
15 | Try {
16 | require(str.length <= limit, s"base58Decode input exceeds $limit")
17 | this.tryDecode(str)
18 | }.flatten
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/crypto/Authorized.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.crypto
2 |
3 | import com.wavesplatform.dex.domain.account.PublicKey
4 |
5 | trait Authorized {
6 | def sender: PublicKey
7 | }
8 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/crypto/Proven.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.crypto
2 |
3 | import monix.eval.Coeval
4 |
5 | trait Proven extends Authorized {
6 |
7 | val bodyBytes: Coeval[Array[Byte]]
8 | def proofs: Proofs
9 | }
10 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/crypto/Verifier.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.crypto
2 |
3 | import com.wavesplatform.dex.domain.crypto
4 | import com.wavesplatform.dex.domain.error.ValidationError.GenericError
5 | import com.wavesplatform.dex.domain.utils.ScorexLogging
6 |
7 | object Verifier extends ScorexLogging {
8 |
9 | def verifyAsEllipticCurveSignature[T <: Proven with Authorized](pt: T): Either[GenericError, T] =
10 | pt.proofs.proofs match {
11 | case p :: Nil =>
12 | Either.cond(crypto.verify(p.arr, pt.bodyBytes(), pt.sender), pt, GenericError(s"Proof doesn't validate as signature for $pt"))
13 | case _ => Left(GenericError("Transactions from non-scripted accounts must have exactly 1 proof"))
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/feature/BlockhainFeature.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.feature
2 |
3 | case class BlockchainFeature private (id: Short, description: String)
4 |
5 | object BlockchainFeatures {
6 |
7 | val SmartAssets: BlockchainFeature = BlockchainFeature(9, "Smart Assets")
8 | val SmartAccountTrading: BlockchainFeature = BlockchainFeature(10, "Smart Account Trading")
9 | val OrderV3: BlockchainFeature = BlockchainFeature(12, "Order Version 3")
10 | val RideV6: BlockchainFeature = BlockchainFeature(17, "Ride V6, MetaMask support")
11 | }
12 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/serialization/ByteAndJsonSerializable.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.serialization
2 |
3 | import monix.eval.Coeval
4 | import play.api.libs.json.JsObject
5 |
6 | trait ByteAndJsonSerializable {
7 | val bytes: Coeval[Array[Byte]]
8 | val json: Coeval[JsObject]
9 | }
10 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/state/LeaseBalance.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.state
2 |
3 | import cats.kernel.Monoid
4 |
5 | case class LeaseBalance(in: Long, out: Long)
6 |
7 | object LeaseBalance {
8 |
9 | val empty: LeaseBalance = LeaseBalance(0, 0)
10 |
11 | implicit val m: Monoid[LeaseBalance] = new Monoid[LeaseBalance] {
12 | override def empty: LeaseBalance = LeaseBalance.empty
13 | override def combine(x: LeaseBalance, y: LeaseBalance): LeaseBalance = LeaseBalance(safeSum(x.in, y.in), safeSum(x.out, y.out))
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/state/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain
2 |
3 | import scala.util.Try
4 |
5 | package object state {
6 | def safeSum(x: Long, y: Long): Long = Try(Math.addExact(x, y)).getOrElse(Long.MinValue)
7 | }
8 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/transaction/ExchangeTransactionParser.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.transaction
2 |
3 | import com.wavesplatform.dex.domain.bytes.deser.EntityParser
4 | import com.wavesplatform.dex.domain.bytes.deser.EntityParser.ConsumedBytesOffset
5 |
6 | import scala.util.Try
7 |
8 | trait ExchangeTransactionParser[T <: ExchangeTransaction] extends EntityParser[T] {
9 |
10 | protected def parseHeader(bytes: Array[Byte]): Try[Int]
11 |
12 | override def parseBytes(bytes: Array[Byte]): Try[(T, ConsumedBytesOffset)] =
13 | parseHeader(bytes) flatMap (offset => super.parseBytes(bytes drop offset))
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/transaction/ExchangeTransactionResult.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain.transaction
2 |
3 | import cats.syntax.either._
4 | import cats.syntax.option._
5 | import com.wavesplatform.dex.domain.error.ValidationError
6 |
7 | // contains transaction and possible validation error
8 | final case class ExchangeTransactionResult[+A <: ExchangeTransaction](transaction: A, error: Option[ValidationError] = None) {
9 |
10 | // returns Right only if there is no error
11 | def toEither: Either[ValidationError, A] = error.fold(transaction.asRight[ValidationError])(_.asLeft)
12 |
13 | // returns Some only if there is no error
14 | def toOption: Option[A] = error.fold(transaction.some)(_ => None)
15 |
16 | def map[B >: A <: ExchangeTransaction](f: A => B): ExchangeTransactionResult[B] =
17 | error.fold(copy(transaction = f(transaction)))(_ => this)
18 |
19 | }
20 |
21 | object ExchangeTransactionResult {
22 |
23 | def fromEither[A <: ExchangeTransaction](maybeError: Either[ValidationError, Unit], tx: A): ExchangeTransactionResult[A] =
24 | ExchangeTransactionResult(tx, maybeError.left.toOption)
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/domain/utils/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.domain
2 |
3 | import scala.util.{Failure, Success, Try}
4 |
5 | package object utils {
6 |
7 | private val BytesMaxValue = 256
8 | private val Base58MaxValue = 58
9 |
10 | private val BytesLog = math.log(BytesMaxValue)
11 | private val BaseLog = math.log(Base58MaxValue)
12 |
13 | def base58Length(byteArrayLength: Int): Int = math.ceil(BytesLog / BaseLog * byteArrayLength).toInt
14 |
15 | implicit final class EitherExt2[A, B](val ei: Either[A, B]) extends AnyVal {
16 |
17 | def explicitGet(): B = ei match {
18 | case Left(value) => throw makeException(value)
19 | case Right(value) => value
20 | }
21 |
22 | def foldToTry: Try[B] = ei.fold(
23 | left => Failure(makeException(left)),
24 | right => Success(right)
25 | )
26 |
27 | @inline
28 | private[this] def makeException(value: Any): Throwable = value match {
29 | case err: Throwable => err
30 | case _ => new RuntimeException(value.toString)
31 | }
32 |
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/fp/MayBeEmpty.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.fp
2 |
3 | trait MayBeEmpty[T] {
4 | def isEmpty(x: T): Boolean
5 | def empty: T
6 | }
7 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/fp/PartialFunctionOps.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.fp
2 |
3 | object PartialFunctionOps {
4 |
5 | implicit final class Implicits[A, B](val self: PartialFunction[A, B]) extends AnyVal {
6 | def toTotal(f: A => B): A => B = self.applyOrElse(_, f)
7 | }
8 |
9 | // Helps scalac to determine types
10 | def mkPartial[A, B](f: PartialFunction[A, B]): PartialFunction[A, B] = f
11 | }
12 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/caches/AssetDescriptionsCache.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.caches
2 |
3 | import java.time.Duration
4 |
5 | import com.wavesplatform.dex.domain.asset.Asset.IssuedAsset
6 | import com.wavesplatform.dex.grpc.integration.dto.BriefAssetDescription
7 |
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | class AssetDescriptionsCache(loader: IssuedAsset => Future[Option[BriefAssetDescription]], expiration: Duration)(
11 | implicit executionContext: ExecutionContext
12 | ) extends BlockchainCache[IssuedAsset, Option[BriefAssetDescription]](
13 | loader,
14 | Some(expiration),
15 | invalidationPredicate = BlockchainCache.noCustomInvalidationLogic
16 | )
17 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/caches/BalancesCache.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.caches
2 |
3 | import com.wavesplatform.dex.domain.account.Address
4 | import com.wavesplatform.dex.domain.asset.Asset
5 |
6 | import scala.concurrent.{ExecutionContext, Future}
7 |
8 | class BalancesCache(loader: (Address, Asset) => Future[Long])(implicit executionContext: ExecutionContext)
9 | extends BlockchainCache[(Address, Asset), BigInt](
10 | loader = { case (address, asset) => loader(address, asset) map BigInt.apply },
11 | expiration = None,
12 | invalidationPredicate = BlockchainCache.noCustomInvalidationLogic
13 | ) {
14 |
15 | def batchPut(batch: Map[Address, Map[Asset, Long]]): Unit =
16 | batch.foreach {
17 | case (address, changedBalances) =>
18 | changedBalances.foreach { case (asset, balance) => put(address -> asset, Future.successful(BigInt(balance))) }
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/caches/FeaturesCache.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.caches
2 |
3 | import scala.concurrent.{ExecutionContext, Future}
4 |
5 | class FeaturesCache(loader: Short => Future[Boolean], invalidationPredicate: Boolean => Boolean)(implicit executionContext: ExecutionContext)
6 | extends BlockchainCache[java.lang.Short, java.lang.Boolean](
7 | loader = featureId => loader(featureId) map boolean2Boolean,
8 | expiration = None,
9 | invalidationPredicate = isFeatureActivated => invalidationPredicate(isFeatureActivated)
10 | )
11 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/BroadcastResult.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients
2 |
3 | // TODO Deprecated, remove in >= 2.3.1
4 | sealed trait BroadcastResult extends Product with Serializable
5 |
6 | object BroadcastResult {
7 | case object Added extends BroadcastResult
8 | case object NotAdded extends BroadcastResult
9 | case class Failed(message: String) extends BroadcastResult
10 | }
11 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/CheckedBroadcastResult.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients
2 |
3 | sealed trait CheckedBroadcastResult extends Product with Serializable
4 |
5 | object CheckedBroadcastResult {
6 | case class Unconfirmed(isNew: Boolean) extends CheckedBroadcastResult
7 | case object Confirmed extends CheckedBroadcastResult
8 | case class Failed(message: String, canRetry: Boolean) extends CheckedBroadcastResult
9 | }
10 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/ControlledStream.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients
2 |
3 | import com.wavesplatform.dex.grpc.integration.clients.ControlledStream.SystemEvent
4 | import monix.reactive.Observable
5 |
6 | trait ControlledStream[T] extends AutoCloseable {
7 | val stream: Observable[T]
8 | val systemStream: Observable[SystemEvent]
9 | def stop(): Unit
10 | def close(): Unit
11 | }
12 |
13 | object ControlledStream {
14 | sealed trait SystemEvent extends Product with Serializable
15 |
16 | object SystemEvent {
17 | case object BecameReady extends SystemEvent
18 |
19 | /**
20 | * Manually or by an error
21 | */
22 | case object Stopped extends SystemEvent
23 |
24 | /**
25 | * During shutdown
26 | */
27 | case object Closed extends SystemEvent
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/RunScriptResult.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients
2 |
3 | sealed trait RunScriptResult extends Product with Serializable
4 |
5 | object RunScriptResult {
6 | case class ScriptError(message: String) extends RunScriptResult
7 | case object Denied extends RunScriptResult
8 | case object Allowed extends RunScriptResult
9 | case class UnexpectedResult(rawResult: String) extends RunScriptResult
10 | case class Exception(name: String, message: String) extends RunScriptResult
11 | }
12 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/blockchainupdates/BlockchainUpdatesControlledStream.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.blockchainupdates
2 |
3 | import com.wavesplatform.dex.grpc.integration.clients.ControlledStream
4 | import com.wavesplatform.events.api.grpc.protobuf.SubscribeEvent
5 |
6 | trait BlockchainUpdatesControlledStream extends ControlledStream[SubscribeEvent] {
7 | def startFrom(height: Int): Unit
8 | def requestNext(): Unit
9 | }
10 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/combined/NoOpCombinedStream.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.combined
2 |
3 | import com.wavesplatform.dex.grpc.integration.clients.domain.WavesNodeEvent
4 | import monix.reactive.Observable
5 |
6 | object NoOpCombinedStream extends CombinedStream {
7 | override def startFrom(height: Int): Unit = {}
8 | override def restart(): Unit = {}
9 | override def currentStatus: CombinedStream.Status = CombinedStream.Status.Closing(blockchainUpdates = true, utxEvents = true)
10 | override def updateProcessedHeight(height: Int): Unit = {}
11 | override def currentProcessedHeight: Int = 0
12 | override val stream: Observable[WavesNodeEvent] = Observable.empty
13 | }
14 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/domain/BlockRef.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.domain
2 |
3 | import com.wavesplatform.dex.domain.bytes.ByteStr
4 |
5 | case class BlockRef(height: Int, id: ByteStr) {
6 | override def toString: String = s"Ref(h=$height, ${id.base58.take(5)})"
7 | }
8 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/domain/BlockchainStatus.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.domain
2 |
3 | import com.wavesplatform.dex.meta.getSimpleName
4 |
5 | sealed trait BlockchainStatus extends Product with Serializable {
6 | def name: String = getSimpleName(this)
7 | }
8 |
9 | object BlockchainStatus {
10 |
11 | case class Normal(main: WavesChain) extends BlockchainStatus {
12 | override def toString: String = s"Normal(${main.history.headOption.map(_.ref)})"
13 | }
14 |
15 | case class TransientRollback(fork: WavesFork, utxUpdate: UtxUpdate) extends BlockchainStatus {
16 | override def toString: String = s"TransientRollback(f=$fork, $utxUpdate)"
17 | }
18 |
19 | case class TransientResolving(main: WavesChain, stashChanges: BlockchainBalance, utxUpdate: UtxUpdate) extends BlockchainStatus {
20 | override def toString: String = s"TransientResolving(${main.history.headOption.map(_.ref)}, $utxUpdate)"
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/domain/TransactionWithChanges.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.domain
2 |
3 | import com.google.protobuf.ByteString
4 | import com.wavesplatform.events.protobuf.StateUpdate
5 | import com.wavesplatform.protobuf.transaction.SignedTransaction
6 |
7 | /**
8 | * @param txId Here only for tests to fix "Class to large" compiler issue
9 | */
10 | case class TransactionWithChanges(txId: ByteString, tx: SignedTransaction, changes: StateUpdate)
11 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/domain/WavesBlock.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.domain
2 |
3 | import com.google.protobuf.ByteString
4 | import com.wavesplatform.dex.domain.bytes.ByteStr
5 |
6 | case class WavesBlock(
7 | ref: BlockRef,
8 | reference: ByteStr,
9 | changes: BlockchainBalance,
10 | tpe: WavesBlock.Type,
11 | confirmedTxs: Map[ByteString, TransactionWithChanges]
12 | ) {
13 |
14 | def diffIndex: DiffIndex = DiffIndex(
15 | regular = changes.regular.view.mapValues(_.keySet).toMap,
16 | outgoingLeasing = changes.outgoingLeasing.keySet
17 | )
18 |
19 | override def toString: String = s"B($ref, tpe=$tpe, c=${confirmedTxs.size})"
20 |
21 | }
22 |
23 | object WavesBlock {
24 | sealed trait Type extends Product with Serializable
25 |
26 | object Type {
27 |
28 | case object FullBlock extends Type {
29 | override val toString = "f"
30 | }
31 |
32 | case object MicroBlock extends Type {
33 | override val toString = "m"
34 | }
35 |
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/domain/portfolio/PessimisticTransaction.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.domain.portfolio
2 |
3 | import com.google.protobuf.ByteString
4 |
5 | case class PessimisticTransaction(txId: ByteString, pessimisticPortfolio: AddressAssets)
6 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/domain/portfolio/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.domain
2 |
3 | import com.wavesplatform.dex.domain.account.Address
4 | import com.wavesplatform.dex.domain.asset.Asset
5 |
6 | package object portfolio {
7 | type AddressAssets = Map[Address, Map[Asset, Long]]
8 | }
9 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/clients/matcherext/UtxEventsControlledStream.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.matcherext
2 |
3 | import com.wavesplatform.dex.grpc.integration.clients.ControlledStream
4 | import com.wavesplatform.dex.grpc.integration.services.UtxEvent
5 |
6 | trait UtxEventsControlledStream extends ControlledStream[UtxEvent] {
7 | def start(): Unit
8 | }
9 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/dto/BriefAssetDescription.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.dto
2 |
3 | import com.wavesplatform.dex.domain.asset.Asset
4 |
5 | case class BriefAssetDescription(name: String, decimals: Int, hasScript: Boolean, isNft: Boolean)
6 |
7 | object BriefAssetDescription {
8 |
9 | val wavesDescription: BriefAssetDescription =
10 | BriefAssetDescription(
11 | name = Asset.WavesName,
12 | decimals = 8,
13 | hasScript = false,
14 | isNft = false
15 | )
16 |
17 | val someWavesDescription: Option[BriefAssetDescription] = Option(wavesDescription)
18 | }
19 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/effect/Implicits.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.effect
2 |
3 | import io.netty.util.concurrent.{Future => NettyFuture}
4 |
5 | import scala.concurrent.{CancellationException, Future, Promise}
6 |
7 | object Implicits {
8 |
9 | implicit final class NettyFutureOps[T](val self: NettyFuture[T]) extends AnyVal {
10 |
11 | def asScala: Future[T] = {
12 | val r = Promise[T]()
13 | self.addListener { (future: NettyFuture[T]) =>
14 | if (future.isSuccess) r.success(future.get())
15 | else if (future.isCancelled) r.failure(new CancellationException)
16 | else r.failure(future.cause())
17 | }
18 | r.future
19 | }
20 |
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/exceptions/UnexpectedConnectionException.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.exceptions
2 |
3 | final case class UnexpectedConnectionException(message: String, cause: Throwable) extends Exception(message, cause)
4 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/exceptions/WavesNodeConnectionLostException.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.exceptions
2 |
3 | final case class WavesNodeConnectionLostException(message: String, cause: Throwable) extends Exception(message, cause)
4 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/integration/settings/WavesBlockchainClientSettings.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.settings
2 |
3 | import com.wavesplatform.dex.grpc.integration.clients.combined.CombinedWavesBlockchainClient
4 |
5 | import scala.concurrent.duration.FiniteDuration
6 |
7 | case class WavesBlockchainClientSettings(
8 | grpc: GrpcClientSettings,
9 | blockchainUpdatesGrpc: GrpcClientSettings,
10 | defaultCachesExpiration: FiniteDuration,
11 | balanceStreamBufferSize: Int,
12 | combinedClientSettings: CombinedWavesBlockchainClient.Settings
13 | )
14 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/observers/ClosingObserver.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.observers
2 |
3 | import java.util.concurrent.atomic.AtomicBoolean
4 |
5 | import io.grpc.stub.{ClientCallStreamObserver, ClientResponseObserver}
6 | import io.grpc.{Status, StatusRuntimeException}
7 |
8 | /**
9 | * Use it only for streams those receive the data, but not send it. See comments.
10 | */
11 | trait ClosingObserver[ArgT, EventT] extends ClientResponseObserver[ArgT, EventT] {
12 | private val closed = new AtomicBoolean(false)
13 | private var requestStream: ClientCallStreamObserver[ArgT] = _
14 |
15 | override def beforeStart(requestStream: ClientCallStreamObserver[ArgT]): Unit = {
16 | this.requestStream = requestStream
17 | onReady()
18 | // requestStream.setOnReadyHandler // This works only for streams which send the data, see CallStreamObserver.isReady
19 | }
20 |
21 | def onReady(): Unit = {}
22 |
23 | def isClosed: Boolean = closed.get()
24 |
25 | def close(): Unit = if (closed.compareAndSet(false, true)) requestStream.cancel("Closing", new StatusRuntimeException(Status.CANCELLED))
26 | }
27 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/grpc/observers/IntegrationObserver.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.observers
2 |
3 | import monix.reactive.Observer
4 |
5 | import java.util.concurrent.ConcurrentLinkedQueue
6 | import java.util.concurrent.atomic.AtomicBoolean
7 |
8 | /**
9 | * 1. Pushes events to monix observer
10 | * 2. Do it on demand (requestNext)
11 | */
12 | abstract class IntegrationObserver[ArgT, EventT](dest: Observer[EventT]) extends ClosingObserver[ArgT, EventT] {
13 |
14 | // Invariant: awaitNext == buffer.isEmpty
15 |
16 | private[observers] val awaitNext = new AtomicBoolean(true)
17 | private[observers] val buffer = new ConcurrentLinkedQueue[EventT]()
18 |
19 | override def onNext(value: EventT): Unit = {
20 | buffer.add(value)
21 | tryPublish()
22 | }
23 |
24 | def requestNext(): Unit = if (awaitNext.compareAndSet(false, true)) tryPublish()
25 |
26 | private def tryPublish(): Unit = if (awaitNext.compareAndSet(true, false)) Option(buffer.poll()) match {
27 | case Some(x) => dest.onNext(x)
28 | case None => awaitNext.set(true)
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/meta/package.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | package object meta {
4 | def getSimpleName(x: Any): String = x.getClass.getName.replaceAll(".*?(\\w+)\\$?$", "$1") // TODO DEX-994
5 | }
6 |
--------------------------------------------------------------------------------
/waves-integration/src/main/scala/com/wavesplatform/dex/remote/Delay.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.remote
2 |
3 | import java.util.concurrent.ThreadLocalRandom
4 | import scala.concurrent.duration._
5 |
6 | object Delay {
7 |
8 | // https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
9 | def fullJitter(base: FiniteDuration, attempt: Int, cap: FiniteDuration): FiniteDuration =
10 | if (attempt == 0) 0.nanos
11 | else {
12 | val max = (base.toNanos * math.pow(2.0, attempt)).toLong
13 | ThreadLocalRandom.current()
14 | .nextLong(
15 | 0L,
16 | math.min(
17 | cap.toNanos,
18 | if (max > 0) max else cap.toNanos
19 | )
20 | )
21 | .nanos
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/waves-integration/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/waves-integration/src/test/resources/logback-verbose.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %date{ISO8601,UTC} %-5level [%.15thread] %logger{26} - %msg%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/waves-integration/src/test/scala/com/wavesplatform/dex/NoShrink.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex
2 |
3 | import org.scalacheck.Shrink
4 |
5 | trait NoShrink {
6 | // NoShrink
7 | implicit def noShrink[A]: Shrink[A] = Shrink.withLazyList(_ => LazyList.empty) // TODO DEX-994
8 | }
9 |
--------------------------------------------------------------------------------
/waves-integration/src/test/scala/com/wavesplatform/dex/grpc/integration/clients/domain/portfolio/DefaultPessimisticPortfoliosTestSuite.scala:
--------------------------------------------------------------------------------
1 | package com.wavesplatform.dex.grpc.integration.clients.domain.portfolio
2 |
3 | import cats.instances.list._
4 | import cats.instances.long._
5 | import cats.instances.map._
6 | import cats.syntax.foldable._
7 |
8 | class DefaultPessimisticPortfoliosTestSuite extends PessimisticPortfoliosTestSuiteBase {
9 |
10 | "DefaultPessimisticPortfolios" - defaultBehaviorTests()
11 |
12 | override def mkPessimisticPortfolios(initialTxs: List[PessimisticTransaction]) = new DefaultPessimisticPortfolios(
13 | initialTxs.foldMap(_.pessimisticPortfolio),
14 | initialTxs.map(tx => tx.txId -> tx.pessimisticPortfolio).toMap
15 | )
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/wavesNode.sbt:
--------------------------------------------------------------------------------
1 | import WavesNodeArtifactsPlugin.autoImport._
2 |
3 | Global / wavesNodeVersion := "1.4.13"
4 |
--------------------------------------------------------------------------------