Skip to content

Commit

Permalink
server: Add sendRawTransaction method to XSNService
Browse files Browse the repository at this point in the history
This is a part for #26.
  • Loading branch information
AlexITC committed Jun 18, 2018
1 parent 5a7fd8a commit 5313e76
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 1 deletion.
20 changes: 19 additions & 1 deletion server/app/com/xsn/explorer/errors/transactionErrors.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.xsn.explorer.errors

import com.alexitc.playsonify.models.{FieldValidationError, InputValidationError, PublicError, ServerError}
import com.alexitc.playsonify.models._
import play.api.i18n.{Lang, MessagesApi}

sealed trait TransactionError
Expand Down Expand Up @@ -29,3 +29,21 @@ case object TransactionUnknownError extends TransactionError with ServerError {

override def toPublicErrorList(messagesApi: MessagesApi)(implicit lang: Lang): List[PublicError] = List.empty
}

case object InvalidRawTransactionError extends TransactionError with InputValidationError {

override def toPublicErrorList(messagesApi: MessagesApi)(implicit lang: Lang): List[PublicError] = {
val message = messagesApi("error.rawTransaction.invalid")
val error = FieldValidationError("hex", message)
List(error)
}
}

case object RawTransactionAlreadyExistsError extends TransactionError with ConflictError {

override def toPublicErrorList(messagesApi: MessagesApi)(implicit lang: Lang): List[PublicError] = {
val message = messagesApi("error.rawTransaction.repeated")
val error = FieldValidationError("hex", message)
List(error)
}
}
16 changes: 16 additions & 0 deletions server/app/com/xsn/explorer/models/HexString.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.xsn.explorer.models

class HexString private (val string: String) extends AnyVal

object HexString {

private val RegEx = "^[A-Fa-f0-9]+$"

def from(string: String): Option[HexString] = {
if (string.length % 2 == 0 && string.matches(RegEx)) {
Option(new HexString(string))
} else {
None
}
}
}
30 changes: 30 additions & 0 deletions server/app/com/xsn/explorer/services/XSNService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ trait XSNService {
def getMasternode(ipAddress: IPAddress): FutureApplicationResult[rpc.Masternode]

def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue]

def sendRawTransaction(hex: HexString): FutureApplicationResult[Unit]
}

class XSNServiceRPCImpl @Inject() (
Expand Down Expand Up @@ -347,6 +349,34 @@ class XSNServiceRPCImpl @Inject() (
}
}

override def sendRawTransaction(hex: HexString): FutureApplicationResult[Unit] = {
val errorCodeMapper = Map(
-26 -> InvalidRawTransactionError,
-22 -> InvalidRawTransactionError,
-27 -> RawTransactionAlreadyExistsError)

val body = s"""
|{
| "jsonrpc": "1.0",
| "method": "sendrawtransaction",
| "params": ["${hex.string}"]
|}
|""".stripMargin

server
.post(body)
.map { response =>
val maybe = getResult[String](response, errorCodeMapper)
.map { _.map(_ => ()) }

maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")

Bad(XSNUnexpectedResponseError).accumulating
}
}
}

private def mapError(json: JsValue, errorCodeMapper: Map[Int, ApplicationError]): Option[ApplicationError] = {
val jsonErrorMaybe = (json \ "error")
.asOpt[JsValue]
Expand Down
2 changes: 2 additions & 0 deletions server/conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ xsn.server.unexpectedError=Unexpected error from the XSN network

error.transaction.format=Invalid transaction format
error.transaction.notFound=Transaction not found
error.rawTransaction.invalid=The transaction is invalid
error.rawTransaction.repeated=The transaction is already in the network

error.address.format=Invalid address format

Expand Down
1 change: 1 addition & 0 deletions server/test/com/xsn/explorer/helpers/DummyXSNService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ class DummyXSNService extends XSNService {
override def getMasternodes(): FutureApplicationResult[List[rpc.Masternode]] = ???
override def getMasternode(ipAddress: IPAddress): FutureApplicationResult[Masternode] = ???
override def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue] = ???
override def sendRawTransaction(hex: HexString): FutureApplicationResult[Unit] = ???
}
56 changes: 56 additions & 0 deletions server/test/com/xsn/explorer/models/HexStringSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.xsn.explorer.models

import org.scalatest.{MustMatchers, OptionValues, WordSpec}

class HexStringSpec extends WordSpec with MustMatchers with OptionValues {

"from" should {
"accept a valid hex" in {
val string = "0100000001d036c70b1df769fa3205f8ff4e361af84073aa14c89de80488048b6ae4904ce9010000006a47304402201f1f9aef5d60f6e84714dfb98ca87ca8a146a2e04a3811d8f0aa770d8ac1c906022054e27a26f806a5d0c0e08332be186a96ee1ac951b8d1e6e3b10072d51eb6dd300121026648fd298f1cc06c474db0864720a9774efbe789dd67b2a46086f9754e4cc3f2ffffffff030000000000000000000e77b9932d0000001976a91436e0c51c93a357e23621bb993d28e5c18f95bb5588ac00d2496b000000001976a9149d1fef13c02f2f23cf9a09ba11987e90Dbf5910d88ac00000000"
val result = HexString.from(string)
result.value.string mustEqual string
}

"accept a valid hex with mixed case" in {
val string = "0100000001d036c70b1df769fA3205f8fF4e361af84073aa14c89de80488048b6aE4904ce9010000006a47304402201f1f9aef5d60f6e84714dfb98ca87ca8a146a2e04a3811d8f0aa770d8ac1c906022054e27a26f806a5d0c0e08332be186a96ee1ac951b8d1e6e3b10072d51eb6dd300121026648fd298f1cc06c474db0864720a9774efbe789dd67b2a46086f9754e4cc3f2ffffffff030000000000000000000e77b9932d0000001976a91436e0c51c93a357e23621bb993d28e5c18f95bb5588ac00d2496b000000001976a9149d1fef13c02f2f23cf9a09ba11987e90Dbf5910d88ac00000000"
val result = HexString.from(string)
result.value.string mustEqual string
}

"accept a string with all hex characters" in {
val string = "abcdef0123456789ABCDEF"
val result = HexString.from(string)
result.value.string mustEqual string
}

"accept a two characters string" in {
val string = "0f"
val result = HexString.from(string)
result.value.string mustEqual string
}

"reject an empty string" in {
val string = ""
val result = HexString.from(string)
result.isEmpty mustEqual true
}

"reject a single character" in {
val string = "a"
val result = HexString.from(string)
result.isEmpty mustEqual true
}

"reject spaces" in {
val string = "a "
val result = HexString.from(string)
result.isEmpty mustEqual true
}

"reject non-hex characters" in {
val string = "abcdefg"
val result = HexString.from(string)
result.isEmpty mustEqual true
}
}
}

0 comments on commit 5313e76

Please sign in to comment.