lib: fix instrumented tests

This commit is contained in:
Oscar Mira 2025-01-29 17:58:12 +01:00
parent 181b3dc442
commit a664ce1652
No known key found for this signature in database
GPG Key ID: B371B98C5DC32237
9 changed files with 107 additions and 57 deletions

View File

@ -1,7 +1,7 @@
package im.molly.monero
import android.os.Parcel
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import kotlin.random.Random
@ -12,13 +12,13 @@ class SecretKeyParcelableTest {
val secret = Random.nextBytes(32)
val originalKey = SecretKey(secret)
val parcel = Parcel.obtain()
val parcel = Parcel.obtain().apply {
originalKey.writeToParcel(this, 0)
setDataPosition(0)
}
originalKey.writeToParcel(parcel, 0)
val recreatedKey = SecretKey.CREATOR.createFromParcel(parcel)
parcel.setDataPosition(0)
val key = SecretKey.create(parcel)
Truth.assertThat(key == originalKey).isTrue()
assertThat(recreatedKey).isEqualTo(originalKey)
}
}

View File

@ -2,63 +2,64 @@ package im.molly.monero
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
class WalletNativeTest {
@LargeTest
@Test
fun keyGenerationIsDeterministic() {
fun keyGenerationIsDeterministic() = runTest {
assertThat(
WalletNative.localSyncWallet(
networkId = MoneroNetwork.Mainnet.id,
secretSpendKey = SecretKey("d2ca26e22489bd9871c910c58dee3ab08e66b9d566825a064c8c0af061cd8706".parseHex()),
).primaryAccountAddress
).publicAddress
).isEqualTo("4AYjQM9HoAFNUeC3cvSfgeAN89oMMpMqiByvunzSzhn97cj726rJj3x8hCbH58UnMqQJShczCxbpWRiCJQ3HCUDHLiKuo4T")
assertThat(
WalletNative.localSyncWallet(
networkId = MoneroNetwork.Testnet.id,
secretSpendKey = SecretKey("48a35268bc33227eea43ac1ecfd144d51efc023c115c26ca68a01cc6201e9900".parseHex()),
).primaryAccountAddress
).publicAddress
).isEqualTo("A1v6gVUcGgGE87c1uFRWB1KfPVik2qLLDJiZT3rhZ8qjF3BGA6oHzeDboD23dH8rFaFFcysyqwF6DBj8WUTBWwEhESB7nZz")
assertThat(
WalletNative.localSyncWallet(
networkId = MoneroNetwork.Stagenet.id,
secretSpendKey = SecretKey("561a8d4e121ffca7321a7dc6af79679ceb4cdc8c0dcb0ef588b574586c5fac04".parseHex()),
).primaryAccountAddress
).publicAddress
).isEqualTo("54kPaUhYgGNBT72N8Bv2DFMqstLGJCEcWg1EAjwpxABkKL3uBtBLAh4VAPKvhWBdaD4ZpiftA8YWFLAxnWL4aQ9TD4vhY4W")
}
@LargeTest
@Test
fun publicAddressesAreDistinct() {
fun publicAddressesAreDistinct() = runTest {
val publicAddress =
WalletNative.localSyncWallet(
networkId = MoneroNetwork.Mainnet.id,
secretSpendKey = randomSecretKey(),
).primaryAccountAddress
).publicAddress
val anotherPublicAddress =
WalletNative.localSyncWallet(
networkId = MoneroNetwork.Mainnet.id,
secretSpendKey = randomSecretKey(),
).primaryAccountAddress
).publicAddress
assertThat(publicAddress).isNotEqualTo(anotherPublicAddress)
}
@Test
fun atGenesisBalanceIsZero() {
fun atGenesisBalanceIsZero() = runTest {
with(
WalletNative.localSyncWallet(
networkId = MoneroNetwork.Mainnet.id,
secretSpendKey = randomSecretKey(),
).currentBalance
).getLedger()
) {
assertThat(totalAmount).isEqualTo(0.toAtomicUnits())
assertThat(totalAmountUnlockedAt(1)).isEqualTo(0.toAtomicUnits())
assertThat(transactions).isEmpty()
assertThat(isBalanceZero).isTrue()
}
}
}

View File

