diff --git a/build.sbt b/build.sbt index 1f2b39060c5..4cb715af15d 100644 --- a/build.sbt +++ b/build.sbt @@ -7,7 +7,7 @@ */ import sbt.Keys._ -import sbt._ +import sbt.{Project, _} import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} val langPublishSettings = Seq( @@ -35,7 +35,7 @@ lazy val lang = PB.protoSources := Seq(PB.externalIncludePath.value, baseDirectory.value.getParentFile / "shared" / "src" / "main" / "protobuf"), includeFilter in PB.generate := { (f: File) => (** / "DAppMeta.proto").matches(f.toPath) || - (** / "waves" / "*.proto").matches(f.toPath) + (** / "waves" / "*.proto").matches(f.toPath) }, PB.deleteTargetDirectory := false ) @@ -112,7 +112,8 @@ inScope(Global)( "-Ywarn-unused:-implicits", "-Xlint", "-opt:l:inline", - "-opt-inline-from:**" + "-opt-inline-from:**", + "-Wconf:cat=deprecation&site=com.wavesplatform.api.grpc.*:s" // Ignore gRPC warnings ), crossPaths := false, scalafmtOnCompile := false, @@ -171,10 +172,13 @@ checkPRRaw := Def .value def checkPR: Command = Command.command("checkPR") { state => - val updatedState = Project + val newState = Project .extract(state) - .appendWithoutSession(Seq(Global / scalacOptions ++= Seq("-Xfatal-warnings")), state) - Project.extract(updatedState).runTask(checkPRRaw, updatedState) + .appendWithoutSession( + Seq(Global / scalacOptions ++= Seq("-Xfatal-warnings")), + state + ) + Project.extract(newState).runTask(checkPRRaw, newState) state } diff --git a/grpc-server/src/main/scala/com/wavesplatform/api/grpc/TransactionsApiGrpcImpl.scala b/grpc-server/src/main/scala/com/wavesplatform/api/grpc/TransactionsApiGrpcImpl.scala index 116e8ef7584..ad7780b77e2 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/api/grpc/TransactionsApiGrpcImpl.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/api/grpc/TransactionsApiGrpcImpl.scala @@ -2,9 +2,11 @@ package com.wavesplatform.api.grpc import com.wavesplatform.account.AddressScheme import com.wavesplatform.api.common.CommonTransactionsApi +import com.wavesplatform.api.common.CommonTransactionsApi.TransactionMeta import com.wavesplatform.protobuf._ import com.wavesplatform.protobuf.transaction._ import com.wavesplatform.protobuf.utils.PBImplicitConversions.PBRecipientImplicitConversionOps +import com.wavesplatform.state.{InvokeScriptResult => VISR} import com.wavesplatform.transaction.Authorized import io.grpc.stub.StreamObserver import io.grpc.{Status, StatusRuntimeException} @@ -18,41 +20,43 @@ class TransactionsApiGrpcImpl(commonApi: CommonTransactionsApi)(implicit sc: Sch override def getTransactions(request: TransactionsRequest, responseObserver: StreamObserver[TransactionResponse]): Unit = responseObserver.interceptErrors { val transactionIds = request.transactionIds.map(_.toByteStr) - val stream = request.recipient match { + val stream: Observable[TransactionMeta] = request.recipient match { + // By recipient case Some(subject) => + val recipientAddrOrAlias = subject + .toAddressOrAlias(AddressScheme.current.chainId) + .fold(e => throw new IllegalArgumentException(e.toString), identity) + + val maybeSender = Option(request.sender) + .collect { case s if !s.isEmpty => s.toAddress } + commonApi.transactionsByAddress( - subject - .toAddressOrAlias(AddressScheme.current.chainId) - .fold(e => throw new IllegalArgumentException(e.toString), identity), - Option(request.sender).collect { case s if !s.isEmpty => s.toAddress }, + recipientAddrOrAlias, + maybeSender, Set.empty, None ) + + // By sender + case None if !request.sender.isEmpty => + val senderAddress = request.sender.toAddress + commonApi.transactionsByAddress( + senderAddress, + Some(senderAddress), + Set.empty, + None + ) + + // By ids case None => - if (request.sender.isEmpty) { - Observable.fromIterable(transactionIds.flatMap(commonApi.transactionById)).map { - case (h, e, s) => (h, e.fold(identity, _._1), s) - } - } else { - val senderAddress = request.sender.toAddress - commonApi.transactionsByAddress( - senderAddress, - Some(senderAddress), - Set.empty, - None - ) - } + Observable.fromIterable(transactionIds.flatMap(commonApi.transactionById)) } val transactionIdSet = transactionIds.toSet - responseObserver.completeWith( stream - .filter { case (_, t, _) => transactionIdSet.isEmpty || transactionIdSet(t.id()) } - .map { - case (h, tx, false) => TransactionResponse(tx.id().toByteString, h, Some(tx.toPB), ApplicationStatus.SCRIPT_EXECUTION_FAILED) - case (h, tx, _) => TransactionResponse(tx.id().toByteString, h, Some(tx.toPB), ApplicationStatus.SUCCEEDED) - } + .filter { case TransactionMeta(_, tx, _) => transactionIdSet.isEmpty || transactionIdSet(tx.id()) } + .map(TransactionsApiGrpcImpl.toTransactionResponse) ) } @@ -74,13 +78,11 @@ class TransactionsApiGrpcImpl(commonApi: CommonTransactionsApi)(implicit sc: Sch override def getStateChanges(request: TransactionsRequest, responseObserver: StreamObserver[InvokeScriptResultResponse]): Unit = responseObserver.interceptErrors { - import com.wavesplatform.state.{InvokeScriptResult => VISR} - val result = Observable(request.transactionIds: _*) .flatMap(txId => Observable.fromIterable(commonApi.transactionById(txId.toByteStr))) .collect { - case (_, Right((tx, Some(isr))), _) => - InvokeScriptResultResponse.of(Some(PBTransactions.protobuf(tx)), Some(VISR.toPB(isr))) + case CommonTransactionsApi.TransactionMeta.Invoke(_, transaction, _, invokeScriptResult) => + InvokeScriptResultResponse.of(Some(PBTransactions.protobuf(transaction)), invokeScriptResult.map(VISR.toPB)) } responseObserver.completeWith(result) @@ -93,9 +95,9 @@ class TransactionsApiGrpcImpl(commonApi: CommonTransactionsApi)(implicit sc: Sch .unconfirmedTransactionById(txId.toByteStr) .map(_ => TransactionStatus(txId, TransactionStatus.Status.UNCONFIRMED)) .orElse { - commonApi.transactionById(txId.toByteStr).map { - case (h, _, false) => TransactionStatus(txId, TransactionStatus.Status.CONFIRMED, h, ApplicationStatus.SCRIPT_EXECUTION_FAILED) - case (h, _, _) => TransactionStatus(txId, TransactionStatus.Status.CONFIRMED, h, ApplicationStatus.SUCCEEDED) + commonApi.transactionById(txId.toByteStr).map { m => + val status = if (m.succeeded) ApplicationStatus.SUCCEEDED else ApplicationStatus.SCRIPT_EXECUTION_FAILED + TransactionStatus(txId, TransactionStatus.Status.CONFIRMED, m.height, status) } } .getOrElse(TransactionStatus(txId, TransactionStatus.Status.NOT_EXISTS)) @@ -115,3 +117,17 @@ class TransactionsApiGrpcImpl(commonApi: CommonTransactionsApi)(implicit sc: Sch _ <- result.resultE.toFuture } yield tx).wrapErrors } + +private object TransactionsApiGrpcImpl { + def toTransactionResponse(meta: TransactionMeta): TransactionResponse = { + val TransactionMeta(height, tx, succeeded) = meta + val transactionId = tx.id().toByteString + val status = if (succeeded) ApplicationStatus.SUCCEEDED else ApplicationStatus.SCRIPT_EXECUTION_FAILED + val invokeScriptResult = meta match { + case TransactionMeta.Invoke(_, _, _, r) => r.map(VISR.toPB) + case _ => None + } + + TransactionResponse(transactionId, height, Some(tx.toPB), status, invokeScriptResult) + } +} diff --git a/node-it/src/test/scala/com/wavesplatform/it/ReportingTestName.scala b/node-it/src/test/scala/com/wavesplatform/it/ReportingTestName.scala index 13aa06b7710..83139fa0014 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/ReportingTestName.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/ReportingTestName.scala @@ -1,6 +1,6 @@ package com.wavesplatform.it -import com.wavesplatform.http.DebugMessage +import com.wavesplatform.api.http.DebugMessage import com.wavesplatform.it.api.AsyncHttpApi._ import com.wavesplatform.utils.ScorexLogging import org.scalatest.{Args, Status, Suite, SuiteMixin} diff --git a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala index eb92ccdce55..66cefd2e6de 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala @@ -52,14 +52,14 @@ object AsyncGrpcApi { def stateChanges( request: TransactionsRequest ): Future[Seq[(com.wavesplatform.transaction.Transaction, StateChangesDetails)]] = { - val (obs, result) = createCallObserver[InvokeScriptResultResponse] - transactions.getStateChanges(request, obs) + val (obs, result) = createCallObserver[TransactionResponse] + transactions.getTransactions(request, obs) result.runToFuture.map { r => import com.wavesplatform.state.{InvokeScriptResult => VISR} r.map { r => val tx = PBTransactions.vanillaUnsafe(r.getTransaction) - assert(r.getResult.transfers.forall(_.address.size() == 20)) - val result = Json.toJson(VISR.fromPB(r.getResult)).as[StateChangesDetails] + assert(r.getInvokeScriptResult.transfers.forall(_.address.size() == 20)) + val result = Json.toJson(VISR.fromPB(r.getInvokeScriptResult)).as[StateChangesDetails] (tx, result) } } diff --git a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncHttpApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncHttpApi.scala index ade2927ca07..58e465d444e 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncHttpApi.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncHttpApi.scala @@ -7,14 +7,13 @@ import java.util.{NoSuchElementException, UUID} import com.google.protobuf.ByteString import com.wavesplatform.account.{AddressOrAlias, AddressScheme, KeyPair} -import com.wavesplatform.api.http.ConnectReq +import com.wavesplatform.api.http.DebugMessage._ import com.wavesplatform.api.http.RewardApiRoute.RewardStatus import com.wavesplatform.api.http.requests.{IssueRequest, TransferRequest} +import com.wavesplatform.api.http.{ConnectReq, DebugMessage, RollbackParams, `X-Api-Key`} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, Base64, EitherExt2} import com.wavesplatform.features.api.ActivationStatus -import com.wavesplatform.http.DebugMessage._ -import com.wavesplatform.http.{DebugMessage, RollbackParams, `X-Api-Key`} import com.wavesplatform.it.Node import com.wavesplatform.it.util.GlobalTimer.{instance => timer} import com.wavesplatform.it.util._ diff --git a/node-it/src/test/scala/com/wavesplatform/it/api/SyncHttpApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/SyncHttpApi.scala index f3ea11917c2..5af19c369b5 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/api/SyncHttpApi.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/api/SyncHttpApi.scala @@ -5,13 +5,12 @@ import java.net.InetSocketAddress import akka.http.scaladsl.model.StatusCodes.BadRequest import akka.http.scaladsl.model.{StatusCode, StatusCodes} import com.wavesplatform.account.{AddressOrAlias, KeyPair} -import com.wavesplatform.api.http.ApiError import com.wavesplatform.api.http.RewardApiRoute.RewardStatus import com.wavesplatform.api.http.requests.IssueRequest +import com.wavesplatform.api.http.{ApiError, DebugMessage} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.features.api.{ActivationStatus, FeatureActivationStatus} -import com.wavesplatform.http.DebugMessage import com.wavesplatform.it.Node import com.wavesplatform.it.sync._ import com.wavesplatform.lang.v1.compiler.Terms diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/PoSSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/PoSSuite.scala index 0b0f0b89109..5d65e69f5b1 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/PoSSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/PoSSuite.scala @@ -2,13 +2,13 @@ package com.wavesplatform.it.sync import com.typesafe.config.Config import com.wavesplatform.account.{KeyPair, PublicKey} +import com.wavesplatform.api.http.DebugMessage import com.wavesplatform.block.Block import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.consensus.FairPoSCalculator import com.wavesplatform.consensus.nxt.NxtLikeConsensusBlockData import com.wavesplatform.crypto -import com.wavesplatform.http.DebugMessage import com.wavesplatform.it.api.AsyncNetworkApi.NodeAsyncNetworkApi import com.wavesplatform.it.api.SyncHttpApi._ import com.wavesplatform.it.transactions.NodesFromDocker diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeScriptTransactionStateChangesSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeScriptTransactionStateChangesSuite.scala index 455d87cb390..846309fdc0c 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeScriptTransactionStateChangesSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeScriptTransactionStateChangesSuite.scala @@ -33,70 +33,6 @@ class InvokeScriptTransactionStateChangesSuite extends BaseTransactionSuite with private lazy val recipientAddress: String = recipient.toAddress.toString private lazy val callerAddress: String = caller.toAddress.toString - test("prepare") { - simpleAsset = sender.issue(contract, "simple", "", 9000, 0).id - assetSponsoredByDApp = sender.issue(contract, "DApp asset", "", 9000, 0).id - assetSponsoredByRecipient = sender.issue(recipient, "Recipient asset", "", 9000, 0, waitForTx = true).id - sender.massTransfer(contract, List(Transfer(callerAddress, 3000), Transfer(recipientAddress, 3000)), 0.01.waves, assetId = Some(simpleAsset)) - sender.massTransfer( - contract, - List(Transfer(callerAddress, 3000), Transfer(recipientAddress, 3000)), - 0.01.waves, - assetId = Some(assetSponsoredByDApp) - ) - sender.massTransfer( - recipient, - List(Transfer(callerAddress, 3000), Transfer(contractAddress, 3000)), - 0.01.waves, - assetId = Some(assetSponsoredByRecipient) - ) - sender.sponsorAsset(contract, assetSponsoredByDApp, 1, fee = sponsorReducedFee + smartFee) - sender.sponsorAsset(recipient, assetSponsoredByRecipient, 5, fee = sponsorReducedFee + smartFee) - - val script = ScriptCompiler - .compile( - """ - |{-# STDLIB_VERSION 3 #-} - |{-# CONTENT_TYPE DAPP #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - |@Callable(i) - |func write(value: Int) = { - | WriteSet([DataEntry("result", value)]) - |} - | - |@Callable(i) - |func sendAsset(recipient: String, amount: Int, assetId: String) = { - | TransferSet([ScriptTransfer(Address(recipient.fromBase58String()), amount, assetId.fromBase58String())]) - |} - | - |@Callable(i) - |func writeAndSendWaves(value: Int, recipient: String, amount: Int) = { - | ScriptResult( - | WriteSet([DataEntry("result", value)]), - | TransferSet([ScriptTransfer(Address(recipient.fromBase58String()), amount, unit)]) - | ) - |} - """.stripMargin, - ScriptEstimatorV2 - ) - .explicitGet() - ._1 - .bytes() - .base64 - sender.setScript(contract, Some(script), setScriptFee, waitForTx = true) - - initCallerTxs = sender.transactionsByAddress(callerAddress, 100).length - initDAppTxs = sender.transactionsByAddress(contractAddress, 100).length - initRecipientTxs = sender.transactionsByAddress(recipientAddress, 100).length - initCallerStateChanges = sender.debugStateChangesByAddress(callerAddress, 100).length - initDAppStateChanges = sender.debugStateChangesByAddress(contractAddress, 100).length - initRecipientStateChanges = sender.debugStateChangesByAddress(recipientAddress, 100).length - initCallerTxs shouldBe initCallerStateChanges - initDAppTxs shouldBe initDAppStateChanges - initRecipientTxs shouldBe initRecipientStateChanges - } - test("write") { val data = 10 @@ -367,6 +303,74 @@ class InvokeScriptTransactionStateChangesSuite extends BaseTransactionSuite with } + protected override def beforeAll(): Unit = { + super.beforeAll() + + simpleAsset = sender.issue(contract, "simple", "", 9000, 0).id + assetSponsoredByDApp = sender.issue(contract, "DApp asset", "", 9000, 0).id + assetSponsoredByRecipient = sender.issue(recipient, "Recipient asset", "", 9000, 0, waitForTx = true).id + sender.massTransfer(contract, List(Transfer(callerAddress, 3000), Transfer(recipientAddress, 3000)), 0.01.waves, assetId = Some(simpleAsset)) + sender.massTransfer( + contract, + List(Transfer(callerAddress, 3000), Transfer(recipientAddress, 3000)), + 0.01.waves, + assetId = Some(assetSponsoredByDApp) + ) + sender.massTransfer( + recipient, + List(Transfer(callerAddress, 3000), Transfer(contractAddress, 3000)), + 0.01.waves, + assetId = Some(assetSponsoredByRecipient) + ) + sender.sponsorAsset(contract, assetSponsoredByDApp, 1, fee = sponsorReducedFee + smartFee) + sender.sponsorAsset(recipient, assetSponsoredByRecipient, 5, fee = sponsorReducedFee + smartFee) + + val script = ScriptCompiler + .compile( + """ + |{-# STDLIB_VERSION 3 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |@Callable(i) + |func write(value: Int) = { + | WriteSet([DataEntry("result", value)]) + |} + | + |@Callable(i) + |func sendAsset(recipient: String, amount: Int, assetId: String) = { + | TransferSet([ScriptTransfer(Address(recipient.fromBase58String()), amount, assetId.fromBase58String())]) + |} + | + |@Callable(i) + |func writeAndSendWaves(value: Int, recipient: String, amount: Int) = { + | ScriptResult( + | WriteSet([DataEntry("result", value)]), + | TransferSet([ScriptTransfer(Address(recipient.fromBase58String()), amount, unit)]) + | ) + |} + """.stripMargin, + ScriptEstimatorV2 + ) + .explicitGet() + ._1 + .bytes() + .base64 + sender.setScript(contract, Some(script), setScriptFee, waitForTx = true) + nodes.waitForEmptyUtx() + nodes.waitForHeightArise() + + initCallerTxs = sender.transactionsByAddress(callerAddress, 100).length + initDAppTxs = sender.transactionsByAddress(contractAddress, 100).length + initRecipientTxs = sender.transactionsByAddress(recipientAddress, 100).length + initCallerStateChanges = sender.debugStateChangesByAddress(callerAddress, 100).length + initDAppStateChanges = sender.debugStateChangesByAddress(contractAddress, 100).length + initRecipientStateChanges = sender.debugStateChangesByAddress(recipientAddress, 100).length + initCallerTxs shouldBe initCallerStateChanges + initDAppTxs shouldBe initDAppStateChanges + initRecipientTxs shouldBe initRecipientStateChanges + } + def txInfoShouldBeEqual(info: TransactionInfo, stateChanges: DebugStateChanges)(implicit pos: Position): Unit = { info._type shouldBe stateChanges._type info.id shouldBe stateChanges.id diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuite.scala index 410b0cadf34..f83af9634b5 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuite.scala @@ -3,9 +3,9 @@ package com.wavesplatform.it.sync.transactions import com.google.common.primitives.Longs import com.typesafe.config.Config import com.wavesplatform.api.http.ApiError.{ScriptExecutionError, TransactionNotAllowedByAccountScript, TransactionNotAllowedByAssetScript} +import com.wavesplatform.api.http.DebugMessage import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.http.DebugMessage import com.wavesplatform.it.api.SyncHttpApi._ import com.wavesplatform.it.api.{DebugStateChanges, TransactionStatus} import com.wavesplatform.it.sync._ diff --git a/node/src/main/resources/swagger-ui/swagger.json b/node/src/main/resources/swagger-ui/swagger.json index aad0dee2fcf..f2c77357515 100644 --- a/node/src/main/resources/swagger-ui/swagger.json +++ b/node/src/main/resources/swagger-ui/swagger.json @@ -2024,6 +2024,48 @@ } } }, + "/transactions/info": { + "get": { + "tags": [ + "transactions" + ], + "summary": "Transaction info", + "description": "Get a transactions by its IDs", + "operationId": "info_multiple", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Transaction IDs base58 encoded", + "required": true, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/parameters/txId" + }, + "minItems": 1 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Transaction" + } + } + } + } + } + } + } + }, "/transactions/sign": { "post": { "tags": [ diff --git a/node/src/main/scala/com/wavesplatform/Application.scala b/node/src/main/scala/com/wavesplatform/Application.scala index 7047e14deab..1dbc3b21a80 100644 --- a/node/src/main/scala/com/wavesplatform/Application.scala +++ b/node/src/main/scala/com/wavesplatform/Application.scala @@ -28,7 +28,6 @@ import com.wavesplatform.extensions.{Context, Extension} import com.wavesplatform.features.EstimatorProvider._ import com.wavesplatform.features.api.ActivationApiRoute import com.wavesplatform.history.{History, StorageFactory} -import com.wavesplatform.http.{DebugApiRoute, NodeApiRoute} import com.wavesplatform.lang.ValidationError import com.wavesplatform.metrics.Metrics import com.wavesplatform.mining.{Miner, MinerDebugInfo, MinerImpl} @@ -370,7 +369,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con RewardApiRoute(blockchainUpdater) ) - val httpService = CompositeHttpService(apiRoutes, settings.restAPISettings) + val httpService = CompositeHttpService(apiRoutes, settings.restAPISettings) val httpFuture = Http().bindAndHandle(httpService.loggingCompositeRoute, settings.restAPISettings.bindAddress, settings.restAPISettings.port) serverBinding = Await.result(httpFuture, 20.seconds) serverBinding.whenTerminated.foreach(_ => httpService.scheduler.shutdown()) diff --git a/node/src/main/scala/com/wavesplatform/api/common/AddressTransactions.scala b/node/src/main/scala/com/wavesplatform/api/common/AddressTransactions.scala index 25848a2432a..65124fb60c7 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/AddressTransactions.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/AddressTransactions.scala @@ -1,9 +1,9 @@ package com.wavesplatform.api.common import com.wavesplatform.account.Address +import com.wavesplatform.api.common.CommonTransactionsApi.TransactionMeta import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.database.{protobuf => pb} -import com.wavesplatform.database.{DBExt, DBResource, Keys} +import com.wavesplatform.database.{DBExt, DBResource, Keys, protobuf => pb} import com.wavesplatform.state.{Diff, Height, InvokeScriptResult, NewTransactionInfo, TransactionId, TxNum} import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.{Authorized, GenesisTransaction, Transaction} @@ -21,28 +21,19 @@ trait AddressTransactions { sender: Option[Address], types: Set[Transaction.Type], fromId: Option[ByteStr] - ): Observable[(Height, Transaction, Boolean)] = - Observable.fromIterator(Task(allAddressTransactions(db, maybeDiff, subject, sender, types, fromId))) - - def invokeScriptResults( - db: DB, - maybeDiff: Option[(Height, Diff)], - subject: Address, - sender: Option[Address], - types: Set[Transaction.Type], - fromId: Option[ByteStr] ): Observable[TransactionMeta] = Observable .fromIterator(Task(allAddressTransactions(db, maybeDiff, subject, sender, types, fromId).map { - case (h, ist: InvokeScriptTransaction, status) => - (h, Right(ist -> maybeDiff.flatMap(_._2.scriptResults.get(ist.id())).orElse(loadInvokeScriptResult(db, ist.id()))), status) - case (h, tx, status) => (h, Left(tx), status) + case (height, transaction, succeeded) => + TransactionMeta.create(height, transaction, succeeded) { ist => + maybeDiff + .flatMap { case (_, diff) => diff.scriptResults.get(ist.id()) } + .orElse(loadInvokeScriptResult(db, ist.id())) + } })) } object AddressTransactions { - type TransactionMeta = (Height, Either[Transaction, (InvokeScriptTransaction, Option[InvokeScriptResult])], Boolean) - private def loadTransaction(db: DB, height: Height, txNum: TxNum, sender: Option[Address]): Option[(Height, Transaction, Boolean)] = db.get(Keys.transactionAt(height, txNum)) match { case Some((tx: Authorized, status)) if sender.forall(_ == tx.sender.toAddress) => Some((height, tx, status)) @@ -53,8 +44,8 @@ object AddressTransactions { private def loadInvokeScriptResult(resource: DBResource, txId: ByteStr): Option[InvokeScriptResult] = for { pb.TransactionMeta(h, txNum, InvokeScriptTransaction.typeId, _) <- resource.get(Keys.transactionMetaById(TransactionId(txId))) - r <- resource.get(Keys.invokeScriptResult(h, TxNum(txNum.toShort))) - } yield r + scriptResult <- resource.get(Keys.invokeScriptResult(h, TxNum(txNum.toShort))) + } yield scriptResult def loadInvokeScriptResult(db: DB, txId: ByteStr): Option[InvokeScriptResult] = db.withResource(r => loadInvokeScriptResult(r, txId)) @@ -111,10 +102,10 @@ object AddressTransactions { fromId: Option[ByteStr] ): Iterator[(Height, Transaction, Boolean)] = (for { - (h, diff) <- maybeDiff.toSeq - NewTransactionInfo(tx, addresses, status) <- diff.transactions.values.toSeq.reverse + (height, diff) <- maybeDiff.toSeq + NewTransactionInfo(tx, addresses, succeeded) <- diff.transactions.values.toSeq.reverse if addresses(subject) - } yield (h, tx, status)) + } yield (height, tx, succeeded)) .dropWhile { case (_, tx, _) => fromId.isDefined && !fromId.contains(tx.id()) } .dropWhile { case (_, tx, _) => fromId.contains(tx.id()) } .filter { case (_, tx, _) => types.isEmpty || types.contains(tx.typeId) } diff --git a/node/src/main/scala/com/wavesplatform/api/common/CommonTransactionsApi.scala b/node/src/main/scala/com/wavesplatform/api/common/CommonTransactionsApi.scala index b68bb43d1d2..0eabf44aa19 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/CommonTransactionsApi.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/CommonTransactionsApi.scala @@ -25,13 +25,6 @@ trait CommonTransactionsApi { def aliasesOfAddress(address: Address): Observable[(Height, CreateAliasTransaction)] - def transactionsByAddress( - subject: AddressOrAlias, - sender: Option[Address], - transactionTypes: Set[Byte], - fromId: Option[ByteStr] = None - ): Observable[(Height, Transaction, Boolean)] - def transactionById(txId: ByteStr): Option[TransactionMeta] def unconfirmedTransactions: Seq[Transaction] @@ -42,7 +35,7 @@ trait CommonTransactionsApi { def broadcastTransaction(tx: Transaction): Future[TracedResult[ValidationError, Boolean]] - def invokeScriptResults( + def transactionsByAddress( subject: AddressOrAlias, sender: Option[Address], transactionTypes: Set[Byte], @@ -53,7 +46,32 @@ trait CommonTransactionsApi { } object CommonTransactionsApi { - type TransactionMeta = (Height, Either[Transaction, (InvokeScriptTransaction, Option[InvokeScriptResult])], Boolean) + sealed trait TransactionMeta { + def height: Height + def transaction: Transaction + def succeeded: Boolean + } + + object TransactionMeta { + final case class Default(height: Height, transaction: Transaction, succeeded: Boolean) extends TransactionMeta + + final case class Invoke(height: Height, transaction: InvokeScriptTransaction, succeeded: Boolean, invokeScriptResult: Option[InvokeScriptResult]) + extends TransactionMeta + + def unapply(tm: TransactionMeta): Option[(Height, Transaction, Boolean)] = + Some((tm.height, tm.transaction, tm.succeeded)) + + def create(height: Height, transaction: Transaction, succeeded: Boolean)( + result: InvokeScriptTransaction => Option[InvokeScriptResult] + ): TransactionMeta = + transaction match { + case ist: InvokeScriptTransaction => + Invoke(height, ist, succeeded, result(ist)) + + case _ => + Default(height, transaction, succeeded) + } + } def apply( maybeDiff: => Option[(Height, Diff)], @@ -73,22 +91,18 @@ object CommonTransactionsApi { sender: Option[Address], transactionTypes: Set[Byte], fromId: Option[ByteStr] = None - ): Observable[(Height, Transaction, Boolean)] = resolve(subject).fold(Observable.empty[(Height, Transaction, Boolean)]) { subjectAddress => + ): Observable[TransactionMeta] = resolve(subject).fold(Observable.empty[TransactionMeta]) { subjectAddress => common.addressTransactions(db, maybeDiff, subjectAddress, sender, transactionTypes, fromId) } override def transactionById(transactionId: ByteStr): Option[TransactionMeta] = blockchain.transactionInfo(transactionId).map { - case (height, ist: InvokeScriptTransaction, status) => - ( - Height(height), - Right( - ist -> maybeDiff.flatMap(_._2.scriptResults.get(transactionId)).orElse(AddressTransactions.loadInvokeScriptResult(db, transactionId)) - ), - status - ) - case (height, tx, status) => (Height(height), Left(tx), status) - + case (height, transaction, succeeded) => + TransactionMeta.create(Height(height), transaction, succeeded) { _ => + maybeDiff + .flatMap { case (_, diff) => diff.scriptResults.get(transactionId) } + .orElse(AddressTransactions.loadInvokeScriptResult(db, transactionId)) + } } override def unconfirmedTransactions: Seq[Transaction] = utx.all @@ -106,16 +120,6 @@ object CommonTransactionsApi { override def broadcastTransaction(tx: Transaction): Future[TracedResult[ValidationError, Boolean]] = publishTransaction(tx) - override def invokeScriptResults( - subject: AddressOrAlias, - sender: Option[Address], - transactionTypes: Set[Byte], - fromId: Option[ByteStr] = None - ): Observable[TransactionMeta] = - resolve(subject).fold(Observable.empty[TransactionMeta]) { subjectAddress => - common.invokeScriptResults(db, maybeDiff, subjectAddress, sender, transactionTypes, fromId) - } - override def transactionProofs(transactionIds: List[ByteStr]): List[TransactionProof] = for { transactionId <- transactionIds diff --git a/node/src/main/scala/com/wavesplatform/api/common/package.scala b/node/src/main/scala/com/wavesplatform/api/common/package.scala index 73da04e798a..85e7bc0f688 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/package.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/package.scala @@ -1,5 +1,6 @@ package com.wavesplatform.api import com.wavesplatform.account.Address +import com.wavesplatform.api.common.CommonTransactionsApi.TransactionMeta import com.wavesplatform.common.state.ByteStr import com.wavesplatform.database.{DBExt, Keys} import com.wavesplatform.state.{Diff, Height} @@ -12,7 +13,9 @@ package object common extends BalanceDistribution with AddressTransactions { def aliasesOfAddress(db: DB, maybeDiff: => Option[(Height, Diff)], address: Address): Observable[(Height, CreateAliasTransaction)] = { val disabledAliases = db.get(Keys.disabledAliases) addressTransactions(db, maybeDiff, address, Some(address), Set(CreateAliasTransaction.typeId), None) - .collect { case (height, cat: CreateAliasTransaction, true) if disabledAliases.isEmpty || !disabledAliases(cat.alias) => height -> cat } + .collect { + case TransactionMeta(height, cat: CreateAliasTransaction, true) if disabledAliases.isEmpty || !disabledAliases(cat.alias) => height -> cat + } } def activeLeases( @@ -22,5 +25,5 @@ package object common extends BalanceDistribution with AddressTransactions { leaseIsActive: ByteStr => Boolean ): Observable[(Height, LeaseTransaction)] = addressTransactions(db, maybeDiff, address, None, Set(LeaseTransaction.typeId), None) - .collect { case (h, lt: LeaseTransaction, true) if leaseIsActive(lt.id()) => h -> lt } + .collect { case TransactionMeta(h, lt: LeaseTransaction, true) if leaseIsActive(lt.id()) => h -> lt } } diff --git a/node/src/main/scala/com/wavesplatform/api/http/AddressApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/AddressApiRoute.scala index 5e16c345bd6..16b634fbb9f 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/AddressApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/AddressApiRoute.scala @@ -12,7 +12,6 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, Base64} import com.wavesplatform.crypto import com.wavesplatform.features.EstimatorProvider._ -import com.wavesplatform.http.BroadcastRoute import com.wavesplatform.lang.contract.DApp import com.wavesplatform.lang.contract.meta.FunctionSignatures import com.wavesplatform.lang.script.ContractScript.ContractScriptImpl diff --git a/node/src/main/scala/com/wavesplatform/http/ApiMarshallers.scala b/node/src/main/scala/com/wavesplatform/api/http/ApiMarshallers.scala similarity index 98% rename from node/src/main/scala/com/wavesplatform/http/ApiMarshallers.scala rename to node/src/main/scala/com/wavesplatform/api/http/ApiMarshallers.scala index 07702009019..48accce9a9b 100644 --- a/node/src/main/scala/com/wavesplatform/http/ApiMarshallers.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/ApiMarshallers.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import akka.NotUsed import akka.http.scaladsl.common.EntityStreamingSupport @@ -9,7 +9,6 @@ import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, PredefinedFromE import akka.http.scaladsl.util.FastFuture import akka.stream.scaladsl.{Flow, Source} import akka.util.ByteString -import com.wavesplatform.api.http.ApiError import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.contract.meta.FunctionSignatures diff --git a/node/src/main/scala/com/wavesplatform/api/http/ApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/ApiRoute.scala index 31971059bc7..de8e8d9a78d 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/ApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/ApiRoute.scala @@ -4,7 +4,6 @@ import akka.http.scaladsl.server._ import com.wavesplatform.api.http.ApiError.ApiKeyNotValid import com.wavesplatform.common.utils.Base58 import com.wavesplatform.crypto -import com.wavesplatform.http.{ApiMarshallers, `X-Api-Key`, api_key} import com.wavesplatform.settings.RestAPISettings import com.wavesplatform.utils._ @@ -23,7 +22,7 @@ trait AuthRoute { this: ApiRoute => case _ => optionalHeaderValueByType[api_key](()).flatMap { case Some(k) if java.util.Arrays.equals(crypto.secureHash(k.value.utf8Bytes), hashFromSettings) => pass - case _ => complete(ApiKeyNotValid) + case _ => complete(ApiKeyNotValid) } } } diff --git a/node/src/main/scala/com/wavesplatform/api/http/BlocksApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/BlocksApiRoute.scala index 2e0a7ab14be..d699d703d14 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/BlocksApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/BlocksApiRoute.scala @@ -4,6 +4,7 @@ import akka.http.scaladsl.server.{Route, StandardRoute} import com.wavesplatform.api.BlockMeta import com.wavesplatform.api.common.CommonBlocksApi import com.wavesplatform.api.http.ApiError.{BlockDoesNotExist, TooBigArrayAllocation} +import com.wavesplatform.api.http.TransactionsApiRoute.TransactionJsonSerializer import com.wavesplatform.block.Block import com.wavesplatform.settings.RestAPISettings import com.wavesplatform.transaction.Asset.Waves @@ -89,14 +90,16 @@ case class BlocksApiRoute(settings: RestAPISettings, commonApi: CommonBlocksApi) } object BlocksApiRoute { - import TransactionsApiRoute.applicationStatus - - private def toJson(v: (BlockMeta, Seq[(Transaction, Boolean)])): JsObject = v._1.json() ++ transactionField(v._1.header.version, v._2) + private def toJson(v: (BlockMeta, Seq[(Transaction, Boolean)])): JsObject = v match { + case (meta, transactions) => + meta.json() ++ transactionField(meta.header.version, transactions) + } private def transactionField(blockVersion: Byte, transactions: Seq[(Transaction, Boolean)]): JsObject = Json.obj( "fee" -> transactions.map(_._1.assetFee).collect { case (Waves, feeAmt) => feeAmt }.sum, "transactions" -> JsArray(transactions.map { - case (transaction, succeeded) => transaction.json() ++ applicationStatus(blockVersion >= Block.ProtoBlockVersion, succeeded) + case (transaction, succeeded) => + transaction.json() ++ TransactionJsonSerializer.applicationStatus(blockVersion >= Block.ProtoBlockVersion, succeeded) }) ) } diff --git a/node/src/main/scala/com/wavesplatform/http/BroadcastRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/BroadcastRoute.scala similarity index 93% rename from node/src/main/scala/com/wavesplatform/http/BroadcastRoute.scala rename to node/src/main/scala/com/wavesplatform/api/http/BroadcastRoute.scala index 1680f52fc02..fcfe09782df 100644 --- a/node/src/main/scala/com/wavesplatform/http/BroadcastRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/BroadcastRoute.scala @@ -1,8 +1,7 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import akka.http.scaladsl.marshalling.{ToResponseMarshallable, ToResponseMarshaller} import akka.http.scaladsl.server.{Directive1, Route} -import com.wavesplatform.api.http.{ApiError, ApiRoute, jsonPostD} import com.wavesplatform.lang.ValidationError import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.transaction.Transaction diff --git a/node/src/main/scala/com/wavesplatform/api/http/CustomDirectives.scala b/node/src/main/scala/com/wavesplatform/api/http/CustomDirectives.scala index 11c1907e38b..47d538406f8 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/CustomDirectives.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/CustomDirectives.scala @@ -1,13 +1,11 @@ package com.wavesplatform.api.http import akka.http.scaladsl.server.{Directive1, _} -import com.wavesplatform.http.ApiMarshallers import com.wavesplatform.utils.ScorexLogging import monix.execution.{Scheduler, UncaughtExceptionReporter} import play.api.libs.json.JsObject trait CustomDirectives extends Directives with ApiMarshallers with ScorexLogging { - def anyParam(paramName: String): Directive1[Iterable[String]] = (get & parameter(paramName.as[String].*).map(_.toSeq.reverse)) | post & (formField(paramName.as[String].*) | diff --git a/node/src/main/scala/com/wavesplatform/http/CustomJson.scala b/node/src/main/scala/com/wavesplatform/api/http/CustomJson.scala similarity index 98% rename from node/src/main/scala/com/wavesplatform/http/CustomJson.scala rename to node/src/main/scala/com/wavesplatform/api/http/CustomJson.scala index 8ecf64ba420..5058462c534 100644 --- a/node/src/main/scala/com/wavesplatform/http/CustomJson.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/CustomJson.scala @@ -1,9 +1,9 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import java.io.IOException -import akka.http.scaladsl.model.{MediaRange, MediaType} import akka.http.scaladsl.model.MediaTypes.`application/json` +import akka.http.scaladsl.model.{MediaRange, MediaType} import com.fasterxml.jackson.core.io.SegmentedStringWriter import com.fasterxml.jackson.core.util.BufferRecyclers import com.fasterxml.jackson.core.{JsonGenerator, JsonProcessingException} diff --git a/node/src/main/scala/com/wavesplatform/http/DebugApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/DebugApiRoute.scala similarity index 90% rename from node/src/main/scala/com/wavesplatform/http/DebugApiRoute.scala rename to node/src/main/scala/com/wavesplatform/api/http/DebugApiRoute.scala index 415c62737a5..a1837c9f122 100644 --- a/node/src/main/scala/com/wavesplatform/http/DebugApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/DebugApiRoute.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import java.net.{InetAddress, InetSocketAddress, URI} import java.util.concurrent.ConcurrentMap @@ -12,11 +12,10 @@ import cats.implicits._ import cats.kernel.Monoid import com.typesafe.config.{ConfigObject, ConfigRenderOptions} import com.wavesplatform.account.Address +import com.wavesplatform.api.common.CommonTransactionsApi.TransactionMeta import com.wavesplatform.api.common.{CommonAccountsApi, CommonAssetsApi, CommonTransactionsApi} -import com.wavesplatform.api.http.TransactionsApiRoute.applicationStatus -import com.wavesplatform.api.http._ +import com.wavesplatform.api.http.TransactionsApiRoute.TransactionJsonSerializer import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lang.ValidationError import com.wavesplatform.mining.{Miner, MinerDebugInfo} import com.wavesplatform.network.{PeerDatabase, PeerInfo, _} @@ -71,6 +70,10 @@ case class DebugApiRoute( private lazy val wavesConfig: JsObject = Json.obj("waves" -> (fullConfig \ "waves").get) override val settings: RestAPISettings = ws.restAPISettings + + private[this] val serializer = TransactionJsonSerializer(blockchain, transactionsApi) + private[this] implicit val transactionMetaWrites = OWrites[TransactionMeta](serializer.transactionWithMetaJson) + override lazy val route: Route = pathPrefix("debug") { stateChanges ~ balanceHistory ~ stateHash ~ validate ~ withAuth { state ~ info ~ stateWaves ~ rollback ~ rollbackTo ~ blacklist ~ portfolios ~ minerInfo ~ configInfo ~ print @@ -246,10 +249,9 @@ case class DebugApiRoute( def stateChangesById: Route = (get & path("stateChanges" / "info" / TransactionId)) { id => transactionsApi.transactionById(id) match { - case Some((height, Right((ist, isr)), succeeded)) => - complete(ist.json() ++ applicationStatus(isBlockV5(height), succeeded) ++ Json.obj("height" -> height.toInt, "stateChanges" -> isr)) - case Some(_) => complete(ApiError.UnsupportedTransactionType) - case None => complete(ApiError.TransactionDoesNotExist) + case Some(meta: TransactionMeta.Invoke) => complete(meta: TransactionMeta) + case Some(_) => complete(ApiError.UnsupportedTransactionType) + case None => complete(ApiError.TransactionDoesNotExist) } } @@ -263,16 +265,11 @@ case class DebugApiRoute( Source .fromPublisher( transactionsApi - .invokeScriptResults(address, None, Set.empty, afterOpt) - .map { - case (height, Right((ist, isr)), succeeded) => - ist.json() ++ applicationStatus(isBlockV5(height), succeeded) ++ Json.obj("height" -> JsNumber(height), "stateChanges" -> isr) - case (height, Left(tx), succeeded) => - tx.json() ++ applicationStatus(isBlockV5(height), succeeded) ++ Json.obj("height" -> JsNumber(height)) - } + .transactionsByAddress(address, None, Set.empty, afterOpt) + .take(limit) + .map(Json.toJsObject(_)) .toReactivePublisher ) - .take(limit) } } } @@ -281,8 +278,8 @@ case class DebugApiRoute( def stateHash: Route = (get & path("stateHash" / IntNumber)) { height => val result = for { - sh <- loadStateHash(height) - h <- blockchain.blockHeader(height) + sh <- loadStateHash(height) + h <- blockchain.blockHeader(height) } yield Json.toJson(sh).as[JsObject] ++ Json.obj("blockId" -> h.id().toString) result match { @@ -290,8 +287,6 @@ case class DebugApiRoute( case None => complete(StatusCodes.NotFound) } } - - private def isBlockV5(height: Int): Boolean = blockchain.isFeatureActivated(BlockchainFeatures.BlockV5, height) } object DebugApiRoute { @@ -313,6 +308,7 @@ object DebugApiRoute { }, m => Json.toJson(m.map { case (assetId, count) => assetId.toString -> count }) ) + implicit val leaseInfoFormat: Format[LeaseBalance] = Json.format case class AccountMiningInfo(address: String, miningBalance: Long, timestamp: Long) diff --git a/node/src/main/scala/com/wavesplatform/http/DebugMessage.scala b/node/src/main/scala/com/wavesplatform/api/http/DebugMessage.scala similarity index 83% rename from node/src/main/scala/com/wavesplatform/http/DebugMessage.scala rename to node/src/main/scala/com/wavesplatform/api/http/DebugMessage.scala index 215a29aaf89..8a9262f57f8 100644 --- a/node/src/main/scala/com/wavesplatform/http/DebugMessage.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/DebugMessage.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import play.api.libs.json.{Format, Json} diff --git a/node/src/main/scala/com/wavesplatform/http/NodeApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/NodeApiRoute.scala similarity index 93% rename from node/src/main/scala/com/wavesplatform/http/NodeApiRoute.scala rename to node/src/main/scala/com/wavesplatform/api/http/NodeApiRoute.scala index 792e005ceae..2fa00038962 100644 --- a/node/src/main/scala/com/wavesplatform/http/NodeApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/NodeApiRoute.scala @@ -1,10 +1,9 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import java.time.Instant import akka.http.scaladsl.server.Route import com.wavesplatform.Shutdownable -import com.wavesplatform.api.http.{ApiRoute, AuthRoute} import com.wavesplatform.settings.{Constants, RestAPISettings} import com.wavesplatform.state.Blockchain import com.wavesplatform.utils.ScorexLogging diff --git a/node/src/main/scala/com/wavesplatform/http/RollbackParams.scala b/node/src/main/scala/com/wavesplatform/api/http/RollbackParams.scala similarity index 86% rename from node/src/main/scala/com/wavesplatform/http/RollbackParams.scala rename to node/src/main/scala/com/wavesplatform/api/http/RollbackParams.scala index b6c32a8e955..7908d967bb9 100644 --- a/node/src/main/scala/com/wavesplatform/http/RollbackParams.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/RollbackParams.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import play.api.libs.json.{Format, Json} diff --git a/node/src/main/scala/com/wavesplatform/api/http/TransactionsApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/TransactionsApiRoute.scala index b3404300819..8eb0d751ab0 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/TransactionsApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/TransactionsApiRoute.scala @@ -8,15 +8,15 @@ import cats.instances.try_._ import cats.syntax.alternative._ import cats.syntax.either._ import cats.syntax.traverse._ -import com.wavesplatform.account.Address +import com.wavesplatform.account.{Address, AddressOrAlias} import com.wavesplatform.api.common.CommonTransactionsApi +import com.wavesplatform.api.common.CommonTransactionsApi.TransactionMeta import com.wavesplatform.api.http.ApiError._ import com.wavesplatform.block.Block import com.wavesplatform.block.Block.TransactionProof import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.Base58 import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.http.BroadcastRoute import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.settings.RestAPISettings import com.wavesplatform.state.Blockchain @@ -44,35 +44,45 @@ case class TransactionsApiRoute( with AuthRoute { import TransactionsApiRoute._ + private[this] val serializer = TransactionJsonSerializer(blockchain, commonApi) + private[this] implicit val transactionMetaWrites = OWrites[TransactionMeta](serializer.transactionWithMetaJson) + override lazy val route: Route = pathPrefix("transactions") { - unconfirmed ~ addressLimit ~ info ~ status ~ sign ~ calculateFee ~ signedBroadcast ~ merkleProof + unconfirmed ~ addressWithLimit ~ info ~ status ~ sign ~ calculateFee ~ signedBroadcast ~ merkleProof } - def addressLimit: Route = { + def addressWithLimit: Route = { (get & path("address" / AddrSegment / "limit" / IntNumber) & parameter("after".?)) { (address, limit, maybeAfter) => val after = maybeAfter.map(s => ByteStr.decodeBase58(s).getOrElse(throw ApiException(CustomValidationError(s"Unable to decode transaction id $s")))) if (limit > settings.transactionsByAddressLimit) throw ApiException(TooBigArrayAllocation) extractScheduler { implicit sc => - complete(transactionsByAddress(address, limit, after).map(txs => List(txs))) + complete(transactionsByAddress(address, limit, after).map(txs => List(txs))) // Double list - [ [tx1, tx2, ...] ] } } } - def info: Route = (pathPrefix("info") & get) { - pathEndOrSingleSlash { - complete(InvalidTransactionId("Transaction ID was not specified")) - } ~ path(TransactionId) { id => - commonApi.transactionById(id) match { - case Some((h, either, succeeded)) => - complete(txToExtendedJson(either.fold(identity, _._1)) ++ applicationStatus(isBlockV5(h), succeeded) + ("height" -> JsNumber(h))) - case None => complete(ApiError.TransactionDoesNotExist) - } + private[this] def readTransactionMeta(id: String): Either[ApiError, TransactionMeta] = + for { + id <- ByteStr.decodeBase58(id).toEither.leftMap(err => CustomValidationError(err.toString)) + meta <- commonApi.transactionById(id).toRight(ApiError.TransactionDoesNotExist) + } yield meta + + def info: Route = pathPrefix("info") { + (get & path(TransactionId)) { id => + complete(commonApi.transactionById(id).toRight(ApiError.TransactionDoesNotExist)) + } ~ (pathEndOrSingleSlash & anyParam("id")) { ids => + val result = for { + _ <- Either.cond(ids.nonEmpty, (), InvalidTransactionId("Transaction ID was not specified")) + statuses <- ids.map(readTransactionMeta).toList.sequence + } yield statuses + + complete(result) } } - private def loadTransactionStatus(id: ByteStr): JsObject = { + private[this] def loadTransactionStatus(id: ByteStr): JsObject = { import Status._ val statusJson = blockchain.transactionInfo(id) match { case Some((height, _, succeeded)) => @@ -80,7 +90,7 @@ case class TransactionsApiRoute( "status" -> Confirmed, "height" -> height, "confirmations" -> (blockchain.height - height).max(0) - ) ++ applicationStatus(isBlockV5(height), succeeded) + ) ++ serializer.applicationStatus(height, succeeded) case None => commonApi.unconfirmedTransactionById(id) match { case Some(_) => Json.obj("status" -> Unconfirmed) @@ -113,7 +123,7 @@ case class TransactionsApiRoute( def unconfirmed: Route = (pathPrefix("unconfirmed") & get) { pathEndOrSingleSlash { - complete(JsArray(commonApi.unconfirmedTransactions.map(txToExtendedJson))) + complete(JsArray(commonApi.unconfirmedTransactions.map(serializer.unconfirmedTxExtendedJson))) } ~ utxSize ~ utxTransactionInfo } @@ -128,7 +138,7 @@ case class TransactionsApiRoute( path(TransactionId) { id => commonApi.unconfirmedTransactionById(id) match { case Some(tx) => - complete(txToExtendedJson(tx)) + complete(serializer.unconfirmedTxExtendedJson(tx)) case None => complete(ApiError.TransactionDoesNotExist) } @@ -184,19 +194,8 @@ case class TransactionsApiRoute( case _ => InvalidSignature } - private def txToExtendedJson(tx: Transaction): JsObject = tx match { - case lease: LeaseTransaction => - import com.wavesplatform.api.http.TransactionsApiRoute.LeaseStatus._ - lease.json() ++ Json.obj("status" -> (if (blockchain.leaseDetails(lease.id()).exists(_.isActive)) Active else Canceled)) - - case leaseCancel: LeaseCancelTransaction => - leaseCancel.json() ++ Json.obj("lease" -> blockchain.transactionInfo(leaseCancel.leaseId).map(_._2.json()).getOrElse[JsValue](JsNull)) - - case t => t.json() - } - def transactionsByAddress(address: Address, limitParam: Int, maybeAfter: Option[ByteStr])(implicit sc: Scheduler): Future[List[JsObject]] = { - val aliasesOfAddress = + val aliasesOfAddress: Task[Set[AddressOrAlias]] = commonApi .aliasesOfAddress(address) .collect { case (_, cat) => cat.alias } @@ -208,26 +207,22 @@ case class TransactionsApiRoute( * Produces compact representation for large transactions by stripping unnecessary data. * Currently implemented for MassTransfer transaction only. */ - def txToCompactJson(address: Address, tx: Transaction): Task[JsObject] = { + def compactJson(address: Address, meta: TransactionMeta): Task[JsObject] = { import com.wavesplatform.transaction.transfer._ - tx match { - case mtt: MassTransferTransaction if mtt.sender.toAddress != address => aliasesOfAddress.map(mtt.compactJson) - case _ => Task.now(txToExtendedJson(tx)) + meta.transaction match { + case mtt: MassTransferTransaction if mtt.sender.toAddress != address => + aliasesOfAddress.map(mtt.compactJson(_) ++ serializer.transactionMetaJson(meta)) + case _ => Task.now(serializer.transactionWithMetaJson(meta)) } } commonApi .transactionsByAddress(address, None, Set.empty, maybeAfter) .take(limitParam) - .mapEval { - case (height, tx, succeeded) => - txToCompactJson(address, tx).map(_ ++ applicationStatus(isBlockV5(height), succeeded) + ("height" -> JsNumber(height))) - } + .mapEval(compactJson(address, _)) .toListL .runToFuture } - - private def isBlockV5(height: Int): Boolean = blockchain.isFeatureActivated(BlockchainFeatures.BlockV5, height) } object TransactionsApiRoute { @@ -247,12 +242,6 @@ object TransactionsApiRoute { val ScriptExecutionFailed = "script_execution_failed" } - def applicationStatus(isBlockV5: Boolean, succeeded: Boolean): JsObject = - if (isBlockV5) - JsObject(Map("applicationStatus" -> JsString(if (succeeded) ApplicationStatus.Succeeded else ApplicationStatus.ScriptExecutionFailed))) - else - JsObject.empty - implicit val transactionProofWrites: Writes[TransactionProof] = Writes { mi => Json.obj( "id" -> mi.id.toString, @@ -269,4 +258,64 @@ object TransactionsApiRoute { merkleProof <- (jsv \ "merkleProof").validate[List[String]].map(_.map(Base58.decode)) } yield TransactionProof(id, transactionIndex, merkleProof) } + + private[http] object TransactionJsonSerializer { + def applicationStatus(isBlockV5: Boolean, succeeded: Boolean): JsObject = + if (isBlockV5) + Json.obj("applicationStatus" -> (if (succeeded) ApplicationStatus.Succeeded else ApplicationStatus.ScriptExecutionFailed)) + else + JsObject.empty + + def height(height: Int): JsObject = + Json.obj("height" -> height) + } + + private[http] final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: CommonTransactionsApi) { + def transactionMetaJson(meta: TransactionMeta): JsObject = { + val specificInfo = meta.transaction match { + case lease: LeaseTransaction => + import com.wavesplatform.api.http.TransactionsApiRoute.LeaseStatus._ + Json.obj("status" -> (if (blockchain.leaseDetails(lease.id()).exists(_.isActive)) Active else Canceled)) + + case leaseCancel: LeaseCancelTransaction => + val leaseId = blockchain.transactionInfo(leaseCancel.leaseId) map { + case (_, tx, _) => tx.id().toString + } + Json.obj("lease" -> leaseId) + + case _ => JsObject.empty + } + + val stateChanges = meta match { + case i: TransactionMeta.Invoke => Json.obj("stateChanges" -> i.invokeScriptResult) + case _ => JsObject.empty + } + + Seq( + TransactionJsonSerializer.height(meta.height), + applicationStatus(meta.height, meta.succeeded), + stateChanges, + specificInfo + ).reduce(_ ++ _) + } + + def transactionWithMetaJson(meta: TransactionMeta): JsObject = { + meta.transaction.json() ++ transactionMetaJson(meta) + } + + def unconfirmedTxExtendedJson(tx: Transaction): JsObject = tx match { + case leaseCancel: LeaseCancelTransaction => + val leaseId = blockchain.transactionInfo(leaseCancel.leaseId) map { + case (_, tx, _) => tx.id().toString + } + leaseCancel.json() ++ Json.obj("lease" -> leaseId) + + case t => t.json() + } + + def applicationStatus(height: Int, succeeded: Boolean): JsObject = + TransactionJsonSerializer.applicationStatus(isBlockV5(height), succeeded) + + private[this] def isBlockV5(height: Int): Boolean = blockchain.isFeatureActivated(BlockchainFeatures.BlockV5, height) + } } diff --git a/node/src/main/scala/com/wavesplatform/http/UnsignedPayment.scala b/node/src/main/scala/com/wavesplatform/api/http/UnsignedPayment.scala similarity index 88% rename from node/src/main/scala/com/wavesplatform/http/UnsignedPayment.scala rename to node/src/main/scala/com/wavesplatform/api/http/UnsignedPayment.scala index a171c17d165..e013f258035 100644 --- a/node/src/main/scala/com/wavesplatform/http/UnsignedPayment.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/UnsignedPayment.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import play.api.libs.json.{Format, Json} diff --git a/node/src/main/scala/com/wavesplatform/http/`X-Api-Key`.scala b/node/src/main/scala/com/wavesplatform/api/http/`X-Api-Key`.scala similarity index 96% rename from node/src/main/scala/com/wavesplatform/http/`X-Api-Key`.scala rename to node/src/main/scala/com/wavesplatform/api/http/`X-Api-Key`.scala index 12c51e77fc7..55a7c556e74 100644 --- a/node/src/main/scala/com/wavesplatform/http/`X-Api-Key`.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/`X-Api-Key`.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.http +package com.wavesplatform.api.http import akka.http.scaladsl.model.headers._ diff --git a/node/src/main/scala/com/wavesplatform/api/http/alias/AliasApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/alias/AliasApiRoute.scala index c7c5e78f873..038ecf4cd11 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/alias/AliasApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/alias/AliasApiRoute.scala @@ -7,9 +7,8 @@ import akka.stream.scaladsl.Source import cats.syntax.either._ import com.wavesplatform.account.Alias import com.wavesplatform.api.common.CommonTransactionsApi -import com.wavesplatform.api.http._ import com.wavesplatform.api.http.requests.CreateAliasRequest -import com.wavesplatform.http.BroadcastRoute +import com.wavesplatform.api.http.{BroadcastRoute, _} import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.settings.RestAPISettings import com.wavesplatform.state.Blockchain diff --git a/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala index 7d69de78fc7..e948cbae722 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala @@ -18,7 +18,6 @@ import com.wavesplatform.api.http._ import com.wavesplatform.api.http.assets.AssetsApiRoute.DistributionParams import com.wavesplatform.api.http.requests._ import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.http.{BroadcastRoute, CustomJson} import com.wavesplatform.lang.ValidationError import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.settings.RestAPISettings diff --git a/node/src/main/scala/com/wavesplatform/api/http/leasing/LeaseApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/leasing/LeaseApiRoute.scala index dccd2a14373..ddb897f823b 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/leasing/LeaseApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/leasing/LeaseApiRoute.scala @@ -2,9 +2,8 @@ package com.wavesplatform.api.http.leasing import akka.http.scaladsl.server.Route import com.wavesplatform.api.common.CommonAccountsApi -import com.wavesplatform.api.http._ import com.wavesplatform.api.http.requests.{LeaseCancelRequest, LeaseRequest} -import com.wavesplatform.http.BroadcastRoute +import com.wavesplatform.api.http.{BroadcastRoute, _} import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.settings.RestAPISettings import com.wavesplatform.state.Blockchain diff --git a/node/src/main/scala/com/wavesplatform/api/http/package.scala b/node/src/main/scala/com/wavesplatform/api/http/package.scala index 10ff077376d..79c4d9a3cb1 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/package.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/package.scala @@ -15,7 +15,6 @@ import com.wavesplatform.api.http.requests._ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.Base58 import com.wavesplatform.crypto -import com.wavesplatform.http.{ApiMarshallers, PlayJsonException} import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction._ diff --git a/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala b/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala index 375b5bd1443..a59e7b3ee35 100644 --- a/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala +++ b/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala @@ -5,6 +5,7 @@ import akka.http.scaladsl.model.MediaTypes.`application/json` import akka.http.scaladsl.model.headers.Accept import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.ScalatestRouteTest +import com.wavesplatform.api.common.CommonTransactionsApi.TransactionMeta import com.wavesplatform.api.common.{CommonAccountsApi, CommonAssetsApi, CommonTransactionsApi} import com.wavesplatform.api.http.assets.AssetsApiRoute import com.wavesplatform.common.state.ByteStr @@ -69,7 +70,7 @@ class CustomJsonMarshallerSpec property("/transactions/info/{id}") { forAll(leaseGen) { lt => val height: Height = Height(1) - (transactionsApi.transactionById _).expects(lt.id()).returning(Some((height, Left(lt), true))).twice() + (transactionsApi.transactionById _).expects(lt.id()).returning(Some(TransactionMeta.Default(height, lt, succeeded = true))).twice() (blockchain.leaseDetails _) .expects(lt.id()) .returning(Some(LeaseDetails(lt.sender, lt.recipient, 1, lt.amount, true))) diff --git a/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala index 0f6199b9623..b3451394298 100644 --- a/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala @@ -6,10 +6,10 @@ import com.wavesplatform.account.{Address, AddressOrAlias} import com.wavesplatform.api.common.CommonAccountsApi import com.wavesplatform.api.http.AddressApiRoute import com.wavesplatform.api.http.ApiError.ApiKeyNotValid +import com.wavesplatform.api.http.ApiMarshallers._ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, Base64, EitherExt2} import com.wavesplatform.db.WithDomain -import com.wavesplatform.http.ApiMarshallers._ import com.wavesplatform.it.util.DoubleExt import com.wavesplatform.lang.contract.DApp import com.wavesplatform.lang.contract.DApp.{CallableAnnotation, CallableFunction, VerifierAnnotation, VerifierFunction} diff --git a/node/src/test/scala/com/wavesplatform/http/ApiErrorMatchers.scala b/node/src/test/scala/com/wavesplatform/http/ApiErrorMatchers.scala index c6fbc0bac37..c99da6ac730 100644 --- a/node/src/test/scala/com/wavesplatform/http/ApiErrorMatchers.scala +++ b/node/src/test/scala/com/wavesplatform/http/ApiErrorMatchers.scala @@ -2,7 +2,7 @@ package com.wavesplatform.http import akka.http.scaladsl.testkit.RouteTest import com.wavesplatform.api.http.ApiError -import com.wavesplatform.http.ApiMarshallers._ +import com.wavesplatform.api.http.ApiMarshallers._ import org.scalatest.Matchers import org.scalatest.matchers.{MatchResult, Matcher} import play.api.libs.json._ diff --git a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala index 4a97baf8b2a..873b4e17da7 100644 --- a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala @@ -4,11 +4,11 @@ import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server.Route import com.wavesplatform.account.Address import com.wavesplatform.api.common.{CommonAccountsApi, CommonAssetsApi} +import com.wavesplatform.api.http.ApiMarshallers._ import com.wavesplatform.api.http.assets.AssetsApiRoute import com.wavesplatform.api.http.requests.{TransferV1Request, TransferV2Request} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.http.ApiMarshallers._ import com.wavesplatform.it.util.DoubleExt import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.estimator.ScriptEstimatorV1 @@ -163,7 +163,10 @@ class AssetsRouteSpec } Get(routePath(s"/${TestValues.asset.id}/distribution/1/limit/1")) ~> route ~> check { - responseAs[JsObject] shouldBe Json.obj("error" -> 199, "message" -> s"Unable to get distribution past height ${blockchain.height - MaxDistributionDepth}") + responseAs[JsObject] shouldBe Json.obj( + "error" -> 199, + "message" -> s"Unable to get distribution past height ${blockchain.height - MaxDistributionDepth}" + ) } } diff --git a/node/src/test/scala/com/wavesplatform/http/BlocksRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/BlocksRouteSpec.scala index 8bcb8157ff8..5a9aa5e3aa8 100644 --- a/node/src/test/scala/com/wavesplatform/http/BlocksRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/BlocksRouteSpec.scala @@ -2,11 +2,11 @@ package com.wavesplatform.http import com.wavesplatform.api.BlockMeta import com.wavesplatform.api.common.CommonBlocksApi +import com.wavesplatform.api.http.ApiMarshallers._ import com.wavesplatform.api.http.BlocksApiRoute import com.wavesplatform.block.Block import com.wavesplatform.block.serialization.BlockHeaderSerializer import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.http.ApiMarshallers._ import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.{NoShrink, TestWallet} import monix.reactive.Observable diff --git a/node/src/test/scala/com/wavesplatform/http/ConsensusRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/ConsensusRouteSpec.scala index 4402c1732d0..a62a38ee330 100644 --- a/node/src/test/scala/com/wavesplatform/http/ConsensusRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/ConsensusRouteSpec.scala @@ -3,9 +3,9 @@ package com.wavesplatform.http import akka.http.scaladsl.server.Route import com.wavesplatform.BlockGen import com.wavesplatform.api.http.ApiError.BlockDoesNotExist +import com.wavesplatform.api.http.ApiMarshallers._ import com.wavesplatform.consensus.nxt.api.http.NxtConsensusApiRoute import com.wavesplatform.db.WithDomain -import com.wavesplatform.http.ApiMarshallers._ import com.wavesplatform.state._ import org.scalatestplus.scalacheck.{ScalaCheckPropertyChecks => PropertyChecks} import play.api.libs.json.JsObject diff --git a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala index cacae727e79..9ca9039f692 100644 --- a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala @@ -2,6 +2,7 @@ package com.wavesplatform.http import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCodes} import com.wavesplatform.api.http.ApiError.ApiKeyNotValid +import com.wavesplatform.api.http.DebugApiRoute import com.wavesplatform.block.SignedBlockHeader import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils._ diff --git a/node/src/test/scala/com/wavesplatform/http/PeersRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/PeersRouteSpec.scala index b5ae90c8a6d..c923bd4f5d3 100644 --- a/node/src/test/scala/com/wavesplatform/http/PeersRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/PeersRouteSpec.scala @@ -4,8 +4,8 @@ import java.net.{InetAddress, InetSocketAddress} import java.util.concurrent.ConcurrentHashMap import com.wavesplatform.api.http.ApiError.ApiKeyNotValid +import com.wavesplatform.api.http.ApiMarshallers._ import com.wavesplatform.api.http.PeersApiRoute -import com.wavesplatform.http.ApiMarshallers._ import com.wavesplatform.network.{PeerDatabase, PeerInfo} import io.netty.channel.Channel import org.scalacheck.{Arbitrary, Gen} diff --git a/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala b/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala index 9af05944e66..e3cec4f3d20 100644 --- a/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala @@ -36,7 +36,7 @@ class ProtoVersionTransactionsSpec with Matchers with OptionValues with TestWallet { - import com.wavesplatform.http.ApiMarshallers._ + import com.wavesplatform.api.http.ApiMarshallers._ private val MinFee: Long = (0.001 * Constants.UnitsInWave).toLong private val DataTxFee: Long = 15000000 diff --git a/node/src/test/scala/com/wavesplatform/http/RestAPISettingsHelper.scala b/node/src/test/scala/com/wavesplatform/http/RestAPISettingsHelper.scala index ecbc01ba8c4..752062fe0d8 100644 --- a/node/src/test/scala/com/wavesplatform/http/RestAPISettingsHelper.scala +++ b/node/src/test/scala/com/wavesplatform/http/RestAPISettingsHelper.scala @@ -1,6 +1,7 @@ package com.wavesplatform.http import com.typesafe.config.ConfigFactory +import com.wavesplatform.api.http.`X-Api-Key` import com.wavesplatform.common.utils.Base58 import com.wavesplatform.crypto import com.wavesplatform.settings._ diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala index 1fcb6e0bde5..a83239f81d9 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala @@ -1,27 +1,28 @@ package com.wavesplatform.http -import akka.http.scaladsl.model.{HttpResponse, StatusCodes} +import akka.http.scaladsl.model._ import com.wavesplatform.account.{AddressScheme, KeyPair, PublicKey} import com.wavesplatform.api.common.CommonTransactionsApi +import com.wavesplatform.api.common.CommonTransactionsApi.TransactionMeta import com.wavesplatform.api.http.ApiError._ +import com.wavesplatform.api.http.ApiMarshallers._ import com.wavesplatform.api.http.TransactionsApiRoute import com.wavesplatform.block.Block import com.wavesplatform.block.Block.TransactionProof import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.Base58 import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.http.ApiMarshallers._ import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, CONST_LONG, FUNCTION_CALL} import com.wavesplatform.network.TransactionPublisher -import com.wavesplatform.state.{Blockchain, Height} -import com.wavesplatform.transaction.{Asset, Proofs, TxVersion} +import com.wavesplatform.state.{Blockchain, Height, InvokeScriptResult} import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.smart.script.trace.{AccountVerifierTrace, TracedResult} import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} +import com.wavesplatform.transaction.{Asset, Proofs, TxHelpers, TxVersion} import com.wavesplatform.{BlockGen, NoShrink, TestTime, TestWallet, TransactionGen} import monix.reactive.Observable import org.scalacheck.Gen._ @@ -29,6 +30,7 @@ import org.scalacheck.{Arbitrary, Gen} import org.scalamock.scalatest.MockFactory import org.scalatest.{Matchers, OptionValues} import org.scalatestplus.scalacheck.{ScalaCheckPropertyChecks => PropertyChecks} +import play.api.libs.json.Json.JsValueWrapper import play.api.libs.json._ import scala.concurrent.Future @@ -252,6 +254,22 @@ class TransactionsRouteSpec } } } + + "provides stateChanges" in forAll(accountGen) { account => + val transaction = TxHelpers.invoke(account.toAddress, "test") + + (() => blockchain.activatedFeatures).expects().returns(Map.empty).anyNumberOfTimes() + (addressTransactions.aliasesOfAddress _).expects(*).returning(Observable.empty).once() + (addressTransactions.transactionsByAddress _) + .expects(account.toAddress, *, *, None) + .returning(Observable(TransactionMeta.Invoke(Height(1), transaction, succeeded = true, Some(InvokeScriptResult())))) + .once() + + Get(routePath(s"/address/${account.toAddress}/limit/1")) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsArray] \ 0 \ 0 \ "stateChanges").as[JsObject] shouldBe Json.toJsObject(InvokeScriptResult()) + } + } } routePath("/info/{id}") - { @@ -274,9 +292,7 @@ class TransactionsRouteSpec forAll(txAvailability) { case (tx, succeed, height, acceptFailedActivationHeight) => - val h: Height = Height(height) - val info = if (tx.typeId == InvokeScriptTransaction.typeId) Right((tx.asInstanceOf[InvokeScriptTransaction], None)) else Left(tx) - (addressTransactions.transactionById _).expects(tx.id()).returning(Some((h, info, succeed))).once() + (addressTransactions.transactionById _).expects(tx.id()).returning(Some(TransactionMeta.Default(Height(height), tx, succeed))).once() (() => blockchain.activatedFeatures) .expects() .returning(Map(BlockchainFeatures.BlockV5.id -> acceptFailedActivationHeight)) @@ -295,6 +311,50 @@ class TransactionsRouteSpec Get(routePath(s"/info/${tx.id().toString}")) ~> route ~> check(validateResponse()) } } + + "provides stateChanges" in forAll(accountGen) { account => + val transaction = TxHelpers.invoke(account.toAddress, "test") + + (() => blockchain.activatedFeatures).expects().returns(Map.empty).anyNumberOfTimes() + (addressTransactions.transactionById _) + .expects(transaction.id()) + .returning(Some(TransactionMeta.Invoke(Height(1), transaction, succeeded = true, Some(InvokeScriptResult())))) + .once() + + Get(routePath(s"/info/${transaction.id()}")) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsObject] \ "stateChanges").as[JsObject] shouldBe Json.toJsObject(InvokeScriptResult()) + } + } + + "handles multiple ids" in { + val txCount = 5 + val txs = (1 to txCount).map(_ => TxHelpers.invoke(TxHelpers.defaultSigner.toAddress, "test")) + txs.foreach( + tx => + (addressTransactions.transactionById _) + .expects(tx.id()) + .returns(Some(TransactionMeta.Invoke(Height(1), tx, succeeded = true, Some(InvokeScriptResult())))) + .repeat(3) + ) + + (() => blockchain.activatedFeatures).expects().returns(Map(BlockchainFeatures.BlockV5.id -> 1)).anyNumberOfTimes() + + def checkResponse(): Unit = txs.zip(responseAs[JsArray].value) foreach { + case (tx, json) => + val extraFields = Json.obj("height" -> 1, "applicationStatus" -> "succeeded", "stateChanges" -> InvokeScriptResult()) + json shouldBe (tx.json() ++ extraFields) + } + + Get(routePath(s"/info?${txs.map("id=" + _.id()).mkString("&")}")) ~> route ~> check(checkResponse()) + Post(routePath("/info"), FormData(txs.map("id" -> _.id().toString): _*)) ~> route ~> check(checkResponse()) + Post( + routePath("/info"), + HttpEntity(ContentTypes.`application/json`, Json.obj("ids" -> Json.arr(txs.map(_.id().toString: JsValueWrapper): _*)).toString()) + ) ~> route ~> check( + checkResponse() + ) + } } routePath("/status/{signature}") - { @@ -392,7 +452,7 @@ class TransactionsRouteSpec routePath("/unconfirmed/info/{id}") - { "handles invalid signature" in { forAll(invalidBase58Gen) { invalidBase58 => - Get(routePath(s"/unconfirmed/info/$invalidBase58")) ~> route should produce(InvalidTransactionId("Wrong char"), true) + Get(routePath(s"/unconfirmed/info/$invalidBase58")) ~> route should produce(InvalidTransactionId("Wrong char"), matchMsg = true) } Get(routePath(s"/unconfirmed/info/")) ~> route should produce(InvalidSignature) @@ -477,7 +537,8 @@ class TransactionsRouteSpec .expects(*, None) .returning( Future.successful(TracedResult(Right(true), List(accountTrace))) - ).once() + ) + .once() Post(routePath("/broadcast?trace=true"), ist.json()) ~> route ~> check { val result = responseAs[JsObject] (result \ "trace").as[JsValue] shouldBe Json.arr(accountTrace.json) @@ -490,7 +551,8 @@ class TransactionsRouteSpec .expects(*, None) .returning( Future.successful(TracedResult(Right(true), List(accountTrace))) - ).twice() + ) + .twice() Post(routePath("/broadcast"), ist.json()) ~> route ~> check { (responseAs[JsObject] \ "trace") shouldBe empty } diff --git a/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala index 4c3c5c74885..34e97f9815c 100644 --- a/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala @@ -5,12 +5,12 @@ import cats.implicits._ import com.google.protobuf.ByteString import com.wavesplatform.account.PublicKey import com.wavesplatform.api.http.ApiError.TooBigArrayAllocation +import com.wavesplatform.api.http.ApiMarshallers._ import com.wavesplatform.api.http.UtilsApiRoute import com.wavesplatform.api.http.requests.ScriptWithImportsRequest import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.crypto -import com.wavesplatform.http.ApiMarshallers._ import com.wavesplatform.lang.Global import com.wavesplatform.lang.contract.DApp import com.wavesplatform.lang.contract.DApp.{CallableAnnotation, CallableFunction, VerifierAnnotation, VerifierFunction} diff --git a/node/src/test/scala/com/wavesplatform/http/WalletRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/WalletRouteSpec.scala index 9416c0dc58a..fc6465a9b96 100644 --- a/node/src/test/scala/com/wavesplatform/http/WalletRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/WalletRouteSpec.scala @@ -2,9 +2,9 @@ package com.wavesplatform.http import com.wavesplatform.TestWallet import com.wavesplatform.api.http.ApiError.ApiKeyNotValid +import com.wavesplatform.api.http.ApiMarshallers._ import com.wavesplatform.api.http.WalletApiRoute import com.wavesplatform.common.utils.Base58 -import com.wavesplatform.http.ApiMarshallers._ import play.api.libs.json.JsObject class WalletRouteSpec extends RouteSpec("/wallet") with RestAPISettingsHelper with TestWallet { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 23c73192ef0..2275d07aa14 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -164,7 +164,7 @@ object Dependencies { ) private[this] val protoSchemasLib = - "com.wavesplatform" % "protobuf-schemas" % "1.2.8" classifier "proto" intransitive() + "com.wavesplatform" % "protobuf-schemas" % "1.2.9-N2265-SNAPSHOT" classifier "proto" intransitive() lazy val scalapbRuntime = Def.setting { val version = scalapb.compiler.Version.scalapbVersion