From a01248e4f70784bbdc1cc97086f18f63ae02d8e7 Mon Sep 17 00:00:00 2001 From: monyedavid Date: Sat, 14 May 2022 05:02:04 +0100 Subject: [PATCH 1/7] Fixes incorrect unconfirmed balance: - BuildUnconfirmedBalance utility - BuildUnconfirmedBalanceSpec - MempoolSpec --- .../http/api/v1/routes/AddressesRoutes.scala | 2 +- .../http/api/v1/services/Mempool.scala | 34 +++++- .../v1/utils/BuildUnconfirmedBalance.scala | 88 ++++++++++++++ .../TransactionSimulator/Simulator.scala | 96 ++++++++++++++++ .../TransactionSimulator/UInputGen.scala | 45 ++++++++ .../TransactionSimulator/UOutputGen.scala | 29 +++++ .../TransactionSimulator/constants.scala | 80 +++++++++++++ .../explorer/v1/services/MempoolSpec.scala | 45 ++++++++ .../utils/BuildUnconfirmedBalanceSpec.scala | 108 ++++++++++++++++++ 9 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/utils/BuildUnconfirmedBalance.scala create mode 100644 modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/Simulator.scala create mode 100644 modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/UInputGen.scala create mode 100644 modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/UOutputGen.scala create mode 100644 modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/constants.scala create mode 100644 modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/utils/BuildUnconfirmedBalanceSpec.scala diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala index 79e9102bf..787fb0405 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala @@ -38,7 +38,7 @@ final class AddressesRoutes[F[_]: Concurrent: ContextShift: Timer: AdaptThrowabl private def getTotalBalanceR = interpreter.toRoutes(defs.getTotalBalanceDef) { addr => - addresses.totalBalanceOf(addr).adaptThrowable.value + mempool.getTotalBalance(addr, addresses.confirmedBalanceOf).adaptThrowable.value } } diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Mempool.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Mempool.scala index 6d1c0b1e1..144128bb8 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Mempool.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Mempool.scala @@ -16,7 +16,8 @@ import org.ergoplatform.explorer.db.repositories.bundles.UtxRepoBundle import org.ergoplatform.explorer.http.api.models.{HeightRange, Items, Paging} import org.ergoplatform.explorer.http.api.streaming.CompileStream import org.ergoplatform.explorer.http.api.v0.models.TxIdResponse -import org.ergoplatform.explorer.http.api.v1.models.{OutputInfo, UOutputInfo, UTransactionInfo} +import org.ergoplatform.explorer.http.api.v1.models.{Balance, OutputInfo, TotalBalance, UOutputInfo, UTransactionInfo} +import org.ergoplatform.explorer.http.api.v1.utils.BuildUnconfirmedBalance import org.ergoplatform.explorer.protocol.TxValidation import org.ergoplatform.explorer.protocol.TxValidation.PartialSemanticValidation import org.ergoplatform.explorer.protocol.sigma.addressToErgoTreeNewtype @@ -41,6 +42,8 @@ trait Mempool[F[_]] { paging: Paging ): F[Items[UTransactionInfo]] + def getTotalBalance(address: Address, confirmedBalanceOf: (Address, Int) => F[Balance]): F[TotalBalance] + def hasUnconfirmedBalance(ergoTree: ErgoTree): F[Boolean] def submit(tx: ErgoLikeTransaction): F[TxIdResponse] @@ -75,6 +78,35 @@ object Mempool { .map(_ > 0) .thrushK(trans.xa) + private def getUnconfirmedBalanceByAddress( + address: Address, + confirmedBalance: Balance + ): F[Balance] = { + val ergoTree = addressToErgoTreeNewtype(address) + txs + .countByErgoTree(ergoTree.value) + .flatMap { total => + txs + .streamRelatedToErgoTree(ergoTree, 0, Int.MaxValue) + .chunkN(settings.chunkSize) + .through(mkTransaction) + .to[List] + .map { poolItems => + total match { + case 0 => Balance.empty + case _ => BuildUnconfirmedBalance(poolItems, confirmedBalance, ergoTree, ergoTree.value) + } + } + } + .thrushK(trans.xa) + } + + def getTotalBalance(address: Address, confirmedBalanceOf: (Address, Int) => F[Balance]): F[TotalBalance] = + for { + confirmed <- confirmedBalanceOf(address, 0) + unconfirmed <- getUnconfirmedBalanceByAddress(address, confirmed) + } yield TotalBalance(confirmed, unconfirmed) + def getByErgoTree(ergoTree: ErgoTree, paging: Paging): F[Items[UTransactionInfo]] = txs .countByErgoTree(ergoTree.value) diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/utils/BuildUnconfirmedBalance.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/utils/BuildUnconfirmedBalance.scala new file mode 100644 index 000000000..e3c421e03 --- /dev/null +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/utils/BuildUnconfirmedBalance.scala @@ -0,0 +1,88 @@ +package org.ergoplatform.explorer.http.api.v1.utils + +import org.ergoplatform.explorer.http.api.models.AssetInstanceInfo +import org.ergoplatform.explorer.http.api.v1.models._ +import org.ergoplatform.explorer.{ErgoTree, HexString, TokenId} + +object BuildUnconfirmedBalance { + + /** Build unconfirmed balance considering MemPool Data + * by reducing a `List[`[[org.ergoplatform.explorer.http.api.v1.models.UTransactionInfo]]`]` + * into a [[org.ergoplatform.explorer.http.api.v1.models.Balance]] + * + * Unconfirmed Balance arithmetic is completed in three steps: + *
  • determine if a [[org.ergoplatform.explorer.http.api.v1.models.UTransactionInfo]] is a credit or debit by + * matching its [[org.ergoplatform.explorer.http.api.v1.models.UInputInfo]] to wallets
  • + *
  • reducing similar [[org.ergoplatform.explorer.http.api.v1.models.UOutputInfo]] + * sums (credits/debits) into a single value: `debitSum/creditSum`
  • + *
  • subtracting or adding `debitSum/creditSum` into iterations current balance
  • + * + * @param items unsigned transactions from the MemPool + * @param confirmedBalance last signed & unspent balance + * @param ergoTree for transaction type identification + * @param hexString for NSum evaluation + * @return + */ + def apply( + items: List[UTransactionInfo], + confirmedBalance: Balance, + ergoTree: ErgoTree, + hexString: HexString + ): Balance = + items.foldLeft(confirmedBalance) { case (balance, transactionInfo) => + transactionInfo.inputs.head.ergoTree match { + case ieT if ieT == ergoTree => + val debitSum = transactionInfo.outputs.foldLeft(0L) { case (sum, outputInfo) => + if (outputInfo.ergoTree != hexString) sum + outputInfo.value + else sum + } + + val debitSumTokenGroups = transactionInfo.outputs + .foldLeft(Map[TokenId, AssetInstanceInfo]()) { case (groupedTokens, outputInfo) => + if (outputInfo.ergoTree != hexString) + outputInfo.assets.foldLeft(groupedTokens) { case (gT, asset) => + val gTAsset = gT.getOrElse(asset.tokenId, asset.copy(amount = 0L)) + gT + (asset.tokenId -> gTAsset.copy(amount = gTAsset.amount + asset.amount)) + } + else groupedTokens + } + + val newTokensBalance = balance.tokens.map { token => + debitSumTokenGroups.get(token.tokenId).map { assetInfo => + token.copy(amount = token.amount - assetInfo.amount) + } match { + case Some(value) => value + case None => token + } + } + + Balance(balance.nanoErgs - debitSum, newTokensBalance) + case _ => + val creditSum = transactionInfo.outputs.foldLeft(0L) { case (sum, outputInfo) => + if (outputInfo.ergoTree == hexString) sum + outputInfo.value + else sum + } + + val creditSumTokenGroups = transactionInfo.outputs + .foldLeft(Map[TokenId, AssetInstanceInfo]()) { case (groupedTokens, outputInfo) => + if (outputInfo.ergoTree == hexString) + outputInfo.assets.foldLeft(groupedTokens) { case (gT, asset) => + val gTAsset = gT.getOrElse(asset.tokenId, asset.copy(amount = 0L)) + gT + (asset.tokenId -> gTAsset.copy(amount = gTAsset.amount + asset.amount)) + } + else groupedTokens + } + + val newTokensBalance = balance.tokens.map { token => + creditSumTokenGroups.get(token.tokenId).map { assetInfo => + token.copy(amount = token.amount + assetInfo.amount) + } match { + case Some(value) => value + case None => token + } + } + + Balance(balance.nanoErgs + creditSum, newTokensBalance) + } + } +} diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/Simulator.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/Simulator.scala new file mode 100644 index 000000000..fc41caf5f --- /dev/null +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/Simulator.scala @@ -0,0 +1,96 @@ +package org.ergoplatform.explorer.utils.TransactionSimulator + +import org.ergoplatform.explorer._ +import org.ergoplatform.explorer.http.api.models.AssetInstanceInfo +import org.ergoplatform.explorer.http.api.v1.models.{UOutputInfo, UTransactionInfo} +import org.ergoplatform.explorer.utils.TransactionSimulator.constants._ + +import java.util.UUID.randomUUID +import scala.util.Try + +/** Simulate Ergo Transfer between two wallets:
    + * - Generate Input & OutputBoxes + */ +object Simulator { + + def apply( + sender: WalletT, + receiver: WalletT, + amountToSend: Long, + tokensToSend: Option[Map[TokenId, Long]] + ): Try[SimulatedTransaction] = Try { + + if (tokensToSend.isDefined) + require( + sender.tokens.toList + .map(_._1) == tokensToSend.map(_.toList.map(_._1)).getOrElse(List()), + "tokens being sent must be available in senders wallet" + ) + + require(sender.balance >= (amountToSend + TransactionFee), "ergo - increase sending wallet balance") + + val senderAddress = Address.fromString[Try](SenderAddressString).get + val receiverAddress = Address.fromString[Try](ReceiverAddressString).get + val feeAddress = Address.fromString[Try](FeeAddressString).get + + val sInputBoxes = List(UInputGen(sender, SenderErgo, senderAddress, TokenStoreT)) + val rInputBoxes = List(UInputGen(receiver, ReceiverErgo, receiverAddress, TokenStoreT)) + + val tFeeOutput = UOutputGen( + TransactionFee, + FeeErgo.value, + feeAddress, + List() + ) + + // For sender: debit output box + fee + val sentAssets: List[AssetInstanceInfo] = + tokensToSend + .map(_.toList.map { case (tId, amountToSend) => + val tokenInfo = TokenStoreT.getOrElse( + tId, + AssetInstanceInfoT(TokenId(SigUSD1), 2, Some("SigUSD1"), Some(2), Some(TokenType("EIP-004"))) + ) + AssetInstanceInfo( + tokenInfo.tokenId, + tokenInfo.index, + amountToSend, + tokenInfo.name, + tokenInfo.decimals, + tokenInfo.`type` + ) + + }) + .getOrElse(List[AssetInstanceInfo]()) + + val outputBoxes: List[UOutputInfo] = + UOutputGen(amountToSend, ReceiverErgo.value, receiverAddress, sentAssets) :: List() + + val sTransactionInfoT = TransactionInfoT( + SenderErgo, + UTransactionInfo( + TxId(randomUUID().toString), + System.currentTimeMillis(), + sInputBoxes, + List(), + tFeeOutput :: outputBoxes, + 3 + ) :: List() + ) + + val rTransactionInfo = TransactionInfoT( + ReceiverErgo, + UTransactionInfo( + TxId(randomUUID().toString), + System.currentTimeMillis(), + sInputBoxes, + List(), + outputBoxes, + 3 + ) :: List() + ) + + SimulatedTransaction(sTransactionInfoT, rTransactionInfo) + } + +} diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/UInputGen.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/UInputGen.scala new file mode 100644 index 000000000..2d02b4021 --- /dev/null +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/UInputGen.scala @@ -0,0 +1,45 @@ +package org.ergoplatform.explorer.utils.TransactionSimulator + +import io.circe.Json +import org.ergoplatform.explorer._ +import org.ergoplatform.explorer.http.api.models.AssetInstanceInfo +import org.ergoplatform.explorer.http.api.v1.models.UInputInfo +import org.ergoplatform.explorer.utils.TransactionSimulator.constants._ + +import java.util.UUID.randomUUID + +object UInputGen { + + def apply( + wallet: WalletT, + ergoTree: ErgoTree, + address: Address, + availableTestTokens: Map[TokenId, AssetInstanceInfoT] + ): UInputInfo = UInputInfo( + BoxId(randomUUID().toString), + wallet.balance, + 0, + None, + None, + TxId(randomUUID().toString), + 0, + ergoTree, + address, + wallet.tokens.toList.map { case (tId, value) => + val tokenInfo = availableTestTokens.getOrElse( + tId, + AssetInstanceInfoT(TokenId(SigUSD1), 2, Some("SigUSD1"), Some(2), Some(TokenType("EIP-004"))) + ) + + AssetInstanceInfo( + tokenInfo.tokenId, + tokenInfo.index, + value, + tokenInfo.name, + tokenInfo.decimals, + tokenInfo.`type` + ) + }, + Json.Null + ) +} diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/UOutputGen.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/UOutputGen.scala new file mode 100644 index 000000000..514371fac --- /dev/null +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/UOutputGen.scala @@ -0,0 +1,29 @@ +package org.ergoplatform.explorer.utils.TransactionSimulator + +import io.circe.Json +import org.ergoplatform.explorer.http.api.models.AssetInstanceInfo +import org.ergoplatform.explorer.http.api.v1.models.UOutputInfo +import org.ergoplatform.explorer.{Address, BoxId, HexString, TxId} + +import java.util.UUID.randomUUID + +object UOutputGen { + + def apply( + value: Long, + ergoTree: HexString, + address: Address, + assets: List[AssetInstanceInfo] + ): UOutputInfo = UOutputInfo( + boxId = BoxId(randomUUID().toString), + transactionId = TxId(randomUUID().toString), + value = value, + index = 0, + creationHeight = 1, + ergoTree = ergoTree, + address = address, + assets = assets, + additionalRegisters = Json.Null, + spentTransactionId = None + ) +} diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/constants.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/constants.scala new file mode 100644 index 000000000..bd99ea2b9 --- /dev/null +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/utils/TransactionSimulator/constants.scala @@ -0,0 +1,80 @@ +package org.ergoplatform.explorer.utils.TransactionSimulator + +import org.ergoplatform.explorer.http.api.v1.models.{Balance, TokenAmount, UTransactionInfo} +import org.ergoplatform.explorer.{ErgoTree, HexString, TokenId, TokenType} + +object constants { + val TransactionFee = 1000000L // 0.001Erg + val SigUSD1: HexString = + HexString.fromStringUnsafe("514083a170fc734071c07748ff444940606654317bd516865120ed702952ab1b") + + val SigUSD2: HexString = + HexString.fromStringUnsafe("be67102886d12d7302abea69838363eda5103709afdc66e935e774aedfad6e5d") + + val SenderAddressString = "3WzSdM7NrjDJswpu2ThfhWvVM1mKJhgnGNieWYcGVsYp3AoirgR5" + val ReceiverAddressString = "3Wx99DApJTpUTPZDhYEerbqWfa9MvuuVJehAFVeepnZMzAN3dfYW" + val RandomAddressString = "9iMUmLz4GZ7HfccBFPNxjQhN4Ysx1M9fJkZZH4keDVRXsejJd6n" + + val SenderHexString = "0008cd03f9070bfe907cd5f9a5e06f907ebe8b002242f2f1ffb420cf2afab792253390bc" + val ReceiverHexString = "0008cd02c9eee86f4ac1b55b655d9275cd71e5ab2e308fb847f8be19f47d59a0d369a91f" + val RandomHexString = "" + + val FeeAddressString = + "2iHkR7CWvD1R4j1yXpSfpU37c6bRdeEaj3kcr5PsLfLRcPb3CCQwzQfkexKouu4WDTc4ybFSRWLsxqWS4U7sgbBkGgGZzXjbf5vxPXbx88eFwFzScV77g9vrUoXnPCqq1WgRvuqVMMuuyAoTWK7LSo" + + val FeeHexString = + "1005040004000e36100204900108cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304" + + val SenderErgo: ErgoTree = ErgoTree( + HexString.fromStringUnsafe(SenderHexString) + ) + + val ReceiverErgo: ErgoTree = ErgoTree( + HexString.fromStringUnsafe(ReceiverHexString) + ) + + val FeeErgo: ErgoTree = ErgoTree(HexString.fromStringUnsafe(FeeHexString)) + + case class WalletT(balance: Long, tokens: TestTokens) + + object WalletT { + + def toBalance(walletT: WalletT): Balance = { + val tokens = walletT.tokens.toList.map { case (tId, value) => + val tokenInfo = TokenStoreT.getOrElse( + tId, + AssetInstanceInfoT(TokenId(SigUSD1), 2, Some("SigUSD1"), Some(2), Some(TokenType("EIP-004"))) + ) + + TokenAmount( + tokenInfo.tokenId, + value, + 2, + tokenInfo.name, + tokenInfo.`type` + ) + } + Balance(walletT.balance, tokens) + } + } + + case class TransactionInfoT(ergoTree: ErgoTree, items: List[UTransactionInfo]) + case class SimulatedTransaction(sender: TransactionInfoT, receiver: TransactionInfoT) + + case class AssetInstanceInfoT( + tokenId: TokenId, + index: Int, + name: Option[String], + decimals: Option[Int], + `type`: Option[TokenType] + ) + + type TestTokens = Map[TokenId, Long] + + val TokenStoreT: Map[TokenId, AssetInstanceInfoT] = + Map( + TokenId(SigUSD1) -> AssetInstanceInfoT(TokenId(SigUSD1), 0, Some("SigUSD1"), Some(2), Some(TokenType("EIP-004"))), + TokenId(SigUSD2) -> AssetInstanceInfoT(TokenId(SigUSD2), 1, Some("SigUSD2"), Some(2), Some(TokenType("EIP-004"))) + ) + +} diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala index b3e751c81..58cf537ea 100644 --- a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala @@ -3,17 +3,23 @@ package org.ergoplatform.explorer.v1.services import cats.{Monad, Parallel} import cats.effect.{Concurrent, ContextShift, IO} import dev.profunktor.redis4cats.algebra.RedisCommands +import doobie.free.connection.ConnectionIO import eu.timepit.refined.api.Refined import eu.timepit.refined.auto._ import eu.timepit.refined.string.ValidByte import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.explorer.Address import org.ergoplatform.explorer.cache.Redis +import org.ergoplatform.explorer.commonGenerators.forSingleInstance import org.ergoplatform.explorer.db.algebra.LiftConnectionIO +import org.ergoplatform.explorer.testSyntax.runConnectionIO._ import org.ergoplatform.explorer.db.{repositories, RealDbTest, Trans} import org.ergoplatform.explorer.http.api.streaming.CompileStream import org.ergoplatform.explorer.http.api.v1.services.{Addresses, Mempool} +import org.ergoplatform.explorer.protocol.sigma import org.ergoplatform.explorer.settings.{RedisSettings, ServiceSettings, UtxCacheSettings} import org.ergoplatform.explorer.testContainers.RedisTest +import org.ergoplatform.explorer.v1.services.constants.SenderAddressString import org.scalatest.{PrivateMethodTester, TryValues} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should @@ -41,6 +47,45 @@ class MS_A extends MempoolSpec { "Mempool Service" should "" in {} + class MS_D extends MempoolSpec { + val networkPrefix: String Refined ValidByte = "16" // strictly run test-suite with testnet network prefix + implicit val addressEncoder: ErgoAddressEncoder = + ErgoAddressEncoder(networkPrefix.value.toByte) + + "Mempool Service" should "get Total balance considering mempool transactions" in { + import tofu.fs2Instances._ + implicit val trans: Trans[ConnectionIO, IO] = Trans.fromDoobie(xa) + val address1S = SenderAddressString + val address1T = Address.fromString[Try](address1S) + lazy val address1Tree = sigma.addressToErgoTreeHex(address1T.get) + withResources[IO](container.mappedPort(6379)) + .use { case (settings, utxCache, redis) => + withServices[IO, ConnectionIO](settings, utxCache, redis) { (mem, addr) => + address1T.isSuccess should be(true) + withLiveRepos[ConnectionIO] { (hRepo, txRepo, outRepo, _, _, _) => + forSingleInstance( + balanceOfAddressGen( + mainChain = true, + address1T.get, + address1Tree, + (100.toNanoErgo, 1) :: (200.toNanoErgo, 1) :: (300.toNanoErgo, 1) :: List[(Long, Int)]() + ) + ) { infoTupleList => + infoTupleList.foreach { case (header, out, tx) => + hRepo.insert(header).runWithIO() + outRepo.insert(out).runWithIO() + txRepo.insert(tx).runWithIO() + } + val tb = mem.getTotalBalance(address1T.get, addr.confirmedBalanceOf).unsafeRunSync() + tb.confirmed.nanoErgs should be(600.toNanoErgo) + } + } + } + } + .unsafeRunSync() + } + } + } object MempoolSpec { diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/utils/BuildUnconfirmedBalanceSpec.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/utils/BuildUnconfirmedBalanceSpec.scala new file mode 100644 index 000000000..4bdfe8faf --- /dev/null +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/utils/BuildUnconfirmedBalanceSpec.scala @@ -0,0 +1,108 @@ +package org.ergoplatform.explorer.v1.utils + +import org.ergoplatform.explorer.TokenId +import org.ergoplatform.explorer.http.api.v1.utils.BuildUnconfirmedBalance +import org.ergoplatform.explorer.utils.TransactionSimulator.Simulator +import org.ergoplatform.explorer.utils.TransactionSimulator.constants.{SigUSD1, SigUSD2, TransactionFee, WalletT} +import org.scalatest._ +import org.scalatest.flatspec._ +import org.scalatest.matchers._ +import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks + +import scala.util.Success + +class BuildUnconfirmedBalanceSpec extends AnyFlatSpec with should.Matchers with ScalaCheckDrivenPropertyChecks { + + // 100Erg 100Token + val senderWallet: WalletT = WalletT( + 100000000000L, + Map( + TokenId(SigUSD1) -> 10000L, + TokenId(SigUSD2) -> 10000L + ) + ) + + // 10Erg 10 + val receivingWallet: WalletT = WalletT( + 100000000000L, + Map( + TokenId(SigUSD1) -> 10000L, + TokenId(SigUSD2) -> 10000L + ) + ) + + "BuildUnconfirmedBalance protocol" should "calculate sending wallet nanoErg unconfirmed balance" in { + val toSend = 5000000000L // 5Erg + val res = Simulator(senderWallet, receivingWallet, toSend, None) + res shouldBe a[Success[_]] + + val txInfo = res.get.sender + + val unconfirmedBalance = + BuildUnconfirmedBalance(txInfo.items, WalletT.toBalance(senderWallet), txInfo.ergoTree, txInfo.ergoTree.value) + + unconfirmedBalance.nanoErgs shouldBe (senderWallet.balance - TransactionFee - toSend) + } + + it should "calculate receiving wallet nanoErg unconfirmed balance" in { + val toSend = 8000000000L // 8Erg + val res = Simulator(senderWallet, receivingWallet, toSend, None) + res shouldBe a[Success[_]] + + val txInfo = res.get.receiver + + val unconfirmedBalance = + BuildUnconfirmedBalance(txInfo.items, WalletT.toBalance(receivingWallet), txInfo.ergoTree, txInfo.ergoTree.value) + + unconfirmedBalance.nanoErgs shouldBe (receivingWallet.balance + toSend) + } + + it should "calculate sending wallet assets unconfirmed balance" in { + val tokenSentAmount = 100L + val res = Simulator( + senderWallet, + receivingWallet, + TransactionFee, + Some( + Map( + TokenId(SigUSD1) -> tokenSentAmount, + TokenId(SigUSD2) -> tokenSentAmount + ) + ) + ) + res shouldBe a[Success[_]] + + val txInfo = res.get.sender + val sWalletAsBalance = WalletT.toBalance(senderWallet) + + val unconfirmedBalance = + BuildUnconfirmedBalance(txInfo.items, sWalletAsBalance, txInfo.ergoTree, txInfo.ergoTree.value) + + unconfirmedBalance.tokens shouldBe sWalletAsBalance.tokens.map(t => t.copy(amount = t.amount - tokenSentAmount)) + + } + + it should "calculate receiving wallet assets unconfirmed balance" in { + val tokenSentAmount = 100L + val res = Simulator( + senderWallet, + receivingWallet, + TransactionFee, + Some( + Map( + TokenId(SigUSD1) -> tokenSentAmount, + TokenId(SigUSD2) -> tokenSentAmount + ) + ) + ) + res shouldBe a[Success[_]] + + val txInfo = res.get.receiver + val rWalletAsBalance = WalletT.toBalance(receivingWallet) + + val unconfirmedBalance = + BuildUnconfirmedBalance(txInfo.items, rWalletAsBalance, txInfo.ergoTree, txInfo.ergoTree.value) + + unconfirmedBalance.tokens shouldBe rWalletAsBalance.tokens.map(t => t.copy(amount = t.amount + tokenSentAmount)) + } +} From 5a3a302d4953622a7d48b8ddbdff12ccdf660386 Mon Sep 17 00:00:00 2001 From: monyedavid Date: Sat, 14 May 2022 12:02:01 +0100 Subject: [PATCH 2/7] - AddressesSpec - get confirmed balance (tokens) of address - get confirmed Balance (nanoErgs) of address --- .../explorer/v1/services/AddressesSpec.scala | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala index 3e11ee9c5..15bbb7e9a 100644 --- a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala @@ -3,13 +3,18 @@ package org.ergoplatform.explorer.v1.services import cats.{Monad, Parallel} import cats.effect.{Concurrent, ContextShift, IO} import dev.profunktor.redis4cats.algebra.RedisCommands +import doobie.free.connection.ConnectionIO import eu.timepit.refined.api.Refined import eu.timepit.refined.auto._ import eu.timepit.refined.string.ValidByte import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.explorer.Address import org.ergoplatform.explorer.cache.Redis +import org.ergoplatform.explorer.commonGenerators.forSingleInstance import org.ergoplatform.explorer.db.{repositories, RealDbTest, Trans} import org.ergoplatform.explorer.db.algebra.LiftConnectionIO +import org.ergoplatform.explorer.testSyntax.runConnectionIO._ +import org.ergoplatform.explorer.db.models.aggregates.AggregatedAsset import org.ergoplatform.explorer.db.repositories.{ AssetRepo, HeaderRepo, @@ -29,9 +34,11 @@ import tofu.syntax.monadic._ import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} import scala.util.Try - import org.ergoplatform.explorer.v1.services.AddressesSpec._ import org.ergoplatform.explorer.db.models.generators._ +import org.ergoplatform.explorer.http.api.v1.models.TokenAmount +import org.ergoplatform.explorer.protocol.sigma +import org.ergoplatform.explorer.v1.services.constants.SenderAddressString trait AddressesSpec extends AnyFlatSpec @@ -47,7 +54,82 @@ class AS_A extends AddressesSpec { implicit val addressEncoder: ErgoAddressEncoder = ErgoAddressEncoder(networkPrefix.value.toByte) - "Address Service" should "" in {} + "Address Service" should "get confirmed Balance (nanoErgs) of address" in { + implicit val trans: Trans[ConnectionIO, IO] = Trans.fromDoobie(xa) + import tofu.fs2Instances._ + withResources[IO](container.mappedPort(6379)) + .use { case (settings, utxCache, redis) => + withServices[IO, ConnectionIO](settings, utxCache, redis) { (addr, _) => + val addressT = Address.fromString[Try](SenderAddressString) + addressT.isSuccess should be(true) + val addressTree = sigma.addressToErgoTreeHex(addressT.get) + val boxValues = List((100.toNanoErgo, 1), (200.toNanoErgo, 1)) + withLiveRepos[ConnectionIO] { (headerRepo, txRepo, oRepo, _, _, _) => + forSingleInstance( + balanceOfAddressGen( + mainChain = true, + address = addressT.get, + addressTree, + values = boxValues + ) + ) { infoTupleList => + infoTupleList.foreach { case (header, out, tx) => + headerRepo.insert(header).runWithIO() + oRepo.insert(out).runWithIO() + txRepo.insert(tx).runWithIO() + } + val balance = addr.confirmedBalanceOf(addressT.get, 0).unsafeRunSync().nanoErgs + balance should be(300.toNanoErgo) + } + } + + } + } + .unsafeRunSync() + } + +} + +class AS_B extends AddressesSpec { + + val networkPrefix: String Refined ValidByte = "16" // strictly run test-suite with testnet network prefix + implicit val addressEncoder: ErgoAddressEncoder = + ErgoAddressEncoder(networkPrefix.value.toByte) + + "Address Service" should "get confirmed balance (tokens) of address" in { + implicit val trans: Trans[ConnectionIO, IO] = Trans.fromDoobie(xa) + import tofu.fs2Instances._ + withResources[IO](container.mappedPort(6379)) + .use { case (settings, utxCache, redis) => + withServices[IO, ConnectionIO](settings, utxCache, redis) { (addr, _) => + val addressT = Address.fromString[Try](SenderAddressString) + addressT.isSuccess should be(true) + val addressTree = sigma.addressToErgoTreeHex(addressT.get) + withLiveRepos[ConnectionIO] { (headerRepo, txRepo, oRepo, _, tokenRepo, assetRepo) => + forSingleInstance( + balanceOfAddressWithTokenGen(mainChain = true, address = addressT.get, addressTree, 1, 5) + ) { infoTupleList => + infoTupleList.foreach { case (header, out, tx, _, token, asset) => + headerRepo.insert(header).runWithIO() + oRepo.insert(out).runWithIO() + txRepo.insert(tx).runWithIO() + tokenRepo.insert(token).runWithIO() + assetRepo.insert(asset).runWithIO() + } + val tokeAmount = infoTupleList.map { x => + val tk = x._5 + val ase = x._6 + TokenAmount(AggregatedAsset(tk.id, ase.amount, tk.name, tk.decimals, tk.`type`)) + } + val tokenBalance = addr.confirmedBalanceOf(addressT.get, 0).unsafeRunSync().tokens + tokenBalance should contain theSameElementsAs tokeAmount + } + } + } + } + .unsafeRunSync() + } + } object AddressesSpec { From f635f372256505835a9e65edc048b8f572f99019 Mon Sep 17 00:00:00 2001 From: monyedavid Date: Sat, 14 May 2022 12:02:01 +0100 Subject: [PATCH 3/7] AddressesSpec: - get confirmed balance (tokens) of address - get confirmed Balance (nanoErgs) of address --- .../explorer/v1/services/AddressesSpec.scala | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala index 3e11ee9c5..15bbb7e9a 100644 --- a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala @@ -3,13 +3,18 @@ package org.ergoplatform.explorer.v1.services import cats.{Monad, Parallel} import cats.effect.{Concurrent, ContextShift, IO} import dev.profunktor.redis4cats.algebra.RedisCommands +import doobie.free.connection.ConnectionIO import eu.timepit.refined.api.Refined import eu.timepit.refined.auto._ import eu.timepit.refined.string.ValidByte import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.explorer.Address import org.ergoplatform.explorer.cache.Redis +import org.ergoplatform.explorer.commonGenerators.forSingleInstance import org.ergoplatform.explorer.db.{repositories, RealDbTest, Trans} import org.ergoplatform.explorer.db.algebra.LiftConnectionIO +import org.ergoplatform.explorer.testSyntax.runConnectionIO._ +import org.ergoplatform.explorer.db.models.aggregates.AggregatedAsset import org.ergoplatform.explorer.db.repositories.{ AssetRepo, HeaderRepo, @@ -29,9 +34,11 @@ import tofu.syntax.monadic._ import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} import scala.util.Try - import org.ergoplatform.explorer.v1.services.AddressesSpec._ import org.ergoplatform.explorer.db.models.generators._ +import org.ergoplatform.explorer.http.api.v1.models.TokenAmount +import org.ergoplatform.explorer.protocol.sigma +import org.ergoplatform.explorer.v1.services.constants.SenderAddressString trait AddressesSpec extends AnyFlatSpec @@ -47,7 +54,82 @@ class AS_A extends AddressesSpec { implicit val addressEncoder: ErgoAddressEncoder = ErgoAddressEncoder(networkPrefix.value.toByte) - "Address Service" should "" in {} + "Address Service" should "get confirmed Balance (nanoErgs) of address" in { + implicit val trans: Trans[ConnectionIO, IO] = Trans.fromDoobie(xa) + import tofu.fs2Instances._ + withResources[IO](container.mappedPort(6379)) + .use { case (settings, utxCache, redis) => + withServices[IO, ConnectionIO](settings, utxCache, redis) { (addr, _) => + val addressT = Address.fromString[Try](SenderAddressString) + addressT.isSuccess should be(true) + val addressTree = sigma.addressToErgoTreeHex(addressT.get) + val boxValues = List((100.toNanoErgo, 1), (200.toNanoErgo, 1)) + withLiveRepos[ConnectionIO] { (headerRepo, txRepo, oRepo, _, _, _) => + forSingleInstance( + balanceOfAddressGen( + mainChain = true, + address = addressT.get, + addressTree, + values = boxValues + ) + ) { infoTupleList => + infoTupleList.foreach { case (header, out, tx) => + headerRepo.insert(header).runWithIO() + oRepo.insert(out).runWithIO() + txRepo.insert(tx).runWithIO() + } + val balance = addr.confirmedBalanceOf(addressT.get, 0).unsafeRunSync().nanoErgs + balance should be(300.toNanoErgo) + } + } + + } + } + .unsafeRunSync() + } + +} + +class AS_B extends AddressesSpec { + + val networkPrefix: String Refined ValidByte = "16" // strictly run test-suite with testnet network prefix + implicit val addressEncoder: ErgoAddressEncoder = + ErgoAddressEncoder(networkPrefix.value.toByte) + + "Address Service" should "get confirmed balance (tokens) of address" in { + implicit val trans: Trans[ConnectionIO, IO] = Trans.fromDoobie(xa) + import tofu.fs2Instances._ + withResources[IO](container.mappedPort(6379)) + .use { case (settings, utxCache, redis) => + withServices[IO, ConnectionIO](settings, utxCache, redis) { (addr, _) => + val addressT = Address.fromString[Try](SenderAddressString) + addressT.isSuccess should be(true) + val addressTree = sigma.addressToErgoTreeHex(addressT.get) + withLiveRepos[ConnectionIO] { (headerRepo, txRepo, oRepo, _, tokenRepo, assetRepo) => + forSingleInstance( + balanceOfAddressWithTokenGen(mainChain = true, address = addressT.get, addressTree, 1, 5) + ) { infoTupleList => + infoTupleList.foreach { case (header, out, tx, _, token, asset) => + headerRepo.insert(header).runWithIO() + oRepo.insert(out).runWithIO() + txRepo.insert(tx).runWithIO() + tokenRepo.insert(token).runWithIO() + assetRepo.insert(asset).runWithIO() + } + val tokeAmount = infoTupleList.map { x => + val tk = x._5 + val ase = x._6 + TokenAmount(AggregatedAsset(tk.id, ase.amount, tk.name, tk.decimals, tk.`type`)) + } + val tokenBalance = addr.confirmedBalanceOf(addressT.get, 0).unsafeRunSync().tokens + tokenBalance should contain theSameElementsAs tokeAmount + } + } + } + } + .unsafeRunSync() + } + } object AddressesSpec { From 4defca8540cb64eec05e62011f8d5d827e403fef Mon Sep 17 00:00:00 2001 From: monyedavid Date: Mon, 23 May 2022 14:16:39 +0100 Subject: [PATCH 4/7] use shared memprops: --- .../http/api/v1/routes/AddressesRoutes.scala | 2 +- .../http/api/v1/services/Addresses.scala | 12 ++++++- .../http/api/v1/services/Mempool.scala | 31 ------------------- .../http/api/v1/shared/MempoolProps.scala | 31 +++++++++++++++++-- .../explorer/v1/services/MempoolSpec.scala | 4 +-- 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala index f4474c9bb..4ff202b80 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala @@ -37,7 +37,7 @@ final class AddressesRoutes[F[_]: Concurrent: ContextShift: Timer: AdaptThrowabl private def getTotalBalanceR = interpreter.toRoutes(defs.getTotalBalanceDef) { addr => - mempool.getTotalBalance(addr, addresses.confirmedBalanceOf).adaptThrowable.value + addresses.totalBalanceOf_(addr).adaptThrowable.value } } diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Addresses.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Addresses.scala index 51fc3ff26..851990844 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Addresses.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Addresses.scala @@ -20,6 +20,10 @@ trait Addresses[F[_]] { def confirmedBalanceOf(address: Address, minConfirmations: Int): F[Balance] def totalBalanceOf(address: Address): F[TotalBalance] + + /** Get TotalBalance of address with consideration of mempool data + */ + def totalBalanceOf_(address: Address): F[TotalBalance] } object Addresses { @@ -30,7 +34,7 @@ object Addresses { (HeaderRepo[F, D], OutputRepo[F, D], AssetRepo[F, D], UOutputRepo[F, D], UAssetRepo[F, D]) .mapN(new Live(memprops, _, _, _, _, _)(trans)) - final class Live[F[_], D[_]: Monad]( + final class Live[F[_]: Monad, D[_]: Monad]( memprops: MempoolProps[F, D], headerRepo: HeaderRepo[D], outputRepo: OutputRepo[D, Stream], @@ -61,5 +65,11 @@ object Addresses { unconfirmed = Balance(offChainBalance, offChainAssets.map(TokenAmount(_))) )) ||> trans.xa } + + def totalBalanceOf_(address: Address): F[TotalBalance] = + for { + confirmed <- confirmedBalanceOf(address, 0) + unconfirmed <- memprops.getUnconfirmedBalanceByAddress(address, confirmed) + } yield TotalBalance(confirmed, unconfirmed) } } diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Mempool.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Mempool.scala index bd8440023..36202edce 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Mempool.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Mempool.scala @@ -43,8 +43,6 @@ trait Mempool[F[_]] { paging: Paging ): F[Items[UTransactionInfo]] - def getTotalBalance(address: Address, confirmedBalanceOf: (Address, Int) => F[Balance]): F[TotalBalance] - def submit(tx: ErgoLikeTransaction): F[TxIdResponse] def streamUnspentOutputs: Stream[F, UOutputInfo] @@ -73,35 +71,6 @@ object Mempool { import repo._ - private def getUnconfirmedBalanceByAddress( - address: Address, - confirmedBalance: Balance - ): F[Balance] = { - val ergoTree = addressToErgoTreeNewtype(address) - txs - .countByErgoTree(ergoTree.value) - .flatMap { total => - txs - .streamRelatedToErgoTree(ergoTree, 0, Int.MaxValue) - .chunkN(settings.chunkSize) - .through(mkTransaction) - .to[List] - .map { poolItems => - total match { - case 0 => Balance.empty - case _ => BuildUnconfirmedBalance(poolItems, confirmedBalance, ergoTree, ergoTree.value) - } - } - } - .thrushK(trans.xa) - } - - def getTotalBalance(address: Address, confirmedBalanceOf: (Address, Int) => F[Balance]): F[TotalBalance] = - for { - confirmed <- confirmedBalanceOf(address, 0) - unconfirmed <- getUnconfirmedBalanceByAddress(address, confirmed) - } yield TotalBalance(confirmed, unconfirmed) - def getByErgoTree(ergoTree: ErgoTree, paging: Paging): F[Items[UTransactionInfo]] = txs .countByErgoTree(ergoTree.value) diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/shared/MempoolProps.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/shared/MempoolProps.scala index f17089429..a25306a3e 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/shared/MempoolProps.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/shared/MempoolProps.scala @@ -11,18 +11,22 @@ import org.ergoplatform.explorer.db.algebra.LiftConnectionIO import org.ergoplatform.explorer.db.models.{UOutput, UTransaction} import org.ergoplatform.explorer.db.repositories.bundles.UtxRepoBundle import org.ergoplatform.explorer.http.api.streaming.CompileStream -import org.ergoplatform.explorer.http.api.v1.models.{UOutputInfo, UTransactionInfo} +import org.ergoplatform.explorer.http.api.v1.models.{Balance, UOutputInfo, UTransactionInfo} import org.ergoplatform.explorer.settings.{ServiceSettings, UtxCacheSettings} -import org.ergoplatform.explorer.ErgoTree +import org.ergoplatform.explorer.{Address, ErgoTree} import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.explorer.http.api.v1.utils.BuildUnconfirmedBalance +import org.ergoplatform.explorer.protocol.sigma.addressToErgoTreeNewtype import org.ergoplatform.explorer.syntax.stream._ import tofu.Throws import tofu.syntax.monadic._ +import tofu.syntax.streams.compile._ trait MempoolProps[F[_], D[_]] { def hasUnconfirmedBalance(ergoTree: ErgoTree): F[Boolean] def mkUnspentOutputInfo: Pipe[D, Chunk[UOutput], UOutputInfo] def mkTransaction: Pipe[D, Chunk[UTransaction], UTransactionInfo] + def getUnconfirmedBalanceByAddress(address: Address, confirmedBalance: Balance): F[Balance] } object MempoolProps { @@ -43,6 +47,29 @@ object MempoolProps { import repo._ + def getUnconfirmedBalanceByAddress( + address: Address, + confirmedBalance: Balance + ): F[Balance] = { + val ergoTree = addressToErgoTreeNewtype(address) + txs + .countByErgoTree(ergoTree.value) + .flatMap { total => + txs + .streamRelatedToErgoTree(ergoTree, 0, Int.MaxValue) + .chunkN(settings.chunkSize) + .through(mkTransaction) + .to[List] + .map { poolItems => + total match { + case 0 => Balance.empty + case _ => BuildUnconfirmedBalance(poolItems, confirmedBalance, ergoTree, ergoTree.value) + } + } + } + .thrushK(trans.xa) + } + def hasUnconfirmedBalance(ergoTree: ErgoTree): F[Boolean] = txs .countByErgoTree(ergoTree.value) diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala index 1aa4b9258..bb44b9acb 100644 --- a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala @@ -61,7 +61,7 @@ class MS_A extends MempoolSpec { lazy val address1Tree = sigma.addressToErgoTreeHex(address1T.get) withResources[IO](container.mappedPort(6379)) .use { case (settings, utxCache, redis) => - withServices[IO, ConnectionIO](settings, utxCache, redis) { (mem, addr) => + withServices[IO, ConnectionIO](settings, utxCache, redis) { (_, addr) => address1T.isSuccess should be(true) withLiveRepos[ConnectionIO] { (hRepo, txRepo, outRepo, _, _, _) => forSingleInstance( @@ -77,7 +77,7 @@ class MS_A extends MempoolSpec { outRepo.insert(out).runWithIO() txRepo.insert(tx).runWithIO() } - val tb = mem.getTotalBalance(address1T.get, addr.confirmedBalanceOf).unsafeRunSync() + val tb = addr.totalBalanceOf_(address1T.get).unsafeRunSync() tb.confirmed.nanoErgs should be(600.toNanoErgo) } } From d883436cd9231cd9e28c95091be193f03466afd4 Mon Sep 17 00:00:00 2001 From: monyedavid Date: Mon, 30 May 2022 14:44:54 +0100 Subject: [PATCH 5/7] naming --- .../explorer/http/api/v1/routes/AddressesRoutes.scala | 2 +- .../explorer/http/api/v1/services/Addresses.scala | 4 ++-- .../org/ergoplatform/explorer/v1/services/MempoolSpec.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala index 4ff202b80..7677afbe1 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/AddressesRoutes.scala @@ -37,7 +37,7 @@ final class AddressesRoutes[F[_]: Concurrent: ContextShift: Timer: AdaptThrowabl private def getTotalBalanceR = interpreter.toRoutes(defs.getTotalBalanceDef) { addr => - addresses.totalBalanceOf_(addr).adaptThrowable.value + addresses.totalBalanceWithConsiderationOfMempoolFor(addr).adaptThrowable.value } } diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Addresses.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Addresses.scala index 851990844..179d3a84c 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Addresses.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Addresses.scala @@ -23,7 +23,7 @@ trait Addresses[F[_]] { /** Get TotalBalance of address with consideration of mempool data */ - def totalBalanceOf_(address: Address): F[TotalBalance] + def totalBalanceWithConsiderationOfMempoolFor(address: Address): F[TotalBalance] } object Addresses { @@ -66,7 +66,7 @@ object Addresses { )) ||> trans.xa } - def totalBalanceOf_(address: Address): F[TotalBalance] = + def totalBalanceWithConsiderationOfMempoolFor(address: Address): F[TotalBalance] = for { confirmed <- confirmedBalanceOf(address, 0) unconfirmed <- memprops.getUnconfirmedBalanceByAddress(address, confirmed) diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala index bb44b9acb..6b8b59600 100644 --- a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala @@ -77,7 +77,7 @@ class MS_A extends MempoolSpec { outRepo.insert(out).runWithIO() txRepo.insert(tx).runWithIO() } - val tb = addr.totalBalanceOf_(address1T.get).unsafeRunSync() + val tb = addr.totalBalanceWithConsiderationOfMempoolFor(address1T.get).unsafeRunSync() tb.confirmed.nanoErgs should be(600.toNanoErgo) } } From a2d02d07d8ba5b0368b962b71849d55a5315c61a Mon Sep 17 00:00:00 2001 From: monyedavid Date: Tue, 31 May 2022 21:45:39 +0100 Subject: [PATCH 6/7] redisTestPort | remove magic number --- .../org/ergoplatform/explorer/v1/services/AddressesSpec.scala | 4 ++-- .../org/ergoplatform/explorer/v1/services/MempoolSpec.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala index e07f68a23..f3da1c820 100644 --- a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/AddressesSpec.scala @@ -58,7 +58,7 @@ class AS_A extends AddressesSpec { "Address Service" should "get confirmed Balance (nanoErgs) of address" in { implicit val trans: Trans[ConnectionIO, IO] = Trans.fromDoobie(xa) import tofu.fs2Instances._ - withResources[IO](container.mappedPort(6379)) + withResources[IO](container.mappedPort(redisTestPort)) .use { case (settings, utxCache, redis) => withServices[IO, ConnectionIO](settings, utxCache, redis) { (addr, _) => val addressT = Address.fromString[Try](SenderAddressString) @@ -100,7 +100,7 @@ class AS_B extends AddressesSpec { "Address Service" should "get confirmed balance (tokens) of address" in { implicit val trans: Trans[ConnectionIO, IO] = Trans.fromDoobie(xa) import tofu.fs2Instances._ - withResources[IO](container.mappedPort(6379)) + withResources[IO](container.mappedPort(redisTestPort)) .use { case (settings, utxCache, redis) => withServices[IO, ConnectionIO](settings, utxCache, redis) { (addr, _) => val addressT = Address.fromString[Try](SenderAddressString) diff --git a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala index 6b8b59600..2147340ee 100644 --- a/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala +++ b/modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/MempoolSpec.scala @@ -59,7 +59,7 @@ class MS_A extends MempoolSpec { val address1S = SenderAddressString val address1T = Address.fromString[Try](address1S) lazy val address1Tree = sigma.addressToErgoTreeHex(address1T.get) - withResources[IO](container.mappedPort(6379)) + withResources[IO](container.mappedPort(redisTestPort)) .use { case (settings, utxCache, redis) => withServices[IO, ConnectionIO](settings, utxCache, redis) { (_, addr) => address1T.isSuccess should be(true) From 4741ecbe411f258e9af9378fbe6a36a396d96b81 Mon Sep 17 00:00:00 2001 From: monyedavid Date: Thu, 2 Jun 2022 15:27:15 +0100 Subject: [PATCH 7/7] match total earlier --- .../http/api/v1/shared/MempoolProps.scala | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/shared/MempoolProps.scala b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/shared/MempoolProps.scala index a25306a3e..eaa7e241d 100644 --- a/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/shared/MempoolProps.scala +++ b/modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/shared/MempoolProps.scala @@ -54,18 +54,17 @@ object MempoolProps { val ergoTree = addressToErgoTreeNewtype(address) txs .countByErgoTree(ergoTree.value) - .flatMap { total => - txs - .streamRelatedToErgoTree(ergoTree, 0, Int.MaxValue) - .chunkN(settings.chunkSize) - .through(mkTransaction) - .to[List] - .map { poolItems => - total match { - case 0 => Balance.empty - case _ => BuildUnconfirmedBalance(poolItems, confirmedBalance, ergoTree, ergoTree.value) + .flatMap { + case 0 => Balance.empty.pure[D] + case _ => + txs + .streamRelatedToErgoTree(ergoTree, 0, Int.MaxValue) + .chunkN(settings.chunkSize) + .through(mkTransaction) + .to[List] + .map { poolItems => + BuildUnconfirmedBalance(poolItems, confirmedBalance, ergoTree, ergoTree.value) } - } } .thrushK(trans.xa) }