diff --git a/README.md b/README.md index 3faf6cc878..8c4c896d91 100644 --- a/README.md +++ b/README.md @@ -45,20 +45,21 @@ docker run -p 6869:6869 -p 6863:6863 -e LTO_NETWORK=TESTNET -e LTO_HEAP_SIZE=2g **You can run the container with these environment variables:** -|Env variable |Description | -|-----------------------------|--------------| -|`LTO_WALLET_SEED` |Plain text seed for node wallet. Container converts it to base58. | -|`LTO_WALLET_SEED_BASE58` |Base58 encoded seed. (Supersedes LTO_WALLET_SEED) | -|`LTO_WALLET_PASSWORD` |Password for wallet file. | -|`LTO_API_KEY` |ApiKey used for the rest api authentication | -|`LTO_NETWORK` |Available values are `MAINNET`, `TESTNET` and `CUSTOM`. (Default: `MAINNET`) | -|`LTO_LOG_LEVEL` |Node logging level, available values: `OFF`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`. | -|`LTO_HEAP_SIZE` |Java Heap Size limit in -X Command-line Options notation (`-Xms=[your value]`). More details [here](https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html) | -|`LTO_CONFIG_FILE` |Path to your LTO Configuration file. | -|`LTO_DECLARED_ADDRESS` |String with IP address and port to send as external address during handshake. Could be set automatically if UPnP is enabled. If `declared-address` is set, which is the common scenario for nodes running in the cloud, the node will just listen to incoming connections on `bind-address:port` and broadcast its `declared-address` to its peers.| -|`LTO_NODE_NAME` |Node name used in the handshake when connecting to other nodes| -|`LTO_ENABLE_REST_API` |To enable the REST API. (For `MAINNET` default is `false` for `TESTNET` default is `true`| -|`LTO_FEATURES` |Features you wish to vote. E.g. set to 4 to start voting for the Smart Accounts feature. You can also vote for multiple features at by comma seperating them (e.g. 4,5)| +| Env variable | Description | +|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `LTO_WALLET_SEED` | Plain text seed for node wallet. Container converts it to base58. | +| `LTO_WALLET_SEED_BASE58` | Base58 encoded seed. (Supersedes LTO_WALLET_SEED) | +| `LTO_WALLET_PASSWORD` | Password for wallet file. | +| `LTO_API_KEY` | ApiKey used for the rest api authentication | +| `LTO_NETWORK` | Available values are `MAINNET`, `TESTNET` and `CUSTOM`. (Default: `MAINNET`) | +| `LTO_LOG_LEVEL` | Node logging level, available values: `OFF`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`. | +| `LTO_HEAP_SIZE` | Java Heap Size limit in -X Command-line Options notation (`-Xms=[your value]`). More details [here](https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html) | +| `LTO_CONFIG_FILE` | Path to your LTO Configuration file. | +| `LTO_DECLARED_ADDRESS` | String with IP address and port to send as external address during handshake. Could be set automatically if UPnP is enabled. If `declared-address` is set, which is the common scenario for nodes running in the cloud, the node will just listen to incoming connections on `bind-address:port` and broadcast its `declared-address` to its peers. | +| `LTO_NODE_NAME` | Node name used in the handshake when connecting to other nodes | +| `LTO_ENABLE_REST_API` | To enable the REST API. (For `MAINNET` default is `false` for `TESTNET` default is `true`) | +| `LTO_ENABLE_MINING` | To enable PoS mining (default is `true`) | +| `LTO_FEATURES` | Features you wish to vote. E.g. set to 4 to start voting for the Smart Accounts feature. You can also vote for multiple features at by comma seperating them (e.g. 4,5) | **Note: All variables are optional.** diff --git a/e2e/requirements.txt b/e2e/requirements.txt index 22cfc6995e..afc08322fb 100644 --- a/e2e/requirements.txt +++ b/e2e/requirements.txt @@ -1,3 +1,3 @@ -lto~=1.2.1 +lto~=1.2.3 behave~=1.2.6 polling~=0.3.2 diff --git a/lto-mainnet.conf b/lto-mainnet.conf index ae731686e7..8e8b9df9ed 100644 --- a/lto-mainnet.conf +++ b/lto-mainnet.conf @@ -62,9 +62,6 @@ lto { # Port to listen to REST API requests port = 6869 - - # Hash of API key string - api-key-hash = "GfSz71FEbo3U3aQv9DoJ5N1nNx8zvBzWwrcNjztX3qmP" } checkpoints.public-key = "4S3nfAPBRJcqHs41gb88fDfFUwbPR4NmYuCch1V8wY7d" diff --git a/lto-testnet.conf b/lto-testnet.conf index a7d549c326..5a2e42eeb7 100644 --- a/lto-testnet.conf +++ b/lto-testnet.conf @@ -64,9 +64,6 @@ lto { # Port to listen to REST API requests port = 6869 - - # Hash of API key string - api-key-hash = "BgLRrVvDTau3j4Fz4YMzUUA31FKkYWoYX73GCm67rNRz" } } diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 1642a6077c..6306eecac7 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -338,7 +338,7 @@ lto { port = 6869 # Hash of API key string - api-key-hash = "AowtuqFQgbp29uzbHFntQjiwxWAUshjyUjhxhY967J6s" + api-key-hash = "-" # Enable/disable CORS support cors = yes diff --git a/src/main/scala/com/ltonetwork/Version.scala b/src/main/scala/com/ltonetwork/Version.scala index 52eadefaa1..f05d777e81 100644 --- a/src/main/scala/com/ltonetwork/Version.scala +++ b/src/main/scala/com/ltonetwork/Version.scala @@ -5,6 +5,6 @@ package com.ltonetwork // In case of not updating the version nodes build from headless sources will fail to connect to newer versions object Version { - val VersionString = "1.4.0" - val VersionTuple: (Int, Int, Int) = (1, 4, 0) + val VersionString = "1.5.x" + val VersionTuple: (Int, Int, Int) = (1, 5, 0) } diff --git a/src/main/scala/com/ltonetwork/api/AddressApiRoute.scala b/src/main/scala/com/ltonetwork/api/AddressApiRoute.scala index 177f10fead..57e17a7bb0 100644 --- a/src/main/scala/com/ltonetwork/api/AddressApiRoute.scala +++ b/src/main/scala/com/ltonetwork/api/AddressApiRoute.scala @@ -456,7 +456,7 @@ case class AddressApiRoute(settings: RestAPISettings, ToResponseMarshallable(result) } - private def verifyPath(address: String, decode: Boolean) = withAuth { + private def verifyPath(address: String, decode: Boolean) = { json[SignedMessage] { m => if (Address.fromString(address).isLeft) { InvalidAddress diff --git a/src/main/scala/com/ltonetwork/api/ApiRoute.scala b/src/main/scala/com/ltonetwork/api/ApiRoute.scala index c456916daa..20a949f693 100644 --- a/src/main/scala/com/ltonetwork/api/ApiRoute.scala +++ b/src/main/scala/com/ltonetwork/api/ApiRoute.scala @@ -1,9 +1,10 @@ package com.ltonetwork.api import akka.http.scaladsl.marshalling.ToResponseMarshallable +import akka.http.scaladsl.model.headers.{Authorization, OAuth2BearerToken} import akka.http.scaladsl.server._ import com.ltonetwork.crypto -import com.ltonetwork.http.{ApiMarshallers, PlayJsonException, api_key, deprecated_api_key} +import com.ltonetwork.http.{ApiKey, ApiMarshallers, PlayJsonException} import com.ltonetwork.settings.RestAPISettings import com.ltonetwork.utils.Base58 import play.api.libs.json.Reads @@ -28,10 +29,10 @@ trait ApiRoute extends Directives with CommonApiFunctions with ApiMarshallers { } def withAuth: Directive0 = apiKeyHash.fold[Directive0](complete(ApiKeyNotValid)) { hashFromSettings => - optionalHeaderValueByType(api_key).flatMap { - case Some(k) if crypto.secureHash(k.value.getBytes()).sameElements(hashFromSettings) => pass + optionalHeaderValueByType(Authorization).map(_.map(_.credentials)).flatMap { + case Some(c: OAuth2BearerToken) if crypto.secureHash(c.token.getBytes()).sameElements(hashFromSettings) => pass case _ => - optionalHeaderValueByType(deprecated_api_key).flatMap { + optionalHeaderValueByType(ApiKey).flatMap { case Some(k) if crypto.secureHash(k.value.getBytes()).sameElements(hashFromSettings) => pass case _ => complete(ApiKeyNotValid) } diff --git a/src/main/scala/com/ltonetwork/api/CompositeHttpService.scala b/src/main/scala/com/ltonetwork/api/CompositeHttpService.scala index 4ad1474dcc..4429fd424c 100644 --- a/src/main/scala/com/ltonetwork/api/CompositeHttpService.scala +++ b/src/main/scala/com/ltonetwork/api/CompositeHttpService.scala @@ -3,11 +3,13 @@ package com.ltonetwork.api import akka.actor.ActorSystem import akka.http.scaladsl.model.HttpMethods._ import akka.http.scaladsl.model.headers._ -import akka.http.scaladsl.model.{HttpRequest, StatusCodes} +import akka.http.scaladsl.model.{HttpEntity, HttpRequest, MediaTypes, StatusCodes} import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.RouteResult.Complete import akka.http.scaladsl.server.directives.{DebuggingDirectives, LoggingMagnet} import akka.http.scaladsl.server.{Directive0, Route, RouteResult} +import com.github.swagger.akka.CustomMediaTypes +import com.github.swagger.akka.SwaggerHttpService.apiDocsBase import com.ltonetwork.api.swagger.SwaggerDocService import com.ltonetwork.settings.RestAPISettings import com.ltonetwork.utils.ScorexLogging @@ -22,16 +24,12 @@ case class CompositeHttpService(system: ActorSystem, apiTypes: Set[Class[_]], ro respondWithHeader(`Access-Control-Allow-Origin`.*) else pass - private val headers: scala.collection.immutable.Seq[String] = scala.collection.immutable.Seq("Authorization", - "Content-Type", - "X-Requested-With", - "Timestamp", - "Signature") ++ - (if (settings.apiKeyDifferentHost) Seq("api_key", "X-API-Key") else Seq.empty[String]) + private val headers: scala.collection.immutable.Seq[String] = + scala.collection.immutable.Seq("Authorization", "Content-Type", "X-Requested-With", "Timestamp", "Signature") ++ + (if (settings.apiKeyDifferentHost) Seq("X-API-Key") else Seq.empty[String]) val compositeRoute: Route = withCors(routes.map(_.route).reduce(_ ~ _)) ~ - swaggerService.routes ~ (pathEndOrSingleSlash | path("swagger")) { redirect("/api-docs/index.html", StatusCodes.PermanentRedirect) } ~ @@ -39,7 +37,13 @@ case class CompositeHttpService(system: ActorSystem, apiTypes: Set[Class[_]], ro pathEndOrSingleSlash { redirect("/api-docs/index.html", StatusCodes.PermanentRedirect) } ~ - getFromResourceDirectory("swagger-ui") + path("swagger.json") { + complete(HttpEntity(MediaTypes.`application/json`, swaggerService.swaggerJson)) + } ~ + path("swagger.yaml") { + complete(HttpEntity(CustomMediaTypes.`text/vnd.yaml`, swaggerService.swaggerYaml)) + } ~ + getFromResourceDirectory("swagger-ui") } ~ options { respondWithDefaultHeaders(`Access-Control-Allow-Credentials`(true), `Access-Control-Allow-Headers`(headers), diff --git a/src/main/scala/com/ltonetwork/api/PeersApiRoute.scala b/src/main/scala/com/ltonetwork/api/PeersApiRoute.scala index f99cafcc40..9f8bd43d2f 100644 --- a/src/main/scala/com/ltonetwork/api/PeersApiRoute.scala +++ b/src/main/scala/com/ltonetwork/api/PeersApiRoute.scala @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.{Content, ExampleObject, Schema} import io.swagger.v3.oas.annotations.parameters.RequestBody import io.swagger.v3.oas.annotations.responses.{ApiResponse, ApiResponses} +import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.tags.Tag import jakarta.ws.rs.{GET, POST, Path} import play.api.libs.json._ @@ -90,6 +91,7 @@ case class PeersApiRoute(settings: RestAPISettings, @Operation( summary = "Connect to peer" ) + @SecurityRequirement(name = "bearerAuth") @RequestBody( description = "Json with data", content = Array( @@ -149,6 +151,7 @@ case class PeersApiRoute(settings: RestAPISettings, @Operation( summary = "Remove all blacklisted peers" ) + @SecurityRequirement(name = "bearerAuth") @ApiResponses( Array( new ApiResponse(responseCode = "200", description = "200") diff --git a/src/main/scala/com/ltonetwork/api/TransactionsApiRoute.scala b/src/main/scala/com/ltonetwork/api/TransactionsApiRoute.scala index b7574f851f..12dc8ed498 100644 --- a/src/main/scala/com/ltonetwork/api/TransactionsApiRoute.scala +++ b/src/main/scala/com/ltonetwork/api/TransactionsApiRoute.scala @@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.media.{Content, Schema} import io.swagger.v3.oas.annotations.parameters.RequestBody import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.{Operation, Parameter, Parameters} +import io.swagger.v3.oas.annotations.security.SecurityRequirement import jakarta.ws.rs.{GET, POST, Path} import play.api.libs.json._ @@ -47,7 +48,7 @@ case class TransactionsApiRoute(settings: RestAPISettings, override lazy val route: Route = pathPrefix("transactions") { - unconfirmed ~ addressLimit ~ info ~ sign ~ calculateFee ~ broadcast + unconfirmed ~ addressLimit ~ info ~ sign ~ submit ~ calculateFee ~ broadcast } private val invalidLimit = StatusCodes.BadRequest -> Json.obj("message" -> "invalid.limit") @@ -196,10 +197,10 @@ case class TransactionsApiRoute(settings: RestAPISettings, ) @RequestBody( description = "Transaction data including type and optional timestamp in milliseconds", - content = Array( - new Content( - schema = new Schema(implementation = classOf[String]), - )), + content = Array(new Content( + schema = new Schema(implementation = classOf[Object]), + mediaType = "application/json", + )), required = true ) def calculateFee: Route = (pathPrefix("calculateFee") & post) { @@ -228,96 +229,83 @@ case class TransactionsApiRoute(settings: RestAPISettings, @Operation( summary = "Sign a transaction" ) + @SecurityRequirement(name = "bearerAuth") @RequestBody( description = "Transaction data including type and optional timestamp in milliseconds", - content = Array( - new Content( - schema = new Schema(implementation = classOf[String]) - )), + content = Array(new Content( + schema = new Schema(implementation = classOf[Object]), + mediaType = "application/json", + )), required = true ) def sign: Route = (pathPrefix("sign") & post & withAuth) { - pathEndOrSingleSlash { - handleExceptions(jsonExceptionHandler) { - json[JsObject] { jsv => - signTransaction((jsv \ "sender").as[String], jsv) - } + handleExceptions(jsonExceptionHandler) { + json[JsObject] { + signTransaction(_) { _.json() } } - } ~ signWithSigner + } } @POST - @Path("/sign/{signerAddress}") + @Path("/broadcast") @Operation( - summary = "Sign a transaction by a private key of signer address" - ) - @Parameters( - Array( - new Parameter( - name = "signerAddress", - description = "Wallet address", - required = true, - schema = new Schema(implementation = classOf[String]), - in = ParameterIn.PATH - ) - ) + summary = "Broadcasts a signed transaction" ) @RequestBody( - description = "Transaction data including type and optional timestamp in milliseconds", - content = Array( - new Content( - schema = new Schema(implementation = classOf[String]) - )), + description = "Transaction data including type and signature", + content = Array(new Content( + schema = new Schema(implementation = classOf[Object]), + mediaType = "application/json", + )), required = true ) - def signWithSigner: Route = pathPrefix(Segment) { signerAddress => + def broadcast: Route = (pathPrefix("broadcast") & post) { handleExceptions(jsonExceptionHandler) { - json[JsObject] { jsv => - signTransaction(signerAddress, jsv) + json[JsObject] { + createTransaction(_) { tx => + doBroadcast(tx) + } } } } - private def signTransaction(signerAddress: String, jsv: JsObject): ToResponseMarshallable = { - TxRequest - .fromJson(jsv) - .flatMap(_.signTx(wallet, signerAddress, time)) - .fold(ApiError.fromValidationError, _.json()) - } - @POST - @Path("/sponsor") + @Path("/submit") @Operation( - summary = "Sponsor a transaction" + summary = "Sign and broadcast a transaction" ) + @SecurityRequirement(name = "bearerAuth") @RequestBody( description = "Transaction data including type and optional timestamp in milliseconds", - content = Array( - new Content( - schema = new Schema(implementation = classOf[String]) - )), + content = Array(new Content( + schema = new Schema(implementation = classOf[Object]), + mediaType = "application/json", + )), required = true ) - def sponsor: Route = (pathPrefix("sign") & post & withAuth) { + def submit: Route = (pathPrefix("submit") & post & withAuth) { pathEndOrSingleSlash { handleExceptions(jsonExceptionHandler) { - json[JsObject] { jsv => - sponsorTransaction((jsv \ "sponsor").as[String], jsv) + json[JsObject] { + signTransaction(_) { tx => + doBroadcast(tx) + } } } - } ~ signWithSigner + } ~ submitByType } @POST - @Path("/sponsor/{signerAddress}") + @Path("/submit/{type}") @Operation( - summary = "Sponsor a transaction by a private key of signer address" + summary = "Sign and broadcast a transaction of a specific type" ) + @SecurityRequirement(name = "bearerAuth") @Parameters( Array( new Parameter( - name = "signerAddress", - description = "Wallet address", + name = "type", + description = "Transaction type", required = true, schema = new Schema(implementation = classOf[String]), in = ParameterIn.PATH @@ -325,45 +313,17 @@ case class TransactionsApiRoute(settings: RestAPISettings, ) ) @RequestBody( - description = "Transaction data including type and optional timestamp in milliseconds", - content = Array( - new Content( - schema = new Schema(implementation = classOf[String]) - )), + description = "Transaction data", + content = Array(new Content( + schema = new Schema(implementation = classOf[Object]), + mediaType = "application/json", + )), required = true ) - def sponsorWithSigner: Route = pathPrefix(Segment) { signerAddress => - handleExceptions(jsonExceptionHandler) { - json[JsObject] { jsv => - sponsorTransaction(signerAddress, jsv) - } - } - } - - private def sponsorTransaction(signerAddress: String, jsv: JsObject): ToResponseMarshallable = { - TxRequest - .fromJson(jsv) - .flatMap(_.sponsorTx(wallet, signerAddress, time)) - .fold(ApiError.fromValidationError, _.json()) - } - - @POST - @Path("/broadcast") - @Operation( - summary = "Broadcasts a signed transaction" - ) - @RequestBody( - description = "Transaction data including type and signature", - content = Array( - new Content( - schema = new Schema(implementation = classOf[String]) - )), - required = true - ) - def broadcast: Route = (pathPrefix("broadcast") & post) { + def submitByType: Route = pathPrefix(Segment) { typeName => handleExceptions(jsonExceptionHandler) { json[JsObject] { - createTransaction(_) { tx => + signTransaction(typeName, _) { tx => doBroadcast(tx) } } @@ -371,13 +331,26 @@ case class TransactionsApiRoute(settings: RestAPISettings, } private def createTransaction(jsv: JsObject)(f: Transaction => ToResponseMarshallable): ToResponseMarshallable = { - TxRequest - .fromJson(jsv) + TxRequest.fromJson(jsv) .flatMap(_.toTx) .flatMap(TransactionsApiRoute.ifPossible(blockchain, _)) .fold(ApiError.fromValidationError, tx => f(tx)) } + private def signTransaction(jsv: JsObject)(f: Transaction => ToResponseMarshallable): ToResponseMarshallable = { + TxRequest.fromJson(jsv) + .flatMap(req => req.signTx(wallet, time)) + .flatMap(TransactionsApiRoute.ifPossible(blockchain, _)) + .fold(ApiError.fromValidationError, tx => f(tx)) + } + + private def signTransaction(typeName: String, jsv: JsObject)(f: Transaction => ToResponseMarshallable): ToResponseMarshallable = { + TxRequest.fromJson(typeName, jsv) + .flatMap(req => req.signTx(wallet, time)) + .flatMap(TransactionsApiRoute.ifPossible(blockchain, _)) + .fold(ApiError.fromValidationError, tx => f(tx)) + } + private def txToExtendedJson(tx: Transaction): JsObject = { import com.ltonetwork.transaction.lease.LeaseTransaction tx match { diff --git a/src/main/scala/com/ltonetwork/api/WalletApiRoute.scala b/src/main/scala/com/ltonetwork/api/WalletApiRoute.scala index d70e1c5ad6..600478b7c0 100644 --- a/src/main/scala/com/ltonetwork/api/WalletApiRoute.scala +++ b/src/main/scala/com/ltonetwork/api/WalletApiRoute.scala @@ -1,22 +1,24 @@ package com.ltonetwork.api import akka.http.scaladsl.server.Route -import com.ltonetwork.account.Address +import com.ltonetwork.account.{Address, PublicKeyAccount} import AddressApiRoute.Signed import com.ltonetwork.crypto import com.ltonetwork.settings.RestAPISettings import com.ltonetwork.utils.Base58 import com.ltonetwork.wallet.Wallet import io.swagger.v3.oas.annotations.enums.ParameterIn -import io.swagger.v3.oas.annotations.media.{Content, Schema} +import io.swagger.v3.oas.annotations.media.{Content, ExampleObject, Schema} import io.swagger.v3.oas.annotations.parameters.RequestBody import io.swagger.v3.oas.annotations.responses.{ApiResponse, ApiResponses} +import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.{Operation, Parameter, Parameters} import jakarta.ws.rs.{DELETE, GET, POST, Path} import play.api.libs.json.{JsArray, JsBoolean, JsString, Json} import java.nio.charset.StandardCharsets +import scala.util.{Success, Try} @Path("/wallet") @Tag(name = "wallet") @@ -45,6 +47,7 @@ case class WalletApiRoute(settings: RestAPISettings, wallet: Wallet) extends Api @Operation( summary = "Create a new account in the wallet (if it exists)" ) + @SecurityRequirement(name = "bearerAuth") def createAddress: Route = (path("addresses") & post & withAuth) { wallet.generateNewAccount() match { case Right(Some(pka)) => complete(Json.obj("address" -> pka.address)) @@ -58,6 +61,7 @@ case class WalletApiRoute(settings: RestAPISettings, wallet: Wallet) extends Api @Operation( summary = "Remove the account with address from the wallet" ) + @SecurityRequirement(name = "bearerAuth") @Parameters( Array( new Parameter( @@ -124,6 +128,7 @@ case class WalletApiRoute(settings: RestAPISettings, wallet: Wallet) extends Api @Operation( summary = "Sign a message with a private key associated with address" ) + @SecurityRequirement(name = "bearerAuth") @Parameters( Array( new Parameter( @@ -162,6 +167,7 @@ case class WalletApiRoute(settings: RestAPISettings, wallet: Wallet) extends Api @Operation( summary = "Sign a message with a private key associated with address" ) + @SecurityRequirement(name = "bearerAuth") @Parameters( Array( new Parameter( diff --git a/src/main/scala/com/ltonetwork/api/requests/AnchorRequest.scala b/src/main/scala/com/ltonetwork/api/requests/AnchorRequest.scala index 55716676c1..322e0c1f98 100644 --- a/src/main/scala/com/ltonetwork/api/requests/AnchorRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/AnchorRequest.scala @@ -10,31 +10,32 @@ import play.api.libs.json.{Format, JsObject, Json} case class AnchorRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - anchors: List[String], + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + anchors: List[String], signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[AnchorTransaction] { protected def sign(tx: AnchorTransaction, signer: PrivateKeyAccount): AnchorTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, AnchorTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, AnchorTransaction] = for { validAnchors <- anchors.traverse(s => parseBase58(s, "invalid anchor", AnchorTransaction.MaxAnchorStringSize)) - validProofs <- toProofs(signature, proofs) tx <- AnchorTransaction.create( version.getOrElse(AnchorTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validAnchors, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/CancelLeaseRequest.scala b/src/main/scala/com/ltonetwork/api/requests/CancelLeaseRequest.scala index 0d94497191..47fbf463fe 100644 --- a/src/main/scala/com/ltonetwork/api/requests/CancelLeaseRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/CancelLeaseRequest.scala @@ -10,30 +10,31 @@ import play.api.libs.json._ case class CancelLeaseRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - leaseId: ByteStr, + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + leaseId: ByteStr, signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[CancelLeaseTransaction] { protected def sign(tx: CancelLeaseTransaction, signer: PrivateKeyAccount): CancelLeaseTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, CancelLeaseTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, CancelLeaseTransaction] = for { - validProofs <- toProofs(signature, proofs) tx <- CancelLeaseTransaction.create( version.getOrElse(CancelLeaseTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, leaseId, sponsor, - validProofs + proofs ) } yield tx } @@ -42,12 +43,14 @@ object CancelLeaseRequest { implicit val jsonFormat: Format[CancelLeaseRequest] = Format( ((JsPath \ "version").readNullable[Byte] and (JsPath \ "timestamp").readNullable[Long] and + (JsPath \ "fee").read[Long] and + (JsPath \ "sender").readNullable[String] and (JsPath \ "senderKeyType").readNullable[String] and (JsPath \ "senderPublicKey").readNullable[String] and - (JsPath \ "fee").read[Long] and - (JsPath \ "leaseId").read[ByteStr].orElse((JsPath \ "txId").read[ByteStr]) and + (JsPath \ "sponsor").readNullable[String] and (JsPath \ "sponsorKeyType").readNullable[String] and (JsPath \ "sponsorPublicKey").readNullable[String] and + (JsPath \ "leaseId").read[ByteStr].orElse((JsPath \ "txId").read[ByteStr]) and (JsPath \ "signature").readNullable[ByteStr] and (JsPath \ "proofs").readNullable[Proofs])(CancelLeaseRequest.apply _), Json.writes[CancelLeaseRequest].transform((json: JsObject) => Json.obj("type" -> CancelLeaseTransaction.typeId.toInt) ++ json) diff --git a/src/main/scala/com/ltonetwork/api/requests/CancelSponsorshipRequest.scala b/src/main/scala/com/ltonetwork/api/requests/CancelSponsorshipRequest.scala index a7a2f11bc7..0f9ee1f6b7 100644 --- a/src/main/scala/com/ltonetwork/api/requests/CancelSponsorshipRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/CancelSponsorshipRequest.scala @@ -9,33 +9,32 @@ import play.api.libs.json.{Format, JsObject, Json} case class CancelSponsorshipRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - recipient: String, + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + recipient: String, signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[CancelSponsorshipTransaction] { protected def sign(tx: CancelSponsorshipTransaction, signer: PrivateKeyAccount): CancelSponsorshipTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, - sponsor: Option[PublicKeyAccount], - time: Option[Time]): Either[ValidationError, CancelSponsorshipTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, CancelSponsorshipTransaction] = for { validRecipient <- Address.fromString(recipient) - validProofs <- toProofs(signature, proofs) tx <- CancelSponsorshipTransaction.create( version.getOrElse(CancelSponsorshipTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validRecipient, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/DataRequest.scala b/src/main/scala/com/ltonetwork/api/requests/DataRequest.scala index 6f7f842f49..ceb6518491 100644 --- a/src/main/scala/com/ltonetwork/api/requests/DataRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/DataRequest.scala @@ -9,30 +9,31 @@ import play.api.libs.json.{Format, JsObject, Json} case class DataRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - data: List[DataEntry[_]], + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + data: List[DataEntry[_]], signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[DataTransaction] { protected def sign(tx: DataTransaction, signer: PrivateKeyAccount): DataTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, DataTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, DataTransaction] = for { - validProofs <- toProofs(signature, proofs) tx <- DataTransaction.create( version.getOrElse(DataTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, data, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/IssueAssociationRequest.scala b/src/main/scala/com/ltonetwork/api/requests/IssueAssociationRequest.scala index 90beb5d497..63facf8378 100644 --- a/src/main/scala/com/ltonetwork/api/requests/IssueAssociationRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/IssueAssociationRequest.scala @@ -10,31 +10,30 @@ import play.api.libs.json._ case class IssueAssociationRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, + sponsor: Option[String] = None, + sponsorKeyType: Option[String] = None, + sponsorPublicKey: Option[String] = None, recipient: String, associationType: Int, expires: Option[Long] = None, hash: Option[ByteStr] = None, - sponsorKeyType: Option[String] = None, - sponsorPublicKey: Option[String] = None, signature: Option[ByteStr] = None, - proofs: Option[Proofs] = None, -) extends TxRequest.For[IssueAssociationTransaction] { + proofs: Option[Proofs] = None) + extends TxRequest.For[IssueAssociationTransaction] { protected def sign(tx: IssueAssociationTransaction, signer: PrivateKeyAccount): IssueAssociationTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, - sponsor: Option[PublicKeyAccount], - time: Option[Time]): Either[ValidationError, IssueAssociationTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, IssueAssociationTransaction] = for { validRecipient <- Address.fromString(recipient) - validProofs <- toProofs(signature, proofs) tx <- IssueAssociationTransaction.create( version.getOrElse(IssueAssociationTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validRecipient, @@ -42,7 +41,7 @@ case class IssueAssociationRequest(version: Option[Byte] = None, expires, hash.noneIfEmpty, sponsor, - validProofs + proofs ) } yield tx } @@ -51,15 +50,17 @@ object IssueAssociationRequest { implicit val jsonFormat: Format[IssueAssociationRequest] = Format( ((JsPath \ "version").readNullable[Byte] and (JsPath \ "timestamp").readNullable[Long] and + (JsPath \ "fee").read[Long] and + (JsPath \ "sender").readNullable[String] and (JsPath \ "senderKeyType").readNullable[String] and (JsPath \ "senderPublicKey").readNullable[String] and - (JsPath \ "fee").read[Long] and + (JsPath \ "sponsor").readNullable[String] and + (JsPath \ "sponsorKeyType").readNullable[String] and + (JsPath \ "sponsorPublicKey").readNullable[String] and (JsPath \ "recipient").read[String].orElse((JsPath \ "party").read[String]) and (JsPath \ "associationType").read[Int] and (JsPath \ "expires").readNullable[Long] and (JsPath \ "hash").readNullable[ByteStr] and - (JsPath \ "sponsorKeyType").readNullable[String] and - (JsPath \ "sponsorPublicKey").readNullable[String] and (JsPath \ "signature").readNullable[ByteStr] and (JsPath \ "proofs").readNullable[Proofs])(IssueAssociationRequest.apply _), Json.writes[IssueAssociationRequest].transform((json: JsObject) => Json.obj("type" -> IssueAssociationTransaction.typeId.toInt) ++ json) diff --git a/src/main/scala/com/ltonetwork/api/requests/LeaseRequest.scala b/src/main/scala/com/ltonetwork/api/requests/LeaseRequest.scala index 0a526e5072..b3c0f88788 100644 --- a/src/main/scala/com/ltonetwork/api/requests/LeaseRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/LeaseRequest.scala @@ -4,38 +4,38 @@ import com.ltonetwork.account.{Address, PrivateKeyAccount, PublicKeyAccount} import com.ltonetwork.state.ByteStr import com.ltonetwork.transaction.lease.LeaseTransaction import com.ltonetwork.transaction.{Proofs, ValidationError} -import com.ltonetwork.utils.Time import play.api.libs.json.{Format, JsObject, Json} case class LeaseRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - recipient: String, - amount: Long, + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + recipient: String, + amount: Long, signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[LeaseTransaction] { protected def sign(tx: LeaseTransaction, signer: PrivateKeyAccount): LeaseTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, LeaseTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, LeaseTransaction] = for { validRecipient <- Address.fromString(recipient) - validProofs <- toProofs(signature, proofs) tx <- LeaseTransaction.create( version.getOrElse(LeaseTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validRecipient, amount, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/MassTransferRequest.scala b/src/main/scala/com/ltonetwork/api/requests/MassTransferRequest.scala index 40d753c617..2c810c5d37 100644 --- a/src/main/scala/com/ltonetwork/api/requests/MassTransferRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/MassTransferRequest.scala @@ -10,33 +10,34 @@ import play.api.libs.json.{Format, JsObject, Json} case class MassTransferRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - transfers: List[Transfer], - attachment: Option[ByteStr] = None, + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + transfers: List[Transfer], + attachment: Option[ByteStr] = None, signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[MassTransferTransaction] { protected def sign(tx: MassTransferTransaction, signer: PrivateKeyAccount): MassTransferTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, MassTransferTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, MassTransferTransaction] = for { validTransfers <- MassTransferTransaction.parseTransfersList(transfers) - validProofs <- toProofs(signature, proofs) tx <- MassTransferTransaction.create( version.getOrElse(MassTransferTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validTransfers, attachment.getOrElse(ByteStr.empty).arr, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/RegisterRequest.scala b/src/main/scala/com/ltonetwork/api/requests/RegisterRequest.scala index 72d887ce14..9872a744df 100644 --- a/src/main/scala/com/ltonetwork/api/requests/RegisterRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/RegisterRequest.scala @@ -11,31 +11,32 @@ import play.api.libs.json.{Format, JsObject, Json} case class RegisterRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - accounts: List[RegisterRequest.Account], + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + accounts: List[RegisterRequest.Account], signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[RegisterTransaction] { protected def sign(tx: RegisterTransaction, signer: PrivateKeyAccount): RegisterTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, RegisterTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, RegisterTransaction] = for { validAccounts <- accounts.traverse(_.toAccount) - validProofs <- toProofs(signature, proofs) tx <- RegisterTransaction.create( version.getOrElse(RegisterTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validAccounts, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/RevokeAssociationRequest.scala b/src/main/scala/com/ltonetwork/api/requests/RevokeAssociationRequest.scala index 4882a432fc..521129a54a 100644 --- a/src/main/scala/com/ltonetwork/api/requests/RevokeAssociationRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/RevokeAssociationRequest.scala @@ -10,37 +10,36 @@ import play.api.libs.json._ case class RevokeAssociationRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, + sponsor: Option[String] = None, + sponsorKeyType: Option[String] = None, + sponsorPublicKey: Option[String] = None, recipient: String, associationType: Int, hash: Option[ByteStr] = None, - sponsorKeyType: Option[String] = None, - sponsorPublicKey: Option[String] = None, signature: Option[ByteStr] = None, proofs: Option[Proofs] = None, ) extends TxRequest.For[RevokeAssociationTransaction] { protected def sign(tx: RevokeAssociationTransaction, signer: PrivateKeyAccount): RevokeAssociationTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, - sponsor: Option[PublicKeyAccount], - time: Option[Time]): Either[ValidationError, RevokeAssociationTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, RevokeAssociationTransaction] = for { validRecipient <- Address.fromString(recipient) - validProofs <- toProofs(signature, proofs) tx <- RevokeAssociationTransaction.create( version.getOrElse(RevokeAssociationTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validRecipient, associationType, hash.noneIfEmpty, sponsor, - validProofs + proofs ) } yield tx } @@ -49,14 +48,16 @@ object RevokeAssociationRequest { implicit val jsonFormat: Format[RevokeAssociationRequest] = Format( ((JsPath \ "version").readNullable[Byte] and (JsPath \ "timestamp").readNullable[Long] and + (JsPath \ "fee").read[Long] and + (JsPath \ "sender").readNullable[String] and (JsPath \ "senderKeyType").readNullable[String] and (JsPath \ "senderPublicKey").readNullable[String] and - (JsPath \ "fee").read[Long] and + (JsPath \ "sponsor").readNullable[String] and + (JsPath \ "sponsorKeyType").readNullable[String] and + (JsPath \ "sponsorPublicKey").readNullable[String] and (JsPath \ "recipient").read[String].orElse((JsPath \ "party").read[String]) and (JsPath \ "associationType").read[Int] and (JsPath \ "hash").readNullable[ByteStr] and - (JsPath \ "sponsorKeyType").readNullable[String] and - (JsPath \ "sponsorPublicKey").readNullable[String] and (JsPath \ "signature").readNullable[ByteStr] and (JsPath \ "proofs").readNullable[Proofs])(RevokeAssociationRequest.apply _), Json.writes[RevokeAssociationRequest].transform((json: JsObject) => Json.obj("type" -> RevokeAssociationTransaction.typeId.toInt) ++ json) diff --git a/src/main/scala/com/ltonetwork/api/requests/SetScriptRequest.scala b/src/main/scala/com/ltonetwork/api/requests/SetScriptRequest.scala index 8bb6d54cce..6e38538f8a 100644 --- a/src/main/scala/com/ltonetwork/api/requests/SetScriptRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/SetScriptRequest.scala @@ -10,12 +10,14 @@ import play.api.libs.json.{Format, JsObject, Json, OWrites} case class SetScriptRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - script: Option[String], + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + script: Option[String], signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[SetScriptTransaction] { @@ -28,19 +30,18 @@ case class SetScriptRequest(version: Option[Byte] = None, protected def sign(tx: SetScriptTransaction, signer: PrivateKeyAccount): SetScriptTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, SetScriptTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, SetScriptTransaction] = for { validScript <- decodedScript - validProofs <- toProofs(signature, proofs) tx <- SetScriptTransaction.create( version.getOrElse(SetScriptTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validScript, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/SponsorshipRequest.scala b/src/main/scala/com/ltonetwork/api/requests/SponsorshipRequest.scala index fc67aae0f6..d4da863f99 100644 --- a/src/main/scala/com/ltonetwork/api/requests/SponsorshipRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/SponsorshipRequest.scala @@ -9,31 +9,32 @@ import play.api.libs.json.{Format, JsObject, Json} case class SponsorshipRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, - recipient: String, + sponsor: Option[String] = None, sponsorKeyType: Option[String] = None, sponsorPublicKey: Option[String] = None, + recipient: String, signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[SponsorshipTransaction] { protected def sign(tx: SponsorshipTransaction, signer: PrivateKeyAccount): SponsorshipTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, SponsorshipTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, SponsorshipTransaction] = for { validRecipient <- Address.fromString(recipient) - validProofs <- toProofs(signature, proofs) tx <- SponsorshipTransaction.create( version.getOrElse(SponsorshipTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validRecipient, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/TransferRequest.scala b/src/main/scala/com/ltonetwork/api/requests/TransferRequest.scala index a0eb93d19b..a6732865a1 100644 --- a/src/main/scala/com/ltonetwork/api/requests/TransferRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/TransferRequest.scala @@ -9,35 +9,36 @@ import play.api.libs.json._ case class TransferRequest(version: Option[Byte] = None, timestamp: Option[Long] = None, + fee: Long, + sender: Option[String] = None, senderKeyType: Option[String] = None, senderPublicKey: Option[String] = None, - fee: Long, + sponsor: Option[String] = None, + sponsorKeyType: Option[String] = None, + sponsorPublicKey: Option[String] = None, recipient: String, amount: Long, attachment: Option[ByteStr] = None, - sponsorKeyType: Option[String] = None, - sponsorPublicKey: Option[String] = None, signature: Option[ByteStr] = None, proofs: Option[Proofs] = None) extends TxRequest.For[TransferTransaction] { protected def sign(tx: TransferTransaction, signer: PrivateKeyAccount): TransferTransaction = tx.signWith(signer) - def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, TransferTransaction] = + def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, TransferTransaction] = for { validRecipient <- Address.fromString(recipient) - validProofs <- toProofs(signature, proofs) tx <- TransferTransaction.create( version.getOrElse(TransferTransaction.latestVersion), None, - timestamp(time), + timestamp, sender, fee, validRecipient, amount, attachment.getOrElse(ByteStr.empty).arr, sponsor, - validProofs + proofs ) } yield tx } diff --git a/src/main/scala/com/ltonetwork/api/requests/TxRequest.scala b/src/main/scala/com/ltonetwork/api/requests/TxRequest.scala index 54aa506632..89899438b3 100644 --- a/src/main/scala/com/ltonetwork/api/requests/TxRequest.scala +++ b/src/main/scala/com/ltonetwork/api/requests/TxRequest.scala @@ -1,7 +1,10 @@ package com.ltonetwork.api.requests +import cats.data.Validated import com.ltonetwork.account.KeyTypes.keyType import com.ltonetwork.account.{PrivateKeyAccount, PublicKeyAccount} +import com.ltonetwork.settings._ +import com.ltonetwork.state.ByteStr import com.ltonetwork.transaction.ValidationError.{GenericError, InvalidPublicKey} import com.ltonetwork.transaction.anchor.AnchorTransaction import com.ltonetwork.transaction.association.{IssueAssociationTransaction, RevokeAssociationTransaction} @@ -11,23 +14,28 @@ import com.ltonetwork.transaction.register.RegisterTransaction import com.ltonetwork.transaction.smart.SetScriptTransaction import com.ltonetwork.transaction.sponsorship.{CancelSponsorshipTransaction, SponsorshipTransaction} import com.ltonetwork.transaction.transfer.{MassTransferTransaction, TransferTransaction} -import com.ltonetwork.transaction.{Transaction, TransactionBuilders, ValidationError} +import com.ltonetwork.transaction.{Proofs, Transaction, TransactionBuilders, ValidationError} import com.ltonetwork.utils.Time import com.ltonetwork.wallet.Wallet +import io.swagger.v3.oas.annotations.Hidden import play.api.libs.json.JsObject trait TxRequest { type TransactionT <: Transaction val timestamp: Option[Long] + val sender: Option[String] val senderKeyType: Option[String] val senderPublicKey: Option[String] + val sponsor: Option[String] val sponsorKeyType: Option[String] val sponsorPublicKey: Option[String] + val signature: Option[ByteStr] + val proofs: Option[Proofs] protected def timestamp(time: Option[Time]): Long = timestamp.getOrElse(time.fold(defaultTimestamp)(_.getTimestamp())) - protected def sign(tx: TransactionT, signer: PrivateKeyAccount): TransactionT + implicit protected def sign(tx: TransactionT, signer: PrivateKeyAccount): TransactionT private def publicKeyAccount(keyTypeOpt: Option[String], publicKey: String): Either[ValidationError, PublicKeyAccount] = for { @@ -35,43 +43,53 @@ trait TxRequest { acc <- PublicKeyAccount.fromBase58String(kt, publicKey) } yield acc - protected def resolveSender: Either[ValidationError, PublicKeyAccount] = - senderPublicKey match { - case Some(key) => publicKeyAccount(senderKeyType, key) - case None => Left(InvalidPublicKey("invalid.senderPublicKey")) - } + protected def resolveSender: Either[ValidationError, PublicKeyAccount] = { + for { + account <- senderPublicKey match { + case Some(key) => publicKeyAccount(senderKeyType, key) + case None => Left(InvalidPublicKey("missing senderPublicKey")) + } + _ <- Validated.cond(sender.forall(account.address == _), (), InvalidPublicKey("senderPublicKey doesn't match sender address")).toEither + } yield account + } - protected def resolveSender(default: PublicKeyAccount): Either[ValidationError, PublicKeyAccount] = - senderPublicKey.map(key => PublicKeyAccount.fromBase58String(key)).getOrElse(Right(default)) + protected def resolveSender(wallet: Wallet): Either[ValidationError, PublicKeyAccount] = + sender.map(wallet.findPrivateKey).getOrElse(resolveSender) + + protected def resolveSponsor: Either[ValidationError, Option[PublicKeyAccount]] = { + for { + account <- sponsorPublicKey + .map(publicKeyAccount(sponsorKeyType, _)) + .fold[Either[ValidationError, Option[PublicKeyAccount]]](Right(None))(_.map(k => Some(k))) + _ <- Validated.cond( + account.isEmpty || sponsor.isEmpty || account.get.address == sponsor.get, + (), + InvalidPublicKey("sponsorPublicKey doesn't match sponsor address") + ).toEither + } yield account + } - protected def resolveSponsor: Either[ValidationError, Option[PublicKeyAccount]] = - sponsorPublicKey - .map(publicKeyAccount(sponsorKeyType, _)) - .fold[Either[ValidationError, Option[PublicKeyAccount]]](Right(None))(_.map(k => Some(k))) + protected def resolveSponsor(wallet: Wallet): Either[ValidationError, Option[PublicKeyAccount]] = + sponsor.map(address => wallet.findPrivateKey(address).map(Some(_))).getOrElse(resolveSponsor) - protected def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], time: Option[Time]): Either[ValidationError, TransactionT] + protected def toTxFrom(sender: PublicKeyAccount, sponsor: Option[PublicKeyAccount], proofs: Proofs, timestamp: Long): Either[ValidationError, TransactionT] def toTx: Either[ValidationError, TransactionT] = for { - sender <- resolveSender - sponsor <- resolveSponsor - tx <- toTxFrom(sender, sponsor, None) + senderAcc <- resolveSender + sponsorAcc <- resolveSponsor + validProofs <- toProofs(signature, proofs) + tx <- toTxFrom(senderAcc, sponsorAcc, validProofs, timestamp.getOrElse(defaultTimestamp)) } yield tx - def signTx(wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, TransactionT] = - for { - signer <- wallet.findPrivateKey(signerAddress) - sender <- resolveSender(signer) - sponsor <- resolveSponsor - tx <- toTxFrom(sender, sponsor, Some(time)) - } yield sign(tx, signer) + def signTx(wallet: Wallet, time: Time): Either[ValidationError, TransactionT] = - def sponsorTx(wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, TransactionT] = for { - signer <- wallet.findPrivateKey(signerAddress) - sender <- resolveSender - tx <- toTxFrom(sender, Some(signer), Some(time)) - } yield sign(tx, signer) + senderAcc <- resolveSender(wallet) + sponsorAcc <- resolveSponsor(wallet) + validProofs <- toProofs(signature, proofs) + tx <- toTxFrom(senderAcc, sponsorAcc, validProofs, timestamp.getOrElse(time.getTimestamp())) + } yield tx.signMaybe(senderAcc).signMaybe(sponsorAcc) } object TxRequest { @@ -79,9 +97,17 @@ object TxRequest { override type TransactionT = T } - def fromJson(jsv: JsObject): Either[ValidationError, TxRequest] = { - val typeId = (jsv \ "type").as[Byte] + def fromJson(jsv: JsObject): Either[ValidationError, TxRequest] = + fromJson((jsv \ "type").as[Byte], jsv) + + def fromJson(typeName: String, jsv: JsObject): Either[ValidationError, TxRequest] = { + for { + typeId <- transactionTypes.get(typeName).toRight(GenericError(s"Bad transaction type ($typeName)")) + txRequest <- fromJson(typeId, jsv) + } yield txRequest + } + def fromJson(typeId: Byte, jsv: JsObject): Either[ValidationError, TxRequest] = { TransactionBuilders.by(typeId) match { case None => Left(GenericError(s"Bad transaction type ($typeId)")) case Some(x) => diff --git a/src/main/scala/com/ltonetwork/api/requests/package.scala b/src/main/scala/com/ltonetwork/api/requests/package.scala index 625547ec60..1142e88cfc 100644 --- a/src/main/scala/com/ltonetwork/api/requests/package.scala +++ b/src/main/scala/com/ltonetwork/api/requests/package.scala @@ -1,10 +1,11 @@ package com.ltonetwork.api import cats.Applicative +import com.ltonetwork.account.{PrivateKeyAccount, PublicKeyAccount} import com.ltonetwork.crypto.{digestLength, signatureLength} import com.ltonetwork.state.ByteStr import com.ltonetwork.transaction.ValidationError._ -import com.ltonetwork.transaction.{Proofs, TransactionBuilder, ValidationError} +import com.ltonetwork.transaction.{Proofs, Transaction, TransactionBuilder, ValidationError} import com.ltonetwork.utils.base58Length import play.api.libs.json._ import supertagged.TaggedType @@ -87,30 +88,11 @@ package object requests { private[requests] def defaultTimestamp = 0L -// implicit val fetchKeyTypeRead: Format[KeyType] = { -// val readStringFromInt: Reads[String] = implicitly[Reads[Int]] -// .map(x => x.toString) -// -// Format[KeyType]( -// Reads { js => -// val kt = (JsPath \ "senderKeyType").read[String].orElse(readStringFromInt).reads(js) -// kt.fold( -// _ => JsError("senderKeyType incorrect"), { -// case "ed25519" => JsSuccess(ED25519) -// case "1" => JsSuccess(ED25519) -// case "secp256k1" => JsSuccess(SECP256K1) -// case "2" => JsSuccess(SECP256K1) -// case "secp256r1" => JsSuccess(SECP256R1) -// case "3" => JsSuccess(SECP256R1) -// } -// ) -// }, -// Writes { -// case ED25519 => JsObject(Seq("senderKeyType" -> JsString("ed25519"))) -// case SECP256K1 => JsObject(Seq("senderKeyType" -> JsString("secp256k1"))) -// case SECP256R1 => JsObject(Seq("senderKeyType" -> JsString("secp256r1"))) -// } -// ) -// } - + implicit class TransactionSignOps[T <: Transaction](val tx: T) extends AnyVal { + def signMaybe(signer: Option[PublicKeyAccount])(implicit sign: (T, PrivateKeyAccount) => T): T = signer match { + case Some(account: PrivateKeyAccount) => sign(tx, account) + case _ => tx + } + def signMaybe(signer: PublicKeyAccount)(implicit sign: (T, PrivateKeyAccount) => T): T = signMaybe(Some(signer)) + } } diff --git a/src/main/scala/com/ltonetwork/api/swagger/SwaggerDocService.scala b/src/main/scala/com/ltonetwork/api/swagger/SwaggerDocService.scala index aab97149e1..7d01366f87 100644 --- a/src/main/scala/com/ltonetwork/api/swagger/SwaggerDocService.scala +++ b/src/main/scala/com/ltonetwork/api/swagger/SwaggerDocService.scala @@ -1,33 +1,42 @@ package com.ltonetwork.api.swagger import akka.actor.ActorSystem -import com.github.swagger.akka.SwaggerHttpService +import akka.http.scaladsl.model.{HttpEntity, MediaTypes} +import akka.http.scaladsl.server.Route +import com.github.swagger.akka.{CustomMediaTypes, SwaggerGenerator, SwaggerHttpService} +import com.github.swagger.akka.SwaggerHttpService.apiDocsBase +import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} import com.ltonetwork.Version import com.ltonetwork.settings.RestAPISettings import io.swagger.v3.oas.models.info.{Info, License} -import io.swagger.v3.oas.models.security.SecurityScheme +import io.swagger.v3.oas.models.security.{SecurityRequirement, SecurityScheme} import io.swagger.v3.oas.models.servers.Server import io.swagger.v3.oas.models.{Components, OpenAPI} -class SwaggerDocService(val actorSystem: ActorSystem, val apiClasses: Set[Class[_]], settings: RestAPISettings) extends SwaggerHttpService { +class SwaggerDocService(val actorSystem: ActorSystem, val apiClasses: Set[Class[_]], settings: RestAPISettings) extends SwaggerGenerator { + import SwaggerHttpService._ override val host: String = settings.bindAddress + ":" + settings.port + lazy val swaggerJson = generateSwaggerJson + lazy val swaggerYaml = generateSwaggerYaml + val license = new License() license.setName("Apache License, Version 2.0") license.setUrl("https://github.com/legalthings/PublicNode/blob/master/LICENSE") override val info = new Info() - .title("LTO Public Full Node") + .title("LTO Public Node") .version(Version.VersionString) .description("The Web Interface to the LTO Public Node API") .license(license) val scheme = new SecurityScheme() scheme.setType(SecurityScheme.Type.HTTP) + scheme.in(SecurityScheme.In.HEADER) scheme.setScheme("bearer") - scheme.setBearerFormat("JWT") override val components = Option(new Components().addSecuritySchemes("bearerAuth", scheme)) + override val securitySchemes = Map({ "bearerAuth" -> scheme }); //Let swagger-ui determine the host and port override val swaggerConfig: OpenAPI = new OpenAPI() diff --git a/src/main/scala/com/ltonetwork/http/ApiKey.scala b/src/main/scala/com/ltonetwork/http/ApiKey.scala new file mode 100644 index 0000000000..cecb28593a --- /dev/null +++ b/src/main/scala/com/ltonetwork/http/ApiKey.scala @@ -0,0 +1,16 @@ +package com.ltonetwork.http + +import akka.http.scaladsl.model.headers._ + +import scala.util.Try + +object ApiKey extends ModeledCustomHeaderCompanion[ApiKey] { + override val name = "X-API-Key" + override def parse(value: String) = Try(new ApiKey(value)) +} + +final class ApiKey(override val value: String) extends ModeledCustomHeader[ApiKey] { + override def companion = ApiKey + override def renderInRequests = true + override def renderInResponses = false +} diff --git a/src/main/scala/com/ltonetwork/http/DebugApiRoute.scala b/src/main/scala/com/ltonetwork/http/DebugApiRoute.scala index 6b49c12910..b9f8b0289e 100644 --- a/src/main/scala/com/ltonetwork/http/DebugApiRoute.scala +++ b/src/main/scala/com/ltonetwork/http/DebugApiRoute.scala @@ -23,6 +23,7 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn import io.swagger.v3.oas.annotations.media.{Content, ExampleObject, Schema} import io.swagger.v3.oas.annotations.parameters.RequestBody import io.swagger.v3.oas.annotations.responses.{ApiResponse, ApiResponses} +import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.{Operation, Parameter, Parameters} import jakarta.ws.rs.{DELETE, GET, POST, Path} @@ -70,6 +71,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "Get sizes and full hashes for last blocks", ) + @SecurityRequirement(name = "bearerAuth") @Parameters( Array( new Parameter( @@ -95,6 +97,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "Prints a string at DEBUG level, strips to 100 chars" ) + @SecurityRequirement(name = "bearerAuth") @RequestBody( description = "Json with data", content = Array( @@ -155,6 +158,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "Get current state" ) + @SecurityRequirement(name = "bearerAuth") @ApiResponses(Array(new ApiResponse(responseCode = "200", description = "Json state"))) def state: Route = (path("state") & get & withAuth) { complete(ng.ltoDistribution(ng.height).map { case (a, b) => a.stringRepr -> b }) @@ -202,6 +206,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "Removes all blocks after given height" ) + @SecurityRequirement(name = "bearerAuth") @RequestBody( description = "Json with data", content = Array( @@ -234,6 +239,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "All info you need to debug" ) + @SecurityRequirement(name = "bearerAuth") @ApiResponses( Array( new ApiResponse(responseCode = "200", description = "Json state") @@ -255,6 +261,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "All miner info you need to debug" ) + @SecurityRequirement(name = "bearerAuth") @ApiResponses( Array( new ApiResponse(responseCode = "200", description = "Json state") @@ -277,6 +284,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "All history info you need to debug" ) + @SecurityRequirement(name = "bearerAuth") @ApiResponses( Array( new ApiResponse(responseCode = "200", description = "Json state") @@ -293,6 +301,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "Currently running node config" ) + @SecurityRequirement(name = "bearerAuth") @Parameters( Array( new Parameter( @@ -317,6 +326,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "Rollback the state to the block with a given signature" ) + @SecurityRequirement(name = "bearerAuth") @Parameters( Array( new Parameter( @@ -344,6 +354,7 @@ case class DebugApiRoute(ws: LtoSettings, @Operation( summary = "Moving peer to blacklist" ) + @SecurityRequirement(name = "bearerAuth") @RequestBody( description = "IP address of node", content = Array( diff --git a/src/main/scala/com/ltonetwork/http/NodeApiRoute.scala b/src/main/scala/com/ltonetwork/http/NodeApiRoute.scala index 2a0f3d6c73..711530d4cc 100644 --- a/src/main/scala/com/ltonetwork/http/NodeApiRoute.scala +++ b/src/main/scala/com/ltonetwork/http/NodeApiRoute.scala @@ -8,6 +8,7 @@ import com.ltonetwork.state.Blockchain import com.ltonetwork.utils.ScorexLogging import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.tags.Tag import jakarta.ws.rs.{GET, POST, Path} import play.api.libs.json.Json @@ -42,6 +43,7 @@ case class NodeApiRoute(settings: RestAPISettings, blockchain: Blockchain, appli @Operation( summary = "Stop the node" ) + @SecurityRequirement(name = "bearerAuth") def stop: Route = (post & path("stop") & withAuth) { log.info("Request to stop application") application.shutdown() diff --git a/src/main/scala/com/ltonetwork/http/api_key.scala b/src/main/scala/com/ltonetwork/http/api_key.scala deleted file mode 100644 index 847c1f38e3..0000000000 --- a/src/main/scala/com/ltonetwork/http/api_key.scala +++ /dev/null @@ -1,27 +0,0 @@ -package com.ltonetwork.http - -import akka.http.scaladsl.model.headers._ - -import scala.util.Try - -object api_key extends ModeledCustomHeaderCompanion[api_key] { - override val name = "X-API-Key" - override def parse(value: String) = Try(new api_key(value)) -} - -final class api_key(override val value: String) extends ModeledCustomHeader[api_key] { - override def companion = api_key - override def renderInRequests = true - override def renderInResponses = false -} - -object deprecated_api_key extends ModeledCustomHeaderCompanion[deprecated_api_key] { - override val name = "api_key" - override def parse(value: String) = Try(new deprecated_api_key(value)) -} - -final class deprecated_api_key(override val value: String) extends ModeledCustomHeader[deprecated_api_key] { - override def companion = deprecated_api_key - override def renderInRequests = true - override def renderInResponses = false -} diff --git a/src/main/scala/com/ltonetwork/settings/FeesSettings.scala b/src/main/scala/com/ltonetwork/settings/FeesSettings.scala index c65b8e3f3f..65f5bb61a9 100644 --- a/src/main/scala/com/ltonetwork/settings/FeesSettings.scala +++ b/src/main/scala/com/ltonetwork/settings/FeesSettings.scala @@ -1,6 +1,5 @@ package com.ltonetwork.settings -import com.ltonetwork.settings.Constants.TransactionNames import com.typesafe.config.Config import net.ceedubs.ficus.Ficus._ @@ -18,23 +17,14 @@ object FeesSettings { .entrySet() .asScala .flatMap { entry => - if (txTypes.contains(entry.getKey)) { + if (transactionTypes.contains(entry.getKey)) { val rawFees = config.as[Map[String, Long]](s"$configPath.${entry.getKey}") val fees = rawFees.map { case (asset, fee) => FeeSettings(asset, fee) }(collection.breakOut) - Some(txTypes(entry.getKey) -> fees) + Some(transactionTypes(entry.getKey) -> fees) } else throw new NoSuchElementException(entry.getKey) }(collection.breakOut) FeesSettings(fees) } - - private def txTypes: Map[String, Byte] = { - val types = TransactionNames.map { - case (typeId, name) => name.replace(" ", "-") -> typeId - } - - // Support old application.conf settings - types + ("lease-cancel" -> types("cancel-lease"), "sponsorship-cancel" -> types("cancel-sponsorship")) - } } diff --git a/src/main/scala/com/ltonetwork/settings/WavesSettings.scala b/src/main/scala/com/ltonetwork/settings/LtoSettings.scala similarity index 100% rename from src/main/scala/com/ltonetwork/settings/WavesSettings.scala rename to src/main/scala/com/ltonetwork/settings/LtoSettings.scala diff --git a/src/main/scala/com/ltonetwork/settings/package.scala b/src/main/scala/com/ltonetwork/settings/package.scala index 1ae0e3ffee..5019bb0be2 100644 --- a/src/main/scala/com/ltonetwork/settings/package.scala +++ b/src/main/scala/com/ltonetwork/settings/package.scala @@ -1,5 +1,6 @@ package com.ltonetwork +import com.ltonetwork.settings.Constants.TransactionNames import com.ltonetwork.state.ByteStr import com.typesafe.config.{Config, ConfigException, ConfigFactory, ConfigValueType} import net.ceedubs.ficus.readers.namemappers.HyphenNameMapper @@ -45,4 +46,13 @@ package object settings { .withFallback(ConfigFactory.defaultReference()) .resolve() } + + def transactionTypes: Map[String, Byte] = { + val types = TransactionNames.map { + case (typeId, name) => name.replace(" ", "-") -> typeId + } + + // Support old application.conf settings + types + ("lease-cancel" -> types("cancel-lease"), "sponsorship-cancel" -> types("cancel-sponsorship")) + } } diff --git a/src/test/scala/com/ltonetwork/RequestGen.scala b/src/test/scala/com/ltonetwork/RequestGen.scala index 5b3ca87f6a..4ae5be158b 100644 --- a/src/test/scala/com/ltonetwork/RequestGen.scala +++ b/src/test/scala/com/ltonetwork/RequestGen.scala @@ -36,22 +36,32 @@ trait RequestGen extends TransactionGen { _: Suite => recipient <- addressValGen amount <- positiveLongGen attachment <- genBoundedString(1, 20).map(b => Some(ByteStr(b))) - } yield TransferRequest(Some(1), None, Some(account.keyType.reference), Some(Base58.encode(account.publicKey)), fee, recipient, amount, attachment) + } yield TransferRequest( + version = Some(1), + fee = fee, + senderKeyType = Some(account.keyType.reference), + senderPublicKey = Some(Base58.encode(account.publicKey)), + recipient = recipient, + amount = amount, + attachment = attachment + ) val broadcastTransferReq: G[TransferRequest] = for { _signature <- signatureGen _timestamp <- ntpTimestampGen _tr <- transferReq } yield - TransferRequest(Some(1), - Some(_timestamp), - _tr.senderKeyType, - _tr.senderPublicKey, - _tr.fee, - _tr.recipient, - _tr.amount, - _tr.attachment, - signature = Some(_signature)) + TransferRequest( + version = Some(1), + timestamp = Some(_timestamp), + senderKeyType = _tr.senderKeyType, + senderPublicKey = _tr.senderPublicKey, + fee = _tr.fee, + recipient = _tr.recipient, + amount = _tr.amount, + attachment = _tr.attachment, + signature = Some(_signature) + ) val leaseReq: G[LeaseRequest] = for { _signature <- signatureGen @@ -59,13 +69,13 @@ trait RequestGen extends TransactionGen { _: Suite => _lease <- leaseGen } yield LeaseRequest( - Some(1), - Some(_timestamp), - Some(_lease.sender.keyType.reference), - Some(Base58.encode(_lease.sender.publicKey)), - _lease.fee, - _lease.recipient.toString, - _lease.amount, + version = Some(1), + timestamp = Some(_timestamp), + senderKeyType = Some(_lease.sender.keyType.reference), + senderPublicKey = Some(Base58.encode(_lease.sender.publicKey)), + fee = _lease.fee, + recipient = _lease.recipient.toString, + amount = _lease.amount, signature = Some(_signature) ) @@ -74,12 +84,12 @@ trait RequestGen extends TransactionGen { _: Suite => _cancel <- cancelLeaseGen } yield CancelLeaseRequest( - Some(1), - Some(_cancel.timestamp), - Some(_cancel.sender.keyType.reference), - Some(Base58.encode(_cancel.sender.publicKey)), - _cancel.fee, - _cancel.leaseId, + version = Some(1), + timestamp = Some(_cancel.timestamp), + senderKeyType = Some(_cancel.sender.keyType.reference), + senderPublicKey = Some(Base58.encode(_cancel.sender.publicKey)), + fee = _cancel.fee, + leaseId = _cancel.leaseId, signature = Some(_signature) ) } diff --git a/src/test/scala/com/ltonetwork/http/AddressRouteSpec.scala b/src/test/scala/com/ltonetwork/http/AddressRouteSpec.scala index c33eb46342..ba18d384bc 100644 --- a/src/test/scala/com/ltonetwork/http/AddressRouteSpec.scala +++ b/src/test/scala/com/ltonetwork/http/AddressRouteSpec.scala @@ -14,7 +14,7 @@ import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks import org.scalatest.prop.TableDrivenPropertyChecks import play.api.libs.json._ import com.ltonetwork.account.Address -import com.ltonetwork.api.{AddressApiRoute, ApiKeyNotValid} +import com.ltonetwork.api.AddressApiRoute import com.ltonetwork.settings.TestFunctionalitySettings import com.ltonetwork.transaction.smart.script.v1.ScriptV1 @@ -76,11 +76,10 @@ class AddressRouteSpec val emptySignature = Json.obj("message" -> JsString(""), "publickey" -> JsString(Base58.encode(account.publicKey)), "signature" -> JsString("")) - Post(uri, validBody) ~> route should produce(ApiKeyNotValid) - Post(uri, emptySignature) ~> api_key(apiKey) ~> route ~> check { + Post(uri, emptySignature) ~> route ~> check { (responseAs[JsObject] \ "valid").as[Boolean] shouldBe false } - Post(uri, validBody) ~> api_key(apiKey) ~> route ~> check { + Post(uri, validBody) ~> route ~> check { (responseAs[JsObject] \ "valid").as[Boolean] shouldBe true } } diff --git a/src/test/scala/com/ltonetwork/http/PeersRouteSpec.scala b/src/test/scala/com/ltonetwork/http/PeersRouteSpec.scala index 737b740d72..e7a73fd5cf 100644 --- a/src/test/scala/com/ltonetwork/http/PeersRouteSpec.scala +++ b/src/test/scala/com/ltonetwork/http/PeersRouteSpec.scala @@ -87,14 +87,14 @@ class PeersRouteSpec extends RouteSpec("/peers") with RestAPISettingsHelper with val route = PeersApiRoute(restAPISettings, connectToPeer, peerDatabase, new ConcurrentHashMap[Channel, PeerInfo]()).route val connectUri = routePath("/connect") Post(connectUri, ConnectReq("example.com", 1)) ~> route should produce(ApiKeyNotValid) - Post(connectUri, "") ~> api_key(apiKey) ~> route ~> check(handled shouldEqual false) - Post(connectUri, Json.obj()) ~> api_key(apiKey) ~> route ~> check { + Post(connectUri, "") ~> ApiKey(apiKey) ~> route ~> check(handled shouldEqual false) + Post(connectUri, Json.obj()) ~> ApiKey(apiKey) ~> route ~> check { (responseAs[JsValue] \ "validationErrors").as[JsObject].keys should not be 'empty } val address = inetSocketAddressGen.sample.get connectToPeer.expects(address).once - val result = Post(connectUri, ConnectReq(address.getHostName, address.getPort)) ~> api_key(apiKey) ~> route ~> runRoute + val result = Post(connectUri, ConnectReq(address.getHostName, address.getPort)) ~> ApiKey(apiKey) ~> route ~> runRoute check { responseAs[ConnectResp].hostname shouldEqual address.getHostName }(result) diff --git a/src/test/scala/com/ltonetwork/http/TransactionsRouteSpec.scala b/src/test/scala/com/ltonetwork/http/TransactionsRouteSpec.scala index 741df34785..7b704e9bc7 100644 --- a/src/test/scala/com/ltonetwork/http/TransactionsRouteSpec.scala +++ b/src/test/scala/com/ltonetwork/http/TransactionsRouteSpec.scala @@ -181,7 +181,7 @@ class TransactionsRouteSpec val route = TransactionsApiRoute(restAPISettings, featuresSettings, feesSettings, wallet, blockchain, utx, allChannels, new TestTime).route - Post(routePath("/sign"), transferTx) ~> api_key(apiKey) ~> route ~> check { + Post(routePath("/sign"), transferTx) ~> ApiKey(apiKey) ~> route ~> check { status shouldEqual StatusCodes.OK (responseAs[JsObject] \ "timestamp").as[Long] should not be 0 (responseAs[JsObject] \ "proofs").as[Proofs].proofs should not be empty diff --git a/src/test/scala/com/ltonetwork/http/WalletRouteSpec.scala b/src/test/scala/com/ltonetwork/http/WalletRouteSpec.scala index 93cd066637..79a24a8a9b 100644 --- a/src/test/scala/com/ltonetwork/http/WalletRouteSpec.scala +++ b/src/test/scala/com/ltonetwork/http/WalletRouteSpec.scala @@ -25,7 +25,7 @@ class WalletRouteSpec extends RouteSpec("/wallet") with RestAPISettingsHelper wi case (account, message) => val uri = routePath(s"/$path/${account.address}") Post(uri, message) ~> route should produce(ApiKeyNotValid) - Post(uri, message) ~> api_key(apiKey) ~> route ~> check { + Post(uri, message) ~> ApiKey(apiKey) ~> route ~> check { val resp = responseAs[JsObject] val signature = Base58.decode((resp \ "signature").as[String]).get @@ -61,13 +61,13 @@ class WalletRouteSpec extends RouteSpec("/wallet") with RestAPISettingsHelper wi routePath("/addresses") in { Post(routePath("/addresses")) ~> route should produce(ApiKeyNotValid) - Post(routePath("/addresses")) ~> api_key(apiKey) ~> route ~> check { + Post(routePath("/addresses")) ~> ApiKey(apiKey) ~> route ~> check { allAddresses should not contain (responseAs[JsObject] \ "address").as[String] } } routePath("/addresses/{address}") in { - Delete(routePath(s"/addresses/${allAddresses.head}")) ~> api_key(apiKey) ~> route ~> check { + Delete(routePath(s"/addresses/${allAddresses.head}")) ~> ApiKey(apiKey) ~> route ~> check { (responseAs[JsObject] \ "deleted").as[Boolean] shouldBe true } } diff --git a/src/test/scala/com/ltonetwork/http/package.scala b/src/test/scala/com/ltonetwork/http/package.scala index 34ef707684..e6fdb8b93f 100644 --- a/src/test/scala/com/ltonetwork/http/package.scala +++ b/src/test/scala/com/ltonetwork/http/package.scala @@ -15,7 +15,7 @@ import scala.util.{Failure, Success} package object http { val LTO: Long = 100000000L - val ApiKeyHeader: api_key = api_key("ridethelto!") + val ApiKeyHeader: ApiKey = ApiKey("ridethelto!") def sameSignature(target: Array[Byte])(actual: Array[Byte]): Boolean = target sameElements actual diff --git a/starter.py b/starter.py index 7d56669bd1..25538c4365 100644 --- a/starter.py +++ b/starter.py @@ -108,11 +108,14 @@ def secureHash(message): nested_set(env_dict, ['lto', 'wallet', 'password'], lto_data[1]) nested_set(env_dict, ['lto', 'rest-api', 'api-key-hash'], api_key_hash) - ENABLE_REST_API = os.environ.get('ENABLE_REST_API', os.environ.get('LTO_ENABLE_REST_API', 'false')) + ENABLE_REST_API = os.environ.get('ENABLE_REST_API', os.environ.get('LTO_ENABLE_REST_API', 'no')) if ENABLE_REST_API.lower() in ['yes', 'true', 't', '1', 'on']: nested_set(env_dict, ['lto', 'rest-api', 'enable'], 'yes') nested_set(env_dict, ['lto', 'rest-api', 'bind-address'], '0.0.0.0') + ENABLE_MINING = os.environ.get('LTO_ENABLE_MINING', 'yes') + nested_set(env_dict, ['lto', 'miner', 'enable'], 'yes' if ENABLE_REST_API.lower() in ['yes', 'true', 't', '1', 'on'] else 'no') + LTO_NODE_NAME = os.getenv('LTO_NODE_NAME') if LTO_NODE_NAME is not None: nested_set(env_dict, ['lto', 'network', 'node-name'], LTO_NODE_NAME)