mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2024-12-25 07:29:23 -05:00
lib: fix transaction timestamps
This commit is contained in:
parent
f5998ac369
commit
2bd227124a
@ -49,7 +49,7 @@ fun TransactionCardExpanded(
|
||||
localDateTime.format(formatter)
|
||||
}
|
||||
Text(
|
||||
text = timestamp ?: "",
|
||||
text = timestamp ?: "Pending",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
Text(
|
||||
@ -64,7 +64,7 @@ fun TransactionCardExpanded(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = "#${transaction.blockHeight}",
|
||||
text = transaction.blockHeight?.toString() ?: "",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
Text(
|
||||
|
@ -54,7 +54,7 @@ fun WalletBalanceView(
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
text = "Balance at Block #${blockchainTime.height}",
|
||||
text = "Balance at ${blockchainTime}",
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
@ -15,6 +15,7 @@ import im.molly.monero.demo.data.model.WalletConfig
|
||||
import im.molly.monero.demo.data.model.WalletTransaction
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Instant
|
||||
|
||||
class WalletViewModel(
|
||||
walletId: Long,
|
||||
@ -65,7 +66,7 @@ private fun walletUiState(
|
||||
val transactions =
|
||||
ledger.transactions
|
||||
.map { WalletTransaction(config.id, it.value) }
|
||||
.sortedByDescending { it.transaction.timestamp }
|
||||
.sortedByDescending { it.transaction.timestamp ?: Instant.MAX }
|
||||
val network = ledger.primaryAddress.network
|
||||
WalletUiState.Loaded(config, network, blockchainTime, balance, transactions)
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
package im.molly.monero;
|
||||
|
||||
parcelable BlockchainTime;
|
@ -1,8 +1,9 @@
|
||||
package im.molly.monero;
|
||||
|
||||
import im.molly.monero.BlockchainTime;
|
||||
import im.molly.monero.internal.TxInfo;
|
||||
|
||||
oneway interface IBalanceListener {
|
||||
void onBalanceChanged(in List<TxInfo> txHistory, int blockchainHeight);
|
||||
void onRefresh(int blockchainHeight);
|
||||
void onBalanceChanged(in List<TxInfo> txHistory, in BlockchainTime blockchainTime);
|
||||
void onRefresh(in BlockchainTime blockchainTime);
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package im.molly.monero;
|
||||
|
||||
import im.molly.monero.BlockchainTime;
|
||||
|
||||
oneway interface IWalletCallbacks {
|
||||
void onRefreshResult(int blockHeight, int status);
|
||||
void onRefreshResult(in BlockchainTime blockchainTime, int status);
|
||||
void onCommitResult(boolean success);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ void initializeJniCache(JNIEnv* env) {
|
||||
"callRemoteNode",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)Lim/molly/monero/HttpResponse;");
|
||||
WalletNative_onRefresh = walletNative
|
||||
.getMethodId(env, "onRefresh", "(IZ)V");
|
||||
.getMethodId(env, "onRefresh", "(IJZ)V");
|
||||
WalletNative_onSuspendRefresh = walletNative
|
||||
.getMethodId(env, "onSuspendRefresh", "(Z)V");
|
||||
|
||||
|
@ -36,7 +36,8 @@ Wallet::Wallet(
|
||||
std::make_unique<RemoteNodeClientFactory>(env, wallet_native)),
|
||||
m_callback(env, wallet_native),
|
||||
m_account_ready(false),
|
||||
m_blockchain_height(1),
|
||||
m_last_block_height(1),
|
||||
m_last_block_timestamp(1397818193),
|
||||
m_restore_height(0),
|
||||
m_refresh_running(false),
|
||||
m_refresh_canceled(false) {
|
||||
@ -97,7 +98,6 @@ bool Wallet::parseFrom(std::istream& input) {
|
||||
return false;
|
||||
if (!serialization::serialize(ar, m_wallet))
|
||||
return false;
|
||||
set_current_blockchain_height(m_wallet.get_blockchain_current_height());
|
||||
captureTxHistorySnapshot(m_tx_history);
|
||||
m_account_ready = true;
|
||||
return true;
|
||||
@ -127,49 +127,40 @@ std::string Wallet::public_address() const {
|
||||
return account.get_public_address_str(m_wallet.nettype());
|
||||
}
|
||||
|
||||
void Wallet::set_current_blockchain_height(uint64_t height) {
|
||||
LOG_FATAL_IF(height >= CRYPTONOTE_MAX_BLOCK_NUMBER, "Blockchain max height reached");
|
||||
m_blockchain_height = height;
|
||||
}
|
||||
|
||||
cryptonote::account_base& Wallet::require_account() {
|
||||
LOG_FATAL_IF(!m_account_ready, "Account is not initialized");
|
||||
return m_wallet.get_account();
|
||||
}
|
||||
|
||||
const payment_details* find_matching_payment(
|
||||
const std::list<std::pair<crypto::hash, payment_details>> pds,
|
||||
const transfer_details& td) {
|
||||
if (td.m_txid == crypto::null_hash) {
|
||||
const payment_details* find_payment_by_txid(
|
||||
const std::list<std::pair<crypto::hash, payment_details>>& pds,
|
||||
const crypto::hash& txid) {
|
||||
if (txid == crypto::null_hash) {
|
||||
return nullptr;
|
||||
}
|
||||
for (const auto& p: pds) {
|
||||
const auto& pd = p.second;
|
||||
if (td.m_amount == pd.m_amount
|
||||
&& td.m_subaddr_index == pd.m_subaddr_index
|
||||
&& td.m_txid == pd.m_tx_hash) {
|
||||
return &pd;
|
||||
for (auto it = pds.begin(); it != pds.end(); ++it) {
|
||||
const auto pd = &it->second;
|
||||
if (txid == pd->m_tx_hash) {
|
||||
return pd;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
const confirmed_transfer_details* find_matching_transfer_for_change(
|
||||
const std::list<std::pair<crypto::hash, confirmed_transfer_details>> txs,
|
||||
const transfer_details& td) {
|
||||
if (td.m_txid == crypto::null_hash || td.m_subaddr_index.minor != 0) {
|
||||
const confirmed_transfer_details* find_transfer_by_txid(
|
||||
const std::list<std::pair<crypto::hash, confirmed_transfer_details>>& txs,
|
||||
const crypto::hash& txid) {
|
||||
if (txid == crypto::null_hash) {
|
||||
return nullptr;
|
||||
}
|
||||
for (const auto& p: txs) {
|
||||
const auto& tx = p.second;
|
||||
if (td.m_amount == tx.m_change
|
||||
&& td.m_subaddr_index.major == tx.m_subaddr_account
|
||||
&& td.m_txid == p.first) {
|
||||
return &tx;
|
||||
for (auto it = txs.begin(); it != txs.end(); ++it) {
|
||||
const auto tx = &it->second;
|
||||
if (txid == it->first) {
|
||||
return tx;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
// Only call this function from the callback thread or during initialization,
|
||||
// as there is no locking mechanism to safeguard reading transaction history
|
||||
@ -205,14 +196,14 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
||||
recv.m_amount = td.m_amount;
|
||||
recv.m_unlock_time = td.m_tx.unlock_time;
|
||||
|
||||
// Check if the payment or change exists and update metadata if found.
|
||||
if (const auto* pd = find_matching_payment(pds, td)) {
|
||||
// Check if the payment or transfer exists and update metadata if found.
|
||||
if (const auto* pd = find_payment_by_txid(pds, td.m_txid)) {
|
||||
recv.m_height = pd->m_block_height;
|
||||
recv.m_timestamp = pd->m_timestamp;
|
||||
recv.m_fee = pd->m_fee;
|
||||
recv.m_coinbase = pd->m_coinbase;
|
||||
recv.m_state = TxInfo::ON_CHAIN;
|
||||
} else if (const auto tx = find_matching_transfer_for_change(txs, td)) {
|
||||
} else if (const auto* tx = find_transfer_by_txid(txs, td.m_txid)) {
|
||||
recv.m_height = tx->m_block_height;
|
||||
recv.m_timestamp = tx->m_timestamp;
|
||||
recv.m_fee = tx->m_amount_in - tx->m_amount_out;
|
||||
@ -336,15 +327,11 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
||||
}
|
||||
}
|
||||
|
||||
void Wallet::handleNewBlock(uint64_t height, bool refresh_running) {
|
||||
set_current_blockchain_height(height);
|
||||
if (m_balance_changed) {
|
||||
m_tx_history_mutex.lock();
|
||||
captureTxHistorySnapshot(m_tx_history);
|
||||
m_tx_history_mutex.unlock();
|
||||
}
|
||||
notifyRefresh(!m_balance_changed && refresh_running);
|
||||
m_balance_changed = false;
|
||||
void Wallet::handleNewBlock(uint64_t height, uint64_t timestamp) {
|
||||
LOG_FATAL_IF(height >= CRYPTONOTE_MAX_BLOCK_NUMBER, "Blockchain max height reached");
|
||||
m_last_block_height = height;
|
||||
m_last_block_timestamp = timestamp;
|
||||
processBalanceChanges(true);
|
||||
}
|
||||
|
||||
void Wallet::handleReorgEvent(uint64_t at_block_height) {
|
||||
@ -355,11 +342,22 @@ void Wallet::handleMoneyEvent(uint64_t at_block_height) {
|
||||
m_balance_changed = true;
|
||||
}
|
||||
|
||||
void Wallet::notifyRefresh(bool debounce) {
|
||||
void Wallet::processBalanceChanges(bool refresh_running) {
|
||||
if (m_balance_changed) {
|
||||
m_tx_history_mutex.lock();
|
||||
captureTxHistorySnapshot(m_tx_history);
|
||||
m_tx_history_mutex.unlock();
|
||||
}
|
||||
notifyRefreshState(!m_balance_changed && refresh_running);
|
||||
m_balance_changed = false;
|
||||
}
|
||||
|
||||
void Wallet::notifyRefreshState(bool debounce) {
|
||||
static std::chrono::steady_clock::time_point last_time;
|
||||
// If debouncing is requested and the blockchain height is a multiple of 100, it limits
|
||||
// the notifications to once every 200 ms.
|
||||
uint32_t height = current_blockchain_height();
|
||||
uint64_t ts = current_blockchain_timestamp();
|
||||
if (debounce) {
|
||||
if (height % 100 == 0) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
@ -373,7 +371,7 @@ void Wallet::notifyRefresh(bool debounce) {
|
||||
}
|
||||
if (!debounce) {
|
||||
m_callback.callVoidMethod(getJniEnv(), WalletNative_onRefresh,
|
||||
height, m_balance_changed);
|
||||
height, ts, m_balance_changed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,7 +407,7 @@ Wallet::Status Wallet::nonReentrantRefresh(bool skip_coinbase) {
|
||||
}
|
||||
m_refresh_running.store(false);
|
||||
// Ensure the latest block and pool state are consistently processed.
|
||||
handleNewBlock(m_wallet.get_blockchain_current_height(), false);
|
||||
processBalanceChanges(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -583,6 +581,16 @@ Java_im_molly_monero_WalletNative_nativeGetCurrentBlockchainHeight(
|
||||
return wallet->current_blockchain_height();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_im_molly_monero_WalletNative_nativeGetCurrentBlockchainTimestamp(
|
||||
JNIEnv* env,
|
||||
jobject thiz,
|
||||
jlong handle) {
|
||||
auto* wallet = reinterpret_cast<Wallet*>(handle);
|
||||
return wallet->current_blockchain_timestamp();
|
||||
}
|
||||
|
||||
ScopedJvmLocalRef<jobject> nativeToJvmTxInfo(JNIEnv* env,
|
||||
const TxInfo& tx) {
|
||||
LOG_FATAL_IF(tx.m_height >= CRYPTONOTE_MAX_BLOCK_NUMBER, "Blockchain max height reached");
|
||||
|
@ -97,13 +97,15 @@ class Wallet : tools::i_wallet2_callback {
|
||||
|
||||
std::string public_address() const;
|
||||
|
||||
void set_current_blockchain_height(uint64_t height);
|
||||
uint32_t current_blockchain_height() const { return static_cast<uint32_t>(m_blockchain_height); }
|
||||
uint32_t current_blockchain_height() const { return static_cast<uint32_t>(m_last_block_height); }
|
||||
uint64_t current_blockchain_timestamp() const { return m_last_block_timestamp; }
|
||||
|
||||
// Extra state that must be persistent but isn't restored by wallet2's serializer.
|
||||
BEGIN_SERIALIZE_OBJECT()
|
||||
VERSION_FIELD(0)
|
||||
FIELD(m_restore_height)
|
||||
FIELD(m_last_block_height)
|
||||
FIELD(m_last_block_timestamp)
|
||||
END_SERIALIZE()
|
||||
|
||||
private:
|
||||
@ -113,7 +115,8 @@ class Wallet : tools::i_wallet2_callback {
|
||||
|
||||
bool m_account_ready;
|
||||
uint64_t m_restore_height;
|
||||
uint64_t m_blockchain_height;
|
||||
uint64_t m_last_block_height;
|
||||
uint64_t m_last_block_timestamp;
|
||||
|
||||
// Saved transaction history.
|
||||
std::vector<TxInfo> m_tx_history;
|
||||
@ -131,20 +134,22 @@ class Wallet : tools::i_wallet2_callback {
|
||||
bool m_refresh_canceled;
|
||||
bool m_balance_changed;
|
||||
|
||||
void notifyRefresh(bool debounce);
|
||||
void processBalanceChanges(bool refresh_running);
|
||||
void notifyRefreshState(bool debounce);
|
||||
|
||||
template<typename T>
|
||||
auto suspendRefreshAndRunLocked(T block) -> decltype(block());
|
||||
|
||||
void captureTxHistorySnapshot(std::vector<TxInfo>& snapshot);
|
||||
void handleNewBlock(uint64_t height, bool refresh_running);
|
||||
void handleNewBlock(uint64_t height, uint64_t timestmap);
|
||||
void handleReorgEvent(uint64_t at_block_height);
|
||||
void handleMoneyEvent(uint64_t at_block_height);
|
||||
|
||||
// Implementation of i_wallet2_callback follows.
|
||||
private:
|
||||
void on_new_block(uint64_t height, const cryptonote::block& block) override {
|
||||
handleNewBlock(height, true);
|
||||
// Block could be empty during a fast refresh.
|
||||
handleNewBlock(height, block.timestamp);
|
||||
}
|
||||
|
||||
void on_reorg(uint64_t height) override {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package im.molly.monero
|
||||
|
||||
import android.os.Parcelable
|
||||
import im.molly.monero.internal.constants.DIFFICULTY_TARGET_V2
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
@ -9,10 +11,11 @@ import java.time.LocalDate
|
||||
/**
|
||||
* A point in the blockchain timeline, which could be either a block height or a timestamp.
|
||||
*/
|
||||
@Parcelize
|
||||
open class BlockchainTime(
|
||||
val height: Int,
|
||||
val timestamp: Instant,
|
||||
) : Comparable<BlockchainTime> {
|
||||
) : Comparable<BlockchainTime>, Parcelable {
|
||||
|
||||
init {
|
||||
require(isBlockHeightInRange(height)) {
|
||||
@ -25,12 +28,15 @@ open class BlockchainTime(
|
||||
override fun compareTo(other: BlockchainTime): Int =
|
||||
this.height.compareTo(other.height)
|
||||
|
||||
override fun toString(): String = "Block #$height | $timestamp"
|
||||
override fun toString(): String = "Block $height | Time $timestamp"
|
||||
|
||||
data object Genesis : BlockchainTime(0, Instant.ofEpochSecond(1397818193))
|
||||
|
||||
class Block(height: Int, referencePoint: BlockchainTime = Genesis) :
|
||||
BlockchainTime(height, estimateTimestamp(height, referencePoint)) {}
|
||||
BlockchainTime(height, estimateTimestamp(height, referencePoint)) {
|
||||
|
||||
override fun toString(): String = "Block $height | Time $timestamp (Estimated)"
|
||||
}
|
||||
|
||||
class Timestamp(timestamp: Instant, referencePoint: BlockchainTime = Genesis) :
|
||||
BlockchainTime(estimateBlockHeight(timestamp, referencePoint), timestamp) {
|
||||
@ -41,6 +47,8 @@ open class BlockchainTime(
|
||||
|
||||
override fun compareTo(other: BlockchainTime): Int =
|
||||
this.timestamp.compareTo(other.timestamp)
|
||||
|
||||
override fun toString(): String = "Block $height (Estimated) | Time $timestamp"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -29,17 +29,14 @@ class MoneroWallet internal constructor(
|
||||
val listener = object : IBalanceListener.Stub() {
|
||||
lateinit var lastKnownLedger: Ledger
|
||||
|
||||
override fun onBalanceChanged(txHistory: List<TxInfo>, blockchainHeight: Int) {
|
||||
val now = Instant.now()
|
||||
val checkedAt = BlockchainTime(blockchainHeight, now)
|
||||
val (txs, spendableEnotes) = txHistory.consolidateTransactions(checkedAt)
|
||||
lastKnownLedger = Ledger(primaryAddress, txs, spendableEnotes, checkedAt)
|
||||
override fun onBalanceChanged(txHistory: List<TxInfo>, blockchainTime: BlockchainTime) {
|
||||
val (txs, spendableEnotes) = txHistory.consolidateTransactions(blockchainTime)
|
||||
lastKnownLedger = Ledger(primaryAddress, txs, spendableEnotes, blockchainTime)
|
||||
sendLedger(lastKnownLedger)
|
||||
}
|
||||
|
||||
override fun onRefresh(blockHeight: Int) {
|
||||
val checkedAt = BlockchainTime.Block(blockHeight)
|
||||
sendLedger(lastKnownLedger.copy(checkedAt = checkedAt))
|
||||
override fun onRefresh(blockchainTime: BlockchainTime) {
|
||||
sendLedger(lastKnownLedger.copy(checkedAt = blockchainTime))
|
||||
}
|
||||
|
||||
private fun sendLedger(ledger: Ledger) {
|
||||
@ -59,8 +56,8 @@ class MoneroWallet internal constructor(
|
||||
skipCoinbaseOutputs: Boolean = false,
|
||||
): RefreshResult = suspendCancellableCoroutine { continuation ->
|
||||
wallet.resumeRefresh(skipCoinbaseOutputs, object : BaseWalletCallbacks() {
|
||||
override fun onRefreshResult(blockHeight: Int, status: Int) {
|
||||
val result = RefreshResult(blockHeight, status)
|
||||
override fun onRefreshResult(blockchainTime: BlockchainTime, status: Int) {
|
||||
val result = RefreshResult(blockchainTime, status)
|
||||
continuation.resume(result) {}
|
||||
}
|
||||
})
|
||||
@ -81,11 +78,11 @@ class MoneroWallet internal constructor(
|
||||
}
|
||||
|
||||
private abstract class BaseWalletCallbacks : IWalletCallbacks.Stub() {
|
||||
override fun onRefreshResult(blockHeight: Int, status: Int) = Unit
|
||||
override fun onRefreshResult(blockchainTime: BlockchainTime, status: Int) = Unit
|
||||
|
||||
override fun onCommitResult(success: Boolean) = Unit
|
||||
}
|
||||
|
||||
class RefreshResult(val blockHeight: Int, private val status: Int) {
|
||||
class RefreshResult(val blockchainTime: BlockchainTime, private val status: Int) {
|
||||
fun isError() = status != WalletNative.Status.OK
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import im.molly.monero.internal.TxInfo
|
||||
import im.molly.monero.internal.consolidateTransactions
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.Closeable
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
@ -103,8 +104,19 @@ class WalletNative private constructor(
|
||||
|
||||
override fun getAccountPrimaryAddress() = nativeGetAccountPrimaryAddress(handle)
|
||||
|
||||
val currentBlockchainHeight: Int
|
||||
get() = nativeGetCurrentBlockchainHeight(handle)
|
||||
private fun createBlockchainTime(height: Int, epochSeconds: Long): BlockchainTime {
|
||||
return if (epochSeconds == 0L) {
|
||||
BlockchainTime.Block(height)
|
||||
} else {
|
||||
BlockchainTime(height, Instant.ofEpochSecond(epochSeconds))
|
||||
}
|
||||
}
|
||||
|
||||
val currentBlockchainTime: BlockchainTime
|
||||
get() = createBlockchainTime(
|
||||
nativeGetCurrentBlockchainHeight(handle),
|
||||
nativeGetCurrentBlockchainTimestamp(handle),
|
||||
)
|
||||
|
||||
val currentBalance: Balance
|
||||
get() = TODO() // txHistorySnapshot().consolidateTransactions().second.balance()
|
||||
@ -133,7 +145,7 @@ class WalletNative private constructor(
|
||||
nativeCancelRefresh(handle)
|
||||
}
|
||||
}
|
||||
callback?.onRefreshResult(currentBlockchainHeight, status)
|
||||
callback?.onRefreshResult(currentBlockchainTime, status)
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +175,7 @@ class WalletNative private constructor(
|
||||
requireNotNull(listener)
|
||||
balanceListenersLock.withLock {
|
||||
balanceListeners.add(listener)
|
||||
listener.onBalanceChanged(txHistorySnapshot(), currentBlockchainHeight)
|
||||
listener.onBalanceChanged(txHistorySnapshot(), currentBlockchainTime)
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,14 +187,15 @@ class WalletNative private constructor(
|
||||
}
|
||||
|
||||
@CalledByNative("wallet.cc")
|
||||
private fun onRefresh(blockchainHeight: Int, balanceChanged: Boolean) {
|
||||
private fun onRefresh(height: Int, timestamp: Long, balanceChanged: Boolean) {
|
||||
balanceListenersLock.withLock {
|
||||
if (balanceListeners.isNotEmpty()) {
|
||||
val call = fun(listener: IBalanceListener) {
|
||||
val blockchainTime = createBlockchainTime(height, timestamp)
|
||||
if (balanceChanged) {
|
||||
listener.onBalanceChanged(txHistorySnapshot(), blockchainHeight)
|
||||
listener.onBalanceChanged(txHistorySnapshot(), blockchainTime)
|
||||
} else {
|
||||
listener.onRefresh(blockchainHeight)
|
||||
listener.onRefresh(blockchainTime)
|
||||
}
|
||||
}
|
||||
balanceListeners.forEach { call(it) }
|
||||
@ -264,6 +277,7 @@ class WalletNative private constructor(
|
||||
private external fun nativeCreate(networkId: Int): Long
|
||||
private external fun nativeDispose(handle: Long)
|
||||
private external fun nativeGetCurrentBlockchainHeight(handle: Long): Int
|
||||
private external fun nativeGetCurrentBlockchainTimestamp(handle: Long): Long
|
||||
private external fun nativeGetTxHistory(handle: Long): Array<TxInfo>
|
||||
private external fun nativeGetAccountPrimaryAddress(handle: Long): String
|
||||
// private external fun nativeGetAccountSubAddress(handle: Long, accountIndex: Int, subAddressIndex: Int): String
|
||||
|
@ -83,7 +83,7 @@ internal fun List<TxInfo>.consolidateTransactions(
|
||||
if (tx.state !is TxState.Failed) {
|
||||
val maxUnlockTime = tx.blockHeight?.let { height ->
|
||||
val defaultUnlockTime = BlockchainTime.Block(
|
||||
height = height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE,
|
||||
height = height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE - 1,
|
||||
referencePoint = blockchainContext,
|
||||
)
|
||||
max(defaultUnlockTime, tx.timeLock ?: BlockchainTime.Genesis)
|
||||
@ -139,14 +139,15 @@ private fun List<TxInfo>.createTransaction(
|
||||
}
|
||||
|
||||
private fun List<TxInfo>.determineTxState(): TxState {
|
||||
val uniqueTx = distinctBy { it.state }.single()
|
||||
val height = maxOf { it.height }
|
||||
val timestamp = maxOf { it.timestamp }
|
||||
|
||||
return when (uniqueTx.state) {
|
||||
return when (val state = first().state) {
|
||||
TxInfo.OFF_CHAIN -> TxState.OffChain
|
||||
TxInfo.PENDING -> TxState.InMemoryPool
|
||||
TxInfo.FAILED -> TxState.Failed
|
||||
TxInfo.ON_CHAIN -> TxState.OnChain(BlockHeader(uniqueTx.height, uniqueTx.timestamp))
|
||||
else -> error("Invalid tx state value: ${uniqueTx.state}")
|
||||
TxInfo.ON_CHAIN -> TxState.OnChain(BlockHeader(height, timestamp))
|
||||
else -> error("Invalid tx state value: $state")
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,7 +158,7 @@ private fun TxInfo.toEnote(blockchainHeight: Int): Enote {
|
||||
subAddressIndex = subAddressMinor
|
||||
)
|
||||
|
||||
val calculatedAge = if (height == 0) 0 else blockchainHeight - height + 1
|
||||
val calculatedAge = if (height > 0) (blockchainHeight - height + 1) else 0
|
||||
|
||||
return Enote(
|
||||
amount = MoneroAmount(atomicUnits = amount),
|
||||
|
Loading…
Reference in New Issue
Block a user