Browse Source

server: Refactor the transaction models to support multisig transactions

This refactor changes the input/output model from transactions to store
the possibly empty address list, which allows to deal with certain
transactions on bitcoin as well as with multisig transactions.
bitcoin
Alexis Hernandez 6 years ago
parent
commit
87b345dd76
  1. 26
      server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala
  2. 24
      server/app/com/xsn/explorer/data/anorm/dao/AddressTransactionDetailsPostgresDAO.scala
  3. 14
      server/app/com/xsn/explorer/data/anorm/dao/TransactionInputPostgresDAO.scala
  4. 20
      server/app/com/xsn/explorer/data/anorm/dao/TransactionOutputPostgresDAO.scala
  5. 14
      server/app/com/xsn/explorer/data/anorm/parsers/CommonParsers.scala
  6. 12
      server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala
  7. 2
      server/app/com/xsn/explorer/models/LightWalletTransaction.scala
  8. 19
      server/app/com/xsn/explorer/models/TransactionDetails.scala
  9. 27
      server/app/com/xsn/explorer/models/TransactionValue.scala
  10. 4
      server/app/com/xsn/explorer/models/persisted/Block.scala
  11. 41
      server/app/com/xsn/explorer/models/persisted/Transaction.scala
  12. 21
      server/app/com/xsn/explorer/models/rpc/TransactionVIN.scala
  13. 2
      server/app/com/xsn/explorer/models/rpc/TransactionVOUT.scala
  14. 4
      server/app/com/xsn/explorer/services/TransactionCollectorService.scala
  15. 123
      server/app/com/xsn/explorer/services/TransactionRPCService.scala
  16. 22
      server/app/com/xsn/explorer/services/logic/BlockLogic.scala
  17. 2
      server/app/com/xsn/explorer/services/logic/TransactionLogic.scala
  18. 27
      server/app/controllers/AddressesController.scala
  19. 26
      server/conf/evolutions/default/18.sql
  20. 8
      server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala
  21. 5
      server/test/com/xsn/explorer/helpers/FileBasedXSNService.scala
  22. 2
      server/test/com/xsn/explorer/helpers/TransactionLoader.scala
  23. 2
      server/test/controllers/AddressesControllerSpec.scala
  24. 66
      server/test/controllers/TransactionsControllerSpec.scala
  25. 62
      server/test/resources/transactions/100e0b7b5bf835afc6459fb6fe2ed51a68bd5b5b01e5c08f937f8b15879eeca6
  26. 62
      server/test/resources/transactions/1361d3574b9091557e62732b7da30f0c10e1411ba2d30973afb41e489e0993fe
  27. 62
      server/test/resources/transactions/56e1844d9d01a4586e903e85bf29c35af2b9e32c5fc792dd098840219fe6354a
  28. 62
      server/test/resources/transactions/63539693bf1d8df5d969e96d7e89f26b3816580bed1712da4b4e60913efc7b34
  29. 62
      server/test/resources/transactions/7943aeb6eee0f92406930b4a1b34a8801477c2bf0d94fa7f2e7a43d61a9cd60c
  30. 62
      server/test/resources/transactions/980dc3eb7577a44d9a57c6d26151e9bd2b517d8b35ac111016b4c890a1f78a8d
  31. 62
      server/test/resources/transactions/a7cf3af87f66e58b43de4eb61e3bad6185fd8e3137d63c1960e4df67088f3fe5
  32. 62
      server/test/resources/transactions/b20e438c4c54cb048dbcb5f39de17dcaf6418adb521f5ff9d29c13239d50bcba
  33. 62
      server/test/resources/transactions/bd715a5f43d73a5f347b7779859b029be069cc8ea6fc6480dfda40b64d828c6c
  34. 62
      server/test/resources/transactions/da1af8037b1cb20f6702eef636d27871ab99bd69031668fafc0ae6cbe4f9941a
  35. 62
      server/test/resources/transactions/e66cfa37f10fe7e75f2414fb3465dda7864acbdb9885f519217dd147178b7188
  36. 88
      server/test/resources/transactions/f296cdf4fd54da9a5adfa1df6f6f321409b8b00b6020ce977e45746a09f964ad

26
server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala

@ -132,23 +132,25 @@ class LedgerPostgresDataHandler @Inject() (
}
private def spendMap(transactions: List[Transaction.HasIO]): Map[Address, BigDecimal] = {
transactions
.map(_.inputs)
.flatMap { inputs =>
inputs.map { input => input.address -> input.value }
}
val addressValueList = for {
tx <- transactions
input <- tx.inputs
address <- input.addresses
} yield address -> input.value
addressValueList
.groupBy(_._1)
.mapValues { list => list.map(_._2).sum }
}
private def receiveMap(transactions: List[Transaction.HasIO]): Map[Address, BigDecimal] = {
transactions
.map(_.outputs)
.flatMap { outputs =>
outputs.map { output =>
output.address -> output.value
}
}
val addressValueList = for {
tx <- transactions
output <- tx.outputs
address <- output.addresses
} yield address -> output.value
addressValueList
.groupBy(_._1)
.mapValues { list => list.map(_._2).sum }
}

24
server/app/com/xsn/explorer/data/anorm/dao/AddressTransactionDetailsPostgresDAO.scala

