Skip to content
Draft

I141 #211

Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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:
* <li> 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 </li>
* <li> reducing similar [[org.ergoplatform.explorer.http.api.v1.models.UOutputInfo]]
* sums (credits/debits) into a single value: `debitSum/creditSum` </li>
* <li> subtracting or adding `debitSum/creditSum` into iterations current balance </li>
*
* @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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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: <br>
* - 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)
}

}
Original file line number Diff line number Diff line change
@@ -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
)
}
Original file line number Diff line number Diff line change
@@ -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
)
}
Loading