mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2024-10-01 03:45:36 -04:00
lib: make output public key optional in Enote
This commit is contained in:
parent
75b33a24f3
commit
7dba0c1b18
@ -178,7 +178,8 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
|||||||
for (const auto& td: tds) {
|
for (const auto& td: tds) {
|
||||||
snapshot.emplace_back(td.m_txid, TxInfo::INCOMING);
|
snapshot.emplace_back(td.m_txid, TxInfo::INCOMING);
|
||||||
TxInfo& recv = snapshot.back();
|
TxInfo& recv = snapshot.back();
|
||||||
recv.m_key = td.get_public_key();
|
recv.m_public_key = td.get_public_key();
|
||||||
|
recv.m_public_key_known = true;
|
||||||
recv.m_key_image = td.m_key_image;
|
recv.m_key_image = td.m_key_image;
|
||||||
recv.m_key_image_known = td.m_key_image_known;
|
recv.m_key_image_known = td.m_key_image_known;
|
||||||
recv.m_subaddress_major = td.m_subaddr_index.major;
|
recv.m_subaddress_major = td.m_subaddr_index.major;
|
||||||
@ -247,7 +248,6 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
|||||||
// Add pending transfers to our own wallet.
|
// Add pending transfers to our own wallet.
|
||||||
snapshot.emplace_back(pair.first, TxInfo::INCOMING);
|
snapshot.emplace_back(pair.first, TxInfo::INCOMING);
|
||||||
TxInfo& recv = snapshot.back();
|
TxInfo& recv = snapshot.back();
|
||||||
// TODO: recv.m_key
|
|
||||||
recv.m_recipient = m_wallet.get_subaddress_as_str(*dest_subaddr_idx);
|
recv.m_recipient = m_wallet.get_subaddress_as_str(*dest_subaddr_idx);
|
||||||
recv.m_subaddress_major = (*dest_subaddr_idx).major;
|
recv.m_subaddress_major = (*dest_subaddr_idx).major;
|
||||||
recv.m_subaddress_minor = (*dest_subaddr_idx).minor;
|
recv.m_subaddress_minor = (*dest_subaddr_idx).minor;
|
||||||
@ -273,7 +273,6 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
|||||||
if (utx.m_change > 0) {
|
if (utx.m_change > 0) {
|
||||||
snapshot.emplace_back(pair.first, TxInfo::INCOMING);
|
snapshot.emplace_back(pair.first, TxInfo::INCOMING);
|
||||||
TxInfo& change = snapshot.back();
|
TxInfo& change = snapshot.back();
|
||||||
// TODO: change.m_key
|
|
||||||
change.m_recipient = m_wallet.get_subaddress_as_str({utx.m_subaddr_account, 0});
|
change.m_recipient = m_wallet.get_subaddress_as_str({utx.m_subaddr_account, 0});
|
||||||
change.m_subaddress_major = utx.m_subaddr_account;
|
change.m_subaddress_major = utx.m_subaddr_account;
|
||||||
change.m_subaddress_minor = 0; // All changes go to 0-th subaddress
|
change.m_subaddress_minor = 0; // All changes go to 0-th subaddress
|
||||||
@ -306,7 +305,6 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
|||||||
for (uint64_t amount: upd.m_amounts) {
|
for (uint64_t amount: upd.m_amounts) {
|
||||||
snapshot.emplace_back(upd.m_tx_hash, TxInfo::INCOMING);
|
snapshot.emplace_back(upd.m_tx_hash, TxInfo::INCOMING);
|
||||||
TxInfo& recv = snapshot.back();
|
TxInfo& recv = snapshot.back();
|
||||||
// TODO: recv.m_key
|
|
||||||
recv.m_recipient = m_wallet.get_subaddress_as_str(upd.m_subaddr_index);
|
recv.m_recipient = m_wallet.get_subaddress_as_str(upd.m_subaddr_index);
|
||||||
recv.m_subaddress_major = upd.m_subaddr_index.major;
|
recv.m_subaddress_major = upd.m_subaddr_index.major;
|
||||||
recv.m_subaddress_minor = upd.m_subaddr_index.minor;
|
recv.m_subaddress_minor = upd.m_subaddr_index.minor;
|
||||||
@ -569,25 +567,26 @@ Java_im_molly_monero_WalletNative_nativeGetCurrentBlockchainHeight(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ScopedJvmLocalRef<jobject> nativeToJvmTxInfo(JNIEnv* env,
|
ScopedJvmLocalRef<jobject> nativeToJvmTxInfo(JNIEnv* env,
|
||||||
const TxInfo& info) {
|
const TxInfo& tx) {
|
||||||
LOG_FATAL_IF(info.m_height >= CRYPTONOTE_MAX_BLOCK_NUMBER, "Blockchain max height reached");
|
LOG_FATAL_IF(tx.m_height >= CRYPTONOTE_MAX_BLOCK_NUMBER, "Blockchain max height reached");
|
||||||
// TODO: Check amount overflow
|
// TODO: Check amount overflow
|
||||||
return {env, TxInfoClass.newObject(
|
return {env, TxInfoClass.newObject(
|
||||||
env, TxInfo_ctor,
|
env, TxInfo_ctor,
|
||||||
nativeToJvmString(env, pod_to_hex(info.m_tx_hash)).obj(),
|
nativeToJvmString(env, pod_to_hex(tx.m_tx_hash)).obj(),
|
||||||
nativeToJvmString(env, pod_to_hex(info.m_key)).obj(),
|
tx.m_public_key_known ? nativeToJvmString(env, pod_to_hex(tx.m_public_key)).obj() : nullptr,
|
||||||
info.m_key_image_known ? nativeToJvmString(env, pod_to_hex(info.m_key_image)).obj(): nullptr,
|
tx.m_key_image_known ? nativeToJvmString(env, pod_to_hex(tx.m_key_image)).obj() : nullptr,
|
||||||
info.m_subaddress_major,
|
tx.m_subaddress_major,
|
||||||
info.m_subaddress_minor,
|
tx.m_subaddress_minor,
|
||||||
(!info.m_recipient.empty()) ? nativeToJvmString(env, info.m_recipient).obj() : nullptr,
|
(!tx.m_recipient.empty()) ? nativeToJvmString(env, tx.m_recipient).obj() : nullptr,
|
||||||
info.m_amount,
|
tx.m_amount,
|
||||||
static_cast<jint>(info.m_height),
|
static_cast<jint>(tx.m_height),
|
||||||
info.m_state,
|
tx.m_state,
|
||||||
info.m_unlock_time,
|
tx.m_unlock_time,
|
||||||
info.m_timestamp,
|
tx.m_timestamp,
|
||||||
info.m_fee,
|
tx.m_fee,
|
||||||
info.m_coinbase,
|
tx.m_change,
|
||||||
info.m_type == TxInfo::INCOMING)
|
tx.m_coinbase,
|
||||||
|
tx.m_type == TxInfo::INCOMING)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ using unconfirmed_transfer_details = tools::wallet2::unconfirmed_transfer_detail
|
|||||||
// Basic structure combining transaction details with input or output info.
|
// Basic structure combining transaction details with input or output info.
|
||||||
struct TxInfo {
|
struct TxInfo {
|
||||||
crypto::hash m_tx_hash;
|
crypto::hash m_tx_hash;
|
||||||
crypto::public_key m_key;
|
crypto::public_key m_public_key;
|
||||||
crypto::key_image m_key_image;
|
crypto::key_image m_key_image;
|
||||||
uint32_t m_subaddress_major;
|
uint32_t m_subaddress_major;
|
||||||
uint32_t m_subaddress_minor;
|
uint32_t m_subaddress_minor;
|
||||||
@ -31,6 +31,7 @@ struct TxInfo {
|
|||||||
uint64_t m_fee;
|
uint64_t m_fee;
|
||||||
uint64_t m_change;
|
uint64_t m_change;
|
||||||
bool m_coinbase;
|
bool m_coinbase;
|
||||||
|
bool m_public_key_known;
|
||||||
bool m_key_image_known;
|
bool m_key_image_known;
|
||||||
|
|
||||||
enum TxType {
|
enum TxType {
|
||||||
@ -47,7 +48,7 @@ struct TxInfo {
|
|||||||
|
|
||||||
TxInfo(crypto::hash tx_hash, TxType type):
|
TxInfo(crypto::hash tx_hash, TxType type):
|
||||||
m_tx_hash(tx_hash),
|
m_tx_hash(tx_hash),
|
||||||
m_key(crypto::public_key{}),
|
m_public_key(crypto::public_key{}),
|
||||||
m_key_image(crypto::key_image{}),
|
m_key_image(crypto::key_image{}),
|
||||||
m_subaddress_major(-1),
|
m_subaddress_major(-1),
|
||||||
m_subaddress_minor(-1),
|
m_subaddress_minor(-1),
|
||||||
@ -59,6 +60,7 @@ struct TxInfo {
|
|||||||
m_fee(0),
|
m_fee(0),
|
||||||
m_change(0),
|
m_change(0),
|
||||||
m_coinbase(false),
|
m_coinbase(false),
|
||||||
|
m_public_key_known(false),
|
||||||
m_key_image_known(false),
|
m_key_image_known(false),
|
||||||
m_type(type),
|
m_type(type),
|
||||||
m_state(OFF_CHAIN) {}
|
m_state(OFF_CHAIN) {}
|
||||||
|
@ -13,7 +13,7 @@ data class Block(
|
|||||||
|
|
||||||
data class BlockHeader(
|
data class BlockHeader(
|
||||||
val height: Int,
|
val height: Int,
|
||||||
val timestamp: Long,
|
val epochSecond: Long,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_HEIGHT = CRYPTONOTE_MAX_BLOCK_NUMBER - 1
|
const val MAX_HEIGHT = CRYPTONOTE_MAX_BLOCK_NUMBER - 1
|
||||||
|
@ -25,6 +25,8 @@ open class BlockchainTime(
|
|||||||
override fun compareTo(other: BlockchainTime): Int =
|
override fun compareTo(other: BlockchainTime): Int =
|
||||||
this.height.compareTo(other.height)
|
this.height.compareTo(other.height)
|
||||||
|
|
||||||
|
override fun toString(): String = "Block #$height | $timestamp"
|
||||||
|
|
||||||
data object Genesis : BlockchainTime(0, Instant.ofEpochSecond(1397818193))
|
data object Genesis : BlockchainTime(0, Instant.ofEpochSecond(1397818193))
|
||||||
|
|
||||||
class Block(height: Int, referencePoint: BlockchainTime = Genesis) :
|
class Block(height: Int, referencePoint: BlockchainTime = Genesis) :
|
||||||
@ -42,19 +44,22 @@ open class BlockchainTime(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val AVERAGE_BLOCK_TIME = Duration.ofSeconds(DIFFICULTY_TARGET_V2)
|
val AVERAGE_BLOCK_TIME: Duration = Duration.ofSeconds(DIFFICULTY_TARGET_V2)
|
||||||
|
|
||||||
fun estimateTimestamp(targetHeight: Int, referencePoint: BlockchainTime): Instant {
|
fun estimateTimestamp(targetHeight: Int, referencePoint: BlockchainTime): Instant {
|
||||||
require(targetHeight >= 0) { "Block height $targetHeight must not be negative" }
|
require(targetHeight >= 0) {
|
||||||
|
"Block height $targetHeight must not be negative"
|
||||||
|
}
|
||||||
|
|
||||||
return if (targetHeight == 0) {
|
return when (targetHeight) {
|
||||||
Genesis.timestamp
|
0 -> Genesis.timestamp
|
||||||
} else {
|
else -> {
|
||||||
val heightDiff = targetHeight - referencePoint.height
|
val heightDiff = targetHeight - referencePoint.height
|
||||||
val estTimeDiff = AVERAGE_BLOCK_TIME.multipliedBy(heightDiff.toLong())
|
val estTimeDiff = AVERAGE_BLOCK_TIME.multipliedBy(heightDiff.toLong())
|
||||||
referencePoint.timestamp.plus(estTimeDiff)
|
referencePoint.timestamp.plus(estTimeDiff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun estimateBlockHeight(targetTime: Instant, referencePoint: BlockchainTime): Int {
|
fun estimateBlockHeight(targetTime: Instant, referencePoint: BlockchainTime): Int {
|
||||||
val timeDiff = Duration.between(referencePoint.timestamp, targetTime)
|
val timeDiff = Duration.between(referencePoint.timestamp, targetTime)
|
||||||
@ -86,6 +91,10 @@ open class BlockchainTime(
|
|||||||
operator fun minus(other: BlockchainTime): BlockchainTimeSpan = other.until(this)
|
operator fun minus(other: BlockchainTime): BlockchainTimeSpan = other.until(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun max(a: BlockchainTime, b: BlockchainTime) = if (a >= b) a else b
|
||||||
|
|
||||||
|
fun min(a: BlockchainTime, b: BlockchainTime) = if (a <= b) a else b
|
||||||
|
|
||||||
data class BlockchainTimeSpan(val duration: Duration, val blocks: Int) {
|
data class BlockchainTimeSpan(val duration: Duration, val blocks: Int) {
|
||||||
companion object {
|
companion object {
|
||||||
val ZERO = BlockchainTimeSpan(duration = Duration.ZERO, blocks = 0)
|
val ZERO = BlockchainTimeSpan(duration = Duration.ZERO, blocks = 0)
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
package im.molly.monero
|
package im.molly.monero
|
||||||
|
|
||||||
data class Enote(
|
class Enote(
|
||||||
val amount: MoneroAmount,
|
val amount: MoneroAmount,
|
||||||
val owner: AccountAddress,
|
val owner: AccountAddress,
|
||||||
val key: PublicKey,
|
val key: PublicKey?,
|
||||||
val keyImage: HashDigest?,
|
val keyImage: HashDigest?,
|
||||||
val age: Int,
|
val age: Int,
|
||||||
|
val sourceTxId: String?,
|
||||||
) {
|
) {
|
||||||
var spent: Boolean = false
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(age >= 0) { "Enote age $age must not be negative" }
|
require(age >= 0) { "Enote age $age must not be negative" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var spent: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun markAsSpent() {
|
||||||
|
spent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode() = System.identityHashCode(this)
|
||||||
|
|
||||||
|
override fun equals(other: Any?) = this === other
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,21 @@ data class Transaction(
|
|||||||
val hash: HashDigest,
|
val hash: HashDigest,
|
||||||
// TODO: val version: ProtocolInfo,
|
// TODO: val version: ProtocolInfo,
|
||||||
val state: TxState,
|
val state: TxState,
|
||||||
val timeLock: BlockchainTime,
|
val timeLock: BlockchainTime?,
|
||||||
val sent: Set<Enote>,
|
val sent: Set<Enote>,
|
||||||
val received: Set<Enote>,
|
val received: Set<Enote>,
|
||||||
val payments: List<PaymentDetail>,
|
val payments: List<PaymentDetail>,
|
||||||
val fee: MoneroAmount,
|
val fee: MoneroAmount,
|
||||||
val change: MoneroAmount,
|
val change: MoneroAmount,
|
||||||
) {
|
) {
|
||||||
val txId: String get() = hash.toString()
|
val txId: String
|
||||||
|
get() = hash.toString()
|
||||||
|
|
||||||
|
val blockHeight: Int?
|
||||||
|
get() = (state as? TxState.OnChain)?.blockHeader?.height
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface TxState {
|
sealed interface TxState {
|
||||||
|
|
||||||
data class OnChain(
|
data class OnChain(
|
||||||
val blockHeader: BlockHeader,
|
val blockHeader: BlockHeader,
|
||||||
) : TxState
|
) : TxState
|
||||||
|
@ -2,18 +2,20 @@ package im.molly.monero.internal
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import im.molly.monero.AccountAddress
|
import im.molly.monero.AccountAddress
|
||||||
import im.molly.monero.MoneroAmount
|
|
||||||
import im.molly.monero.BlockHeader
|
import im.molly.monero.BlockHeader
|
||||||
import im.molly.monero.BlockchainTime
|
import im.molly.monero.BlockchainTime
|
||||||
import im.molly.monero.CalledByNative
|
import im.molly.monero.CalledByNative
|
||||||
import im.molly.monero.Enote
|
import im.molly.monero.Enote
|
||||||
import im.molly.monero.HashDigest
|
import im.molly.monero.HashDigest
|
||||||
|
import im.molly.monero.MoneroAmount
|
||||||
import im.molly.monero.PaymentDetail
|
import im.molly.monero.PaymentDetail
|
||||||
import im.molly.monero.PublicAddress
|
import im.molly.monero.PublicAddress
|
||||||
import im.molly.monero.PublicKey
|
import im.molly.monero.PublicKey
|
||||||
import im.molly.monero.TimeLocked
|
import im.molly.monero.TimeLocked
|
||||||
import im.molly.monero.Transaction
|
import im.molly.monero.Transaction
|
||||||
import im.molly.monero.TxState
|
import im.molly.monero.TxState
|
||||||
|
import im.molly.monero.internal.constants.CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
|
||||||
|
import im.molly.monero.max
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +29,7 @@ import kotlinx.parcelize.Parcelize
|
|||||||
internal data class TxInfo
|
internal data class TxInfo
|
||||||
@CalledByNative("wallet.cc") constructor(
|
@CalledByNative("wallet.cc") constructor(
|
||||||
val txHash: String,
|
val txHash: String,
|
||||||
val key: String,
|
val publicKey: String?,
|
||||||
val keyImage: String?,
|
val keyImage: String?,
|
||||||
val subAddressMajor: Int,
|
val subAddressMajor: Int,
|
||||||
val subAddressMinor: Int,
|
val subAddressMinor: Int,
|
||||||
@ -61,85 +63,87 @@ internal data class TxInfo
|
|||||||
internal fun List<TxInfo>.consolidateTransactions(
|
internal fun List<TxInfo>.consolidateTransactions(
|
||||||
blockchainContext: BlockchainTime,
|
blockchainContext: BlockchainTime,
|
||||||
): Pair<Map<String, Transaction>, Set<TimeLocked<Enote>>> {
|
): Pair<Map<String, Transaction>, Set<TimeLocked<Enote>>> {
|
||||||
val (enoteByKey, enoteByKeyImage) = extractEnotesFromIncomingTxs(blockchainContext)
|
// Extract enotes from incoming transactions
|
||||||
|
val allEnotes = filter { it.incoming }.map { it.toEnote(blockchainContext.height) }
|
||||||
|
|
||||||
val timeLockedEnotes = HashSet<TimeLocked<Enote>>(enoteByKey.size)
|
val enoteByTxId = allEnotes.groupBy { enote -> enote.sourceTxId!! }
|
||||||
|
|
||||||
// Group transactions by their hash and then map each group to a Transaction
|
val enoteByKeyImage = allEnotes.mapNotNull { enote ->
|
||||||
val groupedByTxId = groupBy { it.txHash }
|
enote.keyImage?.let { keyImage -> keyImage.toString() to enote }
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
val validEnotes = HashSet<TimeLocked<Enote>>(allEnotes.size)
|
||||||
|
|
||||||
|
// Group transaction info by their hash and then map each group to a Transaction
|
||||||
|
val groupedByTxId = groupBy { txInfo -> txInfo.txHash }
|
||||||
val txById = groupedByTxId.mapValues { (_, infoList) ->
|
val txById = groupedByTxId.mapValues { (_, infoList) ->
|
||||||
createTransaction(blockchainContext, infoList, enoteByKey, enoteByKeyImage)
|
val tx = infoList.createTransaction(blockchainContext, enoteByTxId, enoteByKeyImage)
|
||||||
.also { tx ->
|
|
||||||
|
// If transaction isn't failed, calculate unlock time and save enotes
|
||||||
if (tx.state !is TxState.Failed) {
|
if (tx.state !is TxState.Failed) {
|
||||||
val lockedEnotesToAdd =
|
val defaultUnlockTime = BlockchainTime.Block(
|
||||||
tx.received.map { enote -> TimeLocked(enote, tx.timeLock) }
|
height = tx.blockHeight!! + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE,
|
||||||
timeLockedEnotes.addAll(lockedEnotesToAdd)
|
referencePoint = blockchainContext,
|
||||||
tx.sent.forEach { enote -> enote.spent = true }
|
)
|
||||||
}
|
val maxUnlockTime = max(defaultUnlockTime, tx.timeLock ?: BlockchainTime.Genesis)
|
||||||
}
|
|
||||||
|
val lockedEnotesToAdd = tx.received.map { enote -> TimeLocked(enote, maxUnlockTime) }
|
||||||
|
validEnotes.addAll(lockedEnotesToAdd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return txById to timeLockedEnotes
|
// Mark the sent enotes as spent
|
||||||
|
tx.sent.forEach { enote -> enote.markAsSpent() }
|
||||||
|
|
||||||
|
tx
|
||||||
|
}
|
||||||
|
|
||||||
|
return txById to validEnotes
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<TxInfo>.extractEnotesFromIncomingTxs(
|
private fun List<TxInfo>.createTransaction(
|
||||||
blockchainContext: BlockchainTime,
|
blockchainContext: BlockchainTime,
|
||||||
): Pair<Map<String, Enote>, Map<String, Enote>> {
|
enoteByTxId: Map<String, List<Enote>>,
|
||||||
val enoteByKey = mutableMapOf<String, Enote>()
|
enoteByKeyImage: Map<String, Enote>,
|
||||||
val enoteByKeyImage = mutableMapOf<String, Enote>()
|
|
||||||
|
|
||||||
for (txInfo in filter { it.incoming }) {
|
|
||||||
enoteByKey.computeIfAbsent(txInfo.key) {
|
|
||||||
val enote = txInfo.toEnote(blockchainContext.height)
|
|
||||||
txInfo.keyImage?.let { keyImage ->
|
|
||||||
enoteByKeyImage[keyImage] = enote
|
|
||||||
}
|
|
||||||
enote
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return enoteByKey to enoteByKeyImage
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createTransaction(
|
|
||||||
blockchainContext: BlockchainTime,
|
|
||||||
infoList: List<TxInfo>,
|
|
||||||
enoteMap: Map<String, Enote>,
|
|
||||||
keyImageMap: Map<String, Enote>,
|
|
||||||
): Transaction {
|
): Transaction {
|
||||||
val txHash = infoList.first().txHash
|
val txHash = first().txHash
|
||||||
val unlockTime = infoList.maxOf { it.unlockTime }
|
|
||||||
val fee = infoList.maxOf { it.fee }
|
|
||||||
val change = infoList.maxOf { it.change }
|
|
||||||
|
|
||||||
val (ins, outs) = infoList.partition { it.incoming }
|
val fee = maxOf { it.fee }
|
||||||
|
val change = maxOf { it.change }
|
||||||
|
|
||||||
val receivedEnotes = ins.map { enoteMap.getValue(it.key) }
|
val timeLock = maxOf { it.unlockTime }.let { unlockTime ->
|
||||||
val spentKeyImages = outs.mapNotNull { it.keyImage }.toSet()
|
if (unlockTime == 0L) null else blockchainContext.resolveUnlockTime(unlockTime)
|
||||||
val sentEnotes = keyImageMap.filterKeys { spentKeyImages.contains(it) }.values
|
}
|
||||||
val payments = outs.map { it.toPaymentDetail() }
|
|
||||||
|
val receivedEnotes = enoteByTxId.getValue(txHash).toSet()
|
||||||
|
|
||||||
|
val outTxs = filter { !it.incoming }
|
||||||
|
|
||||||
|
val spentKeyImages = outTxs.mapNotNull { it.keyImage }
|
||||||
|
val sentEnotes = enoteByKeyImage.filterKeys { ki -> ki in spentKeyImages }.values.toSet()
|
||||||
|
|
||||||
|
val payments = outTxs.map { it.toPaymentDetail() }
|
||||||
|
|
||||||
return Transaction(
|
return Transaction(
|
||||||
hash = HashDigest(txHash),
|
hash = HashDigest(txHash),
|
||||||
state = determineTxState(infoList),
|
state = determineTxState(),
|
||||||
timeLock = blockchainContext.resolveUnlockTime(unlockTime),
|
timeLock = timeLock,
|
||||||
sent = sentEnotes.toSet(),
|
sent = sentEnotes,
|
||||||
received = receivedEnotes.toSet(),
|
received = receivedEnotes,
|
||||||
payments = payments,
|
payments = payments,
|
||||||
fee = MoneroAmount(fee),
|
fee = MoneroAmount(atomicUnits = fee),
|
||||||
change = MoneroAmount(change),
|
change = MoneroAmount(atomicUnits = change),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun determineTxState(infoList: List<TxInfo>): TxState {
|
private fun List<TxInfo>.determineTxState(): TxState {
|
||||||
val txInfo = infoList.distinctBy { it.state }.single()
|
val uniqueTx = distinctBy { it.state }.single()
|
||||||
|
|
||||||
return when (txInfo.state) {
|
return when (uniqueTx.state) {
|
||||||
TxInfo.OFF_CHAIN -> TxState.OffChain
|
TxInfo.OFF_CHAIN -> TxState.OffChain
|
||||||
TxInfo.PENDING -> TxState.InMemoryPool
|
TxInfo.PENDING -> TxState.InMemoryPool
|
||||||
TxInfo.FAILED -> TxState.Failed
|
TxInfo.FAILED -> TxState.Failed
|
||||||
TxInfo.ON_CHAIN -> TxState.OnChain(BlockHeader(txInfo.height, txInfo.timestamp))
|
TxInfo.ON_CHAIN -> TxState.OnChain(BlockHeader(uniqueTx.height, uniqueTx.timestamp))
|
||||||
else -> error("Invalid tx state value: ${txInfo.state}")
|
else -> error("Invalid tx state value: ${uniqueTx.state}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,15 +157,16 @@ private fun TxInfo.toEnote(blockchainHeight: Int): Enote {
|
|||||||
val calculatedAge = if (height == 0) 0 else blockchainHeight - height + 1
|
val calculatedAge = if (height == 0) 0 else blockchainHeight - height + 1
|
||||||
|
|
||||||
return Enote(
|
return Enote(
|
||||||
amount = MoneroAmount(amount),
|
amount = MoneroAmount(atomicUnits = amount),
|
||||||
owner = ownerAddress,
|
owner = ownerAddress,
|
||||||
key = PublicKey(key),
|
key = publicKey?.let { PublicKey(it) },
|
||||||
keyImage = keyImage?.let { HashDigest(it) },
|
keyImage = keyImage?.let { HashDigest(it) },
|
||||||
age = calculatedAge,
|
age = calculatedAge,
|
||||||
|
sourceTxId = txHash,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TxInfo.toPaymentDetail() = PaymentDetail(
|
private fun TxInfo.toPaymentDetail() = PaymentDetail(
|
||||||
amount = MoneroAmount(amount),
|
amount = MoneroAmount(atomicUnits = amount),
|
||||||
recipient = PublicAddress.parse(recipient!!),
|
recipient = PublicAddress.parse(recipient!!),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user