@ -10,16 +10,24 @@ import com.xsn.explorer.models.values.TransactionId
class AddressTransactionDetailsPostgresDAO {
def batchInsertDetails(transaction: Transaction.HasIO)(implicit conn: Connection): Option[Unit] = {
val received = transaction
.outputs
.groupBy(_.address)
.mapValues { outputs => outputs.map(_.value).sum }
val outputAddressValueList = for {
output <- transaction.outputs
address <- output.addresses
} yield address -> output.value
val received = outputAddressValueList
.groupBy(_._1)
.mapValues { _.map(_._2).sum }
.map { case (address, value) => AddressTransactionDetails(address, transaction.id, time = transaction.time, received = value) }
val sent = transaction
.inputs
.groupBy(_.address)
.mapValues { inputs => inputs.map(_.value).sum }
val inputAddressValueList = for {
input <- transaction.inputs
address <- input.addresses
} yield address -> input.value
val sent = inputAddressValueList
.groupBy(_._1)
.mapValues { _.map(_._2).sum }
.map { case (address, value) => AddressTransactionDetails(address, transaction.id, time = transaction.time, sent = value) }
val details = (received ++ sent)

14
server/app/com/xsn/explorer/data/anorm/dao/TransactionInputPostgresDAO.scala

@ -27,15 +27,15 @@ class TransactionInputPostgresDAO {
'from_txid -> input.fromTxid.string: NamedParameter,
'from_output_index -> input.fromOutputIndex: NamedParameter,
'value -> input.value: NamedParameter,
'address -> input.address.string: NamedParameter)
'addresses -> input.addresses.map(_.string): NamedParameter)
}
val batch = BatchSql(
"""
|INSERT INTO transaction_inputs
| (txid, index, from_txid, from_output_index, value, address)
| (txid, index, from_txid, from_output_index, value, addresses)
|VALUES
| ({txid}, {index}, {from_txid}, {from_output_index}, {value}, {address})
| ({txid}, {index}, {from_txid}, {from_output_index}, {value}, ARRAY[{addresses}])
""".stripMargin,
params.head,
params.tail: _*
@ -55,7 +55,7 @@ class TransactionInputPostgresDAO {
"""
|DELETE FROM transaction_inputs
|WHERE txid = {txid}
|RETURNING txid, index, from_txid, from_output_index, value, address
|RETURNING txid, index, from_txid, from_output_index, value, addresses
""".stripMargin
).on(
'txid -> txid.string
@ -65,7 +65,7 @@ class TransactionInputPostgresDAO {
def getInputs(txid: TransactionId)(implicit conn: Connection): List[Transaction.Input] = {
SQL(
"""
|SELECT txid, index, from_txid, from_output_index, value, address
|SELECT txid, index, from_txid, from_output_index, value, addresses
|FROM transaction_inputs
|WHERE txid = {txid}
""".stripMargin
@ -77,10 +77,10 @@ class TransactionInputPostgresDAO {
def getInputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Input] = {
SQL(
"""
|SELECT txid, index, from_txid, from_output_index, value, address
|SELECT txid, index, from_txid, from_output_index, value, addresses
|FROM transaction_inputs
|WHERE txid = {txid} AND
| address = {address}
| {address} = ANY(addresses)
""".stripMargin
).on(
'txid -> txid.string,

20
server/app/com/xsn/explorer/data/anorm/dao/TransactionOutputPostgresDAO.scala

@ -15,9 +15,9 @@ class TransactionOutputPostgresDAO {
def getUnspentOutputs(address: Address)(implicit conn: Connection): List[Transaction.Output] = {
SQL(
"""
|SELECT txid, index, value, address, hex_script
|SELECT txid, index, value, addresses, hex_script
|FROM transaction_outputs
|WHERE address = {address} AND
|WHERE {address} = ANY(addresses) AND
| spent_on IS NULL AND
| value > 0
""".stripMargin
@ -29,7 +29,7 @@ class TransactionOutputPostgresDAO {
def getOutput(txid: TransactionId, index: Int)(implicit conn: Connection): Option[Transaction.Output] = {
SQL(
"""
|SELECT txid, index, value, address, hex_script
|SELECT txid, index, value, addresses, hex_script
|FROM transaction_outputs
|WHERE txid = {txid} AND
| index = {index}
@ -52,16 +52,16 @@ class TransactionOutputPostgresDAO {
'txid -> output.txid.string: NamedParameter,
'index -> output.index: NamedParameter,
'value -> output.value: NamedParameter,
'address -> output.address.string: NamedParameter,
'addresses -> output.addresses.map(_.string): NamedParameter,
'hex_script -> output.script.string: NamedParameter)
}
val batch = BatchSql(
"""
|INSERT INTO transaction_outputs
| (txid, index, value, address, hex_script)
| (txid, index, value, addresses, hex_script)
|VALUES
| ({txid}, {index}, {value}, {address}, {hex_script})
| ({txid}, {index}, {value}, ARRAY[{addresses}], {hex_script})
""".stripMargin,
params.head,
params.tail: _*
@ -81,7 +81,7 @@ class TransactionOutputPostgresDAO {
"""
|DELETE FROM transaction_outputs
|WHERE txid = {txid}
|RETURNING txid, index, hex_script, value, address
|RETURNING txid, index, hex_script, value, addresses
""".stripMargin
).on(
'txid -> txid.string
@ -93,7 +93,7 @@ class TransactionOutputPostgresDAO {
def getOutputs(txid: TransactionId)(implicit conn: Connection): List[Transaction.Output] = {
SQL(
"""
|SELECT txid, index, hex_script, value, address
|SELECT txid, index, hex_script, value, addresses
|FROM transaction_outputs
|WHERE txid = {txid}
""".stripMargin
@ -105,10 +105,10 @@ class TransactionOutputPostgresDAO {
def getOutputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Output] = {
SQL(
"""
|SELECT txid, index, hex_script, value, address
|SELECT txid, index, hex_script, value, addresses
|FROM transaction_outputs
|WHERE txid = {txid} AND
| address = {address}
| {address} = ANY(addresses)
""".stripMargin
).on(
'txid -> txid.string,

14
server/app/com/xsn/explorer/data/anorm/parsers/CommonParsers.scala

@ -1,6 +1,6 @@
package com.xsn.explorer.data.anorm.parsers
import anorm.SqlParser.{int, long, str}
import anorm.SqlParser._
import com.xsn.explorer.models.values._
object CommonParsers {
@ -13,6 +13,18 @@ object CommonParsers {
.map(Address.from)
.map { _.getOrElse(throw new RuntimeException(s"corrupted $field")) }
def parseAddresses = array[String]("addresses")
.map { array =>
array
.map { string =>
Address.from(string) match {
case None => throw new RuntimeException("Corrupted address")
case Some(address) => address
}
}
.toList
}
def parseTransactionId(field: String = "txid") = str(field)
.map(TransactionId.from)
.map { _.getOrElse(throw new RuntimeException(s"corrupted $field")) }

12
server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala

@ -33,20 +33,20 @@ object TransactionParsers {
TransactionWithValues(txid, blockhash, time, size, sent, received)
}
val parseTransactionInput = (parseFromTxid ~ parseFromOutputIndex ~ parseIndex ~ parseValue ~ parseAddress())
.map { case fromTxid ~ fromOutputIndex ~ index ~ value ~ address =>
Transaction.Input(fromTxid, fromOutputIndex, index, value, address)
val parseTransactionInput = (parseFromTxid ~ parseFromOutputIndex ~ parseIndex ~ parseValue ~ parseAddresses)
.map { case fromTxid ~ fromOutputIndex ~ index ~ value ~ addresses =>
Transaction.Input(fromTxid, fromOutputIndex, index, value, addresses)
}
val parseTransactionOutput = (
parseTransactionId() ~
parseIndex ~
parseValue ~
parseAddress() ~
parseAddresses ~
parseHexScript).map {
case txid ~ index ~ value ~ address ~ script =>
Transaction.Output(txid, index, value, address, script)
case txid ~ index ~ value ~ addresses ~ script =>
Transaction.Output(txid, index, value, addresses, script)
}
val parseAddressTransactionDetails = (parseAddress() ~ parseTransactionId() ~ parseSent ~ parseReceived ~ parseTime).map {

2
server/app/com/xsn/explorer/models/LightWalletTransaction.scala

@ -13,6 +13,6 @@ case class LightWalletTransaction(
object LightWalletTransaction {
case class Input(txid: TransactionId, index: Int, value: BigDecimal)
case class Output(index: Int, value: BigDecimal, address: Address)
case class Output(index: Int, value: BigDecimal, addresses: List[Address])
}

19
server/app/com/xsn/explorer/models/TransactionDetails.scala

@ -1,7 +1,7 @@
package com.xsn.explorer.models
import com.xsn.explorer.models.values.{Blockhash, Confirmations, Size, TransactionId}
import play.api.libs.json.{Json, Writes}
import play.api.libs.json.{JsValue, Json, Writes}
case class TransactionDetails(
id: TransactionId,
@ -24,7 +24,7 @@ object TransactionDetails {
def from(tx: rpc.Transaction[rpc.TransactionVIN.HasValues]): TransactionDetails = {
val input = tx.vin.map { vin =>
TransactionValue(vin.address, vin.value)
TransactionValue(vin.addresses, vin.value)
}
val output = tx.vout.flatMap(TransactionValue.from)
@ -32,5 +32,18 @@ object TransactionDetails {
TransactionDetails(tx.id, tx.size, tx.blockhash, tx.time, tx.blocktime, tx.confirmations, input, output)
}
implicit val writes: Writes[TransactionDetails] = Json.writes[TransactionDetails]
implicit val writes: Writes[TransactionDetails] = new Writes[TransactionDetails] {
override def writes(o: TransactionDetails): JsValue = {
Json.obj(
"id" -> o.id,
"size" -> o.size,
"blockhash" -> o.blockhash,
"time" -> o.time,
"blocktime" -> o.blocktime,
"confirmations" -> o.confirmations,
"input" -> o.input,
"output" -> o.output
)
}
}
}

27
server/app/com/xsn/explorer/models/TransactionValue.scala

@ -2,20 +2,29 @@ package com.xsn.explorer.models
import com.xsn.explorer.models.rpc.TransactionVOUT
import com.xsn.explorer.models.values.Address
import play.api.libs.json.{Json, Writes}
import play.api.libs.json.{JsNull, JsString, Json, Writes}
case class TransactionValue(address: Address, value: BigDecimal)
case class TransactionValue(addresses: List[Address], value: BigDecimal) {
def address: Option[Address] = addresses.headOption
}
object TransactionValue {
def from(vout: TransactionVOUT): Option[TransactionValue] = {
val value = vout.value
val addressMaybe = vout.address
def apply(address: Address, value: BigDecimal): TransactionValue = {
TransactionValue(List(address), value)
}
addressMaybe.map { address =>
TransactionValue(address, value)
}
def from(vout: TransactionVOUT): Option[TransactionValue] = {
for (addresses <- vout.addresses)
yield TransactionValue(addresses, vout.value)
}
implicit val writes: Writes[TransactionValue] = Json.writes[TransactionValue]
implicit val writes: Writes[TransactionValue] = (obj: TransactionValue) => {
val address = obj.address.map(_.string).map(JsString.apply).getOrElse(JsNull)
Json.obj(
"address" -> address, // Keeps compatibility
"addresses" -> obj.addresses,
"value" -> obj.value
)
}
}

4
server/app/com/xsn/explorer/models/persisted/Block.scala

@ -43,8 +43,8 @@ object Block {
*/
def collectAddresses: Set[Address] = {
transactions.foldLeft(Set.empty[Address]) { case (acc, tx) =>
val spending = tx.inputs.map(_.address)
val receiving = tx.outputs.map(_.address)
val spending = tx.inputs.flatMap(_.addresses)
val receiving = tx.outputs.flatMap(_.addresses)
spending.toSet ++ receiving.toSet ++ acc
}
}

41
server/app/com/xsn/explorer/models/persisted/Transaction.scala

@ -20,14 +20,43 @@ object Transaction {
fromOutputIndex: Int,
index: Int,
value: BigDecimal,
address: Address)
addresses: List[Address]) {
def address: Option[Address] = addresses.headOption
}
object Input {
def apply(
fromTxid: TransactionId,
fromOutputIndex: Int,
index: Int,
value: BigDecimal,
address: Address): Input = {
Input(fromTxid, fromOutputIndex, index, value, List(address))
}
}
case class Output(
txid: TransactionId,
index: Int,
value: BigDecimal,
address: Address,
script: HexString)
addresses: List[Address],
script: HexString) {
def address: Option[Address] = addresses.headOption
}
object Output {
def apply(
txid: TransactionId,
index: Int,
value: BigDecimal,
address: Address,
script: HexString): Output = {
new Output(txid, index, value, List(address), script)
}
}
case class HasIO(
transaction: Transaction,
@ -55,18 +84,18 @@ object Transaction {
.vin
.zipWithIndex
.map { case (vin, index) =>
Transaction.Input(vin.txid, vin.voutIndex, index, vin.value, vin.address)
Transaction.Input(vin.txid, vin.voutIndex, index, vin.value, vin.addresses)
}
val outputs = tx.vout.flatMap { vout =>
for {
address <- vout.address
addresses <- vout.addresses
script <- vout.scriptPubKey.map(_.hex)
} yield Output(
tx.id,
vout.n,
vout.value,
address,
addresses,
script)
}

21
server/app/com/xsn/explorer/models/rpc/TransactionVIN.scala

@ -8,8 +8,12 @@ sealed trait TransactionVIN {
def txid: TransactionId
def voutIndex: Int
def withValues(value: BigDecimal, addresses: List[Address]): TransactionVIN.HasValues = {
TransactionVIN.HasValues(txid, voutIndex, value, addresses)
}
def withValues(value: BigDecimal, address: Address): TransactionVIN.HasValues = {
TransactionVIN.HasValues(txid, voutIndex, value, address)
TransactionVIN.HasValues(txid, voutIndex, value, List(address))
}
}
@ -20,21 +24,14 @@ object TransactionVIN {
override val txid: TransactionId,
override val voutIndex: Int,
value: BigDecimal,
address: Address) extends TransactionVIN
addresses: List[Address]) extends TransactionVIN
implicit val reads: Reads[TransactionVIN] = {
val builder = (__ \ 'txid).read[TransactionId] and
(__ \ 'vout).read[Int] and
(__ \ 'value).readNullable[BigDecimal] and
(__ \ 'address).readNullable[Address]
builder.apply { (txid, index, valueMaybe, addressMaybe) =>
val maybe = for {
value <- valueMaybe
address <- addressMaybe
} yield HasValues(txid, index, value, address)
(__ \ 'vout).read[Int]
maybe.getOrElse(Raw(txid, index))
builder.apply { (txid, index) =>
Raw(txid, index)
}
}
}

2
server/app/com/xsn/explorer/models/rpc/TransactionVOUT.scala

@ -9,7 +9,7 @@ case class TransactionVOUT(
n: Int,
scriptPubKey: Option[ScriptPubKey] = None) {
val address: Option[Address] = scriptPubKey.flatMap(_.addresses.headOption)
val addresses: Option[List[Address]] = scriptPubKey.map(_.addresses)
}
object TransactionVOUT {

4
server/app/com/xsn/explorer/services/TransactionCollectorService.scala

@ -86,7 +86,7 @@ class TransactionCollectorService @Inject() (
.getOutput(vin.txid, vin.voutIndex)
.toFutureOr
.map { output =>
vin.withValues(value = output.value, address = output.address)
vin.withValues(value = output.value, addresses = output.addresses)
}
.toFuture
.map(vin -> _)
@ -182,7 +182,7 @@ class TransactionCollectorService @Inject() (
transactionValue.map {
case Good(value) =>
val newVIN = vin.withValues(value = value.value, address = value.address)
val newVIN = vin.withValues(value = value.value, addresses = value.addresses)
Good(newVIN)
case Bad(e) => Bad(e)

123
server/app/com/xsn/explorer/services/TransactionRPCService.scala

@ -1,24 +1,21 @@
package com.xsn.explorer.services
import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureListOps, FutureOps, OrOps}
import com.alexitc.playsonify.core.{FutureApplicationResult, FutureOr}
import com.xsn.explorer.errors.{InvalidRawTransactionError, TransactionNotFoundError, XSNWorkQueueDepthExceeded}
import com.xsn.explorer.models.persisted.Transaction
import com.xsn.explorer.models.rpc.TransactionVIN
import com.alexitc.playsonify.core.FutureApplicationResult
import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureOps, OrOps}
import com.xsn.explorer.errors.InvalidRawTransactionError
import com.xsn.explorer.models.TransactionDetails
import com.xsn.explorer.models.values._
import com.xsn.explorer.models.{TPoSContract, TransactionDetails, TransactionValue}
import com.xsn.explorer.services.validators.TransactionIdValidator
import com.xsn.explorer.util.Extensions.FutureOrExt
import javax.inject.Inject
import org.scalactic.{Bad, Good, One, Or}
import org.scalactic.{One, Or}
import org.slf4j.LoggerFactory
import play.api.libs.json.{JsString, JsValue, Json}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal
import scala.concurrent.ExecutionContext
class TransactionRPCService @Inject() (
transactionIdValidator: TransactionIdValidator,
transactionCollectorService: TransactionCollectorService,
xsnService: XSNService)(
implicit ec: ExecutionContext) {
@ -37,88 +34,12 @@ class TransactionRPCService @Inject() (
val result = for {
txid <- transactionIdValidator.validate(txidString).toFutureOr
transaction <- xsnService.getTransaction(txid).toFutureOr
vin <- getTransactionVIN(transaction.vin).toFutureOr
vin <- transactionCollectorService.getRPCTransactionVIN(transaction.vin, List.empty).toFutureOr
} yield TransactionDetails.from(transaction.copy(vin = vin))
result.toFuture
}
def getTransaction(txid: TransactionId): FutureApplicationResult[(Transaction.HasIO, Option[TPoSContract])] = {
val result = for {
tx <- xsnService.getTransaction(txid).toFutureOr
transactionVIN <- getTransactionVIN(tx.vin).toFutureOr
rpcTransaction = tx.copy(vin = transactionVIN)
} yield Transaction.fromRPC(rpcTransaction)
result.toFuture
}
private def getTransactionVIN(list: List[TransactionVIN]): FutureApplicationResult[List[TransactionVIN.HasValues]] = {
def getVIN(vin: TransactionVIN) = {
getTransactionValue(vin)
.map {
case Good(transactionValue) =>
val newVIN = vin.withValues(address = transactionValue.address, value = transactionValue.value)
Good(newVIN)
case Bad(e) => Bad(e)
}
}
def loadVINSequentially(pending: List[TransactionVIN]): FutureOr[List[TransactionVIN.HasValues]] = pending match {
case x :: xs =>
for {
tx <- getVIN(x).toFutureOr
next <- loadVINSequentially(xs)
} yield tx :: next
case _ => Future.successful(Good(List.empty)).toFutureOr
}
list
.map(getVIN)
.toFutureOr
.toFuture
.recoverWith {
case NonFatal(ex) =>
logger.warn(s"Failed to load VIN, trying sequentially, error = ${ex.getMessage}")
loadVINSequentially(list).toFuture
}
}
def getTransactions(ids: List[TransactionId]): FutureApplicationResult[(List[Transaction.HasIO], List[TPoSContract])] = {
def loadTransactionsSlowly(pending: List[TransactionId]): FutureOr[List[(Transaction.HasIO, Option[TPoSContract])]] = pending match {
case x :: xs =>
for {
tx <- getTransaction(x).toFutureOr
next <- loadTransactionsSlowly(xs)
} yield tx :: next
case _ => Future.successful(Good(List.empty)).toFutureOr
}
ids
.map(getTransaction)
.toFutureOr
.recoverWith(XSNWorkQueueDepthExceeded) {
logger.warn("Unable to load transaction due to server overload, loading them slowly")
loadTransactionsSlowly(ids)
}
.toFuture
.recoverWith {
case NonFatal(ex) =>
logger.warn(s"Unable to load transactions due to server error, loading them sequentially, error = ${ex.getMessage}")
loadTransactionsSlowly(ids).toFuture
}
.toFutureOr
.map { result =>
val contracts = result.flatMap(_._2)
val txs = result.map(_._1)
(txs, contracts)
}
.toFuture
}
def sendRawTransaction(hexString: String): FutureApplicationResult[JsValue] = {
val result = for {
hex <- Or.from(HexString.from(hexString), One(InvalidRawTransactionError)).toFutureOr
@ -127,32 +48,4 @@ class TransactionRPCService @Inject() (
result.toFuture
}
private def getTransactionValue(vin: TransactionVIN): FutureApplicationResult[TransactionValue] = {
val valueMaybe = vin match {
case x: TransactionVIN.HasValues => Some(TransactionValue(x.address, x.value))
case _ => None
}
valueMaybe
.map(Good(_))
.map(Future.successful)
.getOrElse {
val txid = vin.txid
val result = for {
tx <- xsnService.getTransaction(txid).toFutureOr
r <- {
val maybe = tx
.vout
.find(_.n == vin.voutIndex)
.flatMap(TransactionValue.from)
Or.from(maybe, One(TransactionNotFoundError)).toFutureOr
}
} yield r
result.toFuture
}
}
}

22
server/app/com/xsn/explorer/services/logic/BlockLogic.scala

@ -77,7 +77,7 @@ class BlockLogic {
val coinstakeVOUT = coinstakeTx.vout.drop(1)
if (coinstakeVOUT.size >= 1 && coinstakeVOUT.size <= 3) {
val value = coinstakeVOUT
.filter(_.address contains coinstakeAddress)
.filter(_.addresses.getOrElse(List.empty) contains coinstakeAddress)
.map(_.value)
.sum
@ -85,12 +85,12 @@ class BlockLogic {
coinstakeAddress,
(value - coinstakeInput) max 0)
val masternodeRewardOUT = coinstakeVOUT.filterNot(_.address contains coinstakeAddress)
val masternodeAddressMaybe = masternodeRewardOUT.flatMap(_.address).headOption
val masternodeRewardOUT = coinstakeVOUT.filterNot(_.addresses.getOrElse(List.empty) contains coinstakeAddress)
val masternodeAddressMaybe = masternodeRewardOUT.flatMap(_.addresses).flatten.headOption
val masternodeRewardMaybe = masternodeAddressMaybe.map { masternodeAddress =>
BlockReward(
masternodeAddress,
masternodeRewardOUT.filter(_.address contains masternodeAddress).map(_.value).sum
masternodeRewardOUT.filter(_.addresses.getOrElse(List.empty) contains masternodeAddress).map(_.value).sum
)
}
@ -117,7 +117,7 @@ class BlockLogic {
val coinstakeVOUT = coinstakeTx.vout
val ownerValue = coinstakeVOUT
.filter(_.address contains contract.owner)
.filter(_.addresses.getOrElse(List.empty) contains contract.owner)
.map(_.value)
.sum
@ -126,19 +126,19 @@ class BlockLogic {
(ownerValue - coinstakeInput) max 0)
// merchant
val merchantValue = coinstakeVOUT.filter(_.address contains contract.merchant).map(_.value).sum
val merchantValue = coinstakeVOUT.filter(_.addresses.getOrElse(List.empty) contains contract.merchant).map(_.value).sum
val merchantReward = BlockReward(contract.merchant, merchantValue)
// master node
val masternodeRewardOUT = coinstakeVOUT.filterNot { out =>
out.address.contains(contract.owner) ||
out.address.contains(contract.merchant)
out.addresses.getOrElse(List.empty).contains(contract.owner) ||
out.addresses.getOrElse(List.empty).contains(contract.merchant)
}
val masternodeAddressMaybe = masternodeRewardOUT.flatMap(_.address).headOption
val masternodeAddressMaybe = masternodeRewardOUT.flatMap(_.addresses.getOrElse(List.empty)).headOption
val masternodeRewardMaybe = masternodeAddressMaybe.map { masternodeAddress =>
BlockReward(
masternodeAddress,
masternodeRewardOUT.filter(_.address contains masternodeAddress).map(_.value).sum
masternodeRewardOUT.filter(_.addresses.getOrElse(List.empty) contains masternodeAddress).map(_.value).sum
)
}
@ -148,6 +148,6 @@ class BlockLogic {
def isPoS(block: rpc.Block, coinbase: rpc.Transaction[_]): Boolean = {
block.nonce == 0 &&
coinbase.vin.isEmpty &&
coinbase.vout.flatMap(_.address).isEmpty
coinbase.vout.flatMap(_.addresses.getOrElse(List.empty)).isEmpty
}
}

2
server/app/com/xsn/explorer/services/logic/TransactionLogic.scala

@ -9,7 +9,7 @@ import org.scalactic.{One, Or}
class TransactionLogic {
def getAddress(vout: TransactionVOUT, error: ApplicationError): ApplicationResult[Address] = {
val maybe = vout.address
val maybe = vout.addresses.flatMap(_.headOption)
Or.from(maybe, One(error))
}

27
server/app/controllers/AddressesController.scala

@ -47,8 +47,16 @@ class AddressesController @Inject() (
* Format to keep compatibility with the previous approach using the RPC api.
*/
implicit private val writes: Writes[Transaction.Output] = Writes { obj =>
val address = obj
.addresses
.headOption
.map(_.string)
.map(JsString.apply)
.getOrElse(JsNull)
val values = Map(
"address" -> JsString(obj.address.string),
"address" -> address, // Keep compatibility with the legacy API
"addresses" -> JsArray(obj.addresses.map(_.string).map(JsString.apply)),
"txid" -> JsString(obj.txid.string),
"script" -> JsString(obj.script.string),
"outputIndex" -> JsNumber(obj.index),
@ -70,7 +78,22 @@ class AddressesController @Inject() (
object AddressesController {
implicit val inputWrites: Writes[LightWalletTransaction.Input] = Json.writes[LightWalletTransaction.Input]
implicit val outputWrites: Writes[LightWalletTransaction.Output] = Json.writes[LightWalletTransaction.Output]
implicit val outputWrites: Writes[LightWalletTransaction.Output] = (obj: LightWalletTransaction.Output) => {
val address = obj
.addresses
.headOption
.map(_.string)
.map(JsString.apply)
.getOrElse(JsNull)
Json.obj(
"index" -> obj.index,
"value" -> obj.value,
"address" -> address,
"addresses" -> obj.addresses
)
}
implicit val lightWalletTransactionWrites: Writes[LightWalletTransaction] = Json.writes[LightWalletTransaction]
}

26
server/conf/evolutions/default/18.sql

@ -0,0 +1,26 @@
# --- !Ups
ALTER TABLE transaction_outputs
ALTER COLUMN address TYPE TEXT[] USING ARRAY[address::TEXT];
ALTER TABLE transaction_outputs
RENAME address TO addresses;
ALTER TABLE transaction_inputs
ALTER COLUMN address TYPE TEXT[] USING ARRAY[address::TEXT];
ALTER TABLE transaction_inputs
RENAME address TO addresses;
# --- !Downs
ALTER TABLE transaction_outputs
ALTER COLUMN addresses TYPE ADDRESS_TYPE USING addresses[1]::ADDRESS_TYPE;
ALTER TABLE transaction_outputs
RENAME addresses TO address;
ALTER TABLE transaction_inputs
ALTER COLUMN addresses TYPE ADDRESS_TYPE USING addresses[1]::ADDRESS_TYPE;
ALTER TABLE transaction_inputs
RENAME addresses TO address;

8
server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala

@ -41,7 +41,7 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be
"getBy address" should {
val address = randomAddress
val partialTransaction = randomTransaction(blockhash = block.hash, utxos = dummyTransaction.outputs)
val outputsForAddress = partialTransaction.outputs.map { _.copy(address = address) }
val outputsForAddress = partialTransaction.outputs.map { _.copy(addresses = List(address)) }
val transaction = partialTransaction.copy(outputs = outputsForAddress)
val query = PaginatedQuery(Offset(0), Limit(10))
@ -60,8 +60,8 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be
transaction.blockhash,
transaction.time,
transaction.size,
received = transaction.outputs.filter(_.address == address).map(_.value).sum,
sent = transaction.inputs.filter(_.address == address).map(_.value).sum)
received = transaction.outputs.filter(_.address contains address).map(_.value).sum,
sent = transaction.inputs.filter(_.address contains address).map(_.value).sum)
val expected = PaginatedResult(query.offset, query.limit, Count(1), List(transactionWithValues))
val result = dataHandler.getBy(address, query, defaultOrdering)
@ -83,7 +83,7 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be
script = HexString.from("2103e8c52f2c5155771492907095753a43ce776e1fa7c5e769a67a9f3db4467ec029ac").get
)
val result = dataHandler.getUnspentOutputs(expected.address).get
val result = dataHandler.getUnspentOutputs(expected.address.get).get
result mustEqual List(expected)
}

5
server/test/com/xsn/explorer/helpers/FileBasedXSNService.scala

@ -44,4 +44,9 @@ class FileBasedXSNService extends DummyXSNService {
val result = Or.from(maybe, One(TransactionNotFoundError))
Future.successful(result)
}
override def getRawTransaction(txid: TransactionId): FutureApplicationResult[JsValue] = {
val tx = TransactionLoader.json(txid.string)
Future.successful(Good(tx))
}
}

2
server/test/com/xsn/explorer/helpers/TransactionLoader.scala

@ -20,7 +20,7 @@ object TransactionLoader {
.vout
.find(_.n == vin.voutIndex)
.flatMap { prev =>
prev.address.map { vin.withValues(prev.value, _) }
prev.addresses.map { vin.withValues(prev.value, _) }
}
}

2
server/test/controllers/AddressesControllerSpec.scala

@ -118,7 +118,7 @@ class AddressesControllerSpec extends MyAPISpec {
def url(address: String) = s"/addresses/$address/utxos"
def matches(json: JsValue, output: Transaction.Output) = {
(json \ "address").as[String] mustEqual output.address.string
(json \ "address").as[String] mustEqual output.address.map(_.string).getOrElse("")
(json \ "txid").as[String] mustEqual output.txid.string
(json \ "outputIndex").as[Int] mustEqual output.index
(json \ "script").as[String] mustEqual output.script.string

66
server/test/controllers/TransactionsControllerSpec.scala

@ -1,67 +1,35 @@
package controllers
import com.alexitc.playsonify.core.FutureApplicationResult
import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.play.PublicErrorRenderer
import com.xsn.explorer.data.TransactionBlockingDataHandler
import com.xsn.explorer.errors.TransactionNotFoundError
import com.xsn.explorer.helpers.{DataHelper, DummyXSNService, TransactionDummyDataHandler, TransactionLoader}
import com.xsn.explorer.helpers.{DataHelper, FileBasedXSNService, TransactionDummyDataHandler, TransactionLoader}
import com.xsn.explorer.models._
import com.xsn.explorer.models.rpc.{Transaction, TransactionVIN}
import com.xsn.explorer.models.values.{Confirmations, Size, TransactionId}
import com.xsn.explorer.models.values._
import com.xsn.explorer.services.XSNService
import controllers.common.MyAPISpec
import org.scalactic.{Bad, Good}
import org.scalactic.Bad
import play.api.inject.bind
import play.api.libs.json.JsValue
import play.api.test.Helpers._
import scala.concurrent.Future
class TransactionsControllerSpec extends MyAPISpec {
import DataHelper._
val coinbaseTx = TransactionLoader.get("024aba1d535cfe5dd3ea465d46a828a57b00e1df012d7a2d158e0f7484173f7c")
val nonCoinbaseTx = TransactionLoader.get("0834641a7d30d8a2d2b451617599670445ee94ed7736e146c13be260c576c641")
val nonCoinbasePreviousTx = TransactionLoader.get("585cec5009c8ca19e83e33d282a6a8de65eb2ca007b54d6572167703768967d9")
val severalInputsTx = TransactionLoader.get("a3c43d22bbba31a6e5c00f565cb9c5a1a365407df4cc90efa8a865656b52c0eb")
val firstAddress = createAddress("Xvjue2ZLnJwTrSLUBx7DTHaSHTdpWrxtLF")
val secondAddress = createAddress("bc1qzhayf65p2j4h3pfw22aujgr5w42xfqzx5uvddt")
val firstTxId = DataHelper.createTransactionId("a3c43d223658a8656a31a6e5c407df4bbb0f565cb9c5a1acc90efa056b52c0eb")
val secondTxId = DataHelper.createTransactionId("8a865656b5a3c43d22b00f565cb9c5a1a3bba31a6e5c65407df4cc90efa2c0eb")
val customXSNService = new DummyXSNService {
val map = Map(
coinbaseTx.id -> coinbaseTx,
nonCoinbaseTx.id -> nonCoinbaseTx,
nonCoinbasePreviousTx.id -> nonCoinbasePreviousTx,
severalInputsTx.id -> severalInputsTx
)
override def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction[TransactionVIN]] = {
val result = map.get(txid)
.map(Good(_))
.getOrElse {
Bad(TransactionNotFoundError).accumulating
}
Future.successful(result)
}
private val coinbaseTx = TransactionLoader.get("024aba1d535cfe5dd3ea465d46a828a57b00e1df012d7a2d158e0f7484173f7c")
private val nonCoinbaseTx = TransactionLoader.get("0834641a7d30d8a2d2b451617599670445ee94ed7736e146c13be260c576c641")
private val severalInputsTx = TransactionLoader.get("a3c43d22bbba31a6e5c00f565cb9c5a1a365407df4cc90efa8a865656b52c0eb")
override def getRawTransaction(txid: TransactionId): FutureApplicationResult[JsValue] = {
val result = map.get(txid)
.map { _ => TransactionLoader.json(txid.string) }
.map(Good(_))
.getOrElse {
Bad(TransactionNotFoundError).accumulating
}
private val customXSNService = new FileBasedXSNService
Future.successful(result)
private val transactionDataHandler = new TransactionDummyDataHandler {
override def getOutput(txid: TransactionId, index: Int): ApplicationResult[persisted.Transaction.Output] = {
Bad(TransactionNotFoundError).accumulating
}
}
val transactionDataHandler = new TransactionDummyDataHandler {}
override val application = guiceApplicationBuilder
.overrides(bind[XSNService].to(customXSNService))
.overrides(bind[TransactionBlockingDataHandler].to(transactionDataHandler))
@ -87,7 +55,7 @@ class TransactionsControllerSpec extends MyAPISpec {
outputJsonList.size mustEqual 1
val outputJson = outputJsonList.head
(outputJson \ "address").as[String] mustEqual tx.vout.head.address.get.string
(outputJson \ "address").as[String] mustEqual tx.vout.head.addresses.flatMap(_.headOption).get.string
(outputJson \ "value").as[BigDecimal] mustEqual tx.vout.head.value
}
@ -97,7 +65,7 @@ class TransactionsControllerSpec extends MyAPISpec {
createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"),
BigDecimal("2343749.965625")))
.map { v =>
TransactionVIN.HasValues(createTransactionId("024aba1d535cfe5dd3ea465d46a828a57b00e1df012d7a2d158e0f7484173f7c"), 0, v.value, v.address)
rpc.TransactionVIN.HasValues(createTransactionId("024aba1d535cfe5dd3ea465d46a828a57b00e1df012d7a2d158e0f7484173f7c"), 0, v.value, v.addresses)
}
val tx = nonCoinbaseTx.copy(vin = input)
@ -118,18 +86,18 @@ class TransactionsControllerSpec extends MyAPISpec {
inputJsonList.size mustEqual 1
val inputJson = inputJsonList.head
(inputJson \ "address").as[String] mustEqual details.input.head.address.string
(inputJson \ "address").as[String] mustEqual details.input.head.address.map(_.string).getOrElse("")
(inputJson \ "value").as[BigDecimal] mustEqual details.input.head.value
val outputJsonList = (json \ "output").as[List[JsValue]]
outputJsonList.size mustEqual 2
val outputJson = outputJsonList.head
(outputJson \ "address").as[String] mustEqual details.output.head.address.string
(outputJson \ "address").as[String] mustEqual details.output.head.address.map(_.string).getOrElse("")
(outputJson \ "value").as[BigDecimal] mustEqual details.output.head.value
val outputJson2 = outputJsonList.drop(1).head
(outputJson2 \ "address").as[String] mustEqual details.output.drop(1).head.address.string
(outputJson2 \ "address").as[String] mustEqual details.output.drop(1).head.address.map(_.string).getOrElse("")
(outputJson2 \ "value").as[BigDecimal] mustEqual details.output.drop(1).head.value
}
@ -154,7 +122,7 @@ class TransactionsControllerSpec extends MyAPISpec {
inputJsonList.size mustEqual 11
inputJsonList.foreach { inputJson =>
(inputJson \ "address").as[String] mustEqual inputValue.address.string
(inputJson \ "address").as[String] mustEqual inputValue.address.map(_.string).getOrElse("")
(inputJson \ "value").as[BigDecimal] mustEqual inputValue.value
}

62
server/test/resources/transactions/100e0b7b5bf835afc6459fb6fe2ed51a68bd5b5b01e5c08f937f8b15879eeca6

@ -0,0 +1,62 @@
{
"txid": "100e0b7b5bf835afc6459fb6fe2ed51a68bd5b5b01e5c08f937f8b15879eeca6",
"hash": "100e0b7b5bf835afc6459fb6fe2ed51a68bd5b5b01e5c08f937f8b15879eeca6",
"version": 1,
"size": 235,
"vsize": 235,
"weight": 940,
"locktime": 0,
"vin": [
{
"txid": "135fb9e9d96586d97fcd8e79cef1bd253bb49d00131355b5c8c1c70f9c757dd2",
"vout": 2,
"scriptSig": {
"asm": "304502210099e54963723e0db2fd4209ecb0d8d7bd71b0da4ec2fa59b32ccfabb6b7d3490f022030561a6d3d7e6e11b467392336db7bb83f37b48ef1944570af1c88d54133d511[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "48304502210099e54963723e0db2fd4209ecb0d8d7bd71b0da4ec2fa59b32ccfabb6b7d3490f022030561a6d3d7e6e11b467392336db7bb83f37b48ef1944570af1c88d54133d511012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "0100000001d27d759c0fc7c1c8b5551313009db43b25bdf1ce798ecd7fd98665d9e9b95f13020000006b48304502210099e54963723e0db2fd4209ecb0d8d7bd71b0da4ec2fa59b32ccfabb6b7d3490f022030561a6d3d7e6e11b467392336db7bb83f37b48ef1944570af1c88d54133d511012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "28fb72e618e737b086143eefaf6d859117ecf354e98347da8be7c382c3ddddce",
"confirmations": 588069,
"time": 1520366729,
"blocktime": 1520366729
}

62
server/test/resources/transactions/1361d3574b9091557e62732b7da30f0c10e1411ba2d30973afb41e489e0993fe

@ -0,0 +1,62 @@
{
"txid": "1361d3574b9091557e62732b7da30f0c10e1411ba2d30973afb41e489e0993fe",
"hash": "1361d3574b9091557e62732b7da30f0c10e1411ba2d30973afb41e489e0993fe",
"version": 1,
"size": 234,
"vsize": 234,
"weight": 936,
"locktime": 0,
"vin": [
{
"txid": "533ddceab3774b117c81c994663f69a40cebae0a9d80db6ff64db8fe2a97b051",
"vout": 1,
"scriptSig": {
"asm": "304402200a0825834e6f912c26854a851e59d171c9fd839f7e61644dfe6588370da0657202202c6f9e08ffc6e9bb9b287157a66d4fb14e663524d8e5018bda555f5352fc4c9d[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "47304402200a0825834e6f912c26854a851e59d171c9fd839f7e61644dfe6588370da0657202202c6f9e08ffc6e9bb9b287157a66d4fb14e663524d8e5018bda555f5352fc4c9d012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "010000000151b0972afeb84df66fdb809d0aaeeb0ca4693f6694c9817c114b77b3eadc3d53010000006a47304402200a0825834e6f912c26854a851e59d171c9fd839f7e61644dfe6588370da0657202202c6f9e08ffc6e9bb9b287157a66d4fb14e663524d8e5018bda555f5352fc4c9d012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "fd067f15e759fd17dde4501cf755518be8b85eb77ed3f28fdd240dbc29841337",
"confirmations": 587629,
"time": 1520393339,
"blocktime": 1520393339
}

62
server/test/resources/transactions/56e1844d9d01a4586e903e85bf29c35af2b9e32c5fc792dd098840219fe6354a

@ -0,0 +1,62 @@
{
"txid": "56e1844d9d01a4586e903e85bf29c35af2b9e32c5fc792dd098840219fe6354a",
"hash": "56e1844d9d01a4586e903e85bf29c35af2b9e32c5fc792dd098840219fe6354a",
"version": 1,
"size": 235,
"vsize": 235,
"weight": 940,
"locktime": 0,
"vin": [
{
"txid": "815382b900ea1aa08df29d7caa7235714c23bddfcc7f5c1429790bf4ef855381",
"vout": 2,
"scriptSig": {
"asm": "3045022100fb79c4a782e839cac2e1ee305c362032ac02cedcbe0fad91e6e3557c6e79f12402205fc8e3983a5289654ff5c5f5704568bd00bb972c6124a277c51db45c8924a2c0[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "483045022100fb79c4a782e839cac2e1ee305c362032ac02cedcbe0fad91e6e3557c6e79f12402205fc8e3983a5289654ff5c5f5704568bd00bb972c6124a277c51db45c8924a2c0012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "0100000001815385eff40b7929145c7fccdfbd234c713572aa7c9df28da01aea00b9825381020000006b483045022100fb79c4a782e839cac2e1ee305c362032ac02cedcbe0fad91e6e3557c6e79f12402205fc8e3983a5289654ff5c5f5704568bd00bb972c6124a277c51db45c8924a2c0012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "b7a006c1b8bbf858f42dc7d7906151c5f074e1297f78eb4afa08ddd05f2daf24",
"confirmations": 587762,
"time": 1520384997,
"blocktime": 1520384997
}

62
server/test/resources/transactions/63539693bf1d8df5d969e96d7e89f26b3816580bed1712da4b4e60913efc7b34

@ -0,0 +1,62 @@
{
"txid": "63539693bf1d8df5d969e96d7e89f26b3816580bed1712da4b4e60913efc7b34",
"hash": "63539693bf1d8df5d969e96d7e89f26b3816580bed1712da4b4e60913efc7b34",
"version": 1,
"size": 234,
"vsize": 234,
"weight": 936,
"locktime": 0,
"vin": [
{
"txid": "67bd941a90e99db9be6cdb2551249cf0ec9732b5cc8ed5d7917795b5f43af64f",
"vout": 2,
"scriptSig": {
"asm": "304402201f444235e301daf80909e2bd8a7d8ff9fad883ed6005c85a365a6b1526e21afc022031e4a0a60abff969f9b09eebf36468bd60b4e48db4ef60d4df346ea4787e4c47[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "47304402201f444235e301daf80909e2bd8a7d8ff9fad883ed6005c85a365a6b1526e21afc022031e4a0a60abff969f9b09eebf36468bd60b4e48db4ef60d4df346ea4787e4c47012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "01000000014ff63af4b5957791d7d58eccb53297ecf09c245125db6cbeb99de9901a94bd67020000006a47304402201f444235e301daf80909e2bd8a7d8ff9fad883ed6005c85a365a6b1526e21afc022031e4a0a60abff969f9b09eebf36468bd60b4e48db4ef60d4df346ea4787e4c47012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "dee3fab1c4e5187f6e42771f1cee64adbc67eca095d74a372f43a05022298a58",
"confirmations": 587635,
"time": 1520392854,
"blocktime": 1520392854
}

62
server/test/resources/transactions/7943aeb6eee0f92406930b4a1b34a8801477c2bf0d94fa7f2e7a43d61a9cd60c

@ -0,0 +1,62 @@
{
"txid": "7943aeb6eee0f92406930b4a1b34a8801477c2bf0d94fa7f2e7a43d61a9cd60c",
"hash": "7943aeb6eee0f92406930b4a1b34a8801477c2bf0d94fa7f2e7a43d61a9cd60c",
"version": 1,
"size": 234,
"vsize": 234,
"weight": 936,
"locktime": 0,
"vin": [
{
"txid": "1a68bd141ff69bf579f3c128433dd77da98a75e39b37760558226cb2add78741",
"vout": 1,
"scriptSig": {
"asm": "304402201aee4cbc775a3b1354fe6456ac9f0688e0d73a6b3520f9f110c627a94955e07f0220101b3a44d24e6b6b05112011fca6bd8803239809659d5970c9f65e69c4f0bb93[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "47304402201aee4cbc775a3b1354fe6456ac9f0688e0d73a6b3520f9f110c627a94955e07f0220101b3a44d24e6b6b05112011fca6bd8803239809659d5970c9f65e69c4f0bb93012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "01000000014187d7adb26c22580576379be3758aa97dd73d4328c1f379f59bf61f14bd681a010000006a47304402201aee4cbc775a3b1354fe6456ac9f0688e0d73a6b3520f9f110c627a94955e07f0220101b3a44d24e6b6b05112011fca6bd8803239809659d5970c9f65e69c4f0bb93012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "5efbab10c6c38003cb27db443b51b00c00483c2db603a22c047ed699b3c804e1",
"confirmations": 588053,
"time": 1520367738,
"blocktime": 1520367738
}

62
server/test/resources/transactions/980dc3eb7577a44d9a57c6d26151e9bd2b517d8b35ac111016b4c890a1f78a8d

@ -0,0 +1,62 @@
{
"txid": "980dc3eb7577a44d9a57c6d26151e9bd2b517d8b35ac111016b4c890a1f78a8d",
"hash": "980dc3eb7577a44d9a57c6d26151e9bd2b517d8b35ac111016b4c890a1f78a8d",
"version": 1,
"size": 234,
"vsize": 234,
"weight": 936,
"locktime": 0,
"vin": [
{
"txid": "533ddceab3774b117c81c994663f69a40cebae0a9d80db6ff64db8fe2a97b051",
"vout": 2,
"scriptSig": {
"asm": "304402206f75863b212819912e871f9071825d0274b8bdb4d736bad4c86ad95a6f3497a2022066145d851d955fbf1d6966422be25dd8b4b44f0bd8a5c67bf04800ff88764acd[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "47304402206f75863b212819912e871f9071825d0274b8bdb4d736bad4c86ad95a6f3497a2022066145d851d955fbf1d6966422be25dd8b4b44f0bd8a5c67bf04800ff88764acd012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "010000000151b0972afeb84df66fdb809d0aaeeb0ca4693f6694c9817c114b77b3eadc3d53020000006a47304402206f75863b212819912e871f9071825d0274b8bdb4d736bad4c86ad95a6f3497a2022066145d851d955fbf1d6966422be25dd8b4b44f0bd8a5c67bf04800ff88764acd012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "17ec63913d4798460cf65fe995d6829bc6f634be9f31b7f6a69ca95ea0b333f3",
"confirmations": 588045,
"time": 1520368002,
"blocktime": 1520368002
}

62
server/test/resources/transactions/a7cf3af87f66e58b43de4eb61e3bad6185fd8e3137d63c1960e4df67088f3fe5

@ -0,0 +1,62 @@
{
"txid": "a7cf3af87f66e58b43de4eb61e3bad6185fd8e3137d63c1960e4df67088f3fe5",
"hash": "a7cf3af87f66e58b43de4eb61e3bad6185fd8e3137d63c1960e4df67088f3fe5",
"version": 1,
"size": 235,
"vsize": 235,
"weight": 940,
"locktime": 0,
"vin": [
{
"txid": "4a577640c37dd796ae6f8e0538db636983110f01dc42aeefe254bf370b645d1b",
"vout": 2,
"scriptSig": {
"asm": "3045022100838d27b2a574dd99ae90db1703cbcdccdc59edeb98a6421216aefeec1b036e0f02206e5ec511ef654ba8d3df0bfddf513538909f4ff113b9d3c4752804425c39edf5[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "483045022100838d27b2a574dd99ae90db1703cbcdccdc59edeb98a6421216aefeec1b036e0f02206e5ec511ef654ba8d3df0bfddf513538909f4ff113b9d3c4752804425c39edf5012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "01000000011b5d640b37bf54e2efae42dc010f11836963db38058e6fae96d77dc34076574a020000006b483045022100838d27b2a574dd99ae90db1703cbcdccdc59edeb98a6421216aefeec1b036e0f02206e5ec511ef654ba8d3df0bfddf513538909f4ff113b9d3c4752804425c39edf5012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "98513955cc3342bd26c7dfb88c69765ce05ffc1493c0a9beb38c1e02fc11e6c0",
"confirmations": 587836,
"time": 1520380830,
"blocktime": 1520380830
}

62
server/test/resources/transactions/b20e438c4c54cb048dbcb5f39de17dcaf6418adb521f5ff9d29c13239d50bcba

@ -0,0 +1,62 @@
{
"txid": "b20e438c4c54cb048dbcb5f39de17dcaf6418adb521f5ff9d29c13239d50bcba",
"hash": "b20e438c4c54cb048dbcb5f39de17dcaf6418adb521f5ff9d29c13239d50bcba",
"version": 1,
"size": 235,
"vsize": 235,
"weight": 940,
"locktime": 0,
"vin": [
{
"txid": "537e1fee3c18dce91cf5b743e0fb9836704b80f57fd320e82e00dcfc38e9b4b4",
"vout": 2,
"scriptSig": {
"asm": "3045022100f95f0737e8e386862b7740e8929a712e7aeab0953c1ec51c28142c81f17d091a022019798947402d08debca1e413428e37dd4f1fcdd703a360ccbc1abccaedecbee4[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "483045022100f95f0737e8e386862b7740e8929a712e7aeab0953c1ec51c28142c81f17d091a022019798947402d08debca1e413428e37dd4f1fcdd703a360ccbc1abccaedecbee4012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "0100000001b4b4e938fcdc002ee820d37ff5804b703698fbe043b7f51ce9dc183cee1f7e53020000006b483045022100f95f0737e8e386862b7740e8929a712e7aeab0953c1ec51c28142c81f17d091a022019798947402d08debca1e413428e37dd4f1fcdd703a360ccbc1abccaedecbee4012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "c1d2b6ceb55487d4d35f90d45d44ae34faf2462a4457e645a3d50ab8bdf74c0a",
"confirmations": 588087,
"time": 1520366239,
"blocktime": 1520366239
}

62
server/test/resources/transactions/bd715a5f43d73a5f347b7779859b029be069cc8ea6fc6480dfda40b64d828c6c

@ -0,0 +1,62 @@
{
"txid": "bd715a5f43d73a5f347b7779859b029be069cc8ea6fc6480dfda40b64d828c6c",
"hash": "bd715a5f43d73a5f347b7779859b029be069cc8ea6fc6480dfda40b64d828c6c",
"version": 1,
"size": 235,
"vsize": 235,
"weight": 940,
"locktime": 0,
"vin": [
{
"txid": "40ceeb530b29e5426236d3fcf8cc2162625bdd6a09f61f291452b351a2e411cf",
"vout": 1,
"scriptSig": {
"asm": "3045022100ae6b492bd7fc007ca208ed609b8ad3e617c3d890ef4d24c92d6449f4db2b20810220588a30755b267d23a894af3f47512190c17beb969f145a2ac9a41bff91e93ea2[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "483045022100ae6b492bd7fc007ca208ed609b8ad3e617c3d890ef4d24c92d6449f4db2b20810220588a30755b267d23a894af3f47512190c17beb969f145a2ac9a41bff91e93ea2012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "0100000001cf11e4a251b35214291ff6096add5b626221ccf8fcd3366242e5290b53ebce40010000006b483045022100ae6b492bd7fc007ca208ed609b8ad3e617c3d890ef4d24c92d6449f4db2b20810220588a30755b267d23a894af3f47512190c17beb969f145a2ac9a41bff91e93ea2012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "0bf973bfa84d49f85f6af32e53ccb577e43f725a6996c37ccc377f54ceebb463",
"confirmations": 587655,
"time": 1520391921,
"blocktime": 1520391921
}

62
server/test/resources/transactions/da1af8037b1cb20f6702eef636d27871ab99bd69031668fafc0ae6cbe4f9941a

@ -0,0 +1,62 @@
{
"txid": "da1af8037b1cb20f6702eef636d27871ab99bd69031668fafc0ae6cbe4f9941a",
"hash": "da1af8037b1cb20f6702eef636d27871ab99bd69031668fafc0ae6cbe4f9941a",
"version": 1,
"size": 235,
"vsize": 235,
"weight": 940,
"locktime": 0,
"vin": [
{
"txid": "d5fd9995d441cb1fb885f2ad22962c41adfd8c407e423973c3b950210acbf941",
"vout": 2,
"scriptSig": {
"asm": "3045022100e0928e1b40b8882d361412e2ebdc3362652a36a5c5f809b37e43e4fc4e09966902200846c62e14dbbc6c0f10cecc5852d6a4d42247a98287e6359d633e5df9e65f76[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "483045022100e0928e1b40b8882d361412e2ebdc3362652a36a5c5f809b37e43e4fc4e09966902200846c62e14dbbc6c0f10cecc5852d6a4d42247a98287e6359d633e5df9e65f76012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "010000000141f9cb0a2150b9c37339427e408cfdad412c9622adf285b81fcb41d49599fdd5020000006b483045022100e0928e1b40b8882d361412e2ebdc3362652a36a5c5f809b37e43e4fc4e09966902200846c62e14dbbc6c0f10cecc5852d6a4d42247a98287e6359d633e5df9e65f76012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "92435e3280730249ce3fac51e4c8d690c300a1493032ca5482e01f0179050d05",
"confirmations": 586977,
"time": 1520433722,
"blocktime": 1520433722
}

62
server/test/resources/transactions/e66cfa37f10fe7e75f2414fb3465dda7864acbdb9885f519217dd147178b7188

@ -0,0 +1,62 @@
{
"txid": "e66cfa37f10fe7e75f2414fb3465dda7864acbdb9885f519217dd147178b7188",
"hash": "e66cfa37f10fe7e75f2414fb3465dda7864acbdb9885f519217dd147178b7188",
"version": 1,
"size": 234,
"vsize": 234,
"weight": 936,
"locktime": 0,
"vin": [
{
"txid": "342d9b16e5bdeeeb4ada59de3179dbe40a1563447b3afd0e0451f31819b45ba6",
"vout": 2,
"scriptSig": {
"asm": "30440220686a805f81afd322e3a412410679d139481b513f04a7550f1d44d41f6cb407c6022058881887de78b94f223369df3996b071c0ec49bf6bbaba73ea10326ced3589b7[ALL] 03624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1",
"hex": "4730440220686a805f81afd322e3a412410679d139481b513f04a7550f1d44d41f6cb407c6022058881887de78b94f223369df3996b071c0ec49bf6bbaba73ea10326ced3589b7012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 73242.18642578,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
},
{
"value": 73242.18642578,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 3cc9ede1da2d7351aaebaf6a25d2657e0b05a716 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"
]
}
}
],
"hex": "0100000001a65bb41918f351040efd3a7b4463150ae4db7931de59da4aebeebde5169b2d34020000006a4730440220686a805f81afd322e3a412410679d139481b513f04a7550f1d44d41f6cb407c6022058881887de78b94f223369df3996b071c0ec49bf6bbaba73ea10326ced3589b7012103624fbfb0079e85bbc9aaeba6f48581326ad01194b3c54ce22852a27b1d2892d1ffffffff030000000000000000009250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac9250734da90600001976a9143cc9ede1da2d7351aaebaf6a25d2657e0b05a71688ac00000000",
"blockhash": "2855f642d5e64f2a627eda761d55b9a8ab9e5eccd572655439a27fa429548815",
"confirmations": 587817,
"time": 1520381857,
"blocktime": 1520381857
}

88
server/test/resources/transactions/f296cdf4fd54da9a5adfa1df6f6f321409b8b00b6020ce977e45746a09f964ad

@ -0,0 +1,88 @@
{
"txid": "f296cdf4fd54da9a5adfa1df6f6f321409b8b00b6020ce977e45746a09f964ad",
"hash": "f296cdf4fd54da9a5adfa1df6f6f321409b8b00b6020ce977e45746a09f964ad",
"version": 1,
"size": 196,
"vsize": 196,
"weight": 784,
"locktime": 0,
"vin": [
{
"txid": "b5dddd087c051aeec5ce59a96adafc5449bb212ecd3892d7ff6456e573d7737b",
"vout": 1,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0,
"n": 0,
"scriptPubKey": {
"asm": "",
"hex": "",
"type": "nonstandard"
}
},
{
"value": 2536.19375,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 c9ba3c497107845ea80dcf64951ced030556384e OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914c9ba3c497107845ea80dcf64951ced030556384e88ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"Xu5UkgRL8YRqoW6uEW8SxMLDkJwbjFVfge"
]
}
},
{
"value": 2513.91875,
"n": 2,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 c9ba3c497107845ea80dcf64951ced030556384e OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914c9ba3c497107845ea80dcf64951ced030556384e88ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"Xu5UkgRL8YRqoW6uEW8SxMLDkJwbjFVfge"
]
}
},
{
"value": 0.225,
"n": 3,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 0651c16ab25fa5a6b8c0598b6fd18c493ad25700 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9140651c16ab25fa5a6b8c0598b6fd18c493ad2570088ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XbGFpsuhv6AH3gp3dx5eQrAexP5kESh9bY"
]
}
},
{
"value": 22.5,
"n": 4,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 b4b58ae50a9e0a6e5837c1fe80c0502054534124 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914b4b58ae50a9e0a6e5837c1fe80c050205453412488ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"XsALwaebmmPSXViyfacBRhp68f7KWwiNvW"
]
}
}
],
"hex": "01000000017b73d773e55664ffd79238cd2e21bb4954fcda6aa959cec5ee1a057c08ddddb50100000000ffffffff05000000000000000000988be40c3b0000001976a914c9ba3c497107845ea80dcf64951ced030556384e88acb8971f883a0000001976a914c9ba3c497107845ea80dcf64951ced030556384e88aca0525701000000001976a9140651c16ab25fa5a6b8c0598b6fd18c493ad2570088ac80461c86000000001976a914b4b58ae50a9e0a6e5837c1fe80c050205453412488ac00000000",
"blockhash": "0ef7374452abd7dc71a9350a16813ba2df3489c626076296824aebf203c20cda",
"confirmations": 556369,
"time": 1522420058,
"blocktime": 1522420058
}
Loading…
Cancel
Save