Browse Source

server: Let scalafmt do its job

develop
Alexis Hernandez 6 years ago
parent
commit
0113bbad47
  1. 41
      server/app/com/xsn/explorer/cache/BlockHeaderCache.scala
  2. 6
      server/app/com/xsn/explorer/config/ExplorerConfig.scala
  3. 10
      server/app/com/xsn/explorer/config/LedgerSynchronizerConfig.scala
  4. 2
      server/app/com/xsn/explorer/config/RPCConfig.scala
  5. 10
      server/app/com/xsn/explorer/data/BlockDataHandler.scala
  6. 19
      server/app/com/xsn/explorer/data/TransactionDataHandler.scala
  7. 25
      server/app/com/xsn/explorer/data/anorm/BalancePostgresDataHandler.scala
  8. 20
      server/app/com/xsn/explorer/data/anorm/BlockPostgresDataHandler.scala
  9. 72
      server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala
  10. 4
      server/app/com/xsn/explorer/data/anorm/StatisticsPostgresDataHandler.scala
  11. 4
      server/app/com/xsn/explorer/data/anorm/TPoSContractPostgresDataHandler.scala
  12. 54
      server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala
  13. 43
      server/app/com/xsn/explorer/data/anorm/dao/AddressTransactionDetailsPostgresDAO.scala
  14. 5
      server/app/com/xsn/explorer/data/anorm/dao/AggregatedAmountPostgresDAO.scala
  15. 54
      server/app/com/xsn/explorer/data/anorm/dao/BalancePostgresDAO.scala
  16. 23
      server/app/com/xsn/explorer/data/anorm/dao/BlockFilterPostgresDAO.scala
  17. 103
      server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala
  18. 1
      server/app/com/xsn/explorer/data/anorm/dao/StatisticsPostgresDAO.scala
  19. 47
      server/app/com/xsn/explorer/data/anorm/dao/TPoSContractDAO.scala
  20. 43
      server/app/com/xsn/explorer/data/anorm/dao/TransactionInputPostgresDAO.scala
  21. 50
      server/app/com/xsn/explorer/data/anorm/dao/TransactionOutputPostgresDAO.scala
  22. 197
      server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala
  23. 5
      server/app/com/xsn/explorer/data/anorm/parsers/BalanceParsers.scala
  24. 64
      server/app/com/xsn/explorer/data/anorm/parsers/BlockParsers.scala
  25. 29
      server/app/com/xsn/explorer/data/anorm/parsers/CommonParsers.scala
  26. 26
      server/app/com/xsn/explorer/data/anorm/parsers/TPoSContractParsers.scala
  27. 42
      server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala
  28. 22
      server/app/com/xsn/explorer/data/async/BalanceFutureDataHandler.scala
  29. 15
      server/app/com/xsn/explorer/data/async/BlockFutureDataHandler.scala
  30. 14
      server/app/com/xsn/explorer/data/async/LedgerFutureDataHandler.scala
  31. 7
      server/app/com/xsn/explorer/data/async/StatisticsFutureDataHandler.scala
  32. 7
      server/app/com/xsn/explorer/data/async/TPoSContractFutureDataHandler.scala
  33. 22
      server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala
  34. 6
      server/app/com/xsn/explorer/gcs/GolombCodedSet.scala
  35. 56
      server/app/com/xsn/explorer/gcs/GolombEncoding.scala
  36. 13
      server/app/com/xsn/explorer/gcs/SipHashKey.scala
  37. 10
      server/app/com/xsn/explorer/gcs/UnsignedByte.scala
  38. 2
      server/app/com/xsn/explorer/models/BlockExtractionMethod.scala
  39. 3
      server/app/com/xsn/explorer/models/LightWalletTransaction.scala
  40. 3
      server/app/com/xsn/explorer/models/Statistics.scala
  41. 15
      server/app/com/xsn/explorer/models/StatisticsDetails.scala
  42. 9
      server/app/com/xsn/explorer/models/TPoSContract.scala
  43. 3
      server/app/com/xsn/explorer/models/TransactionDetails.scala
  44. 3
      server/app/com/xsn/explorer/models/TransactionWithValues.scala
  45. 3
      server/app/com/xsn/explorer/models/blockRewards.scala
  46. 3
      server/app/com/xsn/explorer/models/persisted/AddressTransactionDetails.scala
  47. 8
      server/app/com/xsn/explorer/models/persisted/Balance.scala
  48. 12
      server/app/com/xsn/explorer/models/persisted/Block.scala
  49. 14
      server/app/com/xsn/explorer/models/persisted/BlockHeader.scala
  50. 54
      server/app/com/xsn/explorer/models/persisted/Transaction.scala
  51. 4
      server/app/com/xsn/explorer/models/persisted/package.scala
  52. 1
      server/app/com/xsn/explorer/models/rpc/AddressBalance.scala
  53. 78
      server/app/com/xsn/explorer/models/rpc/Block.scala
  54. 17
      server/app/com/xsn/explorer/models/rpc/Masternode.scala
  55. 12
      server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala
  56. 9
      server/app/com/xsn/explorer/models/rpc/ServerStatistics.scala
  57. 24
      server/app/com/xsn/explorer/models/rpc/Transaction.scala
  58. 5
      server/app/com/xsn/explorer/models/rpc/TransactionVIN.scala
  59. 9
      server/app/com/xsn/explorer/models/rpc/TransactionVOUT.scala
  60. 22
      server/app/com/xsn/explorer/models/transformers/package.scala
  61. 16
      server/app/com/xsn/explorer/models/values/Address.scala
  62. 8
      server/app/com/xsn/explorer/models/values/Blockhash.scala
  63. 2
      server/app/com/xsn/explorer/models/values/IPAddress.scala
  64. 8
      server/app/com/xsn/explorer/models/values/TransactionId.scala
  65. 4
      server/app/com/xsn/explorer/models/values/package.scala
  66. 7
      server/app/com/xsn/explorer/modules/ExecutorsModule.scala
  67. 3
      server/app/com/xsn/explorer/parsers/OrderingConditionParser.scala
  68. 13
      server/app/com/xsn/explorer/play/LoggingFilter.scala
  69. 2
      server/app/com/xsn/explorer/play/MyHttpErrorHandler.scala
  70. 6
      server/app/com/xsn/explorer/services/AddressService.scala
  71. 11
      server/app/com/xsn/explorer/services/BalanceService.scala
  72. 126
      server/app/com/xsn/explorer/services/BlockService.scala
  73. 137
      server/app/com/xsn/explorer/services/LedgerSynchronizerService.scala
  74. 21
      server/app/com/xsn/explorer/services/MasternodeService.scala
  75. 17
      server/app/com/xsn/explorer/services/StatisticsService.scala
  76. 6
      server/app/com/xsn/explorer/services/TPoSContractService.scala
  77. 105
      server/app/com/xsn/explorer/services/TransactionCollectorService.scala
  78. 6
      server/app/com/xsn/explorer/services/TransactionRPCService.scala
  79. 27
      server/app/com/xsn/explorer/services/TransactionService.scala
  80. 451
      server/app/com/xsn/explorer/services/XSNService.scala
  81. 44
      server/app/com/xsn/explorer/services/logic/BlockLogic.scala
  82. 6
      server/app/com/xsn/explorer/services/logic/TransactionLogic.scala
  83. 6
      server/app/com/xsn/explorer/services/validators/package.scala
  84. 29
      server/app/com/xsn/explorer/tasks/PollerSynchronizerTask.scala
  85. 3
      server/app/com/xsn/explorer/util/Extensions.scala
  86. 42
      server/app/controllers/AddressesController.scala
  87. 6
      server/app/controllers/BalancesController.scala
  88. 31
      server/app/controllers/BlocksController.scala
  89. 4
      server/app/controllers/HealthController.scala
  90. 4
      server/app/controllers/MaintenanceController.scala
  91. 4
      server/app/controllers/MasternodesController.scala
  92. 4
      server/app/controllers/StatisticsController.scala
  93. 4
      server/app/controllers/TransactionsController.scala
  94. 24
      server/app/controllers/common/Codecs.scala
  95. 6
      server/app/controllers/common/MyJsonControllerComponents.scala

41
server/app/com/xsn/explorer/cache/BlockHeaderCache.scala

