├── .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 | ![Dependencies](./images/m-dep.svg) -------------------------------------------------------------------------------- /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 | ![How Matcher places orders](./images/places.svg) 10 | 11 | ### Cancels 12 | 13 | Works similarly: 14 | ![How Matcher cancels orders](./images/cancels.svg) 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 | --------------------------------------------------------------------------------