@ -72,6 +72,7 @@ void Wallet::restoreAccount(const std::vector<char>& secret_scalar, uint64_t res
std::lock_guard<std::mutex> lock(m_wallet_mutex);
auto& account = m_wallet.get_account();
GenerateAccountKeys(account, secret_scalar);
m_subaddresses[{0, 0}] = m_wallet.get_subaddress_as_str({0, 0});
if (restore_point < CRYPTONOTE_MAX_BLOCK_NUMBER) {
m_restore_height = restore_point;
m_last_block_timestamp = 0;

View File

@ -10,6 +10,9 @@ data class Ledger(
val transactions: Collection<Transaction>
get() = transactionById.values
val isBalanceZero: Boolean
get() = getBalance().totalAmount.isZero
fun getBalance(): Balance = enoteSet.calculateBalance()
fun getBalanceForAccount(accountIndex: Int): Balance =

View File

@ -1,7 +1,7 @@
package im.molly.monero
import im.molly.monero.internal.LedgerFactory
import im.molly.monero.internal.TxInfo
import im.molly.monero.internal.consolidateTransactions
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
@ -86,7 +86,7 @@ class MoneroWallet internal constructor(
suspendCancellableCoroutine { continuation ->
wallet.getAddressesForAccount(accountIndex, object : BaseWalletCallbacks() {
override fun onSubAddressListReceived(subAddresses: Array<String>) {
val accounts = parseAndAggregateAddresses(subAddresses)
val accounts = parseAndAggregateAddresses(subAddresses.asIterable())
continuation.resume(accounts.single()) {}
}
})
@ -96,23 +96,12 @@ class MoneroWallet internal constructor(
suspendCancellableCoroutine { continuation ->
wallet.getAllAddresses(object : BaseWalletCallbacks() {
override fun onSubAddressListReceived(subAddresses: Array<String>) {
val accounts = parseAndAggregateAddresses(subAddresses)
val accounts = parseAndAggregateAddresses(subAddresses.asIterable())
continuation.resume(accounts) {}
}
})
}
private fun parseAndAggregateAddresses(subAddresses: Array<String>): List<WalletAccount> =
subAddresses.map { AccountAddress.parseWithIndexes(it) }
.groupBy { it.accountIndex }
.map { (index, addresses) ->
WalletAccount(
addresses = addresses,
accountIndex = index,
)
}
.sortedBy { it.accountIndex }
/**
* A [Flow] of ledger changes.
*/
@ -125,17 +114,11 @@ class MoneroWallet internal constructor(
subAddresses: Array<String>,
blockchainTime: BlockchainTime,
) {
val indexedAccounts = parseAndAggregateAddresses(subAddresses)
val (txById, enotes) = txHistory.consolidateTransactions(
accounts = indexedAccounts,
blockchainContext = blockchainTime,
)
val ledger = Ledger(
publicAddress = publicAddress,
indexedAccounts = indexedAccounts,
transactionById = txById,
enoteSet = enotes,
checkedAt = blockchainTime,
val accounts = parseAndAggregateAddresses(subAddresses.asIterable())
val ledger = LedgerFactory.createFromTxHistory(
txHistory = txHistory,
accounts = accounts,
blockchainTime = blockchainTime,
)
sendLedger(ledger)
}
@ -145,7 +128,7 @@ class MoneroWallet internal constructor(
}
override fun onSubAddressListUpdated(subAddresses: Array<String>) {
val accountsUpdated = parseAndAggregateAddresses(subAddresses)
val accountsUpdated = parseAndAggregateAddresses(subAddresses.asIterable())
if (lastKnownLedger.indexedAccounts != accountsUpdated) {
sendLedger(lastKnownLedger.copy(indexedAccounts = accountsUpdated))
}

View File

@ -13,3 +13,11 @@ fun Iterable<WalletAccount>.findAddressByIndex(
it.accountIndex == accountIndex && it.subAddressIndex == subAddressIndex
}
}
fun parseAndAggregateAddresses(addresses: Iterable<String>): List<WalletAccount> {
return addresses
.map { AccountAddress.parseWithIndexes(it) }
.groupBy { it.accountIndex }
.toSortedMap()
.map { (index, addresses) -> WalletAccount(addresses, index) }
}

View File

@ -1,5 +1,7 @@
package im.molly.monero
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
@ -7,3 +9,16 @@ interface WalletDataStore {
suspend fun write(writer: (OutputStream) -> Unit)
suspend fun read(): InputStream
}
class InMemoryWalletDataStore : WalletDataStore {
private val data = ByteArrayOutputStream()
override suspend fun write(writer: (OutputStream) -> Unit) {
data.reset()
writer(data)
}
override suspend fun read(): InputStream {
return ByteArrayInputStream(data.toByteArray())
}
}

View File

@ -6,6 +6,7 @@ import im.molly.monero.internal.HttpRequest
import im.molly.monero.internal.HttpResponse
import im.molly.monero.internal.IHttpRequestCallback
import im.molly.monero.internal.IHttpRpcClient
import im.molly.monero.internal.LedgerFactory
import im.molly.monero.internal.TxInfo
import kotlinx.coroutines.*
import java.io.Closeable
@ -27,7 +28,7 @@ internal class WalletNative private constructor(
companion object {
suspend fun localSyncWallet(
networkId: Int,
storageAdapter: IStorageAdapter,
storageAdapter: IStorageAdapter = StorageAdapter(InMemoryWalletDataStore()),
rpcClient: IHttpRpcClient? = null,
secretSpendKey: SecretKey? = null,
restorePoint: Long? = null,
@ -105,7 +106,26 @@ internal class WalletNative private constructor(
}
}
override fun getPublicAddress() = nativeGetPublicAddress(handle)
override fun getPublicAddress(): String = nativeGetPublicAddress(handle)
fun getCurrentBlockchainTime(): BlockchainTime {
return network.blockchainTime(
nativeGetCurrentBlockchainHeight(handle),
nativeGetCurrentBlockchainTimestamp(handle),
)
}
fun getAllAccounts(): List<WalletAccount> {
return parseAndAggregateAddresses(getSubAddresses().asIterable())
}
fun getLedger(): Ledger {
return LedgerFactory.createFromTxHistory(
txHistory = getTxHistorySnapshot(),
accounts = getAllAccounts(),
blockchainTime = getCurrentBlockchainTime(),
)
}
private fun MoneroNetwork.blockchainTime(height: Int, epochSecond: Long): BlockchainTime {
// Block timestamp could be zero during a fast refresh.
@ -116,15 +136,6 @@ internal class WalletNative private constructor(
return BlockchainTime(height = height, timestamp = timestamp, network = this)
}
val currentBlockchainTime: BlockchainTime
get() = network.blockchainTime(
nativeGetCurrentBlockchainHeight(handle),
nativeGetCurrentBlockchainTimestamp(handle),
)
val currentBalance: Balance
get() = TODO() // txHistorySnapshot().consolidateTransactions().second.balance()
private fun getSubAddresses(accountIndex: Int? = null): Array<String> {
return nativeGetSubAddresses(accountIndex ?: -1, handle)
}
@ -155,7 +166,7 @@ internal class WalletNative private constructor(
nativeCancelRefresh(handle)
}
}
callback?.onRefreshResult(currentBlockchainTime, status)
callback?.onRefreshResult(getCurrentBlockchainTime(), status)
}
}
@ -268,7 +279,7 @@ internal class WalletNative private constructor(
balanceListenersLock.withLock {
balanceListeners.add(listener)
listener.onBalanceChanged(txHistory, subAddresses, currentBlockchainTime)
listener.onBalanceChanged(txHistory, subAddresses, getCurrentBlockchainTime())
}
}

View File

@ -0,0 +1,28 @@
package im.molly.monero.internal
import im.molly.monero.BlockchainTime
import im.molly.monero.Ledger
import im.molly.monero.WalletAccount
import im.molly.monero.findAddressByIndex
internal object LedgerFactory {
fun createFromTxHistory(
txHistory: List<TxInfo>,
accounts: List<WalletAccount>,
blockchainTime: BlockchainTime,
): Ledger {
val (txById, enotes) = txHistory.consolidateTransactions(
accounts = accounts,
blockchainContext = blockchainTime,
)
val publicAddress = accounts.findAddressByIndex(accountIndex = 0)
checkNotNull(publicAddress)
return Ledger(
publicAddress = publicAddress,
indexedAccounts = accounts,
transactionById = txById,
enoteSet = enotes,
checkedAt = blockchainTime,
)
}
}