Browse Source

server: Add sendRawTransaction method to XSNService

This is a part for #26.
prometheus-integration
Alexis Hernandez 7 years ago
parent
commit
5313e76738
  1. 20
      server/app/com/xsn/explorer/errors/transactionErrors.scala
  2. 16
      server/app/com/xsn/explorer/models/HexString.scala
  3. 30
      server/app/com/xsn/explorer/services/XSNService.scala
  4. 2
      server/conf/messages
  5. 1
      server/test/com/xsn/explorer/helpers/DummyXSNService.scala
  6. 56
      server/test/com/xsn/explorer/models/HexStringSpec.scala

20
server/app/com/xsn/explorer/errors/transactionErrors.scala

@ -1,6 +1,6 @@
package com.xsn.explorer.errors 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} import play.api.i18n.{Lang, MessagesApi}
sealed trait TransactionError sealed trait TransactionError
@ -29,3 +29,21 @@ case object TransactionUnknownError extends TransactionError with ServerError {
override def toPublicErrorList(messagesApi: MessagesApi)(implicit lang: Lang): List[PublicError] = List.empty 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
server/app/com/xsn/explorer/models/HexString.scala

@ -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
server/app/com/xsn/explorer/services/XSNService.scala

@ -43,6 +43,8 @@ trait XSNService {
def getMasternode(ipAddress: IPAddress): FutureApplicationResult[rpc.Masternode] def getMasternode(ipAddress: IPAddress): FutureApplicationResult[rpc.Masternode]
def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue] def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue]
def sendRawTransaction(hex: HexString): FutureApplicationResult[Unit]
} }
class XSNServiceRPCImpl @Inject() ( class XSNServiceRPCImpl @Inject() (
@ -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] = { private def mapError(json: JsValue, errorCodeMapper: Map[Int, ApplicationError]): Option[ApplicationError] = {
val jsonErrorMaybe = (json \ "error") val jsonErrorMaybe = (json \ "error")
.asOpt[JsValue] .asOpt[JsValue]

2
server/conf/messages

@ -4,6 +4,8 @@ xsn.server.unexpectedError=Unexpected error from the XSN network
error.transaction.format=Invalid transaction format error.transaction.format=Invalid transaction format
error.transaction.notFound=Transaction not found 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 error.address.format=Invalid address format

1
server/test/com/xsn/explorer/helpers/DummyXSNService.scala

@ -21,4 +21,5 @@ class DummyXSNService extends XSNService {
override def getMasternodes(): FutureApplicationResult[List[rpc.Masternode]] = ??? override def getMasternodes(): FutureApplicationResult[List[rpc.Masternode]] = ???
override def getMasternode(ipAddress: IPAddress): FutureApplicationResult[Masternode] = ??? override def getMasternode(ipAddress: IPAddress): FutureApplicationResult[Masternode] = ???
override def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue] = ??? override def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue] = ???
override def sendRawTransaction(hex: HexString): FutureApplicationResult[Unit] = ???
} }

56
server/test/com/xsn/explorer/models/HexStringSpec.scala

@ -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
}
}
}
Loading…
Cancel
Save