@ -28,30 +28,28 @@ class BlockHeaderCache(cache: Cache[BlockHeaderCache.Key, BlockHeaderCache.Encod
import BlockHeaderCache._
def getOrSet(
key: Key,
entrySize: Int)(
f: => FutureApplicationResult[Value])(
implicit ec: ExecutionContext,
writes: Writes[Value]): FutureApplicationResult[EncodedValue] = {
def getOrSet(key: Key, entrySize: Int)(
f: => FutureApplicationResult[Value]
)(implicit ec: ExecutionContext, writes: Writes[Value]): FutureApplicationResult[EncodedValue] = {
if (isCacheable(key, entrySize)) {
get(key)
.map(v => Future.successful(Good(v)))
.getOrElse {
val result = for {
r <- f.toFutureOr
} yield (r.data.size, Json.toJson(r))
// set cache only if the response is complete
val _ = result.map { case (size, json) =>
.map(v => Future.successful(Good(v)))
.getOrElse {
val result = for {
r <- f.toFutureOr
} yield (r.data.size, Json.toJson(r))
// set cache only if the response is complete
val _ = result.map {
case (size, json) =>
if (size == key.limit.int) {
cache.put(key, json)
}
}
result.map(_._2).toFuture
}
result.map(_._2).toFuture
}
} else {
val result = for {
r <- f.toFutureOr
@ -69,7 +67,7 @@ class BlockHeaderCache(cache: Cache[BlockHeaderCache.Key, BlockHeaderCache.Encod
*/
def isCacheable(key: Key, entrySize: Int): Boolean = {
key.orderingCondition == OrderingCondition.AscendingOrder &&
key.limit.int == entrySize
key.limit.int == entrySize
}
def get(key: Key): Option[EncodedValue] = {
@ -85,9 +83,10 @@ object BlockHeaderCache {
type EncodedValue = JsValue
def default: BlockHeaderCache = {
val cache = Caffeine.newBuilder()
.maximumSize(250000)
.build[Key, EncodedValue]
val cache = Caffeine
.newBuilder()
.maximumSize(250000)
.build[Key, EncodedValue]
new BlockHeaderCache(cache)
}

6
server/app/com/xsn/explorer/config/ExplorerConfig.scala

@ -15,12 +15,12 @@ object ExplorerConfig {
case class LiteVersionConfig(enabled: Boolean, syncTransactionsFromBlock: Int)
class Play @Inject() (config: Configuration) extends ExplorerConfig {
class Play @Inject()(config: Configuration) extends ExplorerConfig {
override val genesisBlock: Blockhash = {
Blockhash
.from(config.get[String]("explorer.genesisBlock"))
.getOrElse(throw new RuntimeException("The given genesisBlock is incorrect"))
.from(config.get[String]("explorer.genesisBlock"))
.getOrElse(throw new RuntimeException("The given genesisBlock is incorrect"))
}
override def liteVersionConfig: LiteVersionConfig = {

10
server/app/com/xsn/explorer/config/LedgerSynchronizerConfig.scala

@ -15,11 +15,13 @@ trait LedgerSynchronizerConfig {
def interval: FiniteDuration
}
class LedgerSynchronizerPlayConfig @Inject() (config: Configuration) extends LedgerSynchronizerConfig {
class LedgerSynchronizerPlayConfig @Inject()(config: Configuration) extends LedgerSynchronizerConfig {
override lazy val enabled: Boolean = config.getOptional[Boolean]("synchronizer.enabled").getOrElse(false)
override lazy val initialDelay: FiniteDuration = config.getOptional[FiniteDuration]("synchronizer.initialDelay").getOrElse(15.seconds)
override lazy val initialDelay: FiniteDuration =
config.getOptional[FiniteDuration]("synchronizer.initialDelay").getOrElse(15.seconds)
override lazy val interval: FiniteDuration = config.getOptional[FiniteDuration]("synchronizer.interval").getOrElse(60.seconds)
}
override lazy val interval: FiniteDuration =
config.getOptional[FiniteDuration]("synchronizer.interval").getOrElse(60.seconds)
}

2
server/app/com/xsn/explorer/config/RPCConfig.scala

@ -20,7 +20,7 @@ object RPCConfig {
case class Password(string: String) extends AnyVal
}
class PlayRPCConfig @Inject() (config: Configuration) extends RPCConfig {
class PlayRPCConfig @Inject()(config: Configuration) extends RPCConfig {
import RPCConfig._

10
server/app/com/xsn/explorer/data/BlockDataHandler.scala

@ -15,9 +15,7 @@ trait BlockDataHandler[F[_]] {
def getBy(height: Height): F[Block]
def getBy(
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[BlockField]): F[PaginatedResult[Block]]
def getBy(paginatedQuery: PaginatedQuery, ordering: FieldOrdering[BlockField]): F[PaginatedResult[Block]]
def delete(blockhash: Blockhash): F[Block]
@ -25,7 +23,11 @@ trait BlockDataHandler[F[_]] {
def getFirstBlock(): F[Block]
def getHeaders(limit: Limit, orderingCondition: OrderingCondition, lastSeenHash: Option[Blockhash]): F[List[BlockHeader]]
def getHeaders(
limit: Limit,
orderingCondition: OrderingCondition,
lastSeenHash: Option[Blockhash]
): F[List[BlockHeader]]
}
trait BlockBlockingDataHandler extends BlockDataHandler[ApplicationResult]

19
server/app/com/xsn/explorer/data/TransactionDataHandler.scala

@ -15,9 +15,15 @@ trait TransactionDataHandler[F[_]] {
def getBy(
address: Address,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): F[PaginatedResult[TransactionWithValues]]
ordering: FieldOrdering[TransactionField]
): F[PaginatedResult[TransactionWithValues]]
def getBy(address: Address, limit: Limit, lastSeenTxid: Option[TransactionId], orderingCondition: OrderingCondition): F[List[Transaction.HasIO]]
def getBy(
address: Address,
limit: Limit,
lastSeenTxid: Option[TransactionId],
orderingCondition: OrderingCondition
): F[List[Transaction.HasIO]]
def getUnspentOutputs(address: Address): F[List[Transaction.Output]]
@ -26,17 +32,20 @@ trait TransactionDataHandler[F[_]] {
def getByBlockhash(
blockhash: Blockhash,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): F[PaginatedResult[TransactionWithValues]]
ordering: FieldOrdering[TransactionField]
): F[PaginatedResult[TransactionWithValues]]
def getByBlockhash(
blockhash: Blockhash,
limit: Limit,
lastSeenTxid: Option[TransactionId]): F[List[TransactionWithValues]]
lastSeenTxid: Option[TransactionId]
): F[List[TransactionWithValues]]
def getTransactionsWithIOBy(
blockhash: Blockhash,
limit: Limit,
lastSeenTxid: Option[TransactionId]): F[List[Transaction.HasIO]]
lastSeenTxid: Option[TransactionId]
): F[List[Transaction.HasIO]]
}
trait TransactionBlockingDataHandler extends TransactionDataHandler[ApplicationResult]

25
server/app/com/xsn/explorer/data/anorm/BalancePostgresDataHandler.scala

@ -13,9 +13,7 @@ import javax.inject.Inject
import org.scalactic.{Good, One, Or}
import play.api.db.Database
class BalancePostgresDataHandler @Inject() (
override val database: Database,
balancePostgresDAO: BalancePostgresDAO)
class BalancePostgresDataHandler @Inject()(override val database: Database, balancePostgresDAO: BalancePostgresDAO)
extends BalanceBlockingDataHandler
with AnormPostgresDataHandler {
@ -27,8 +25,8 @@ class BalancePostgresDataHandler @Inject() (
override def get(
query: PaginatedQuery,
ordering: FieldOrdering[BalanceField]): ApplicationResult[PaginatedResult[Balance]] = withConnection { implicit conn =>
ordering: FieldOrdering[BalanceField]
): ApplicationResult[PaginatedResult[Balance]] = withConnection { implicit conn =>
val balances = balancePostgresDAO.get(query, ordering)
val total = balancePostgresDAO.count
val result = PaginatedResult(query.offset, query.limit, total, balances)
@ -46,8 +44,8 @@ class BalancePostgresDataHandler @Inject() (
override def getNonZeroBalances(
query: PaginatedQuery,
ordering: FieldOrdering[BalanceField]): ApplicationResult[PaginatedResult[Balance]] = withConnection { implicit conn =>
ordering: FieldOrdering[BalanceField]
): ApplicationResult[PaginatedResult[Balance]] = withConnection { implicit conn =>
val balances = balancePostgresDAO.getNonZeroBalances(query, ordering)
val total = balancePostgresDAO.countNonZeroBalances
val result = PaginatedResult(query.offset, query.limit, total, balances)
@ -55,11 +53,12 @@ class BalancePostgresDataHandler @Inject() (
Good(result)
}
override def getHighestBalances(limit: Limit, lastSeenAddress: Option[Address]): ApplicationResult[List[Balance]] = withConnection { implicit conn =>
val result = lastSeenAddress
.map { balancePostgresDAO.getHighestBalances(_, limit) }
.getOrElse { balancePostgresDAO.getHighestBalances(limit) }
override def getHighestBalances(limit: Limit, lastSeenAddress: Option[Address]): ApplicationResult[List[Balance]] =
withConnection { implicit conn =>
val result = lastSeenAddress
.map { balancePostgresDAO.getHighestBalances(_, limit) }
.getOrElse { balancePostgresDAO.getHighestBalances(limit) }
Good(result)
}
Good(result)
}
}

20
server/app/com/xsn/explorer/data/anorm/BlockPostgresDataHandler.scala

@ -14,9 +14,7 @@ import javax.inject.Inject
import org.scalactic.{Good, One, Or}
import play.api.db.Database
class BlockPostgresDataHandler @Inject() (
override val database: Database,
blockPostgresDAO: BlockPostgresDAO)
class BlockPostgresDataHandler @Inject()(override val database: Database, blockPostgresDAO: BlockPostgresDAO)
extends BlockBlockingDataHandler
with AnormPostgresDataHandler {
@ -32,8 +30,8 @@ class BlockPostgresDataHandler @Inject() (
override def getBy(
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[BlockField]): ApplicationResult[PaginatedResult[Block]] = withConnection { implicit conn =>
ordering: FieldOrdering[BlockField]
): ApplicationResult[PaginatedResult[Block]] = withConnection { implicit conn =>
val data = blockPostgresDAO.getBy(paginatedQuery, ordering)
val total = blockPostgresDAO.count
val result = PaginatedResult(paginatedQuery.offset, paginatedQuery.limit, total, data)
@ -59,13 +57,13 @@ class BlockPostgresDataHandler @Inject() (
override def getHeaders(
limit: pagination.Limit,
orderingCondition: OrderingCondition,
lastSeenHash: Option[Blockhash]): ApplicationResult[List[BlockHeader]] = withConnection { implicit conn =>
lastSeenHash: Option[Blockhash]
): ApplicationResult[List[BlockHeader]] = withConnection { implicit conn =>
val result = lastSeenHash
.map { hash =>
blockPostgresDAO.getHeaders(hash, limit, orderingCondition)
}
.getOrElse { blockPostgresDAO.getHeaders(limit, orderingCondition) }
.map { hash =>
blockPostgresDAO.getHeaders(hash, limit, orderingCondition)
}
.getOrElse { blockPostgresDAO.getHeaders(limit, orderingCondition) }
Good(result)
}

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

@ -17,14 +17,14 @@ import org.scalactic.Good
import org.slf4j.LoggerFactory
import play.api.db.Database
class LedgerPostgresDataHandler @Inject() (
class LedgerPostgresDataHandler @Inject()(
override val database: Database,
blockPostgresDAO: BlockPostgresDAO,
blockFilterPostgresDAO: BlockFilterPostgresDAO,
transactionPostgresDAO: TransactionPostgresDAO,
balancePostgresDAO: BalancePostgresDAO,
aggregatedAmountPostgresDAO: AggregatedAmountPostgresDAO)
extends LedgerBlockingDataHandler
aggregatedAmountPostgresDAO: AggregatedAmountPostgresDAO
) extends LedgerBlockingDataHandler
with AnormPostgresDataHandler {
private val logger = LoggerFactory.getLogger(this.getClass)
@ -33,9 +33,7 @@ class LedgerPostgresDataHandler @Inject() (
* Push a block into the database chain, note that even if the block is supposed
* to have a next block, we remove the link because that block is not stored yet.
*/
override def push(
block: Block.HasTransactions,
tposContracts: List[TPoSContract]): ApplicationResult[Unit] = {
override def push(block: Block.HasTransactions, tposContracts: List[TPoSContract]): ApplicationResult[Unit] = {
// the filter is computed outside the transaction to avoid unnecessary locking
val filter = GolombEncoding.encode(block)
@ -45,8 +43,8 @@ class LedgerPostgresDataHandler @Inject() (
} yield ()
result
.map(Good(_))
.getOrElse(throw new RuntimeException("Unable to push block"))
.map(Good(_))
.getOrElse(throw new RuntimeException("Unable to push block"))
}
def fromError(e: ApplicationError) = e match {
@ -68,21 +66,23 @@ class LedgerPostgresDataHandler @Inject() (
} yield block
result
.map(Good(_))
.getOrElse(throw new RuntimeException("Unable to pop block"))
.map(Good(_))
.getOrElse(throw new RuntimeException("Unable to pop block"))
}
private def upsertBlockCascade(
block: Block.HasTransactions,
filter: Option[GolombCodedSet],
tposContracts: List[TPoSContract])(
implicit conn: Connection): Option[Unit] = {
tposContracts: List[TPoSContract]
)(implicit conn: Connection): Option[Unit] = {
val result = for {
// block
_ <- deleteBlockCascade(block.block).orElse(Some(()))
_ <- blockPostgresDAO.insert(block.block)
_ = filter.foreach { f => blockFilterPostgresDAO.insert(block.hash, f) }
_ = filter.foreach { f =>
blockFilterPostgresDAO.insert(block.hash, f)
}
// batch insert
_ <- transactionPostgresDAO.insert(block.transactions, tposContracts)
@ -99,7 +99,7 @@ class LedgerPostgresDataHandler @Inject() (
// link previous block (if possible)
block.previousBlockhash.foreach { previousBlockhash =>
blockPostgresDAO
.setNextBlockhash(previousBlockhash, block.hash)
.setNextBlockhash(previousBlockhash, block.hash)
}
result
@ -116,10 +116,14 @@ class LedgerPostgresDataHandler @Inject() (
// balances
balanceList = balances(deletedTransactions)
_ <- balanceList
.map { b => b.copy(spent = -b.spent, received = -b.received) }
.map { b => balancePostgresDAO.upsert(b) }
.toList
.everything
.map { b =>
b.copy(spent = -b.spent, received = -b.received)
}
.map { b =>
balancePostgresDAO.upsert(b)
}
.toList
.everything
// compute aggregated amount
delta = balanceList.map(_.available).sum
@ -128,7 +132,9 @@ class LedgerPostgresDataHandler @Inject() (
}
private def insertBalanceBatch(balanceList: Iterable[Balance])(implicit conn: Connection) = {
balanceList.map { b => balancePostgresDAO.upsert(b) }
balanceList.map { b =>
balancePostgresDAO.upsert(b)
}
}
private def spendMap(transactions: List[Transaction.HasIO]): Map[Address, BigDecimal] = {
@ -139,8 +145,10 @@ class LedgerPostgresDataHandler @Inject() (
} yield address -> input.value
addressValueList
.groupBy(_._1)
.mapValues { list => list.map(_._2).sum }
.groupBy(_._1)
.mapValues { list =>
list.map(_._2).sum
}
}
private def receiveMap(transactions: List[Transaction.HasIO]): Map[Address, BigDecimal] = {
@ -151,23 +159,27 @@ class LedgerPostgresDataHandler @Inject() (
} yield address -> output.value
addressValueList
.groupBy(_._1)
.mapValues { list => list.map(_._2).sum }
.groupBy(_._1)
.mapValues { list =>
list.map(_._2).sum
}
}
private def balances(transactions: List[Transaction.HasIO]) = {
val spentList = spendMap(transactions).map { case (address, spent) =>
Balance(address, spent = spent)
val spentList = spendMap(transactions).map {
case (address, spent) =>
Balance(address, spent = spent)
}
val receiveList = receiveMap(transactions).map { case (address, received) =>
Balance(address, received = received)
val receiveList = receiveMap(transactions).map {
case (address, received) =>
Balance(address, received = received)
}
val result = (spentList ++ receiveList)
.groupBy(_.address)
.mapValues { _.reduce(mergeBalances) }
.values
.groupBy(_.address)
.mapValues { _.reduce(mergeBalances) }
.values
result
}

4
server/app/com/xsn/explorer/data/anorm/StatisticsPostgresDataHandler.scala

@ -9,9 +9,7 @@ import com.xsn.explorer.models.Statistics
import org.scalactic.Good
import play.api.db.Database
class StatisticsPostgresDataHandler @Inject() (
override val database: Database,
statisticsDAO: StatisticsPostgresDAO)
class StatisticsPostgresDataHandler @Inject()(override val database: Database, statisticsDAO: StatisticsPostgresDAO)
extends StatisticsBlockingDataHandler
with AnormPostgresDataHandler {

4
server/app/com/xsn/explorer/data/anorm/TPoSContractPostgresDataHandler.scala

@ -9,9 +9,7 @@ import javax.inject.Inject
import org.scalactic.Good
import play.api.db.Database
class TPoSContractPostgresDataHandler @Inject() (
override val database: Database,
tposContractDAO: TPoSContractDAO)
class TPoSContractPostgresDataHandler @Inject()(override val database: Database, tposContractDAO: TPoSContractDAO)
extends TPoSContractBlockingDataHandler
with AnormPostgresDataHandler {

54
server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala

@ -14,18 +14,18 @@ import javax.inject.Inject
import org.scalactic.{Good, One, Or}
import play.api.db.Database
class TransactionPostgresDataHandler @Inject() (
class TransactionPostgresDataHandler @Inject()(
override val database: Database,
transactionOutputDAO: TransactionOutputPostgresDAO,
transactionPostgresDAO: TransactionPostgresDAO)
extends TransactionBlockingDataHandler
with AnormPostgresDataHandler {
transactionPostgresDAO: TransactionPostgresDAO
) extends TransactionBlockingDataHandler
with AnormPostgresDataHandler {
override def getBy(
address: Address,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = withConnection { implicit conn =>
ordering: FieldOrdering[TransactionField]
): ApplicationResult[PaginatedResult[TransactionWithValues]] = withConnection { implicit conn =>
val transactions = transactionPostgresDAO.getBy(address, paginatedQuery, ordering)
val total = transactionPostgresDAO.countBy(address)
val result = PaginatedResult(paginatedQuery.offset, paginatedQuery.limit, total, transactions)
@ -37,30 +37,32 @@ class TransactionPostgresDataHandler @Inject() (
address: Address,
limit: Limit,
lastSeenTxid: Option[TransactionId],
orderingCondition: OrderingCondition): ApplicationResult[List[Transaction.HasIO]] = withConnection { implicit conn =>
orderingCondition: OrderingCondition
): ApplicationResult[List[Transaction.HasIO]] = withConnection { implicit conn =>
val transactions = lastSeenTxid
.map { transactionPostgresDAO.getBy(address, _, limit, orderingCondition) }
.getOrElse { transactionPostgresDAO.getBy(address, limit, orderingCondition) }
.map { transactionPostgresDAO.getBy(address, _, limit, orderingCondition) }
.getOrElse { transactionPostgresDAO.getBy(address, limit, orderingCondition) }
Good(transactions)
}
override def getUnspentOutputs(address: Address): ApplicationResult[List[Transaction.Output]] = withConnection { implicit conn =>
val result = transactionOutputDAO.getUnspentOutputs(address)
Good(result)
override def getUnspentOutputs(address: Address): ApplicationResult[List[Transaction.Output]] = withConnection {
implicit conn =>
val result = transactionOutputDAO.getUnspentOutputs(address)
Good(result)
}
override def getOutput(txid: TransactionId, index: Int): ApplicationResult[Transaction.Output] = withConnection { implicit conn =>
val maybe = transactionOutputDAO.getOutput(txid, index)
Or.from(maybe, One(TransactionError.OutputNotFound(txid, index)))
override def getOutput(txid: TransactionId, index: Int): ApplicationResult[Transaction.Output] = withConnection {
implicit conn =>
val maybe = transactionOutputDAO.getOutput(txid, index)
Or.from(maybe, One(TransactionError.OutputNotFound(txid, index)))
}
override def getByBlockhash(
blockhash: Blockhash,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = withConnection { implicit conn =>
ordering: FieldOrdering[TransactionField]
): ApplicationResult[PaginatedResult[TransactionWithValues]] = withConnection { implicit conn =>
val transactions = transactionPostgresDAO.getByBlockhash(blockhash, paginatedQuery, ordering)
val total = transactionPostgresDAO.countByBlockhash(blockhash)
val result = PaginatedResult(paginatedQuery.offset, paginatedQuery.limit, total, transactions)
@ -71,11 +73,11 @@ class TransactionPostgresDataHandler @Inject() (
override def getByBlockhash(
blockhash: Blockhash,
limit: Limit,
lastSeenTxid: Option[TransactionId]): ApplicationResult[List[TransactionWithValues]] = withConnection { implicit conn =>
lastSeenTxid: Option[TransactionId]
): ApplicationResult[List[TransactionWithValues]] = withConnection { implicit conn =>
val transactions = lastSeenTxid
.map { transactionPostgresDAO.getByBlockhash(blockhash, _, limit) }
.getOrElse { transactionPostgresDAO.getByBlockhash(blockhash, limit) }
.map { transactionPostgresDAO.getByBlockhash(blockhash, _, limit) }
.getOrElse { transactionPostgresDAO.getByBlockhash(blockhash, limit) }
Good(transactions)
}
@ -83,11 +85,11 @@ class TransactionPostgresDataHandler @Inject() (
override def getTransactionsWithIOBy(
blockhash: Blockhash,
limit: Limit,
lastSeenTxid: Option[TransactionId]): ApplicationResult[List[Transaction.HasIO]] = withConnection { implicit conn =>
lastSeenTxid: Option[TransactionId]
): ApplicationResult[List[Transaction.HasIO]] = withConnection { implicit conn =>
val transactions = lastSeenTxid
.map { transactionPostgresDAO.getTransactionsWithIOBy(blockhash, _, limit) }
.getOrElse { transactionPostgresDAO.getTransactionsWithIOBy(blockhash, limit) }
.map { transactionPostgresDAO.getTransactionsWithIOBy(blockhash, _, limit) }
.getOrElse { transactionPostgresDAO.getTransactionsWithIOBy(blockhash, limit) }
Good(transactions)
}

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

@ -10,7 +10,7 @@ import com.xsn.explorer.models.values.TransactionId
import javax.inject.Inject
import org.slf4j.LoggerFactory
class AddressTransactionDetailsPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
class AddressTransactionDetailsPostgresDAO @Inject()(explorerConfig: ExplorerConfig) {
private val logger = LoggerFactory.getLogger(this.getClass)
@ -21,9 +21,12 @@ class AddressTransactionDetailsPostgresDAO @Inject() (explorerConfig: ExplorerCo
} 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) }
.groupBy(_._1)
.mapValues { _.map(_._2).sum }
.map {
case (address, value) =>
AddressTransactionDetails(address, transaction.id, time = transaction.time, received = value)
}
val inputAddressValueList = for {
input <- transaction.inputs
@ -31,18 +34,22 @@ class AddressTransactionDetailsPostgresDAO @Inject() (explorerConfig: ExplorerCo
} 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) }
.groupBy(_._1)
.mapValues { _.map(_._2).sum }
.map {
case (address, value) =>
AddressTransactionDetails(address, transaction.id, time = transaction.time, sent = value)
}
val details = (received ++ sent)
.groupBy(_.address)
.mapValues {
case head :: list => list.foldLeft(head) { (acc, current) =>
.groupBy(_.address)
.mapValues {
case head :: list =>
list.foldLeft(head) { (acc, current) =>
current.copy(received = current.received + acc.received, sent = current.sent + acc.sent)
}
}
.values
}
.values
batchInsertDetails(details.toList)
}
@ -53,11 +60,12 @@ class AddressTransactionDetailsPostgresDAO @Inject() (explorerConfig: ExplorerCo
case _ =>
val params = details.map { d =>
List(
'address -> d.address.string: NamedParameter,
'address -> d.address.string: NamedParameter,
'txid -> d.txid.string: NamedParameter,
'received -> d.received: NamedParameter,
'sent -> d.sent: NamedParameter,
'time -> d.time: NamedParameter)
'time -> d.time: NamedParameter
)
}
val batch = BatchSql(
@ -75,7 +83,7 @@ class AddressTransactionDetailsPostgresDAO @Inject() (explorerConfig: ExplorerCo
val success = result.forall(_ == 1)
if (success ||
explorerConfig.liteVersionConfig.enabled) {
explorerConfig.liteVersionConfig.enabled) {
Some(())
} else {
@ -92,8 +100,9 @@ class AddressTransactionDetailsPostgresDAO @Inject() (explorerConfig: ExplorerCo
|RETURNING address, txid, received, sent, time
""".stripMargin
).on(
'txid -> txid.string
).as(parseAddressTransactionDetails.*)
'txid -> txid.string
)
.as(parseAddressTransactionDetails.*)
result
}

5
server/app/com/xsn/explorer/data/anorm/dao/AggregatedAmountPostgresDAO.scala

@ -14,8 +14,9 @@ class AggregatedAmountPostgresDAO {
|WHERE name = 'available_coins'
""".stripMargin
).on(
'delta -> delta
).executeUpdate()
'delta -> delta
)
.executeUpdate()
require(affectedRows == 1)
}

54
server/app/com/xsn/explorer/data/anorm/dao/BalancePostgresDAO.scala

@ -13,7 +13,7 @@ import com.xsn.explorer.models.values.Address
import javax.inject.Inject
import org.slf4j.LoggerFactory
class BalancePostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQLInterpreter) {
class BalancePostgresDAO @Inject()(fieldOrderingSQLInterpreter: FieldOrderingSQLInterpreter) {
private val logger = LoggerFactory.getLogger(this.getClass)
@ -33,16 +33,14 @@ class BalancePostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQ
|RETURNING address, received, spent
""".stripMargin
).on(
'address -> partial.address.string,
'received -> partial.received,
'spent -> partial.spent,
).as(parseBalance.singleOpt)
'address -> partial.address.string,
'received -> partial.received,
'spent -> partial.spent
)
.as(parseBalance.singleOpt)
}
def get(
query: PaginatedQuery,
ordering: FieldOrdering[BalanceField])(
implicit conn: Connection): List[Balance] = {
def get(query: PaginatedQuery, ordering: FieldOrdering[BalanceField])(implicit conn: Connection): List[Balance] = {
val orderBy = fieldOrderingSQLInterpreter.toOrderByClause(ordering)
SQL(
@ -58,15 +56,15 @@ class BalancePostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQ
|LIMIT {limit}
""".stripMargin
).on(
'offset -> query.offset.int,
'limit -> query.limit.int
).as(parseBalance.*)
'offset -> query.offset.int,
'limit -> query.limit.int
)
.as(parseBalance.*)
}
def getNonZeroBalances(
query: PaginatedQuery,
ordering: FieldOrdering[BalanceField])(
implicit conn: Connection): List[Balance] = {
def getNonZeroBalances(query: PaginatedQuery, ordering: FieldOrdering[BalanceField])(
implicit conn: Connection
): List[Balance] = {
val orderBy = fieldOrderingSQLInterpreter.toOrderByClause(ordering)
SQL(
@ -82,9 +80,10 @@ class BalancePostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQ
|LIMIT {limit}
""".stripMargin
).on(
'offset -> query.offset.int,
'limit -> query.limit.int
).as(parseBalance.*)
'offset -> query.offset.int,
'limit -> query.limit.int
)
.as(parseBalance.*)
}
def count(implicit conn: Connection): Count = {
@ -110,8 +109,9 @@ class BalancePostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQ
|wHERE address = {address}
""".stripMargin
).on(
'address -> address.string
).as(parseBalance.singleOpt)
'address -> address.string
)
.as(parseBalance.singleOpt)
}
def countNonZeroBalances(implicit conn: Connection): Count = {
@ -142,8 +142,9 @@ class BalancePostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQ
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int
).as(parseBalance.*)
'limit -> limit.int
)
.as(parseBalance.*)
}
/**
@ -168,8 +169,9 @@ class BalancePostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQ
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int,
'lastSeenAddress -> lastSeenAddress.string
).as(parseBalance.*)
'limit -> limit.int,
'lastSeenAddress -> lastSeenAddress.string
)
.as(parseBalance.*)
}
}

23
server/app/com/xsn/explorer/data/anorm/dao/BlockFilterPostgresDAO.scala

@ -21,12 +21,13 @@ class BlockFilterPostgresDAO {
|RETURNING blockhash, m, n, p, hex
""".stripMargin
).on(
'blockhash -> blockhash.string,
'm -> filter.m,
'n -> filter.n,
'p -> filter.p,
'hex -> filter.hex.string
).as(parseFilter.single)
'blockhash -> blockhash.string,
'm -> filter.m,
'n -> filter.n,
'p -> filter.p,
'hex -> filter.hex.string
)
.as(parseFilter.single)
}
def delete(blockhash: Blockhash)(implicit conn: Connection): Option[GolombCodedSet] = {
@ -37,8 +38,9 @@ class BlockFilterPostgresDAO {
|RETURNING blockhash, m, n, p, hex
""".stripMargin
).on(
'blockhash -> blockhash.string
).as(parseFilter.singleOpt)
'blockhash -> blockhash.string
)
.as(parseFilter.singleOpt)
}
def getBy(blockhash: Blockhash)(implicit conn: Connection): Option[GolombCodedSet] = {
@ -49,7 +51,8 @@ class BlockFilterPostgresDAO {
|WHERE blockhash = {blockhash}
""".stripMargin
).on(
'blockhash -> blockhash.string
).as(parseFilter.singleOpt)
'blockhash -> blockhash.string
)
.as(parseFilter.singleOpt)
}
}

103
server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala

@ -12,9 +12,10 @@ import com.xsn.explorer.models.persisted.{Block, BlockHeader}
import com.xsn.explorer.models.values.{Blockhash, Height}
import javax.inject.Inject
class BlockPostgresDAO @Inject() (
class BlockPostgresDAO @Inject()(
blockFilterPostgresDAO: BlockFilterPostgresDAO,
fieldOrderingSQLInterpreter: FieldOrderingSQLInterpreter) {
fieldOrderingSQLInterpreter: FieldOrderingSQLInterpreter
) {
def insert(block: Block)(implicit conn: Connection): Option[Block] = {
SQL(
@ -33,28 +34,26 @@ class BlockPostgresDAO @Inject() (
| height, version, time, median_time, nonce, bits, chainwork, difficulty, extraction_method
""".stripMargin
).on(
'blockhash -> block.hash.string,
'previous_blockhash -> block.previousBlockhash.map(_.string),
'next_blockhash -> block.nextBlockhash.map(_.string),
'tpos_contract -> block.tposContract.map(_.string),
'merkle_root -> block.merkleRoot.string,
'size -> block.size.int,
'height -> block.height.int,
'version -> block.version,
'time -> block.time,
'median_time -> block.medianTime,
'nonce -> block.nonce,
'bits -> block.bits,
'chainwork -> block.chainwork,
'difficulty -> block.difficulty,
'extraction_method -> block.extractionMethod.entryName
).as(parseBlock.singleOpt)
'blockhash -> block.hash.string,
'previous_blockhash -> block.previousBlockhash.map(_.string),
'next_blockhash -> block.nextBlockhash.map(_.string),
'tpos_contract -> block.tposContract.map(_.string),
'merkle_root -> block.merkleRoot.string,
'size -> block.size.int,
'height -> block.height.int,
'version -> block.version,
'time -> block.time,
'median_time -> block.medianTime,
'nonce -> block.nonce,
'bits -> block.bits,
'chainwork -> block.chainwork,
'difficulty -> block.difficulty,
'extraction_method -> block.extractionMethod.entryName
)
.as(parseBlock.singleOpt)
}
def setNextBlockhash(
blockhash: Blockhash,
nextBlockhash: Blockhash)(
implicit conn: Connection): Option[Block] = {
def setNextBlockhash(blockhash: Blockhash, nextBlockhash: Blockhash)(implicit conn: Connection): Option[Block] = {
SQL(
"""
@ -65,9 +64,10 @@ class BlockPostgresDAO @Inject() (
| height, version, time, median_time, nonce, bits, chainwork, difficulty, extraction_method
""".stripMargin
).on(
'blockhash -> blockhash.string,
'next_blockhash -> nextBlockhash.string
).as(parseBlock.singleOpt)
'blockhash -> blockhash.string,
'next_blockhash -> nextBlockhash.string
)
.as(parseBlock.singleOpt)
}
def getBy(blockhash: Blockhash)(implicit conn: Connection): Option[Block] = {
@ -79,8 +79,9 @@ class BlockPostgresDAO @Inject() (
|WHERE blockhash = {blockhash}
""".stripMargin
).on(
"blockhash" -> blockhash.string
).as(parseBlock.singleOpt)
"blockhash" -> blockhash.string
)
.as(parseBlock.singleOpt)
}
def getBy(height: Height)(implicit conn: Connection): Option[Block] = {
@ -92,14 +93,14 @@ class BlockPostgresDAO @Inject() (
|WHERE height = {height}
""".stripMargin
).on(
"height" -> height.int
).as(parseBlock.singleOpt)
"height" -> height.int
)
.as(parseBlock.singleOpt)
}
def getBy(
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[BlockField])(
implicit conn: Connection): List[Block] = {
def getBy(paginatedQuery: PaginatedQuery, ordering: FieldOrdering[BlockField])(
implicit conn: Connection
): List[Block] = {
val orderBy = fieldOrderingSQLInterpreter.toOrderByClause(ordering)
SQL(
@ -112,9 +113,10 @@ class BlockPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'offset -> paginatedQuery.offset.int,
'limit -> paginatedQuery.limit.int
).as(parseBlock.*)
'offset -> paginatedQuery.offset.int,
'limit -> paginatedQuery.limit.int
)
.as(parseBlock.*)
}
def count(implicit conn: Connection): Count = {
@ -137,8 +139,9 @@ class BlockPostgresDAO @Inject() (
| height, version, time, median_time, nonce, bits, chainwork, difficulty, extraction_method
""".stripMargin
).on(
"blockhash" -> blockhash.string
).as(parseBlock.singleOpt)
"blockhash" -> blockhash.string
)
.as(parseBlock.singleOpt)
}
def getLatestBlock(implicit conn: Connection): Option[Block] = {
@ -164,18 +167,21 @@ class BlockPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int
).as(parseHeader.*)
'limit -> limit.int
)
.as(parseHeader.*)
for {
header <- headers
filterMaybe = blockFilterPostgresDAO.getBy(header.hash)
} yield filterMaybe
.map(header.withFilter)
.getOrElse(header)
.map(header.withFilter)
.getOrElse(header)
}
def getHeaders(lastSeenHash: Blockhash, limit: Limit, orderingCondition: OrderingCondition)(implicit conn: Connection): List[BlockHeader] = {
def getHeaders(lastSeenHash: Blockhash, limit: Limit, orderingCondition: OrderingCondition)(
implicit conn: Connection
): List[BlockHeader] = {
val order = toSQL(orderingCondition)
val comparator = orderingCondition match {
case OrderingCondition.DescendingOrder => "<"
@ -196,16 +202,17 @@ class BlockPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'lastSeenHash -> lastSeenHash.string,
'limit -> limit.int
).as(parseHeader.*)
'lastSeenHash -> lastSeenHash.string,
'limit -> limit.int
)
.as(parseHeader.*)
for {
header <- headers
filterMaybe = blockFilterPostgresDAO.getBy(header.hash)
} yield filterMaybe
.map(header.withFilter)
.getOrElse(header)
.map(header.withFilter)
.getOrElse(header)
}
private def toSQL(condition: OrderingCondition): String = condition match {

1
server/app/com/xsn/explorer/data/anorm/dao/StatisticsPostgresDAO.scala

@ -31,6 +31,7 @@ class StatisticsPostgresDAO {
}
object StatisticsPostgresDAO {
/**
* We need to exclude the burn address from the total supply.
*/

47
server/app/com/xsn/explorer/data/anorm/dao/TPoSContractDAO.scala

@ -21,14 +21,15 @@ class TPoSContractDAO {
|RETURNING txid, index, owner, merchant, merchant_commission, state, time
""".stripMargin
).on(
'txid -> contract.id.txid.string,
'index -> contract.id.index,
'owner -> contract.details.owner.string,
'merchant -> contract.details.merchant.string,
'merchant_commission -> contract.details.merchantCommission.int,
'state -> contract.state.entryName,
'time -> contract.time
).as(parseTPoSContract.single)
'txid -> contract.id.txid.string,
'index -> contract.id.index,
'owner -> contract.details.owner.string,
'merchant -> contract.details.merchant.string,
'merchant_commission -> contract.details.merchantCommission.int,
'state -> contract.state.entryName,
'time -> contract.time
)
.as(parseTPoSContract.single)
}
def deleteBy(txid: TransactionId)(implicit conn: Connection): Option[TPoSContract] = {
@ -39,8 +40,9 @@ class TPoSContractDAO {
|RETURNING txid, index, owner, merchant, merchant_commission, state, time
""".stripMargin
).on(
'txid -> txid.string
).as(parseTPoSContract.singleOpt)
'txid -> txid.string
)
.as(parseTPoSContract.singleOpt)
}
def close(id: TPoSContract.Id, closedOn: TransactionId)(implicit conn: Connection): Unit = {
@ -53,11 +55,12 @@ class TPoSContractDAO {
| index = {index}
""".stripMargin
).on(
'txid -> id.txid.string,
'index -> id.index,
'state -> TPoSContract.State.Closed.entryName,
'closed_on -> closedOn.string
).executeUpdate()
'txid -> id.txid.string,
'index -> id.index,
'state -> TPoSContract.State.Closed.entryName,
'closed_on -> closedOn.string
)
.executeUpdate()
}
def open(id: TPoSContract.Id)(implicit conn: Connection): Unit = {
@ -70,10 +73,11 @@ class TPoSContractDAO {
| index = {index}
""".stripMargin
).on(
'txid -> id.txid.string,
'index -> id.index,
'state -> TPoSContract.State.Active.entryName,
).executeUpdate()
'txid -> id.txid.string,
'index -> id.index,
'state -> TPoSContract.State.Active.entryName
)
.executeUpdate()
}
def getBy(address: Address)(implicit conn: Connection): List[TPoSContract] = {
@ -85,7 +89,8 @@ class TPoSContractDAO {
|ORDER BY time DESC
""".stripMargin
).on(
'address -> address.string
).as(parseTPoSContract.*)
'address -> address.string
)
.as(parseTPoSContract.*)
}
}

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

@ -10,26 +10,28 @@ import com.xsn.explorer.models.values.{Address, TransactionId}
import javax.inject.Inject
import org.slf4j.LoggerFactory
class TransactionInputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
class TransactionInputPostgresDAO @Inject()(explorerConfig: ExplorerConfig) {
private val logger = LoggerFactory.getLogger(this.getClass)
def batchInsertInputs(
inputs: List[(TransactionId, Transaction.Input)])(
implicit conn: Connection): Option[List[(TransactionId, Transaction.Input)]] = {
inputs: List[(TransactionId, Transaction.Input)]
)(implicit conn: Connection): Option[List[(TransactionId, Transaction.Input)]] = {
inputs match {
case Nil => Some(inputs)
case _ =>
val params = inputs.map { case (txid, input) =>
List(
'txid -> txid.string: NamedParameter,
'index -> input.index: NamedParameter,
'from_txid -> input.fromTxid.string: NamedParameter,
'from_output_index -> input.fromOutputIndex: NamedParameter,
'value -> input.value: NamedParameter,
'addresses -> input.addresses.map(_.string).toArray: NamedParameter)
val params = inputs.map {
case (txid, input) =>
List(
'txid -> txid.string: NamedParameter,
'index -> input.index: NamedParameter,
'from_txid -> input.fromTxid.string: NamedParameter,
'from_output_index -> input.fromOutputIndex: NamedParameter,
'value -> input.value: NamedParameter,
'addresses -> input.addresses.map(_.string).toArray: NamedParameter
)
}
val batch = BatchSql(
@ -46,7 +48,7 @@ class TransactionInputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
val result = batch.execute()
val success = result.forall(_ == 1)
if (success ||
explorerConfig.liteVersionConfig.enabled) {
explorerConfig.liteVersionConfig.enabled) {
Some(inputs)
} else {
@ -63,8 +65,9 @@ class TransactionInputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
|RETURNING txid, index, from_txid, from_output_index, value, addresses
""".stripMargin
).on(
'txid -> txid.string
).as(parseTransactionInput.*)
'txid -> txid.string
)
.as(parseTransactionInput.*)
}
def getInputs(txid: TransactionId)(implicit conn: Connection): List[Transaction.Input] = {
@ -75,8 +78,9 @@ class TransactionInputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
|WHERE txid = {txid}
""".stripMargin
).on(
'txid -> txid.string
).as(parseTransactionInput.*)
'txid -> txid.string
)
.as(parseTransactionInput.*)
}
def getInputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Input] = {
@ -88,8 +92,9 @@ class TransactionInputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
| {address} = ANY(addresses)
""".stripMargin
).on(
'txid -> txid.string,
'address -> address.string
).as(parseTransactionInput.*)
'txid -> txid.string,
'address -> address.string
)
.as(parseTransactionInput.*)
}
}

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

@ -10,7 +10,7 @@ import com.xsn.explorer.models.values.{Address, TransactionId}
import javax.inject.Inject
import org.slf4j.LoggerFactory
class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
class TransactionOutputPostgresDAO @Inject()(explorerConfig: ExplorerConfig) {
private val logger = LoggerFactory.getLogger(this.getClass)
@ -24,8 +24,9 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
| value > 0
""".stripMargin
).on(
'address -> address.string
).as(parseTransactionOutput.*)
'address -> address.string
)
.as(parseTransactionOutput.*)
}
def getOutput(txid: TransactionId, index: Int)(implicit conn: Connection): Option[Transaction.Output] = {
@ -37,14 +38,15 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
| index = {index}
""".stripMargin
).on(
'txid -> txid.string,
'index -> index
).as(parseTransactionOutput.singleOpt)
'txid -> txid.string,
'index -> index
)
.as(parseTransactionOutput.singleOpt)
}
def batchInsertOutputs(
outputs: List[Transaction.Output])(
implicit conn: Connection): Option[List[Transaction.Output]] = {
outputs: List[Transaction.Output]
)(implicit conn: Connection): Option[List[Transaction.Output]] = {
outputs match {
case Nil => Some(outputs)
@ -55,7 +57,8 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
'index -> output.index: NamedParameter,
'value -> output.value: NamedParameter,
'addresses -> output.addresses.map(_.string).toArray: NamedParameter,
'hex_script -> output.script.string: NamedParameter)
'hex_script -> output.script.string: NamedParameter
)
}
val batch = BatchSql(
@ -71,10 +74,10 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
val success = batch.execute().forall(_ == 1)
if (success ||
explorerConfig.liteVersionConfig.enabled) {
explorerConfig.liteVersionConfig.enabled) {
Some(outputs)
} else{
} else {
None
}
}
@ -88,8 +91,9 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
|RETURNING txid, index, hex_script, value, addresses
""".stripMargin
).on(
'txid -> txid.string
).as(parseTransactionOutput.*)
'txid -> txid.string
)
.as(parseTransactionOutput.*)
result
}
@ -102,8 +106,9 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
|WHERE txid = {txid}
""".stripMargin
).on(
'txid -> txid.string
).as(parseTransactionOutput.*)
'txid -> txid.string
)
.as(parseTransactionOutput.*)
}
def getOutputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Output] = {
@ -115,9 +120,10 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
| {address} = ANY(addresses)
""".stripMargin
).on(
'txid -> txid.string,
'address -> address.string
).as(parseTransactionOutput.*)
'txid -> txid.string,
'address -> address.string
)
.as(parseTransactionOutput.*)
}
def batchSpend(txid: TransactionId, inputs: List[Transaction.Input])(implicit conn: Connection): Option[Unit] = {
@ -125,8 +131,10 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
case Nil => Option(())
case _ =>
val txidArray = inputs
.map { input => s"'${input.fromTxid.string}'" }
.mkString("[", ",", "]")
.map { input =>
s"'${input.fromTxid.string}'"
}
.mkString("[", ",", "]")
val indexArray = inputs.map(_.fromOutputIndex).mkString("[", ",", "]")
@ -150,7 +158,7 @@ class TransactionOutputPostgresDAO @Inject() (explorerConfig: ExplorerConfig) {
).executeUpdate()
if (result == inputs.size ||
explorerConfig.liteVersionConfig.enabled) {
explorerConfig.liteVersionConfig.enabled) {
Option(())
} else {

197
server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala

@ -15,13 +15,14 @@ import com.xsn.explorer.models.values.{Address, Blockhash, TransactionId}
import javax.inject.Inject
import org.slf4j.LoggerFactory
class TransactionPostgresDAO @Inject() (
class TransactionPostgresDAO @Inject()(
explorerConfig: ExplorerConfig,
transactionInputDAO: TransactionInputPostgresDAO,
transactionOutputDAO: TransactionOutputPostgresDAO,
tposContractDAO: TPoSContractDAO,
addressTransactionDetailsDAO: AddressTransactionDetailsPostgresDAO,
fieldOrderingSQLInterpreter: FieldOrderingSQLInterpreter) {
fieldOrderingSQLInterpreter: FieldOrderingSQLInterpreter
) {
private val logger = LoggerFactory.getLogger(this.getClass)
@ -40,20 +41,26 @@ class TransactionPostgresDAO @Inject() (
} yield Transaction.HasIO(partialTx, inputs = transaction.inputs, outputs = transaction.outputs)
}
def insert(transactions: List[Transaction.HasIO], tposContracts: List[TPoSContract])(implicit conn: Connection): Option[List[Transaction]] = {
def insert(transactions: List[Transaction.HasIO], tposContracts: List[TPoSContract])(
implicit conn: Connection
): Option[List[Transaction]] = {
for {
r <- batchInsert(transactions.map(_.transaction))
outputs = transactions.flatMap(_.outputs)
_ <- transactionOutputDAO.batchInsertOutputs(outputs)
inputs = transactions.flatMap { tx => tx.inputs.map(tx.id -> _) }
inputs = transactions.flatMap { tx =>
tx.inputs.map(tx.id -> _)
}
_ <- transactionInputDAO.batchInsertInputs(inputs)
} yield {
insertDetails(transactions)
spend(transactions)
closeContracts(transactions)
tposContracts.foreach { contract => tposContractDAO.create(contract) }
tposContracts.foreach { contract =>
tposContractDAO.create(contract)
}
r
}
}
@ -65,7 +72,9 @@ class TransactionPostgresDAO @Inject() (
}
private def spend(transactions: List[Transaction.HasIO])(implicit conn: Connection): Unit = {
val spendResult = transactions.map { tx => transactionOutputDAO.batchSpend(tx.id, tx.inputs) }
val spendResult = transactions.map { tx =>
transactionOutputDAO.batchSpend(tx.id, tx.inputs)
}
if (explorerConfig.liteVersionConfig.enabled) {
()
@ -89,13 +98,15 @@ class TransactionPostgresDAO @Inject() (
transactions match {
case Nil => Some(transactions)
case _ =>
val params = transactions.zipWithIndex.map { case (transaction, index) =>
List(
'txid -> transaction.id.string: NamedParameter,
'blockhash -> transaction.blockhash.string: NamedParameter,
'time -> transaction.time: NamedParameter,
'size -> transaction.size.int: NamedParameter,
'index -> index: NamedParameter)
val params = transactions.zipWithIndex.map {
case (transaction, index) =>
List(
'txid -> transaction.id.string: NamedParameter,
'blockhash -> transaction.blockhash.string: NamedParameter,
'time -> transaction.time: NamedParameter,
'size -> transaction.size.int: NamedParameter,
'index -> index: NamedParameter
)
}
val batch = BatchSql(
@ -130,20 +141,23 @@ class TransactionPostgresDAO @Inject() (
|ORDER BY index DESC
""".stripMargin
).on(
'blockhash -> blockhash.string
).as(parseTransaction.*)
'blockhash -> blockhash.string
)
.as(parseTransaction.*)
val result = expectedTransactions.map { tx =>
val _ = (
tposContractDAO.deleteBy(tx.id),
addressTransactionDetailsDAO.deleteDetails(tx.id)
tposContractDAO.deleteBy(tx.id),
addressTransactionDetailsDAO.deleteDetails(tx.id)
)
val inputs = transactionInputDAO.deleteInputs(tx.id)
val outputs = transactionOutputDAO.deleteOutputs(tx.id)
inputs
.map { input => TPoSContract.Id(input.fromTxid, input.fromOutputIndex) }
.foreach(tposContractDAO.open(_))
.map { input =>
TPoSContract.Id(input.fromTxid, input.fromOutputIndex)
}
.foreach(tposContractDAO.open(_))
Transaction.HasIO(tx, inputs = inputs, outputs = outputs)
}
@ -155,19 +169,22 @@ class TransactionPostgresDAO @Inject() (
|RETURNING txid, blockhash, time, size
""".stripMargin
).on(
'blockhash -> blockhash.string
).as(parseTransaction.*)
'blockhash -> blockhash.string
)
.as(parseTransaction.*)
Option(deletedTransactions)
.filter(_.size == expectedTransactions.size)
.map(_ => result)
.getOrElse { throw new RuntimeException("Failed to delete transactions consistently")} // this should not happen
.filter(_.size == expectedTransactions.size)
.map(_ => result)
.getOrElse { throw new RuntimeException("Failed to delete transactions consistently") } // this should not happen
}
/**
* Get the transactions by the given address (sorted by time).
*/
def getBy(address: Address, limit: Limit, orderingCondition: OrderingCondition)(implicit conn: Connection): List[Transaction.HasIO] = {
def getBy(address: Address, limit: Limit, orderingCondition: OrderingCondition)(
implicit conn: Connection
): List[Transaction.HasIO] = {
val order = toSQL(orderingCondition)
val transactions = SQL(
@ -179,9 +196,10 @@ class TransactionPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'address -> address.string,
'limit -> limit.int
).as(parseTransaction.*)
'address -> address.string,
'limit -> limit.int
)
.as(parseTransaction.*)
for {
tx <- transactions
@ -198,12 +216,9 @@ class TransactionPostgresDAO @Inject() (
* - When orderingCondition = DescendingOrder, the transactions that occurred before the last seen transaction are retrieved.
* - When orderingCondition = AscendingOrder, the transactions that occurred after the last seen transaction are retrieved.
*/
def getBy(
address: Address,
lastSeenTxid: TransactionId,
limit: Limit,
orderingCondition: OrderingCondition)(
implicit conn: Connection): List[Transaction.HasIO] = {
def getBy(address: Address, lastSeenTxid: TransactionId, limit: Limit, orderingCondition: OrderingCondition)(
implicit conn: Connection
): List[Transaction.HasIO] = {
val order = toSQL(orderingCondition)
val comparator = orderingCondition match {
@ -227,10 +242,11 @@ class TransactionPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'address -> address.string,
'limit -> limit.int,
'lastSeenTxid -> lastSeenTxid.string
).as(parseTransaction.*)
'address -> address.string,
'limit -> limit.int,
'lastSeenTxid -> lastSeenTxid.string
)
.as(parseTransaction.*)
for {
tx <- transactions
@ -241,11 +257,9 @@ class TransactionPostgresDAO @Inject() (
}
}
def getBy(
address: Address,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField])(
implicit conn: Connection): List[TransactionWithValues] = {
def getBy(address: Address, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField])(
implicit conn: Connection
): List[TransactionWithValues] = {
val orderBy = fieldOrderingSQLInterpreter.toOrderByClause(ordering)
@ -260,10 +274,11 @@ class TransactionPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'address -> address.string,
'offset -> paginatedQuery.offset.int,
'limit -> paginatedQuery.limit.int
).as(parseTransactionWithValues.*)
'address -> address.string,
'offset -> paginatedQuery.offset.int,
'limit -> paginatedQuery.limit.int
)
.as(parseTransactionWithValues.*)
}
def countBy(address: Address)(implicit conn: Connection): Count = {
@ -274,17 +289,16 @@ class TransactionPostgresDAO @Inject() (
| WHERE address = {address}
""".stripMargin
).on(
'address -> address.string
).as(SqlParser.scalar[Int].single)
'address -> address.string
)
.as(SqlParser.scalar[Int].single)
Count(result)
}
def getByBlockhash(
blockhash: Blockhash,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField])(
implicit conn: Connection): List[TransactionWithValues] = {
def getByBlockhash(blockhash: Blockhash, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField])(
implicit conn: Connection
): List[TransactionWithValues] = {
val orderBy = fieldOrderingSQLInterpreter.toOrderByClause(ordering)
@ -305,10 +319,11 @@ class TransactionPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'blockhash -> blockhash.string,
'offset -> paginatedQuery.offset.int,
'limit -> paginatedQuery.limit.int
).as(parseTransactionWithValues.*)
'blockhash -> blockhash.string,
'offset -> paginatedQuery.offset.int,
'limit -> paginatedQuery.limit.int
)
.as(parseTransactionWithValues.*)
}
def countByBlockhash(blockhash: Blockhash)(implicit conn: Connection): Count = {
@ -319,8 +334,9 @@ class TransactionPostgresDAO @Inject() (
|WHERE blockhash = {blockhash}
""".stripMargin
).on(
'blockhash -> blockhash.string
).as(SqlParser.scalar[Int].single)
'blockhash -> blockhash.string
)
.as(SqlParser.scalar[Int].single)
Count(result)
}
@ -337,12 +353,15 @@ class TransactionPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int,
'blockhash -> blockhash.string
).as(parseTransactionWithValues.*)
'limit -> limit.int,
'blockhash -> blockhash.string
)
.as(parseTransactionWithValues.*)
}
def getByBlockhash(blockhash: Blockhash, lastSeenTxid: TransactionId, limit: Limit)(implicit conn: Connection): List[TransactionWithValues] = {
def getByBlockhash(blockhash: Blockhash, lastSeenTxid: TransactionId, limit: Limit)(
implicit conn: Connection
): List[TransactionWithValues] = {
SQL(
"""
|WITH CTE AS (
@ -360,13 +379,16 @@ class TransactionPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int,
'blockhash -> blockhash.string,
'lastSeenTxid -> lastSeenTxid.string
).as(parseTransactionWithValues.*)
'limit -> limit.int,
'blockhash -> blockhash.string,
'lastSeenTxid -> lastSeenTxid.string
)
.as(parseTransactionWithValues.*)
}
def getTransactionsWithIOBy(blockhash: Blockhash, limit: Limit)(implicit conn: Connection): List[Transaction.HasIO] = {
def getTransactionsWithIOBy(blockhash: Blockhash, limit: Limit)(
implicit conn: Connection
): List[Transaction.HasIO] = {
val transactions = SQL(
"""
|SELECT t.txid, t.blockhash, t.time, t.size
@ -376,9 +398,10 @@ class TransactionPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int,
'blockhash -> blockhash.string
).as(parseTransaction.*)
'limit -> limit.int,
'blockhash -> blockhash.string
)
.as(parseTransaction.*)
for {
tx <- transactions
@ -389,7 +412,9 @@ class TransactionPostgresDAO @Inject() (
}
}
def getTransactionsWithIOBy(blockhash: Blockhash, lastSeenTxid: TransactionId, limit: Limit)(implicit conn: Connection): List[Transaction.HasIO] = {
def getTransactionsWithIOBy(blockhash: Blockhash, lastSeenTxid: TransactionId, limit: Limit)(
implicit conn: Connection
): List[Transaction.HasIO] = {
val transactions = SQL(
"""
|WITH CTE AS (
@ -405,10 +430,11 @@ class TransactionPostgresDAO @Inject() (
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int,
'blockhash -> blockhash.string,
'lastSeenTxid -> lastSeenTxid.string
).as(parseTransaction.*)
'limit -> limit.int,
'blockhash -> blockhash.string,
'lastSeenTxid -> lastSeenTxid.string
)
.as(parseTransaction.*)
for {
tx <- transactions
@ -419,7 +445,9 @@ class TransactionPostgresDAO @Inject() (
}
}
private def upsertTransaction(index: Int, transaction: Transaction)(implicit conn: Connection): Option[Transaction] = {
private def upsertTransaction(index: Int, transaction: Transaction)(
implicit conn: Connection
): Option[Transaction] = {
SQL(
"""
|INSERT INTO transactions
@ -434,12 +462,13 @@ class TransactionPostgresDAO @Inject() (
|RETURNING txid, blockhash, time, size
""".stripMargin
).on(
'txid -> transaction.id.string,
'blockhash -> transaction.blockhash.string,
'time -> transaction.time,
'size -> transaction.size.int,
'index -> index
).as(parseTransaction.singleOpt)
'txid -> transaction.id.string,
'blockhash -> transaction.blockhash.string,
'time -> transaction.time,
'size -> transaction.size.int,
'index -> index
)
.as(parseTransaction.singleOpt)
}
private def toSQL(condition: OrderingCondition): String = condition match {

5
server/app/com/xsn/explorer/data/anorm/parsers/BalanceParsers.scala

@ -11,7 +11,8 @@ object BalanceParsers {
val parseReceived = get[BigDecimal]("received")
val parseSpent = get[BigDecimal]("spent")
val parseBalance = (parseAddress() ~ parseReceived ~ parseSpent).map { case address ~ received ~ spent =>
Balance(address, received, spent)
val parseBalance = (parseAddress() ~ parseReceived ~ parseSpent).map {
case address ~ received ~ spent =>
Balance(address, received, spent)
}
}

64
server/app/com/xsn/explorer/data/anorm/parsers/BlockParsers.scala

@ -16,8 +16,8 @@ object BlockParsers {
val parseMerkleRoot = parseBlockhash("merkle_root")
val parseExtractionMethod = str("extraction_method")
.map(BlockExtractionMethod.withNameInsensitiveOption)
.map { _.getOrElse(throw new RuntimeException("corrupted extraction_method")) }
.map(BlockExtractionMethod.withNameInsensitiveOption)
.map { _.getOrElse(throw new RuntimeException("corrupted extraction_method")) }
val parseHeight = int("height").map(Height.apply)
val parseVersion = int("version")
@ -27,39 +27,37 @@ object BlockParsers {
val parseChainwork = str("chainwork")
val parseDifficulty = get[BigDecimal]("difficulty")
val parseBlock = (
parseBlockhash() ~
parseNextBlockhash.? ~
parsePreviousBlockhash.? ~
parseTposContract.? ~
parseMerkleRoot ~
parseSize ~
parseHeight ~
parseVersion ~
parseTime ~
parseMedianTime ~
parseNonce ~
parseBits ~
parseChainwork ~
parseDifficulty ~
parseExtractionMethod).map {
val parseBlock = (parseBlockhash() ~
parseNextBlockhash.? ~
parsePreviousBlockhash.? ~
parseTposContract.? ~
parseMerkleRoot ~
parseSize ~
parseHeight ~
parseVersion ~
parseTime ~
parseMedianTime ~
parseNonce ~
parseBits ~
parseChainwork ~
parseDifficulty ~
parseExtractionMethod).map {
case hash ~
nextBlockhash ~
previousBlockhash ~
tposContract ~
merkleRoot ~
size ~
height ~
version ~
time ~
medianTime ~
nonce ~
bits ~
chainwork ~
difficulty ~
extractionMethod =>
nextBlockhash ~
previousBlockhash ~
tposContract ~
merkleRoot ~
size ~
height ~
version ~
time ~
medianTime ~
nonce ~
bits ~
chainwork ~
difficulty ~
extractionMethod =>
Block(
hash = hash,
previousBlockhash = previousBlockhash,

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

@ -5,11 +5,13 @@ import com.xsn.explorer.models.values._
object CommonParsers {
def parseBlockhash(field: String = "blockhash") = str(field)
def parseBlockhash(field: String = "blockhash") =
str(field)
.map(Blockhash.from)
.map { _.getOrElse(throw new RuntimeException(s"corrupted $field")) }
def parseAddress(field: String = "address") = str(field)
def parseAddress(field: String = "address") =
str(field)
.map { string =>
Address.from(string) match {
case None => throw new RuntimeException(s"Corrupted $field: $string")
@ -17,23 +19,24 @@ object CommonParsers {
}
}
def parseAddresses = array[String]("addresses")
def parseAddresses =
array[String]("addresses")
.map { array =>
array
.map { string =>
Address.from(string) match {
case None => throw new RuntimeException(s"Corrupted address: $string")
case Some(address) => address
}
}
.toList
array.map { string =>
Address.from(string) match {
case None => throw new RuntimeException(s"Corrupted address: $string")
case Some(address) => address
}
}.toList
}
def parseTransactionId(field: String = "txid") = str(field)
def parseTransactionId(field: String = "txid") =
str(field)
.map(TransactionId.from)
.map { _.getOrElse(throw new RuntimeException(s"corrupted $field")) }
def parseHexString(field: String) = str(field)
def parseHexString(field: String) =
str(field)
.map(HexString.from)
.map { _.getOrElse(throw new RuntimeException(s"corrupted $field")) }

26
server/app/com/xsn/explorer/data/anorm/parsers/TPoSContractParsers.scala

@ -10,22 +10,22 @@ object TPoSContractParsers {
val parseOwner = parseAddress("owner")
val parseMerchant = parseAddress("merchant")
val parseMerchantCommission = int("merchant_commission")
.map(TPoSContract.Commission.from)
.map { _.getOrElse(throw new RuntimeException("corrupted merchant_commission")) }
.map(TPoSContract.Commission.from)
.map { _.getOrElse(throw new RuntimeException("corrupted merchant_commission")) }
val parseTPoSContractState = str("state")
.map(TPoSContract.State.withNameInsensitiveOption)
.map { _.getOrElse(throw new RuntimeException("corrupted state")) }
val parseTPoSContract = (
parseTransactionId() ~
parseIndex ~
parseOwner ~
parseMerchant ~
parseMerchantCommission ~
parseTime ~
parseTPoSContractState).map {
.map(TPoSContract.State.withNameInsensitiveOption)
.map { _.getOrElse(throw new RuntimeException("corrupted state")) }
val parseTPoSContract = (parseTransactionId() ~
parseIndex ~
parseOwner ~
parseMerchant ~
parseMerchantCommission ~
parseTime ~
parseTPoSContractState).map {
case txid ~ index ~ owner ~ merchant ~ merchantCommission ~ time ~ state =>
val details = TPoSContract.Details(owner = owner, merchant = merchant, merchantCommission = merchantCommission)

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

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

22
server/app/com/xsn/explorer/data/async/BalanceFutureDataHandler.scala

@ -13,18 +13,18 @@ import javax.inject.Inject
import scala.concurrent.Future
class BalanceFutureDataHandler @Inject() (
blockingDataHandler: BalanceBlockingDataHandler)(
implicit ec: DatabaseExecutionContext)
extends BalanceDataHandler[FutureApplicationResult] {
class BalanceFutureDataHandler @Inject()(blockingDataHandler: BalanceBlockingDataHandler)(
implicit ec: DatabaseExecutionContext
) extends BalanceDataHandler[FutureApplicationResult] {
override def upsert(balance: Balance): FutureApplicationResult[Balance] = Future {
blockingDataHandler.upsert(balance)
}
override def get(query: PaginatedQuery, ordering: FieldOrdering[BalanceField]): FuturePaginatedResult[Balance] = Future {
blockingDataHandler.get(query, ordering)
}
override def get(query: PaginatedQuery, ordering: FieldOrdering[BalanceField]): FuturePaginatedResult[Balance] =
Future {
blockingDataHandler.get(query, ordering)
}
override def getBy(address: Address): FutureApplicationResult[Balance] = Future {
blockingDataHandler.getBy(address)
@ -32,12 +32,16 @@ class BalanceFutureDataHandler @Inject() (
override def getNonZeroBalances(
query: PaginatedQuery,
ordering: FieldOrdering[BalanceField]): FuturePaginatedResult[Balance] = Future {
ordering: FieldOrdering[BalanceField]
): FuturePaginatedResult[Balance] = Future {
blockingDataHandler.getNonZeroBalances(query, ordering)
}
override def getHighestBalances(limit: pagination.Limit, lastSeenAddress: Option[Address]): FutureApplicationResult[List[Balance]] = Future {
override def getHighestBalances(
limit: pagination.Limit,
lastSeenAddress: Option[Address]
): FutureApplicationResult[List[Balance]] = Future {
blockingDataHandler.getHighestBalances(limit, lastSeenAddress)
}
}

15
server/app/com/xsn/explorer/data/async/BlockFutureDataHandler.scala

@ -13,10 +13,9 @@ import javax.inject.Inject
import scala.concurrent.Future
class BlockFutureDataHandler @Inject() (
blockBlockingDataHandler: BlockBlockingDataHandler)(
implicit ec: DatabaseExecutionContext)
extends BlockDataHandler[FutureApplicationResult] {
class BlockFutureDataHandler @Inject()(blockBlockingDataHandler: BlockBlockingDataHandler)(
implicit ec: DatabaseExecutionContext
) extends BlockDataHandler[FutureApplicationResult] {
override def getBy(blockhash: Blockhash): FutureApplicationResult[Block] = Future {
blockBlockingDataHandler.getBy(blockhash)
@ -26,7 +25,10 @@ class BlockFutureDataHandler @Inject() (
blockBlockingDataHandler.getBy(height)
}
override def getBy(paginatedQuery: PaginatedQuery, ordering: FieldOrdering[BlockField]): FuturePaginatedResult[Block] = Future {
override def getBy(
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[BlockField]
): FuturePaginatedResult[Block] = Future {
blockBlockingDataHandler.getBy(paginatedQuery, ordering)
}
@ -45,7 +47,8 @@ class BlockFutureDataHandler @Inject() (
override def getHeaders(
limit: pagination.Limit,
orderingCondition: OrderingCondition,
lastSeenHash: Option[Blockhash]): FutureApplicationResult[List[BlockHeader]] = Future {
lastSeenHash: Option[Blockhash]
): FutureApplicationResult[List[BlockHeader]] = Future {
blockBlockingDataHandler.getHeaders(limit, orderingCondition, lastSeenHash)
}

14
server/app/com/xsn/explorer/data/async/LedgerFutureDataHandler.scala

@ -9,14 +9,14 @@ import javax.inject.Inject
import scala.concurrent.Future
class LedgerFutureDataHandler @Inject() (
blockingDataHandler: LedgerBlockingDataHandler)(
implicit ec: DatabaseExecutionContext)
extends LedgerDataHandler[FutureApplicationResult] {
class LedgerFutureDataHandler @Inject()(blockingDataHandler: LedgerBlockingDataHandler)(
implicit ec: DatabaseExecutionContext
) extends LedgerDataHandler[FutureApplicationResult] {
override def push(block: Block.HasTransactions, tposContracts: List[TPoSContract]): FutureApplicationResult[Unit] = Future {
blockingDataHandler.push(block, tposContracts)
}
override def push(block: Block.HasTransactions, tposContracts: List[TPoSContract]): FutureApplicationResult[Unit] =
Future {
blockingDataHandler.push(block, tposContracts)
}
override def pop(): FutureApplicationResult[Block] = Future {
blockingDataHandler.pop()

7
server/app/com/xsn/explorer/data/async/StatisticsFutureDataHandler.scala

@ -9,10 +9,9 @@ import com.xsn.explorer.models.Statistics
import scala.concurrent.Future
class StatisticsFutureDataHandler @Inject() (
blockingDataHandler: StatisticsBlockingDataHandler)(
implicit ec: DatabaseExecutionContext)
extends StatisticsDataHandler[FutureApplicationResult] {
class StatisticsFutureDataHandler @Inject()(blockingDataHandler: StatisticsBlockingDataHandler)(
implicit ec: DatabaseExecutionContext
) extends StatisticsDataHandler[FutureApplicationResult] {
override def getStatistics(): FutureApplicationResult[Statistics] = Future {
blockingDataHandler.getStatistics()

7
server/app/com/xsn/explorer/data/async/TPoSContractFutureDataHandler.scala

@ -9,10 +9,9 @@ import javax.inject.Inject
import scala.concurrent.Future
class TPoSContractFutureDataHandler @Inject() (
blockingDataHandler: TPoSContractBlockingDataHandler)(
implicit ec: DatabaseExecutionContext)
extends TPoSContractDataHandler[FutureApplicationResult] {
class TPoSContractFutureDataHandler @Inject()(blockingDataHandler: TPoSContractBlockingDataHandler)(
implicit ec: DatabaseExecutionContext
) extends TPoSContractDataHandler[FutureApplicationResult] {
override def getBy(address: Address): FutureApplicationResult[List[TPoSContract]] = Future {
blockingDataHandler.getBy(address)

22
server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala

@ -13,15 +13,15 @@ import javax.inject.Inject
import scala.concurrent.Future
class TransactionFutureDataHandler @Inject() (
blockingDataHandler: TransactionBlockingDataHandler)(
implicit ec: DatabaseExecutionContext)
extends TransactionDataHandler[FutureApplicationResult] {
class TransactionFutureDataHandler @Inject()(blockingDataHandler: TransactionBlockingDataHandler)(
implicit ec: DatabaseExecutionContext
) extends TransactionDataHandler[FutureApplicationResult] {
override def getBy(
address: Address,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): FuturePaginatedResult[TransactionWithValues] = Future {
ordering: FieldOrdering[TransactionField]
): FuturePaginatedResult[TransactionWithValues] = Future {
blockingDataHandler.getBy(address, paginatedQuery, ordering)
}
@ -30,7 +30,8 @@ class TransactionFutureDataHandler @Inject() (
address: Address,
limit: Limit,
lastSeenTxid: Option[TransactionId],
orderingCondition: OrderingCondition): FutureApplicationResult[List[Transaction.HasIO]] = Future {
orderingCondition: OrderingCondition
): FutureApplicationResult[List[Transaction.HasIO]] = Future {
blockingDataHandler.getBy(address, limit, lastSeenTxid, orderingCondition)
}
@ -46,7 +47,8 @@ class TransactionFutureDataHandler @Inject() (
override def getByBlockhash(
blockhash: Blockhash,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): FuturePaginatedResult[TransactionWithValues] = Future {
ordering: FieldOrdering[TransactionField]
): FuturePaginatedResult[TransactionWithValues] = Future {
blockingDataHandler.getByBlockhash(blockhash, paginatedQuery, ordering)
}
@ -54,7 +56,8 @@ class TransactionFutureDataHandler @Inject() (
override def getByBlockhash(
blockhash: Blockhash,
limit: Limit,
lastSeenTxid: Option[TransactionId]): FutureApplicationResult[List[TransactionWithValues]] = Future {
lastSeenTxid: Option[TransactionId]
): FutureApplicationResult[List[TransactionWithValues]] = Future {
blockingDataHandler.getByBlockhash(blockhash, limit, lastSeenTxid)
}
@ -62,7 +65,8 @@ class TransactionFutureDataHandler @Inject() (
override def getTransactionsWithIOBy(
blockhash: Blockhash,
limit: Limit,
lastSeenTxid: Option[TransactionId]): FutureApplicationResult[List[Transaction.HasIO]] = Future {
lastSeenTxid: Option[TransactionId]
): FutureApplicationResult[List[Transaction.HasIO]] = Future {
blockingDataHandler.getTransactionsWithIOBy(blockhash, limit, lastSeenTxid)
}

6
server/app/com/xsn/explorer/gcs/GolombCodedSet.scala

@ -2,11 +2,7 @@ package com.xsn.explorer.gcs
import com.xsn.explorer.models.values.HexString
class GolombCodedSet(
val p: Int,
val m: Int,
val n: Int,
val hex: HexString)
class GolombCodedSet(val p: Int, val m: Int, val n: Int, val hex: HexString)
object GolombCodedSet {

56
server/app/com/xsn/explorer/gcs/GolombEncoding.scala

@ -34,15 +34,13 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) {
val diffList = differences(sortedHashes)
val encodedBits = diffList.flatMap(golombEncode)
val encodedBytes = encodedBits
.grouped(8)
.map { bits => UnsignedByte.parse(bits.padTo(8, Bit.Zero)) }
.toList
GolombCodedSet.apply(
p = p,
m = m,
n = words.size,
data = encodedBytes)
.grouped(8)
.map { bits =>
UnsignedByte.parse(bits.padTo(8, Bit.Zero))
}
.toList
GolombCodedSet.apply(p = p, m = m, n = words.size, data = encodedBytes)
}
/**
@ -57,12 +55,14 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) {
*/
private[gcs] def decode(encoded: List[UnsignedByte], n: Int): SortedSet[BigInt] = {
val encodedBits = encoded.flatMap(_.bits)
val (_, _, result) = List.fill(n)(0)
.foldLeft((encodedBits, BigInt(0), List.empty[BigInt])) { case ((bits, acc, hashes), _) =>
val (_, _, result) = List
.fill(n)(0)
.foldLeft((encodedBits, BigInt(0), List.empty[BigInt])) {
case ((bits, acc, hashes), _) =>
val (remaining, delta) = golombDecode(bits)
val hash = acc + delta
(remaining, hash, hash :: hashes)
}
}
result.to[SortedSet]
}
@ -74,14 +74,14 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) {
val modulus = BigInt(m) * words.size
val f = fastReduction(_: BigInt, modulus)
words
.map(hash)
.map(f)
.to[SortedSet]
.map(hash)
.map(f)
.to[SortedSet]
}
private def golombEncode(x: BigInt): List[Bit] = {
val q = (x >> p).toInt
val r = (x & ((1 << p)-1)).toInt
val r = (x & ((1 << p) - 1)).toInt
val qBits = List.fill[Bit](q)(Bit.One) :+ Bit.Zero
val rBits = toBits(r, p)
@ -102,9 +102,9 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) {
private def differences(sortedHashes: SortedSet[BigInt]): List[BigInt] = {
(BigInt(0) :: sortedHashes.toList)
.sliding(2)
.map { case a :: b :: Nil => b - a }
.toList
.sliding(2)
.map { case a :: b :: Nil => b - a }
.toList
}
private def hash(string: String): BigInt = {
@ -113,16 +113,16 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) {
}
private def toBigInt(bits: List[Bit]): BigInt = {
bits.foldLeft(BigInt(0)) { case (acc, cur) =>
(acc * 2) + cur.toInt
bits.foldLeft(BigInt(0)) {
case (acc, cur) =>
(acc * 2) + cur.toInt
}
}
private def toBits(x: Long, size: Int): List[Bit] = {
val bits = x
.toBinaryString
.flatMap(Bit.from)
.toList
val bits = x.toBinaryString
.flatMap(Bit.from)
.toList
List.fill(size - bits.size)(Bit.Zero) ++ bits
}
@ -151,11 +151,11 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) {
*/
private def fastReduction(v: BigInt, modulus: BigInt): BigInt = {
val nHi = modulus >> 32
val nLo = modulus & 0xFFFFFFFFL
val nLo = modulus & 0XFFFFFFFFL
// First, we'll spit the item we need to reduce into its higher and lower bits.
val vhi = v >> 32
val vlo = v & 0xFFFFFFFFL
val vlo = v & 0XFFFFFFFFL
// Then, we distribute multiplication over each part.
val vnphi = vhi * nHi
@ -164,7 +164,7 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) {
val vnplo = vlo * nLo
// We calculate the carry bit.
val carry = ((vnpmid & 0xFFFFFFFFL) + (npvmid & 0xFFFFFFFFL) + (vnplo >> 32)) >> 32
val carry = ((vnpmid & 0XFFFFFFFFL) + (npvmid & 0XFFFFFFFFL) + (vnplo >> 32)) >> 32
// Last, we add the high bits, the middle bits, and the carry.
val result = vnphi + (vnpmid >> 32) + (npvmid >> 32) + carry

13
server/app/com/xsn/explorer/gcs/SipHashKey.scala

@ -37,12 +37,13 @@ object SipHashKey {
}
def fromBtcutil(hash: Blockhash): SipHashKey = {
val bytes = hash
.string
.take(32)
.grouped(2)
.map { hex => Integer.parseInt(hex, 16).asInstanceOf[Byte] }
.toList
val bytes = hash.string
.take(32)
.grouped(2)
.map { hex =>
Integer.parseInt(hex, 16).asInstanceOf[Byte]
}
.toList
fromBtcutil(bytes)
}

10
server/app/com/xsn/explorer/gcs/UnsignedByte.scala

@ -16,17 +16,19 @@ class UnsignedByte(val byte: Byte) extends AnyVal {
def bits: List[Bit] = {
toFixedBinaryString
.flatMap(Bit.from)
.toList
.flatMap(Bit.from)
.toList
}
}
object UnsignedByte {
def parse(bits: List[Bit]): UnsignedByte = {
require(bits.size <= 8)
val int = bits.foldLeft(0) { case (acc, cur) =>
(acc * 2) + cur.toInt
val int = bits.foldLeft(0) {
case (acc, cur) =>
(acc * 2) + cur.toInt
}
new UnsignedByte(int.asInstanceOf[Byte])

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

@ -11,4 +11,4 @@ object BlockExtractionMethod extends Enum[BlockExtractionMethod] {
final case object ProofOfWork extends BlockExtractionMethod("PoW")
final case object ProofOfStake extends BlockExtractionMethod("PoS")
final case object TrustlessProofOfStake extends BlockExtractionMethod("TPoS")
}
}

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

@ -8,7 +8,8 @@ case class LightWalletTransaction(
size: Size,
time: Long,
inputs: List[LightWalletTransaction.Input],
outputs: List[LightWalletTransaction.Output])
outputs: List[LightWalletTransaction.Output]
)
object LightWalletTransaction {

3
server/app/com/xsn/explorer/models/Statistics.scala

@ -6,7 +6,8 @@ case class Statistics(
blocks: Int,
transactions: Int,
totalSupply: Option[BigDecimal],
circulatingSupply: Option[BigDecimal])
circulatingSupply: Option[BigDecimal]
)
object Statistics {

15
server/app/com/xsn/explorer/models/StatisticsDetails.scala

@ -7,21 +7,22 @@ case class StatisticsDetails(statistics: Statistics, masternodes: Option[Int], d
object StatisticsDetails {
implicit val writes: Writes[StatisticsDetails] = Writes { obj =>
val values = Map(
"blocks" -> JsNumber(obj.statistics.blocks),
"transactions" -> JsNumber(obj.statistics.transactions))
val values =
Map("blocks" -> JsNumber(obj.statistics.blocks), "transactions" -> JsNumber(obj.statistics.transactions))
val extras = List(
"totalSupply" -> obj.statistics.totalSupply.map(JsNumber.apply),
"circulatingSupply" -> obj.statistics.circulatingSupply.map(JsNumber.apply),
"masternodes" -> obj.masternodes.map(c => JsNumber.apply(c)),
"difficulty" -> obj.difficulty.map(c => JsNumber.apply(c))
).flatMap { case (key, maybe) =>
maybe.map(key -> _)
).flatMap {
case (key, maybe) =>
maybe.map(key -> _)
}
val result = extras.foldLeft(values) { case (acc, value) =>
acc + value
val result = extras.foldLeft(values) {
case (acc, value) =>
acc + value
}
JsObject.apply(result)

9
server/app/com/xsn/explorer/models/TPoSContract.scala

@ -6,11 +6,7 @@ import play.api.libs.json.{Json, Writes}
import scala.util.Try
case class TPoSContract(
id: TPoSContract.Id,
details: TPoSContract.Details,
time: Long,
state: TPoSContract.State) {
case class TPoSContract(id: TPoSContract.Id, details: TPoSContract.Details, time: Long, state: TPoSContract.State) {
val txid: TransactionId = id.txid
}
@ -19,6 +15,7 @@ object TPoSContract {
case class Id(txid: TransactionId, index: Int)
class Commission private (val int: Int) extends AnyVal
object Commission {
val range = 1 until 100
@ -30,6 +27,7 @@ object TPoSContract {
}
case class Details(owner: Address, merchant: Address, merchantCommission: Commission)
object Details {
/**
@ -59,6 +57,7 @@ object TPoSContract {
}
sealed abstract class State(override val entryName: String) extends EnumEntry
object State extends Enum[State] {
val values = findValues

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

@ -11,7 +11,8 @@ case class TransactionDetails(
blocktime: Long,
confirmations: Confirmations,
input: List[TransactionValue],
output: List[TransactionValue]) {
output: List[TransactionValue]
) {
lazy val fee: BigDecimal = {
val vin = input.map(_.value).sum

3
server/app/com/xsn/explorer/models/TransactionWithValues.scala

@ -9,7 +9,8 @@ case class TransactionWithValues(
time: Long,
size: Size,
sent: BigDecimal,
received: BigDecimal)
received: BigDecimal
)
object TransactionWithValues {

3
server/app/com/xsn/explorer/models/blockRewards.scala

@ -14,4 +14,5 @@ object BlockRewards {
case class PoWBlockRewards(reward: BlockReward) extends BlockRewards
case class PoSBlockRewards(coinstake: BlockReward, masternode: Option[BlockReward]) extends BlockRewards
case class TPoSBlockRewards(owner: BlockReward, merchant: BlockReward, masternode: Option[BlockReward]) extends BlockRewards
case class TPoSBlockRewards(owner: BlockReward, merchant: BlockReward, masternode: Option[BlockReward])
extends BlockRewards

3
server/app/com/xsn/explorer/models/persisted/AddressTransactionDetails.scala

@ -7,4 +7,5 @@ case class AddressTransactionDetails(
txid: TransactionId,
time: Long,
received: BigDecimal = 0,
sent: BigDecimal = 0)
sent: BigDecimal = 0
)

8
server/app/com/xsn/explorer/models/persisted/Balance.scala

@ -3,10 +3,7 @@ package com.xsn.explorer.models.persisted
import com.xsn.explorer.models.values.Address
import play.api.libs.json._
case class Balance(
address: Address,
received: BigDecimal = BigDecimal(0),
spent: BigDecimal = BigDecimal(0)) {
case class Balance(address: Address, received: BigDecimal = BigDecimal(0), spent: BigDecimal = BigDecimal(0)) {
def available: BigDecimal = received - spent
}
@ -17,7 +14,8 @@ object Balance {
"address" -> JsString(obj.address.string),
"received" -> JsNumber(obj.received),
"spent" -> JsNumber(obj.spent),
"available" -> JsNumber(obj.available))
"available" -> JsNumber(obj.available)
)
JsObject.apply(values)
}

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

@ -18,7 +18,8 @@ case class Block(
bits: String,
chainwork: String,
difficulty: BigDecimal,
extractionMethod: BlockExtractionMethod) {
extractionMethod: BlockExtractionMethod
) {
def withTransactions(transactions: List[Transaction.HasIO]): Block.HasTransactions = {
Block.HasTransactions(this, transactions)
@ -42,10 +43,11 @@ object Block {
* Collect the addresses involved in the block.
*/
def collectAddresses: Set[Address] = {
transactions.foldLeft(Set.empty[Address]) { case (acc, tx) =>
val spending = tx.inputs.flatMap(_.addresses)
val receiving = tx.outputs.flatMap(_.addresses)
spending.toSet ++ receiving.toSet ++ acc
transactions.foldLeft(Set.empty[Address]) {
case (acc, tx) =>
val spending = tx.inputs.flatMap(_.addresses)
val receiving = tx.outputs.flatMap(_.addresses)
spending.toSet ++ receiving.toSet ++ acc
}
}
}

14
server/app/com/xsn/explorer/models/persisted/BlockHeader.scala

@ -15,9 +15,9 @@ sealed trait BlockHeader extends Product with Serializable {
def withFilter(filter: GolombCodedSet): BlockHeader.HasFilter = {
this
.into[BlockHeader.HasFilter]
.withFieldConst(_.filter, filter)
.transform
.into[BlockHeader.HasFilter]
.withFieldConst(_.filter, filter)
.transform
}
}
@ -28,7 +28,8 @@ object BlockHeader {
previousBlockhash: Option[Blockhash],
merkleRoot: Blockhash,
height: Height,
time: Long) extends BlockHeader
time: Long
) extends BlockHeader
case class HasFilter(
hash: Blockhash,
@ -36,7 +37,8 @@ object BlockHeader {
merkleRoot: Blockhash,
height: Height,
time: Long,
filter: GolombCodedSet) extends BlockHeader
filter: GolombCodedSet
) extends BlockHeader
private implicit val filterWrites: Writes[GolombCodedSet] = (obj: GolombCodedSet) => {
Json.obj(
@ -62,4 +64,4 @@ object BlockHeader {
"filter" -> filterMaybe
)
}
}
}

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

@ -4,11 +4,7 @@ import com.xsn.explorer.models._
import com.xsn.explorer.models.rpc.TransactionVIN
import com.xsn.explorer.models.values._
case class Transaction(
id: TransactionId,
blockhash: Blockhash,
time: Long,
size: Size)
case class Transaction(id: TransactionId, blockhash: Blockhash, time: Long, size: Size)
object Transaction {
@ -20,48 +16,34 @@ object Transaction {
fromOutputIndex: Int,
index: Int,
value: BigDecimal,
addresses: List[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 = {
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,
addresses: List[Address],
script: HexString) {
case class Output(txid: TransactionId, index: Int, value: BigDecimal, 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 = {
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,
inputs: List[Transaction.Input],
outputs: List[Transaction.Output]) {
case class HasIO(transaction: Transaction, inputs: List[Transaction.Input], outputs: List[Transaction.Output]) {
require(
outputs.forall(_.txid == transaction.id),
@ -80,23 +62,17 @@ object Transaction {
* As the TPoS contracts aren't stored in the persisted transaction, they are returned on the result.
*/
def fromRPC(tx: rpc.Transaction[TransactionVIN.HasValues]): (HasIO, Option[TPoSContract]) = {
val inputs = tx
.vin
.zipWithIndex
.map { case (vin, index) =>
val inputs = tx.vin.zipWithIndex
.map {
case (vin, index) =>
Transaction.Input(vin.txid, vin.voutIndex, index, vin.value, vin.addresses)
}
}
val outputs = tx.vout.flatMap { vout =>
for {
addresses <- vout.addresses if vout.value > 0
script <- vout.scriptPubKey.map(_.hex)
} yield Output(
tx.id,
vout.n,
vout.value,
addresses,
script)
} yield Output(tx.id, vout.n, vout.value, addresses, script)
}
val transaction = Transaction(

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

@ -7,6 +7,4 @@ package com.xsn.explorer.models
* is that there are fields which shouldn't be persisted, for example, the number
* of confirmations for a block.
*/
package object persisted {
}
package object persisted {}

1
server/app/com/xsn/explorer/models/rpc/AddressBalance.scala

@ -7,6 +7,7 @@ import play.api.libs.json._
case class AddressBalance(balance: BigDecimal, received: BigDecimal)
object AddressBalance {
/**
* The RPC server is giving us these values in satoshis, we transform
* them to BigDecimal to match the format used by the application.

78
server/app/com/xsn/explorer/models/rpc/Block.scala

@ -26,7 +26,8 @@ case class Block(
bits: String,
chainwork: String,
difficulty: BigDecimal,
tposContract: Option[TransactionId]) {
tposContract: Option[TransactionId]
) {
/**
* Every block until 75 is PoW.
@ -48,30 +49,59 @@ object Block {
implicit val reads: Reads[Block] = {
val builder = (__ \ 'hash).read[Blockhash] and
(__ \ 'previousblockhash).readNullable[Blockhash] and
(__ \ 'nextblockhash).readNullable[Blockhash] and
(__ \ 'merkleroot).read[Blockhash] and
(__ \ 'tx).read[List[TransactionId]] and
(__ \ 'confirmations).read[Confirmations] and
(__ \ 'size).read[Size] and
(__ \ 'height).read[Height] and
(__ \ 'version).read[Int] and
(__ \ 'time).read[Long] and
(__ \ 'mediantime).read[Long] and
(__ \ 'nonce).read[Long] and
(__ \ 'bits).read[String] and
(__ \ 'chainwork).read[String] and
(__ \ 'difficulty).read[BigDecimal] and
(__ \ 'tposcontract).readNullable[TransactionId]
(__ \ 'previousblockhash).readNullable[Blockhash] and
(__ \ 'nextblockhash).readNullable[Blockhash] and
(__ \ 'merkleroot).read[Blockhash] and
(__ \ 'tx).read[List[TransactionId]] and
(__ \ 'confirmations).read[Confirmations] and
(__ \ 'size).read[Size] and
(__ \ 'height).read[Height] and
(__ \ 'version).read[Int] and
(__ \ 'time).read[Long] and
(__ \ 'mediantime).read[Long] and
(__ \ 'nonce).read[Long] and
(__ \ 'bits).read[String] and
(__ \ 'chainwork).read[String] and
(__ \ 'difficulty).read[BigDecimal] and
(__ \ 'tposcontract).readNullable[TransactionId]
builder.apply { (hash, previous, next, root, transactions,
confirmations, size, height, version, time,
medianTime, nonce, bits, chainwork, difficulty, tposContract) =>
Block(
hash, previous, next, root, transactions,
confirmations, size, height, version, time,
medianTime, nonce, bits, chainwork, difficulty, tposContract)
builder.apply {
(
hash,
previous,
next,
root,
transactions,
confirmations,
size,
height,
version,
time,
medianTime,
nonce,
bits,
chainwork,
difficulty,
tposContract
) =>
Block(
hash,
previous,
next,
root,
transactions,
confirmations,
size,
height,
version,
time,
medianTime,
nonce,
bits,
chainwork,
difficulty,
tposContract
)
}
}

17
server/app/com/xsn/explorer/models/rpc/Masternode.scala

@ -33,20 +33,21 @@ object Masternode {
*/
def fromMap(values: Map[String, String]): List[Masternode] = {
values
.map { case (key, value) =>
.map {
case (key, value) =>
val list = value.split(" ").map(_.trim).filter(_.nonEmpty).toList
parseValues(key, list)
}
.toList
.flatten
}
.toList
.flatten
}
private def parseTxid(key: String): Option[TransactionId] = {
key
.split("\\,")
.headOption
.flatMap(_.split("\\(").lift(1))
.flatMap(TransactionId.from)
.split("\\,")
.headOption
.flatMap(_.split("\\(").lift(1))
.flatMap(TransactionId.from)
}
private def parseValues(key: String, values: List[String]): Option[Masternode] = values match {

12
server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala

@ -5,11 +5,7 @@ import com.xsn.explorer.models.values.{Address, HexString}
import play.api.libs.functional.syntax._
import play.api.libs.json.{Reads, __}
case class ScriptPubKey(
`type`: String,
asm: String,
hex: HexString,
addresses: List[Address]) {
case class ScriptPubKey(`type`: String, asm: String, hex: HexString, addresses: List[Address]) {
/**
* Get TPoS contract details if available
@ -23,9 +19,9 @@ object ScriptPubKey {
implicit val reads: Reads[Option[ScriptPubKey]] = {
val builder = (__ \ 'type).read[String] and
(__ \ 'asm).read[String] and
(__ \ 'hex).read[String].map(HexString.from) and
(__ \ 'addresses).readNullable[List[Address]].map(_ getOrElse List.empty)
(__ \ 'asm).read[String] and
(__ \ 'hex).read[String].map(HexString.from) and
(__ \ 'addresses).readNullable[List[Address]].map(_ getOrElse List.empty)
builder.apply { (t, asm, hexString, addresses) =>
for {

9
server/app/com/xsn/explorer/models/rpc/ServerStatistics.scala

@ -4,17 +4,14 @@ import com.xsn.explorer.models.values.Height
import play.api.libs.functional.syntax._
import play.api.libs.json.{Json, Reads, Writes, __}
case class ServerStatistics(
height: Height,
transactions: Int,
totalSupply: BigDecimal)
case class ServerStatistics(height: Height, transactions: Int, totalSupply: BigDecimal)
object ServerStatistics {
implicit val reads: Reads[ServerStatistics] = {
val builder = (__ \ 'height).read[Height] and
(__ \ 'transactions).read[Int] and
(__ \ 'total_amount).read[BigDecimal]
(__ \ 'transactions).read[Int] and
(__ \ 'total_amount).read[BigDecimal]
builder.apply { (height, transactions, totalSupply) =>
ServerStatistics(height, transactions, totalSupply)

24
server/app/com/xsn/explorer/models/rpc/Transaction.scala

@ -12,21 +12,25 @@ case class Transaction[VIN <: TransactionVIN](
blocktime: Long,
confirmations: Confirmations,
vin: List[VIN],
vout: List[TransactionVOUT])
vout: List[TransactionVOUT]
)
object Transaction {
implicit val reads: Reads[Transaction[TransactionVIN]] = {
val builder = (__ \ 'txid).read[TransactionId] and
(__ \ 'size).read[Size] and
(__ \ 'blockhash).read[Blockhash] and
(__ \ 'time).readNullable[Long] and
(__ \ 'blocktime).readNullable[Long] and
(__ \ 'confirmations).read[Confirmations] and
(__ \ 'vout).read[List[TransactionVOUT]] and
(__ \ 'vin).readNullable[List[JsValue]]
.map(_ getOrElse List.empty)
.map { list => list.flatMap(_.asOpt[TransactionVIN]) }
(__ \ 'size).read[Size] and
(__ \ 'blockhash).read[Blockhash] and
(__ \ 'time).readNullable[Long] and
(__ \ 'blocktime).readNullable[Long] and
(__ \ 'confirmations).read[Confirmations] and
(__ \ 'vout).read[List[TransactionVOUT]] and
(__ \ 'vin)
.readNullable[List[JsValue]]
.map(_ getOrElse List.empty)
.map { list =>
list.flatMap(_.asOpt[TransactionVIN])
}
// TODO: Enfore blocktime and time fields when https://github.com/X9Developers/XSN/issues/72 is fixed.
builder.apply { (id, size, blockHash, time, blockTime, confirmations, vout, vin) =>

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

@ -24,11 +24,12 @@ object TransactionVIN {
override val txid: TransactionId,
override val voutIndex: Int,
value: BigDecimal,
addresses: List[Address]) extends TransactionVIN
addresses: List[Address]
) extends TransactionVIN
implicit val reads: Reads[TransactionVIN] = {
val builder = (__ \ 'txid).read[TransactionId] and
(__ \ 'vout).read[Int]
(__ \ 'vout).read[Int]
builder.apply { (txid, index) =>
Raw(txid, index)

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

@ -4,10 +4,7 @@ import com.xsn.explorer.models.values.Address
import play.api.libs.functional.syntax._
import play.api.libs.json.{Reads, __}
case class TransactionVOUT(
value: BigDecimal,
n: Int,
scriptPubKey: Option[ScriptPubKey] = None) {
case class TransactionVOUT(value: BigDecimal, n: Int, scriptPubKey: Option[ScriptPubKey] = None) {
val addresses: Option[List[Address]] = scriptPubKey.map(_.addresses)
}
@ -16,8 +13,8 @@ object TransactionVOUT {
implicit val reads: Reads[TransactionVOUT] = {
val builder = (__ \ 'value).read[BigDecimal] and
(__ \ 'n).read[Int] and
(__ \ 'scriptPubKey).read[Option[ScriptPubKey]]
(__ \ 'n).read[Int] and
(__ \ 'scriptPubKey).read[Option[ScriptPubKey]]
builder.apply { (value, n, script) =>
TransactionVOUT(value, n, script)

22
server/app/com/xsn/explorer/models/transformers/package.scala

@ -11,17 +11,17 @@ package object transformers {
def toPersistedBlock(rpcBlock: rpc.Block, extractionMethod: BlockExtractionMethod): persisted.Block = {
rpcBlock
.into[Block]
.withFieldConst(_.extractionMethod, extractionMethod)
.transform
.into[Block]
.withFieldConst(_.extractionMethod, extractionMethod)
.transform
}
def toLightWalletTransactionInput(input: Transaction.Input): LightWalletTransaction.Input = {
input
.into[LightWalletTransaction.Input]
.withFieldRenamed(_.fromOutputIndex, _.index)
.withFieldRenamed(_.fromTxid, _.txid)
.transform
.into[LightWalletTransaction.Input]
.withFieldRenamed(_.fromOutputIndex, _.index)
.withFieldRenamed(_.fromTxid, _.txid)
.transform
}
def toLightWalletTransactionOutput(output: Transaction.Output): LightWalletTransaction.Output = {
@ -33,9 +33,9 @@ package object transformers {
val outputs = tx.outputs.map(toLightWalletTransactionOutput)
tx.transaction
.into[LightWalletTransaction]
.withFieldConst(_.inputs, inputs)
.withFieldConst(_.outputs, outputs)
.transform
.into[LightWalletTransaction]
.withFieldConst(_.inputs, inputs)
.withFieldConst(_.outputs, outputs)
.transform
}
}

16
server/app/com/xsn/explorer/models/values/Address.scala

@ -22,18 +22,20 @@ object Address {
def fromHex(hex: String): Option[Address] = {
Try { DatatypeConverter.parseHexBinary(hex) }
.map { bytes => new String(bytes) }
.toOption
.flatMap(from)
.map { bytes =>
new String(bytes)
}
.toOption
.flatMap(from)
}
implicit val reads: Reads[Address] = Reads { json =>
json.validate[String].flatMap { string =>
from(string)
.map(JsSuccess.apply(_))
.getOrElse {
JsError.apply("Invalid address")
}
.map(JsSuccess.apply(_))
.getOrElse {
JsError.apply("Invalid address")
}
}
}
}

8
server/app/com/xsn/explorer/models/values/Blockhash.scala

@ -24,10 +24,10 @@ object Blockhash {
implicit val reads: Reads[Blockhash] = Reads { json =>
json.validate[String].flatMap { string =>
from(string)
.map(JsSuccess.apply(_))
.getOrElse {
JsError.apply("Invalid blockhash")
}
.map(JsSuccess.apply(_))
.getOrElse {
JsError.apply("Invalid blockhash")
}
}
}
}

2
server/app/com/xsn/explorer/models/values/IPAddress.scala

@ -2,7 +2,7 @@ package com.xsn.explorer.models.values
import com.alexitc.playsonify.models.WrappedString
class IPAddress (val string: String) extends WrappedString
class IPAddress(val string: String) extends WrappedString
object IPAddress {
private val pattern = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$".r.pattern

8
server/app/com/xsn/explorer/models/values/TransactionId.scala

@ -24,10 +24,10 @@ object TransactionId {
implicit val reads: Reads[TransactionId] = Reads { json =>
json.validate[String].flatMap { string =>
from(string)
.map(JsSuccess.apply(_))
.getOrElse {
JsError.apply("Invalid transaction")
}
.map(JsSuccess.apply(_))
.getOrElse {
JsError.apply("Invalid transaction")
}
}
}
}

4
server/app/com/xsn/explorer/models/values/package.scala

@ -3,6 +3,4 @@ package com.xsn.explorer.models
/**
* Package dedicated to store value classes that are core to the project.
*/
package object values {
}
package object values {}

7
server/app/com/xsn/explorer/modules/ExecutorsModule.scala

@ -1,7 +1,12 @@
package com.xsn.explorer.modules
import com.google.inject.AbstractModule
import com.xsn.explorer.executors.{DatabaseAkkaExecutionContext, DatabaseExecutionContext, ExternalServiceAkkaExecutionContext, ExternalServiceExecutionContext}
import com.xsn.explorer.executors.{
DatabaseAkkaExecutionContext,
DatabaseExecutionContext,
ExternalServiceAkkaExecutionContext,
ExternalServiceExecutionContext
}
class ExecutorsModule extends AbstractModule {

3
server/app/com/xsn/explorer/parsers/OrderingConditionParser.scala

@ -6,8 +6,7 @@ import org.scalactic.{One, Or}
class OrderingConditionParser {
def parse(
unsafeOrderingCondition: String): Option[OrderingCondition] = unsafeOrderingCondition.toLowerCase match {
def parse(unsafeOrderingCondition: String): Option[OrderingCondition] = unsafeOrderingCondition.toLowerCase match {
case "asc" => Some(OrderingCondition.AscendingOrder)
case "desc" => Some(OrderingCondition.DescendingOrder)

13
server/app/com/xsn/explorer/play/LoggingFilter.scala

@ -8,24 +8,21 @@ import play.api.mvc.{Filter, RequestHeader, Result}
import scala.concurrent.{ExecutionContext, Future}
class LoggingFilter @Inject() (
implicit val mat: Materializer,
ec: ExecutionContext)
extends Filter {
class LoggingFilter @Inject()(implicit val mat: Materializer, ec: ExecutionContext) extends Filter {
private val logger = LoggerFactory.getLogger(this.getClass)
def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
def apply(nextFilter: RequestHeader => Future[Result])(requestHeader: RequestHeader): Future[Result] = {
val startTime = System.currentTimeMillis
nextFilter(requestHeader).map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
logger.info(s"${requestHeader.method} ${requestHeader.uri} took $requestTime ms and returned ${result.header.status}")
logger.info(
s"${requestHeader.method} ${requestHeader.uri} took $requestTime ms and returned ${result.header.status}"
)
result.withHeaders("Request-Time" -> requestTime.toString)
}

2
server/app/com/xsn/explorer/play/MyHttpErrorHandler.scala

@ -11,7 +11,7 @@ import play.api.mvc.Results.{InternalServerError, Status}
import scala.concurrent.Future
class MyHttpErrorHandler @Inject() (errorRenderer: PublicErrorRenderer) extends HttpErrorHandler {
class MyHttpErrorHandler @Inject()(errorRenderer: PublicErrorRenderer) extends HttpErrorHandler {
private val logger = LoggerFactory.getLogger(this.getClass)

6
server/app/com/xsn/explorer/services/AddressService.scala

@ -9,11 +9,11 @@ import javax.inject.Inject
import scala.concurrent.ExecutionContext
class AddressService @Inject() (
class AddressService @Inject()(
addressValidator: AddressValidator,
balanceFutureDataHandler: BalanceFutureDataHandler,
transactionFutureDataHandler: TransactionFutureDataHandler)(
implicit ec: ExecutionContext) {
transactionFutureDataHandler: TransactionFutureDataHandler
)(implicit ec: ExecutionContext) {
def getBy(addressString: String): FutureApplicationResult[Balance] = {
val result = for {

11
server/app/com/xsn/explorer/services/BalanceService.scala

@ -14,12 +14,12 @@ import javax.inject.Inject
import scala.concurrent.ExecutionContext
class BalanceService @Inject() (
class BalanceService @Inject()(
paginatedQueryValidator: PaginatedQueryValidator,
balanceOrderingParser: BalanceOrderingParser,
addressValidator: AddressValidator,
balanceFutureDataHandler: BalanceFutureDataHandler)(
implicit ec: ExecutionContext) {
balanceFutureDataHandler: BalanceFutureDataHandler
)(implicit ec: ExecutionContext) {
def get(paginatedQuery: PaginatedQuery, orderingQuery: OrderingQuery): FuturePaginatedResult[Balance] = {
val result = for {
@ -31,7 +31,10 @@ class BalanceService @Inject() (
result.toFuture
}
def getHighest(limit: Limit, lastSeenAddressString: Option[String]): FutureApplicationResult[WrappedResult[List[Balance]]] = {
def getHighest(
limit: Limit,
lastSeenAddressString: Option[String]
): FutureApplicationResult[WrappedResult[List[Balance]]] = {
val result = for {
_ <- paginatedQueryValidator.validate(PaginatedQuery(Offset(0), limit), 100).toFutureOr
lastSeenAddress <- validate(lastSeenAddressString, addressValidator.validate).toFutureOr

126
server/app/com/xsn/explorer/services/BlockService.scala

@ -22,7 +22,7 @@ import play.api.libs.json.{JsValue, Writes}
import scala.concurrent.{ExecutionContext, Future}
class BlockService @Inject() (
class BlockService @Inject()(
xsnService: XSNService,
blockDataHandler: BlockFutureDataHandler,
paginatedQueryValidator: PaginatedQueryValidator,
@ -30,23 +30,21 @@ class BlockService @Inject() (
blockLogic: BlockLogic,
transactionLogic: TransactionLogic,
orderingConditionParser: OrderingConditionParser,
blockHeaderCache: BlockHeaderCache)(
implicit ec: ExecutionContext) {
blockHeaderCache: BlockHeaderCache
)(implicit ec: ExecutionContext) {
private val maxHeadersPerQuery = 1000
def getBlockHeaders(
limit: Limit,
lastSeenHashString: Option[String],
orderingConditionString: String)(
implicit writes: Writes[BlockHeader]): FutureApplicationResult[(WrappedResult[List[BlockHeader]], Boolean)] = {
def getBlockHeaders(limit: Limit, lastSeenHashString: Option[String], orderingConditionString: String)(
implicit writes: Writes[BlockHeader]
): FutureApplicationResult[(WrappedResult[List[BlockHeader]], Boolean)] = {
val result = for {
lastSeenHash <- validate(lastSeenHashString, blockhashValidator.validate).toFutureOr
_ <- paginatedQueryValidator.validate(PaginatedQuery(Offset(0), limit), maxHeadersPerQuery).toFutureOr
orderingCondition <- orderingConditionParser.parseReuslt(orderingConditionString).toFutureOr
headers <-blockDataHandler.getHeaders(limit, orderingCondition, lastSeenHash).toFutureOr
headers <- blockDataHandler.getHeaders(limit, orderingCondition, lastSeenHash).toFutureOr
latestBlock <- blockDataHandler.getLatestBlock().toFutureOr
} yield (WrappedResult(headers), canCacheResult(orderingCondition, limit.int, latestBlock, headers))
@ -57,24 +55,23 @@ class BlockService @Inject() (
ordering: OrderingCondition,
expectedSize: Int,
latestKnownBlock: persisted.Block,
result: List[BlockHeader]): Boolean = {
result: List[BlockHeader]
): Boolean = {
ordering == OrderingCondition.AscendingOrder && // from oldest to newest
result.size == expectedSize && // a complete query
expectedSize > 0 && // non empty result
result.lastOption.exists(_.height.int + 20 < latestKnownBlock.height.int) // there are at least 20 more blocks (unlikely to occur rollbacks)
result.size == expectedSize && // a complete query
expectedSize > 0 && // non empty result
result.lastOption.exists(_.height.int + 20 < latestKnownBlock.height.int) // there are at least 20 more blocks (unlikely to occur rollbacks)
}
private def getCacheableHeaders(
limit: Limit,
orderingCondition: OrderingCondition,
lastSeenHash: Option[Blockhash])(
implicit writes: Writes[BlockHeader]) = {
private def getCacheableHeaders(limit: Limit, orderingCondition: OrderingCondition, lastSeenHash: Option[Blockhash])(
implicit writes: Writes[BlockHeader]
) = {
val cacheKey = BlockHeaderCache.Key(limit, orderingCondition, lastSeenHash)
blockHeaderCache.getOrSet(cacheKey, maxHeadersPerQuery) {
val result = for {
headers <-blockDataHandler.getHeaders(limit, orderingCondition, lastSeenHash).toFutureOr
headers <- blockDataHandler.getHeaders(limit, orderingCondition, lastSeenHash).toFutureOr
} yield WrappedResult(headers)
result.toFuture
@ -111,8 +108,8 @@ class BlockService @Inject() (
def getDetails(height: Height): FutureApplicationResult[BlockDetails] = {
val result = for {
blockhash <- xsnService
.getBlockhash(height)
.toFutureOr
.getBlockhash(height)
.toFutureOr
details <- getDetailsPrivate(blockhash).toFutureOr
} yield details
@ -123,19 +120,19 @@ class BlockService @Inject() (
private def getDetailsPrivate(blockhash: Blockhash): FutureApplicationResult[BlockDetails] = {
val result = for {
block <- xsnService
.getBlock(blockhash)
.toFutureOr
.getBlock(blockhash)
.toFutureOr
rewards <- getBlockRewards(block)
.toFutureOr
.map(Option.apply)
.recoverFrom(BlockRewardsNotFoundError)(Option.empty)
rewards <- getBlockRewards(block).toFutureOr
.map(Option.apply)
.recoverFrom(BlockRewardsNotFoundError)(Option.empty)
} yield BlockDetails(block, rewards)
result.toFuture
}
def getLatestBlocks(): FutureApplicationResult[List[Block]] = {
/**
* Temporal workaround to retrieve the latest blocks, they
* will be retrieved from the database once available.
@ -162,13 +159,10 @@ class BlockService @Inject() (
} else if (block.transactions.isEmpty) {
Future.successful(Good(BlockExtractionMethod.ProofOfWork))
} else {
isPoS(block)
.toFutureOr
.map {
case true => BlockExtractionMethod.ProofOfStake
case false => BlockExtractionMethod.ProofOfWork
}
.toFuture
isPoS(block).toFutureOr.map {
case true => BlockExtractionMethod.ProofOfStake
case false => BlockExtractionMethod.ProofOfWork
}.toFuture
}
}
@ -217,29 +211,29 @@ class BlockService @Inject() (
private def getPoSBlockRewards(block: Block): FutureApplicationResult[PoSBlockRewards] = {
val result = for {
coinstakeTxId <- blockLogic
.getCoinstakeTransactionId(block)
.toFutureOr
.getCoinstakeTransactionId(block)
.toFutureOr
coinstakeTx <- xsnService
.getTransaction(coinstakeTxId)
.toFutureOr
.getTransaction(coinstakeTxId)
.toFutureOr
coinstakeTxVIN <- transactionLogic
.getVIN(coinstakeTx, BlockRewardsNotFoundError)
.toFutureOr
.getVIN(coinstakeTx, BlockRewardsNotFoundError)
.toFutureOr
previousToCoinstakeTx <- xsnService
.getTransaction(coinstakeTxVIN.txid)
.toFutureOr
.getTransaction(coinstakeTxVIN.txid)
.toFutureOr
previousToCoinstakeVOUT <- transactionLogic
.getVOUT(coinstakeTxVIN, previousToCoinstakeTx, BlockRewardsNotFoundError)
.toFutureOr
.getVOUT(coinstakeTxVIN, previousToCoinstakeTx, BlockRewardsNotFoundError)
.toFutureOr
coinstakeAddress <- transactionLogic
.getAddress(previousToCoinstakeVOUT, BlockRewardsNotFoundError)
.toFutureOr
.getAddress(previousToCoinstakeVOUT, BlockRewardsNotFoundError)
.toFutureOr
rewards <- blockLogic
.getPoSRewards(coinstakeTx, coinstakeAddress, previousToCoinstakeVOUT.value)
.toFutureOr
.getPoSRewards(coinstakeTx, coinstakeAddress, previousToCoinstakeVOUT.value)
.toFutureOr
} yield rewards
result.toFuture
@ -248,31 +242,31 @@ class BlockService @Inject() (
private def getTPoSBlockRewards(block: Block): FutureApplicationResult[BlockRewards] = {
val result = for {
coinstakeTxId <- blockLogic
.getCoinstakeTransactionId(block)
.toFutureOr
.getCoinstakeTransactionId(block)
.toFutureOr
coinstakeTx <- xsnService
.getTransaction(coinstakeTxId)
.toFutureOr
.getTransaction(coinstakeTxId)
.toFutureOr
coinstakeTxVIN <- transactionLogic
.getVIN(coinstakeTx, BlockRewardsNotFoundError)
.toFutureOr
.getVIN(coinstakeTx, BlockRewardsNotFoundError)
.toFutureOr
coinstakeInput <- getCoinstakeInput(coinstakeTxVIN).toFutureOr
tposTxId <- blockLogic
.getTPoSTransactionId(block)
.toFutureOr
.getTPoSTransactionId(block)
.toFutureOr
tposTx <- xsnService
.getTransaction(tposTxId)
.toFutureOr
.getTransaction(tposTxId)
.toFutureOr
contract <- blockLogic
.getTPoSContractDetails(tposTx)
.toFutureOr
.getTPoSContractDetails(tposTx)
.toFutureOr
rewards <- blockLogic
.getTPoSRewards(coinstakeTx, contract, coinstakeInput)
.toFutureOr
.getTPoSRewards(coinstakeTx, contract, coinstakeInput)
.toFutureOr
} yield rewards
result.toFuture
@ -282,11 +276,11 @@ class BlockService @Inject() (
def loadFromTx = {
val result = for {
previousToCoinstakeTx <- xsnService
.getTransaction(coinstakeTxVIN.txid)
.toFutureOr
.getTransaction(coinstakeTxVIN.txid)
.toFutureOr
previousToCoinstakeVOUT <- transactionLogic
.getVOUT(coinstakeTxVIN, previousToCoinstakeTx, BlockRewardsNotFoundError)
.toFutureOr
.getVOUT(coinstakeTxVIN, previousToCoinstakeTx, BlockRewardsNotFoundError)
.toFutureOr
} yield previousToCoinstakeVOUT.value
result.toFuture

137
server/app/com/xsn/explorer/services/LedgerSynchronizerService.scala

@ -16,14 +16,14 @@ import org.slf4j.LoggerFactory
import scala.concurrent.{ExecutionContext, Future}
class LedgerSynchronizerService @Inject() (
class LedgerSynchronizerService @Inject()(
explorerConfig: ExplorerConfig,
xsnService: XSNService,
blockService: BlockService,
transactionCollectorService: TransactionCollectorService,
ledgerDataHandler: LedgerFutureDataHandler,
blockDataHandler: BlockFutureDataHandler)(
implicit ec: ExecutionContext) {
blockDataHandler: BlockFutureDataHandler
)(implicit ec: ExecutionContext) {
import LedgerSynchronizerService._
@ -50,15 +50,17 @@ class LedgerSynchronizerService @Inject() (
val result = for {
latestBlockMaybe <- blockDataHandler
.getLatestBlock()
.toFutureOr
.map(Option.apply)
.recoverFrom(BlockNotFoundError)(None)
.getLatestBlock()
.toFutureOr
.map(Option.apply)
.recoverFrom(BlockNotFoundError)(None)
_ <- latestBlockMaybe
.map { latestBlock => onLatestBlock(latestBlock, block) }
.getOrElse { onEmptyLedger(block) }
.toFutureOr
.map { latestBlock =>
onLatestBlock(latestBlock, block)
}
.getOrElse { onEmptyLedger(block) }
.toFutureOr
} yield ()
result.toFuture
@ -94,7 +96,7 @@ class LedgerSynchronizerService @Inject() (
*/
private def onLatestBlock(ledgerBlock: Block, newBlock: rpc.Block): FutureApplicationResult[Unit] = {
if (ledgerBlock.height.int + 1 == newBlock.height.int &&
newBlock.previousBlockhash.contains(ledgerBlock.hash)) {
newBlock.previousBlockhash.contains(ledgerBlock.hash)) {
logger.info(s"Appending block ${newBlock.height}, hash = ${newBlock.hash}")
appendBlock(newBlock)
@ -119,22 +121,26 @@ class LedgerSynchronizerService @Inject() (
} else {
val result = for {
expectedBlockMaybe <- blockDataHandler
.getBy(newBlock.hash)
.toFutureOr
.map(Option.apply)
.recoverFrom(BlockNotFoundError)(None)
.getBy(newBlock.hash)
.toFutureOr
.map(Option.apply)
.recoverFrom(BlockNotFoundError)(None)
_ = logger.info(s"Checking possible existing block ${newBlock.height}, hash = ${newBlock.hash}, exists = ${expectedBlockMaybe.isDefined}")
_ = logger.info(
s"Checking possible existing block ${newBlock.height}, hash = ${newBlock.hash}, exists = ${expectedBlockMaybe.isDefined}"
)
_ <- expectedBlockMaybe
.map { _ => Future.successful(Good(())) }
.getOrElse {
val x = for {
_ <- trimTo(newBlock.height).toFutureOr
_ <- synchronize(newBlock).toFutureOr
} yield ()
x.toFuture
}
.toFutureOr
.map { _ =>
Future.successful(Good(()))
}
.getOrElse {
val x = for {
_ <- trimTo(newBlock.height).toFutureOr
_ <- synchronize(newBlock).toFutureOr
} yield ()
x.toFuture
}
.toFutureOr
} yield ()
result.toFuture
@ -158,15 +164,16 @@ class LedgerSynchronizerService @Inject() (
logger.info(s"Syncing block range = $range")
// TODO: check, it might be safer to use the nextBlockhash instead of the height
range.foldLeft[FutureApplicationResult[Unit]](Future.successful(Good(()))) { case (previous, height) =>
val result = for {
_ <- previous.toFutureOr
blockhash <- xsnService.getBlockhash(Height(height)).toFutureOr
block <- getRPCBlock(blockhash).toFutureOr
_ <- synchronize(block).toFutureOr
} yield ()
result.toFuture
range.foldLeft[FutureApplicationResult[Unit]](Future.successful(Good(()))) {
case (previous, height) =>
val result = for {
_ <- previous.toFutureOr
blockhash <- xsnService.getBlockhash(Height(height)).toFutureOr
block <- getRPCBlock(blockhash).toFutureOr
_ <- synchronize(block).toFutureOr
} yield ()
result.toFuture
}
}
@ -175,7 +182,7 @@ class LedgerSynchronizerService @Inject() (
rpcBlock <- xsnService.getBlock(blockhash).toFutureOr
} yield {
if (explorerConfig.liteVersionConfig.enabled &&
rpcBlock.height.int < explorerConfig.liteVersionConfig.syncTransactionsFromBlock) {
rpcBlock.height.int < explorerConfig.liteVersionConfig.syncTransactionsFromBlock) {
// lite version, ignore transactions
rpcBlock.copy(transactions = List.empty)
@ -187,7 +194,8 @@ class LedgerSynchronizerService @Inject() (
result.toFuture
}
private def ExcludedTransactions = List("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468").flatMap(TransactionId.from)
private def ExcludedTransactions =
List("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468").flatMap(TransactionId.from)
private def getBlockData(rpcBlock: rpc.Block): FutureApplicationResult[BlockData] = {
val result = for {
@ -199,7 +207,9 @@ class LedgerSynchronizerService @Inject() (
} yield {
if (transactions.size != filteredTransactions.size) {
// see https://github.com/bitpay/insight-api/issues/42
logger.warn(s"The block = ${rpcBlock.hash} has phantom ${transactions.size - filteredTransactions.size} transactions, they are being discarded")
logger.warn(
s"The block = ${rpcBlock.hash} has phantom ${transactions.size - filteredTransactions.size} transactions, they are being discarded"
)
}
val block = toPersistedBlock(rpcBlock, extractionMethod).withTransactions(filteredTransactions)
@ -211,16 +221,16 @@ class LedgerSynchronizerService @Inject() (
private def getValidContracts(contracts: List[TPoSContract]): FutureApplicationResult[List[TPoSContract]] = {
val listF = contracts
.map { contract =>
xsnService
.isTPoSContract(contract.txid)
.toFutureOr
.map { valid =>
if (valid) Some(contract)
else None
}
.toFuture
}
.map { contract =>
xsnService
.isTPoSContract(contract.txid)
.toFutureOr
.map { valid =>
if (valid) Some(contract)
else None
}
.toFuture
}
val futureList = Future.sequence(listF)
futureList.map { list =>
@ -230,11 +240,12 @@ class LedgerSynchronizerService @Inject() (
}
val initial: ApplicationResult[List[TPoSContract]] = Good(List.empty)
x.foldLeft(initial) { case (acc, cur) =>
cur match {
case Good(contract) => acc.map(contract :: _)
case Bad(e) => acc.badMap(prev => prev ++ e)
}
x.foldLeft(initial) {
case (acc, cur) =>
cur match {
case Good(contract) => acc.map(contract :: _)
case Bad(e) => acc.badMap(prev => prev ++ e)
}
}
}
}
@ -245,19 +256,19 @@ class LedgerSynchronizerService @Inject() (
*/
private def trimTo(height: Height): FutureApplicationResult[Unit] = {
val result = ledgerDataHandler
.pop()
.toFutureOr
.flatMap { block =>
logger.info(s"Trimmed block ${block.height} from the ledger")
val result = if (block.height == height) {
Future.successful(Good(()))
} else {
trimTo(height)
}
result.toFutureOr
.pop()
.toFutureOr
.flatMap { block =>
logger.info(s"Trimmed block ${block.height} from the ledger")
val result = if (block.height == height) {
Future.successful(Good(()))
} else {
trimTo(height)
}
result.toFutureOr
}
result.toFuture
}
}

21
server/app/com/xsn/explorer/services/MasternodeService.scala

@ -15,13 +15,16 @@ import org.scalactic.{Bad, Good}
import scala.concurrent.ExecutionContext
class MasternodeService @Inject() (
class MasternodeService @Inject()(
queryValidator: PaginatedQueryValidator,
masternodeOrderingParser: MasternodeOrderingParser,
xsnService: XSNService)(
implicit ec: ExecutionContext) {
xsnService: XSNService
)(implicit ec: ExecutionContext) {
def getMasternodes(paginatedQuery: PaginatedQuery, orderingQuery: OrderingQuery): FuturePaginatedResult[Masternode] = {
def getMasternodes(
paginatedQuery: PaginatedQuery,
orderingQuery: OrderingQuery
): FuturePaginatedResult[Masternode] = {
val result = for {
validatedQuery <- queryValidator.validate(paginatedQuery, 2000).toFutureOr
ordering <- masternodeOrderingParser.from(orderingQuery).toFutureOr
@ -34,10 +37,10 @@ class MasternodeService @Inject() (
def getMasternode(ipAddressString: String): FutureApplicationResult[Masternode] = {
val result = for {
ipAddress <- IPAddress
.from(ipAddressString)
.map(Good(_))
.getOrElse(Bad(IPAddressFormatError).accumulating)
.toFutureOr
.from(ipAddressString)
.map(Good(_))
.getOrElse(Bad(IPAddressFormatError).accumulating)
.toFutureOr
masternode <- xsnService.getMasternode(ipAddress).toFutureOr
} yield masternode
@ -47,7 +50,7 @@ class MasternodeService @Inject() (
private def build(list: List[Masternode], query: PaginatedQuery, ordering: FieldOrdering[MasternodeField]) = {
val partial = sort(list, ordering)
.slice(query.offset.int, query.offset.int + query.limit.int)
.slice(query.offset.int, query.offset.int + query.limit.int)
PaginatedResult(query.offset, query.limit, Count(list.size), partial)
}

17
server/app/com/xsn/explorer/services/StatisticsService.scala

@ -9,10 +9,9 @@ import org.scalactic.{Bad, Good}
import scala.concurrent.ExecutionContext
class StatisticsService @Inject() (
xsnService: XSNService,
statisticsFutureDataHandler: StatisticsFutureDataHandler)(
implicit ec: ExecutionContext) {
class StatisticsService @Inject()(xsnService: XSNService, statisticsFutureDataHandler: StatisticsFutureDataHandler)(
implicit ec: ExecutionContext
) {
def getStatistics(): FutureApplicationResult[StatisticsDetails] = {
val dbStats = statisticsFutureDataHandler.getStatistics()
@ -30,10 +29,10 @@ class StatisticsService @Inject() (
private def discardErrors[T](value: FutureApplicationResult[T]): FutureApplicationResult[Option[T]] = {
value
.map {
case Good(result) => Good(Some(result))
case Bad(_) => Good(None)
}
.recover { case _: Throwable => Good(None) }
.map {
case Good(result) => Good(Some(result))
case Bad(_) => Good(None)
}
.recover { case _: Throwable => Good(None) }
}
}

6
server/app/com/xsn/explorer/services/TPoSContractService.scala

@ -9,10 +9,10 @@ import javax.inject.Inject
import scala.concurrent.ExecutionContext
class TPoSContractService @Inject() (
class TPoSContractService @Inject()(
addressValidator: AddressValidator,
tposContractFutureDataHandler: TPoSContractFutureDataHandler)(
implicit ec: ExecutionContext) {
tposContractFutureDataHandler: TPoSContractFutureDataHandler
)(implicit ec: ExecutionContext) {
def getBy(addressString: String): FutureApplicationResult[WrappedResult[List[TPoSContract]]] = {
val result = for {

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

@ -11,10 +11,10 @@ import org.scalactic.{Bad, Good, One, Or}
import scala.concurrent.{ExecutionContext, Future}
class TransactionCollectorService @Inject() (
class TransactionCollectorService @Inject()(
xsnService: XSNService,
transactionDataHandler: TransactionFutureDataHandler)(
implicit ec: ExecutionContext) {
transactionDataHandler: TransactionFutureDataHandler
)(implicit ec: ExecutionContext) {
import TransactionCollectorService._
@ -35,7 +35,10 @@ class TransactionCollectorService @Inject() (
* When dealing with the RPC API, we try to get the details in parallel, if the server gets overloaded,
* we'll try to load the data sequentially.
*/
def collect(txidList: List[TransactionId], excludedTransactions: List[TransactionId]): FutureApplicationResult[Result] = {
def collect(
txidList: List[TransactionId],
excludedTransactions: List[TransactionId]
): FutureApplicationResult[Result] = {
val futureOr = for {
rpcTransactions <- getRPCTransactions(txidList).toFutureOr
completeTransactions <- completeValues(rpcTransactions, excludedTransactions).toFutureOr
@ -49,23 +52,27 @@ class TransactionCollectorService @Inject() (
futureOr.toFuture
}
private[services] def completeValues(rpcTransactions: List[RPCTransaction], excludedTransactions: List[TransactionId]): FutureApplicationResult[List[RPCCompleteTransaction]] = {
val neutral: FutureApplicationResult[List[rpc.Transaction[rpc.TransactionVIN.HasValues]]] = Future.successful(Good(List.empty))
val future = rpcTransactions.foldLeft(neutral) { case (acc, tx) =>
val result = for {
previous <- acc.toFutureOr
completeVIN <- getRPCTransactionVIN(tx.vin, excludedTransactions).toFutureOr
completeTX = tx.copy(vin = completeVIN)
} yield completeTX :: previous
private[services] def completeValues(
rpcTransactions: List[RPCTransaction],
excludedTransactions: List[TransactionId]
): FutureApplicationResult[List[RPCCompleteTransaction]] = {
val neutral: FutureApplicationResult[List[rpc.Transaction[rpc.TransactionVIN.HasValues]]] =
Future.successful(Good(List.empty))
val future = rpcTransactions.foldLeft(neutral) {
case (acc, tx) =>
val result = for {
previous <- acc.toFutureOr
completeVIN <- getRPCTransactionVIN(tx.vin, excludedTransactions).toFutureOr
completeTX = tx.copy(vin = completeVIN)
} yield completeTX :: previous
result.toFuture
result.toFuture
}
// keep original ordering
future
.toFutureOr
.map(_.reverse)
.toFuture
future.toFutureOr
.map(_.reverse)
.toFuture
}
/**
@ -74,28 +81,33 @@ class TransactionCollectorService @Inject() (
* - The inputs that aren't present in the database are retrieved from the RPC API.
* - The ones that weren't retrieved are retried sequentially using the RPC API.
*/
private[services] def getRPCTransactionVIN(vinList: List[rpc.TransactionVIN], excludedTransactions: List[TransactionId]): FutureApplicationResult[List[rpc.TransactionVIN.HasValues]] = {
private[services] def getRPCTransactionVIN(
vinList: List[rpc.TransactionVIN],
excludedTransactions: List[TransactionId]
): FutureApplicationResult[List[rpc.TransactionVIN.HasValues]] = {
val filtered = vinList.filterNot(excludedTransactions contains _.txid)
getDBPartialVINList(filtered)
.flatMap(completeRPCVINSequentially)
.flatMap(completeRPCVINSequentially)
}
private[services] def getDBPartialVINList(vinList: List[rpc.TransactionVIN]): Future[List[PartialTransactionVIN]] = {
val futures = for (vin <- vinList) yield {
transactionDataHandler
.getOutput(vin.txid, vin.voutIndex)
.toFutureOr
.map { output =>
vin.withValues(value = output.value, addresses = output.addresses)
}
.toFuture
.map(vin -> _)
.getOutput(vin.txid, vin.voutIndex)
.toFutureOr
.map { output =>
vin.withValues(value = output.value, addresses = output.addresses)
}
.toFuture
.map(vin -> _)
}
Future.sequence(futures)
}
private[services] def completeRPCVINSequentially(partial: List[PartialTransactionVIN]): FutureApplicationResult[List[rpc.TransactionVIN.HasValues]] = {
private[services] def completeRPCVINSequentially(
partial: List[PartialTransactionVIN]
): FutureApplicationResult[List[rpc.TransactionVIN.HasValues]] = {
val neutral: FutureApplicationResult[List[rpc.TransactionVIN.HasValues]] = Future.successful(Good(List.empty))
val result = partial.foldLeft(neutral) {
case (acc, (vin, Bad(_))) =>
@ -114,10 +126,9 @@ class TransactionCollectorService @Inject() (
result.toFuture
}
result
.toFutureOr
.map(_.reverse)
.toFuture
result.toFutureOr
.map(_.reverse)
.toFuture
}
/**
@ -125,12 +136,16 @@ class TransactionCollectorService @Inject() (
* - Try to get them all from the RPC API in parallel
* - Retry the ones that weren't retrieved in parallel by retrieving them sequentially.
*/
private[services] def getRPCTransactions(txidList: List[TransactionId]): FutureApplicationResult[List[RPCTransaction]] = {
private[services] def getRPCTransactions(
txidList: List[TransactionId]
): FutureApplicationResult[List[RPCTransaction]] = {
getPartialRPCTransactions(txidList)
.flatMap(completeRPCTransactionsSequentially)
.flatMap(completeRPCTransactionsSequentially)
}
private[services] def getPartialRPCTransactions(txidList: List[TransactionId]): Future[List[PartialRPCTransaction]] = {
private[services] def getPartialRPCTransactions(
txidList: List[TransactionId]
): Future[List[PartialRPCTransaction]] = {
val futures = for (txid <- txidList) yield {
for {
r <- xsnService.getTransaction(txid)
@ -140,7 +155,9 @@ class TransactionCollectorService @Inject() (
Future.sequence(futures)
}
private[services] def completeRPCTransactionsSequentially(partial: List[PartialRPCTransaction]): FutureApplicationResult[List[RPCTransaction]] = {
private[services] def completeRPCTransactionsSequentially(
partial: List[PartialRPCTransaction]
): FutureApplicationResult[List[RPCTransaction]] = {
val neutral: FutureApplicationResult[List[RPCTransaction]] = Future.successful(Good(List.empty))
val result = partial.foldLeft(neutral) {
case (acc, (txid, Bad(_))) =>
@ -159,23 +176,23 @@ class TransactionCollectorService @Inject() (
result.toFuture
}
result
.toFutureOr
.map(_.reverse)
.toFuture
result.toFutureOr
.map(_.reverse)
.toFuture
}
/**
* Retrieves the values for the given VIN from the RPC API.
*/
private[services] def getRPCTransactionVINWithValues(vin: rpc.TransactionVIN): FutureApplicationResult[rpc.TransactionVIN.HasValues] = {
private[services] def getRPCTransactionVINWithValues(
vin: rpc.TransactionVIN
): FutureApplicationResult[rpc.TransactionVIN.HasValues] = {
val transactionValue = for {
txResult <- xsnService.getTransaction(vin.txid)
} yield txResult.flatMap { tx =>
val maybe = tx
.vout
.find(_.n == vin.voutIndex)
.flatMap(TransactionValue.from)
val maybe = tx.vout
.find(_.n == vin.voutIndex)
.flatMap(TransactionValue.from)
Or.from(maybe, One(TransactionError.OutputNotFound(tx.id, vin.voutIndex)))
}

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

@ -13,11 +13,11 @@ import play.api.libs.json.{JsString, JsValue, Json}
import scala.concurrent.ExecutionContext
class TransactionRPCService @Inject() (
class TransactionRPCService @Inject()(
transactionIdValidator: TransactionIdValidator,
transactionCollectorService: TransactionCollectorService,
xsnService: XSNService)(
implicit ec: ExecutionContext) {
xsnService: XSNService
)(implicit ec: ExecutionContext) {
private val logger = LoggerFactory.getLogger(this.getClass)

27
server/app/com/xsn/explorer/services/TransactionService.scala

@ -15,15 +15,15 @@ import org.slf4j.LoggerFactory
import scala.concurrent.ExecutionContext
class TransactionService @Inject() (
class TransactionService @Inject()(
paginatedQueryValidator: PaginatedQueryValidator,
orderingConditionParser: OrderingConditionParser,
transactionOrderingParser: TransactionOrderingParser,
addressValidator: AddressValidator,
transactionIdValidator: TransactionIdValidator,
blockhashValidator: BlockhashValidator,
transactionFutureDataHandler: TransactionFutureDataHandler)(
implicit ec: ExecutionContext) {
transactionFutureDataHandler: TransactionFutureDataHandler
)(implicit ec: ExecutionContext) {
private val logger = LoggerFactory.getLogger(this.getClass)
@ -32,7 +32,8 @@ class TransactionService @Inject() (
def getTransactions(
addressString: String,
paginatedQuery: PaginatedQuery,
orderingQuery: OrderingQuery): FuturePaginatedResult[TransactionWithValues] = {
orderingQuery: OrderingQuery
): FuturePaginatedResult[TransactionWithValues] = {
val result = for {
address <- addressValidator.validate(addressString).toFutureOr
@ -48,7 +49,8 @@ class TransactionService @Inject() (
addressString: String,
limit: Limit,
lastSeenTxidString: Option[String],
orderingConditionString: String): FutureApplicationResult[WrappedResult[List[LightWalletTransaction]]] = {
orderingConditionString: String
): FutureApplicationResult[WrappedResult[List[LightWalletTransaction]]] = {
val result = for {
address <- addressValidator.validate(addressString).toFutureOr
@ -67,7 +69,11 @@ class TransactionService @Inject() (
result.toFuture
}
def getByBlockhash(blockhashString: String, paginatedQuery: PaginatedQuery, orderingQuery: OrderingQuery): FuturePaginatedResult[TransactionWithValues] = {
def getByBlockhash(
blockhashString: String,
paginatedQuery: PaginatedQuery,
orderingQuery: OrderingQuery
): FuturePaginatedResult[TransactionWithValues] = {
val result = for {
blockhash <- blockhashValidator.validate(blockhashString).toFutureOr
validatedQuery <- paginatedQueryValidator.validate(paginatedQuery, maxTransactionsPerQuery).toFutureOr
@ -78,7 +84,11 @@ class TransactionService @Inject() (
result.toFuture
}
def getByBlockhash(blockhashString: String, limit: Limit, lastSeenTxidString: Option[String]): FutureApplicationResult[WrappedResult[List[TransactionWithValues]]] = {
def getByBlockhash(
blockhashString: String,
limit: Limit,
lastSeenTxidString: Option[String]
): FutureApplicationResult[WrappedResult[List[TransactionWithValues]]] = {
val result = for {
blockhash <- blockhashValidator.validate(blockhashString).toFutureOr
_ <- paginatedQueryValidator.validate(PaginatedQuery(Offset(0), limit), maxTransactionsPerQuery).toFutureOr
@ -93,7 +103,8 @@ class TransactionService @Inject() (
def getLightWalletTransactionsByBlockhash(
blockhashString: String,
limit: Limit,
lastSeenTxidString: Option[String]): FutureApplicationResult[WrappedResult[List[LightWalletTransaction]]] = {
lastSeenTxidString: Option[String]
): FutureApplicationResult[WrappedResult[List[LightWalletTransaction]]] = {
val result = for {
blockhash <- blockhashValidator.validate(blockhashString).toFutureOr

451
server/app/com/xsn/explorer/services/XSNService.scala

@ -63,50 +63,49 @@ trait XSNService {
}
class XSNServiceRPCImpl @Inject() (
ws: WSClient,
rpcConfig: RPCConfig,
explorerConfig: ExplorerConfig)(
implicit ec: ExternalServiceExecutionContext)
extends XSNService {
class XSNServiceRPCImpl @Inject()(ws: WSClient, rpcConfig: RPCConfig, explorerConfig: ExplorerConfig)(
implicit ec: ExternalServiceExecutionContext
) extends XSNService {
private val logger = LoggerFactory.getLogger(this.getClass)
private val server = ws.url(rpcConfig.host.string)
.withAuth(rpcConfig.username.string, rpcConfig.password.string, WSAuthScheme.BASIC)
.withHttpHeaders("Content-Type" -> "text/plain")
private val server = ws
.url(rpcConfig.host.string)
.withAuth(rpcConfig.username.string, rpcConfig.password.string, WSAuthScheme.BASIC)
.withHttpHeaders("Content-Type" -> "text/plain")
override def getTransaction(txid: TransactionId): FutureApplicationResult[rpc.Transaction[rpc.TransactionVIN]] = {
val errorCodeMapper = Map(-5 -> TransactionError.NotFound(txid))
server
.post(s"""{ "jsonrpc": "1.0", "method": "getrawtransaction", "params": ["${txid.string}", 1] }""")
.map { response =>
val maybe = getResult[rpc.Transaction[rpc.TransactionVIN]](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, txid = ${txid.string}, status = ${response.status}, response = ${response.body}")
.post(s"""{ "jsonrpc": "1.0", "method": "getrawtransaction", "params": ["${txid.string}", 1] }""")
.map { response =>
val maybe = getResult[rpc.Transaction[rpc.TransactionVIN]](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(
s"Unexpected response from XSN Server, txid = ${txid.string}, status = ${response.status}, response = ${response.body}"
)
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getRawTransaction(txid: TransactionId): FutureApplicationResult[JsValue] = {
val errorCodeMapper = Map(-5 -> TransactionError.NotFound(txid))
server
.post(s"""{ "jsonrpc": "1.0", "method": "getrawtransaction", "params": ["${txid.string}", 1] }""")
.map { response =>
val maybe = getResult[JsValue](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, txid = ${txid.string}, status = ${response.status}, response = ${response.body}")
.post(s"""{ "jsonrpc": "1.0", "method": "getrawtransaction", "params": ["${txid.string}", 1] }""")
.map { response =>
val maybe = getResult[JsValue](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(
s"Unexpected response from XSN Server, txid = ${txid.string}, status = ${response.status}, response = ${response.body}"
)
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getAddressBalance(address: Address): FutureApplicationResult[rpc.AddressBalance] = {
@ -124,16 +123,17 @@ class XSNServiceRPCImpl @Inject() (
val errorCodeMapper = Map(-5 -> AddressFormatError)
server
.post(body)
.map { response =>
val maybe = getResult[rpc.AddressBalance](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, address = ${address.string}, response = ${response.body}")
.post(body)
.map { response =>
val maybe = getResult[rpc.AddressBalance](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(
s"Unexpected response from XSN Server, status = ${response.status}, address = ${address.string}, response = ${response.body}"
)
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getTransactions(address: Address): FutureApplicationResult[List[TransactionId]] = {
@ -151,16 +151,17 @@ class XSNServiceRPCImpl @Inject() (
val errorCodeMapper = Map(-5 -> AddressFormatError)
server
.post(body)
.map { response =>
val maybe = getResult[List[TransactionId]](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, address = ${address.string}, response = ${response.body}")
.post(body)
.map { response =>
val maybe = getResult[List[TransactionId]](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(
s"Unexpected response from XSN Server, status = ${response.status}, address = ${address.string}, response = ${response.body}"
)
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getBlock(blockhash: Blockhash): FutureApplicationResult[rpc.Block] = {
@ -168,21 +169,22 @@ class XSNServiceRPCImpl @Inject() (
val body = s"""{ "jsonrpc": "1.0", "method": "getblock", "params": ["${blockhash.string}"] }"""
server
.post(body)
.map { response =>
val maybe = getResult[rpc.Block](response, errorCodeMapper)
maybe
.map {
case Good(block) => Good(cleanGenesisBlock(block))
case x => x
}
.getOrElse {
logger.warn(s"Unexpected response from XSN Server, blockhash = ${blockhash.string}, status = ${response.status}, response = ${response.body}")
.post(body)
.map { response =>
val maybe = getResult[rpc.Block](response, errorCodeMapper)
maybe
.map {
case Good(block) => Good(cleanGenesisBlock(block))
case x => x
}
.getOrElse {
logger.warn(
s"Unexpected response from XSN Server, blockhash = ${blockhash.string}, status = ${response.status}, response = ${response.body}"
)
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
}
override def getRawBlock(blockhash: Blockhash): FutureApplicationResult[JsValue] = {
@ -190,16 +192,17 @@ class XSNServiceRPCImpl @Inject() (
val body = s"""{ "jsonrpc": "1.0", "method": "getblock", "params": ["${blockhash.string}"] }"""
server
.post(body)
.map { response =>
val maybe = getResult[JsValue](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, blockhash = ${blockhash.string}, status = ${response.status}, response = ${response.body}")
.post(body)
.map { response =>
val maybe = getResult[JsValue](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(
s"Unexpected response from XSN Server, blockhash = ${blockhash.string}, status = ${response.status}, response = ${response.body}"
)
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getBlockhash(height: Height): FutureApplicationResult[Blockhash] = {
@ -207,16 +210,17 @@ class XSNServiceRPCImpl @Inject() (
val body = s"""{ "jsonrpc": "1.0", "method": "getblockhash", "params": [${height.int}] }"""
server
.post(body)
.map { response =>
val maybe = getResult[Blockhash](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, blockhash = ${height.int}, status = ${response.status}, response = ${response.body}")
.post(body)
.map { response =>
val maybe = getResult[Blockhash](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(
s"Unexpected response from XSN Server, blockhash = ${height.int}, status = ${response.status}, response = ${response.body}"
)
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getLatestBlock(): FutureApplicationResult[rpc.Block] = {
@ -229,28 +233,27 @@ class XSNServiceRPCImpl @Inject() (
|""".stripMargin
server
.post(body)
.flatMap { response =>
val result = for {
blockhash <- getResult[Blockhash](response)
.orElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
None
}
.getOrElse(Bad(XSNUnexpectedResponseError).accumulating)
.toFutureOr
block <- getBlock(blockhash).
map {
case Good(block) => Good(cleanGenesisBlock(block))
case x => x
}
.toFutureOr
} yield block
result.toFuture
}
.post(body)
.flatMap { response =>
val result = for {
blockhash <- getResult[Blockhash](response)
.orElse {
logger.warn(
s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}"
)
None
}
.getOrElse(Bad(XSNUnexpectedResponseError).accumulating)
.toFutureOr
block <- getBlock(blockhash).map {
case Good(block) => Good(cleanGenesisBlock(block))
case x => x
}.toFutureOr
} yield block
result.toFuture
}
}
override def getServerStatistics(): FutureApplicationResult[rpc.ServerStatistics] = {
@ -263,15 +266,15 @@ class XSNServiceRPCImpl @Inject() (
|""".stripMargin
server
.post(body)
.map { response =>
val maybe = getResult[rpc.ServerStatistics](response)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
.post(body)
.map { response =>
val maybe = getResult[rpc.ServerStatistics](response)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getMasternodeCount(): FutureApplicationResult[Int] = {
@ -284,15 +287,15 @@ class XSNServiceRPCImpl @Inject() (
|""".stripMargin
server
.post(body)
.map { response =>
val maybe = getResult[Int](response)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
.post(body)
.map { response =>
val maybe = getResult[Int](response)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getDifficulty(): FutureApplicationResult[BigDecimal] = {
@ -316,7 +319,6 @@ class XSNServiceRPCImpl @Inject() (
}
}
override def getMasternodes(): FutureApplicationResult[List[rpc.Masternode]] = {
val body = s"""
|{
@ -327,20 +329,20 @@ class XSNServiceRPCImpl @Inject() (
|""".stripMargin
server
.post(body)
.map { response =>
val maybe = getResult[Map[String, String]](response)
.map {
case Good(map) => Good(rpc.Masternode.fromMap(map))
case Bad(errors) => Bad(errors)
}
.post(body)
.map { response =>
val maybe = getResult[Map[String, String]](response)
.map {
case Good(map) => Good(rpc.Masternode.fromMap(map))
case Bad(errors) => Bad(errors)
}
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getMasternode(ipAddress: IPAddress): FutureApplicationResult[rpc.Masternode] = {
@ -353,26 +355,26 @@ class XSNServiceRPCImpl @Inject() (
|""".stripMargin
server
.post(body)
.map { response =>
val maybe = getResult[Map[String, String]](response)
.map {
case Good(map) =>
rpc.Masternode
.fromMap(map)
.headOption
.map(Good(_))
.getOrElse(Bad(MasternodeNotFoundError).accumulating)
case Bad(errors) => Bad(errors)
}
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
.post(body)
.map { response =>
val maybe = getResult[Map[String, String]](response)
.map {
case Good(map) =>
rpc.Masternode
.fromMap(map)
.headOption
.map(Good(_))
.getOrElse(Bad(MasternodeNotFoundError).accumulating)
case Bad(errors) => Bad(errors)
}
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue] = {
@ -387,22 +389,23 @@ class XSNServiceRPCImpl @Inject() (
|""".stripMargin
server
.post(body)
.map { response =>
val maybe = getResult[JsValue](response)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
.post(body)
.map { response =>
val maybe = getResult[JsValue](response)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def sendRawTransaction(hex: HexString): FutureApplicationResult[String] = {
val errorCodeMapper = Map(
-26 -> TransactionError.InvalidRawTransaction,
-22 -> TransactionError.InvalidRawTransaction,
-27 -> TransactionError.RawTransactionAlreadyExists)
-27 -> TransactionError.RawTransactionAlreadyExists
)
val body = s"""
|{
@ -413,16 +416,16 @@ class XSNServiceRPCImpl @Inject() (
|""".stripMargin
server
.post(body)
.map { response =>
val maybe = getResult[String](response, errorCodeMapper).map { _.map(_.toString()) }
.post(body)
.map { response =>
val maybe = getResult[String](response, errorCodeMapper).map { _.map(_.toString()) }
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def isTPoSContract(txid: TransactionId): FutureApplicationResult[Boolean] = {
@ -437,17 +440,17 @@ class XSNServiceRPCImpl @Inject() (
)
server
.post(body)
.map { response =>
val maybe = getResult[String](response)
.map { _.map(_ == "Contract is valid") }
.post(body)
.map { response =>
val maybe = getResult[String](response)
.map { _.map(_ == "Contract is valid") }
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
override def estimateSmartFee(confirmationsTarget: Int): FutureApplicationResult[JsValue] = {
@ -460,89 +463,91 @@ class XSNServiceRPCImpl @Inject() (
|""".stripMargin
server
.post(body)
.map { response =>
val maybe = getResult[JsValue](response)
.post(body)
.map { response =>
val maybe = getResult[JsValue](response)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
Bad(XSNUnexpectedResponseError).accumulating
}
Bad(XSNUnexpectedResponseError).accumulating
}
}
}
private def mapError(json: JsValue, errorCodeMapper: Map[Int, ApplicationError]): Option[ApplicationError] = {
val jsonErrorMaybe = (json \ "error")
.asOpt[JsValue]
.filter(_ != JsNull)
.asOpt[JsValue]
.filter(_ != JsNull)
val errorMaybe = jsonErrorMaybe
.flatMap { jsonError =>
// from error code if possible
(jsonError \ "code")
.asOpt[Int]
.flatMap(errorCodeMapper.get)
.orElse {
// from message
(jsonError \ "message")
.asOpt[String]
.filter(_.nonEmpty)
.map(XSNMessageError.apply)
}
}
.flatMap { jsonError =>
// from error code if possible
(jsonError \ "code")
.asOpt[Int]
.flatMap(errorCodeMapper.get)
.orElse {
// from message
(jsonError \ "message")
.asOpt[String]
.filter(_.nonEmpty)
.map(XSNMessageError.apply)
}
}
errorMaybe
.collect {
case XSNMessageError("Work queue depth exceeded") => XSNWorkQueueDepthExceeded
}
.orElse(errorMaybe)
.collect {
case XSNMessageError("Work queue depth exceeded") => XSNWorkQueueDepthExceeded
}
.orElse(errorMaybe)
}
private def getResult[A](
response: WSResponse,
errorCodeMapper: Map[Int, ApplicationError] = Map.empty)(
implicit reads: Reads[A]): Option[ApplicationResult[A]] = {
private def getResult[A](response: WSResponse, errorCodeMapper: Map[Int, ApplicationError] = Map.empty)(
implicit reads: Reads[A]
): Option[ApplicationResult[A]] = {
val maybe = Option(response)
.filter(_.status == 200)
.flatMap { r => Try(r.json).toOption }
.flatMap { json =>
if (logger.isDebugEnabled) {
val x = (json \ "result").validate[A]
x.asEither.left.foreach { errors =>
val msg = errors
.map { case (path, error) => path.toJsonString -> error.toString() }
.mkString(", ")
logger.debug(s"Failed to decode result, errors = ${msg}")
}
.filter(_.status == 200)
.flatMap { r =>
Try(r.json).toOption
}
.flatMap { json =>
if (logger.isDebugEnabled) {
val x = (json \ "result").validate[A]
x.asEither.left.foreach { errors =>
val msg = errors
.map { case (path, error) => path.toJsonString -> error.toString() }
.mkString(", ")
logger.debug(s"Failed to decode result, errors = ${msg}")
}
(json \ "result")
.asOpt[A]
.map { Good(_) }
.orElse {
mapError(json, errorCodeMapper)
.map(Bad.apply)
.map(_.accumulating)
}
}
(json \ "result")
.asOpt[A]
.map { Good(_) }
.orElse {
mapError(json, errorCodeMapper)
.map(Bad.apply)
.map(_.accumulating)
}
}
maybe
.orElse {
// if there is no result nor error, it is probably that the server returned non 200 status
Try(response.json)
.toOption
.flatMap { json => mapError(json, errorCodeMapper) }
.map { e => Bad(e).accumulating }
}
.orElse {
// if still there is no error, the response might not be a json
Try(response.body)
.collect {
case "Work queue depth exceeded" => Bad(XSNWorkQueueDepthExceeded).accumulating
}
.toOption
}
.orElse {
// if there is no result nor error, it is probably that the server returned non 200 status
Try(response.json).toOption
.flatMap { json =>
mapError(json, errorCodeMapper)
}
.map { e =>
Bad(e).accumulating
}
}
.orElse {
// if still there is no error, the response might not be a json
Try(response.body).collect {
case "Work queue depth exceeded" => Bad(XSNWorkQueueDepthExceeded).accumulating
}.toOption
}
}
override val genesisBlockhash: Blockhash = explorerConfig.genesisBlock

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

@ -35,11 +35,10 @@ class BlockLogic {
}
def getTPoSContractDetails(tposContract: Transaction[_]): ApplicationResult[TPoSContract.Details] = {
val maybe = tposContract
.vout
.flatMap(_.scriptPubKey)
.flatMap(_.getTPoSContractDetails)
.headOption
val maybe = tposContract.vout
.flatMap(_.scriptPubKey)
.flatMap(_.getTPoSContractDetails)
.headOption
Or.from(maybe, One(BlockNotFoundError))
}
@ -65,19 +64,18 @@ class BlockLogic {
def getPoSRewards(
coinstakeTx: Transaction[_],
coinstakeAddress: Address,
coinstakeInput: BigDecimal): ApplicationResult[PoSBlockRewards] = {
coinstakeInput: BigDecimal
): ApplicationResult[PoSBlockRewards] = {
// first vout is empty, useless
val coinstakeVOUT = coinstakeTx.vout.drop(1)
if (coinstakeVOUT.size >= 1 && coinstakeVOUT.size <= 3) {
val value = coinstakeVOUT
.filter(_.addresses.getOrElse(List.empty) contains coinstakeAddress)
.map(_.value)
.sum
.filter(_.addresses.getOrElse(List.empty) contains coinstakeAddress)
.map(_.value)
.sum
val coinstakeReward = BlockReward(
coinstakeAddress,
(value - coinstakeInput) max 0)
val coinstakeReward = BlockReward(coinstakeAddress, (value - coinstakeInput) max 0)
val masternodeRewardOUT = coinstakeVOUT.filterNot(_.addresses.getOrElse(List.empty) contains coinstakeAddress)
val masternodeAddressMaybe = masternodeRewardOUT.flatMap(_.addresses).flatten.headOption
@ -97,7 +95,8 @@ class BlockLogic {
def getTPoSRewards(
coinstakeTx: Transaction[_],
contract: TPoSContract.Details,
coinstakeInput: BigDecimal): ApplicationResult[TPoSBlockRewards] = {
coinstakeInput: BigDecimal
): ApplicationResult[TPoSBlockRewards] = {
/**
* While we expected the following
@ -111,22 +110,21 @@ class BlockLogic {
val coinstakeVOUT = coinstakeTx.vout
val ownerValue = coinstakeVOUT
.filter(_.addresses.getOrElse(List.empty) contains contract.owner)
.map(_.value)
.sum
.filter(_.addresses.getOrElse(List.empty) contains contract.owner)
.map(_.value)
.sum
val ownerReward = BlockReward(
contract.owner,
(ownerValue - coinstakeInput) max 0)
val ownerReward = BlockReward(contract.owner, (ownerValue - coinstakeInput) max 0)
// merchant
val merchantValue = coinstakeVOUT.filter(_.addresses.getOrElse(List.empty) 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.addresses.getOrElse(List.empty).contains(contract.owner) ||
out.addresses.getOrElse(List.empty).contains(contract.merchant)
out.addresses.getOrElse(List.empty).contains(contract.merchant)
}
val masternodeAddressMaybe = masternodeRewardOUT.flatMap(_.addresses.getOrElse(List.empty)).headOption
val masternodeRewardMaybe = masternodeAddressMaybe.map { masternodeAddress =>
@ -141,7 +139,7 @@ class BlockLogic {
def isPoS(block: rpc.Block, coinbase: rpc.Transaction[_]): Boolean = {
block.nonce == 0 &&
coinbase.vin.isEmpty &&
coinbase.vout.flatMap(_.addresses.getOrElse(List.empty)).isEmpty
coinbase.vin.isEmpty &&
coinbase.vout.flatMap(_.addresses.getOrElse(List.empty)).isEmpty
}
}

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

@ -18,7 +18,11 @@ class TransactionLogic {
Or.from(maybe, One(error))
}
def getVOUT(vin: TransactionVIN, previousTX: Transaction[_], error: ApplicationError): ApplicationResult[TransactionVOUT] = {
def getVOUT(
vin: TransactionVIN,
previousTX: Transaction[_],
error: ApplicationError
): ApplicationResult[TransactionVOUT] = {
getVOUT(vin.voutIndex, previousTX, error)
}

6
server/app/com/xsn/explorer/services/validators/package.scala

@ -13,7 +13,9 @@ package object validators {
def validate[T](maybe: Option[String], validator: String => ApplicationResult[T]): ApplicationResult[Option[T]] = {
maybe
.map { string => validator(string).map(Option.apply) }
.getOrElse(Good(None))
.map { string =>
validator(string).map(Option.apply)
}
.getOrElse(Good(None))
}
}

29
server/app/com/xsn/explorer/tasks/PollerSynchronizerTask.scala

@ -12,12 +12,12 @@ import org.slf4j.LoggerFactory
import scala.concurrent.ExecutionContext
import scala.util.control.NonFatal
class PollerSynchronizerTask @Inject() (
class PollerSynchronizerTask @Inject()(
config: LedgerSynchronizerConfig,
actorSystem: ActorSystem,
xsnService: XSNService,
ledgerSynchronizerService: LedgerSynchronizerService)(
implicit ec: ExecutionContext) {
ledgerSynchronizerService: LedgerSynchronizerService
)(implicit ec: ExecutionContext) {
private val logger = LoggerFactory.getLogger(this.getClass)
@ -40,17 +40,16 @@ class PollerSynchronizerTask @Inject() (
_ <- ledgerSynchronizerService.synchronize(block.hash).toFutureOr
} yield ()
result
.toFuture
.map {
case Bad(errors) => logger.error(s"Failed to sync latest block, errors = $errors")
case _ => ()
}
.recover {
case NonFatal(ex) => logger.error("Failed to sync latest block", ex)
}
.foreach { _ =>
actorSystem.scheduler.scheduleOnce(config.interval) { run() }
}
result.toFuture
.map {
case Bad(errors) => logger.error(s"Failed to sync latest block, errors = $errors")
case _ => ()
}
.recover {
case NonFatal(ex) => logger.error("Failed to sync latest block", ex)
}
.foreach { _ =>
actorSystem.scheduler.scheduleOnce(config.interval) { run() }
}
}
}

3
server/app/com/xsn/explorer/util/Extensions.scala

@ -11,6 +11,7 @@ object Extensions {
private val SatoshiScale = 100000000L
implicit class BigDecimalExt(val inner: BigDecimal) extends AnyVal {
def fromSatoshis: BigDecimal = {
inner / SatoshiScale
}
@ -21,6 +22,7 @@ object Extensions {
}
implicit class ListOptionExt[+A](val inner: List[Option[A]]) extends AnyVal {
def everything: Option[List[A]] = {
if (inner.forall(_.isDefined)) {
Some(inner.flatten)
@ -31,6 +33,7 @@ object Extensions {
}
implicit class FutureOrExt[+A](val inner: FutureOr[A]) {
def recoverFrom[B >: A](error: ApplicationError)(f: => B)(implicit ec: ExecutionContext): FutureOr[B] = {
val future = inner.toFuture.map {
case Good(result) => Good(result)

42
server/app/controllers/AddressesController.scala

@ -10,12 +10,12 @@ import controllers.common.{Codecs, MyJsonController, MyJsonControllerComponents}
import javax.inject.Inject
import play.api.libs.json._
class AddressesController @Inject() (
class AddressesController @Inject()(
addressService: AddressService,
transactionService: TransactionService,
tposContractService: TPoSContractService,
cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
cc: MyJsonControllerComponents
) extends MyJsonController(cc) {
import AddressesController._
import Codecs._
@ -30,29 +30,19 @@ class AddressesController @Inject() (
transactionService.getTransactions(address, paginatedQuery, OrderingQuery(ordering))
}
def getLightWalletTransactions(
address: String,
limit: Int,
lastSeenTxid: Option[String],
orderingCondition: String) = public { _ =>
transactionService.getLightWalletTransactions(
address,
Limit(limit),
lastSeenTxid,
orderingCondition)
}
def getLightWalletTransactions(address: String, limit: Int, lastSeenTxid: Option[String], orderingCondition: String) =
public { _ =>
transactionService.getLightWalletTransactions(address, Limit(limit), lastSeenTxid, orderingCondition)
}
/**
* 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 address = obj.addresses.headOption
.map(_.string)
.map(JsString.apply)
.getOrElse(JsNull)
val values = Map(
"address" -> address, // Keep compatibility with the legacy API
@ -79,12 +69,10 @@ object AddressesController {
implicit val inputWrites: Writes[LightWalletTransaction.Input] = Json.writes[LightWalletTransaction.Input]
implicit val outputWrites: Writes[LightWalletTransaction.Output] = (obj: LightWalletTransaction.Output) => {
val address = obj
.addresses
.headOption
.map(_.string)
.map(JsString.apply)
.getOrElse(JsNull)
val address = obj.addresses.headOption
.map(_.string)
.map(JsString.apply)
.getOrElse(JsNull)
Json.obj(
"index" -> obj.index,

6
server/app/controllers/BalancesController.scala

@ -6,9 +6,7 @@ import com.xsn.explorer.services.BalanceService
import controllers.common.{Codecs, MyJsonController, MyJsonControllerComponents}
import javax.inject.Inject
class BalancesController @Inject() (
balanceService: BalanceService,
cc: MyJsonControllerComponents)
class BalancesController @Inject()(balanceService: BalanceService, cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
import Codecs._
@ -23,4 +21,4 @@ class BalancesController @Inject() (
def getHighest(limit: Int, lastSeenAddress: Option[String]) = public { _ =>
balanceService.getHighest(Limit(limit), lastSeenAddress)
}
}
}

31
server/app/controllers/BlocksController.scala

@ -12,11 +12,11 @@ import play.api.libs.json.{Json, Writes}
import scala.util.Try
class BlocksController @Inject() (
class BlocksController @Inject()(
blockService: BlockService,
transactionService: TransactionService,
cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
cc: MyJsonControllerComponents
) extends MyJsonController(cc) {
import BlocksController._
import Codecs._
@ -27,17 +27,18 @@ class BlocksController @Inject() (
def getBlockHeaders(lastSeenHash: Option[String], limit: Int, orderingCondition: String) = public { _ =>
blockService
.getBlockHeaders(Limit(limit), lastSeenHash, orderingCondition)
.toFutureOr
.map { case (value, cacheable) =>
.getBlockHeaders(Limit(limit), lastSeenHash, orderingCondition)
.toFutureOr
.map {
case (value, cacheable) =>
val response = Ok(Json.toJson(value))
if (cacheable) {
response.withHeaders("Cache-Control" -> "public, max-age=31536000")
} else {
response.withHeaders("Cache-Control" -> "no-store")
}
}
.toFuture
}
.toFuture
}
/**
@ -47,16 +48,16 @@ class BlocksController @Inject() (
*/
def getDetails(query: String) = public { _ =>
Try(query.toInt)
.map(Height.apply)
.map(blockService.getDetails)
.getOrElse(blockService.getDetails(query))
.map(Height.apply)
.map(blockService.getDetails)
.getOrElse(blockService.getDetails(query))
}
def getRawBlock(query: String) = public { _ =>
Try(query.toInt)
.map(Height.apply)
.map(blockService.getRawBlock)
.getOrElse(blockService.getRawBlock(query))
.map(Height.apply)
.map(blockService.getRawBlock)
.getOrElse(blockService.getRawBlock(query))
}
def getTransactions(blockhash: String, offset: Int, limit: Int, orderBy: String) = public { _ =>
@ -104,4 +105,4 @@ object BlocksController {
"outputs" -> Json.toJson(obj.outputs)
)
}
}
}

4
server/app/controllers/HealthController.scala

@ -4,9 +4,7 @@ import javax.inject.Inject
import controllers.common.{MyJsonController, MyJsonControllerComponents}
class HealthController @Inject() (
cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
class HealthController @Inject()(cc: MyJsonControllerComponents) extends MyJsonController(cc) {
def check() = Action {
Ok

4
server/app/controllers/MaintenanceController.scala

@ -7,9 +7,7 @@ import play.api.libs.json.JsObject
import scala.concurrent.Future
class MaintenanceController @Inject() (
components: MyJsonControllerComponents)
extends MyJsonController(components) {
class MaintenanceController @Inject()(components: MyJsonControllerComponents) extends MyJsonController(components) {
def run(query: String) = public { _ =>
Future.successful(Good(JsObject.empty))

4
server/app/controllers/MasternodesController.scala

@ -6,9 +6,7 @@ import com.xsn.explorer.services.MasternodeService
import controllers.common.{Codecs, MyJsonController, MyJsonControllerComponents}
import javax.inject.Inject
class MasternodesController @Inject() (
masternodeService: MasternodeService,
cc: MyJsonControllerComponents)
class MasternodesController @Inject()(masternodeService: MasternodeService, cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
import Codecs._

4
server/app/controllers/StatisticsController.scala

@ -4,9 +4,7 @@ import com.xsn.explorer.services.StatisticsService
import controllers.common.{MyJsonController, MyJsonControllerComponents}
import javax.inject.Inject
class StatisticsController @Inject() (
statisticsService: StatisticsService,
cc: MyJsonControllerComponents)
class StatisticsController @Inject()(statisticsService: StatisticsService, cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
def getStatus() = public { _ =>

4
server/app/controllers/TransactionsController.scala

@ -5,9 +5,7 @@ import com.xsn.explorer.services.TransactionRPCService
import controllers.common.{MyJsonController, MyJsonControllerComponents}
import javax.inject.Inject
class TransactionsController @Inject() (
transactionRPCService: TransactionRPCService,
cc: MyJsonControllerComponents)
class TransactionsController @Inject()(transactionRPCService: TransactionRPCService, cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
import Context._

24
server/app/controllers/common/Codecs.scala

@ -7,12 +7,12 @@ import play.api.libs.json._
object Codecs {
implicit def everyReads[T](implicit readsT: Reads[T]): Reads[Every[T]] = Reads[Every[T]] { json =>
json
.validate[List[T]]
.flatMap { list =>
Every.from(list)
Every
.from(list)
.map(JsSuccess.apply(_))
.getOrElse {
JsError.apply("A non-empty list is expected")
@ -20,13 +20,13 @@ object Codecs {
}
}
implicit def paginatedResultWrites[T](implicit writesT: Writes[T]): Writes[PaginatedResult[T]] = OWrites[PaginatedResult[T]] { result =>
Json.obj(
"offset" -> result.offset,
"limit" -> result.limit,
"total" -> result.total,
"data" -> result.data
)
}
}
implicit def paginatedResultWrites[T](implicit writesT: Writes[T]): Writes[PaginatedResult[T]] =
OWrites[PaginatedResult[T]] { result =>
Json.obj(
"offset" -> result.offset,
"limit" -> result.limit,
"total" -> result.total,
"data" -> result.data
)
}
}

6
server/app/controllers/common/MyJsonControllerComponents.scala

@ -6,10 +6,10 @@ import play.api.mvc.MessagesControllerComponents
import scala.concurrent.ExecutionContext
class MyJsonControllerComponents @Inject() (
class MyJsonControllerComponents @Inject()(
override val messagesControllerComponents: MessagesControllerComponents,
override val executionContext: ExecutionContext,
override val publicErrorRenderer: PublicErrorRenderer,
override val i18nService: I18nPlayService,
override val authenticatorService: MyAuthenticatorService)
extends JsonControllerComponents[Nothing]
override val authenticatorService: MyAuthenticatorService
) extends JsonControllerComponents[Nothing]

Loading…
Cancel
Save