[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-wallet-kotlin] 01/02: Get withdrawal details from exchange with s
From: |
gnunet |
Subject: |
[taler-wallet-kotlin] 01/02: Get withdrawal details from exchange with selected denominations |
Date: |
Wed, 15 Jul 2020 22:12:32 +0200 |
This is an automated email from the git hooks/post-receive script.
torsten-grote pushed a commit to branch master
in repository wallet-kotlin.
commit 3704cda578d0ad4ccbc5392dc40cbc8252da90e9
Author: Torsten Grote <t@grobox.de>
AuthorDate: Wed Jul 15 11:24:59 2020 -0300
Get withdrawal details from exchange with selected denominations
This also moves denomination related classes into their own file
---
.../net/taler/wallet/kotlin/crypto/RefreshTest.kt | 12 +-
.../net/taler/wallet/kotlin/crypto/Refresh.kt | 11 +-
.../taler/wallet/kotlin/exchange/Denomination.kt | 234 +++++++++++++++++++++
.../net/taler/wallet/kotlin/exchange/Exchange.kt | 5 +-
.../taler/wallet/kotlin/exchange/ExchangeRecord.kt | 95 +--------
.../net/taler/wallet/kotlin/exchange/Keys.kt | 91 --------
.../net/taler/wallet/kotlin/operations/Withdraw.kt | 146 +++++++++++--
.../kotlin/net/taler/wallet/kotlin/DbTest.kt | 4 +-
.../wallet/kotlin/exchange/DenominationTest.kt | 91 ++++++++
.../wallet/kotlin/{ => exchange}/Denominations.kt | 8 +-
.../taler/wallet/kotlin/operations/WithdrawTest.kt | 58 ++---
11 files changed, 500 insertions(+), 255 deletions(-)
diff --git
a/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
b/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
index 4ed903e..6cdad75 100644
--- a/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
+++ b/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
@@ -22,12 +22,12 @@ import net.taler.wallet.kotlin.CoinRecord
import net.taler.wallet.kotlin.CoinSourceType.WITHDRAW
import net.taler.wallet.kotlin.CoinStatus.DORMANT
import net.taler.wallet.kotlin.Timestamp
-import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo
import net.taler.wallet.kotlin.crypto.Refresh.RefreshPlanchetRecord
import net.taler.wallet.kotlin.crypto.Refresh.RefreshSessionRecord
-import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination
import net.taler.wallet.kotlin.exchange.DenominationRecord
-import net.taler.wallet.kotlin.exchange.DenominationStatus
+import net.taler.wallet.kotlin.exchange.DenominationSelectionInfo
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+import net.taler.wallet.kotlin.exchange.SelectedDenomination
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@@ -83,7 +83,7 @@ class RefreshTest {
stampExpireLegal = Timestamp(1688304984000),
stampExpireWithdraw = Timestamp(1594301784000),
stampStart = Timestamp(1593696984000),
- status = DenominationStatus.VerifiedGood,
+ status = VerifiedGood,
value = Amount("TESTKUDOS", fraction = 0,
value = 1)
)
),
@@ -104,7 +104,7 @@ class RefreshTest {
stampExpireLegal = Timestamp(1688304984000),
stampExpireWithdraw = Timestamp(1594301784000),
stampStart = Timestamp(1593696984000),
- status = DenominationStatus.VerifiedGood,
+ status = VerifiedGood,
value = Amount("TESTKUDOS", fraction =
10000000, value = 0)
)
),
@@ -125,7 +125,7 @@ class RefreshTest {
stampExpireLegal = Timestamp(1688304984000),
stampExpireWithdraw = Timestamp(1594301784000),
stampStart = Timestamp(1593696984000),
- status = DenominationStatus.VerifiedGood,
+ status = VerifiedGood,
value = Amount("TESTKUDOS", fraction =
1000000, value = 0)
)
)
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
index 602a1ab..cd24b07 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
@@ -23,7 +23,8 @@ import net.taler.wallet.kotlin.Timestamp
import net.taler.wallet.kotlin.crypto.Signature.Companion.WALLET_COIN_LINK
import net.taler.wallet.kotlin.crypto.Signature.Companion.WALLET_COIN_MELT
import net.taler.wallet.kotlin.crypto.Signature.PurposeBuilder
-import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.DenominationSelectionInfo
+import net.taler.wallet.kotlin.exchange.SelectedDenomination
internal class Refresh(private val crypto: Crypto) {
@@ -119,14 +120,6 @@ internal class Refresh(private val crypto: Crypto) {
val blindingKey: String
)
- data class DenominationSelectionInfo(
- val totalCoinValue: Amount,
- val totalWithdrawCost: Amount,
- val selectedDenominations: List<SelectedDenomination>
- )
-
- data class SelectedDenomination(val count: Int, val denominationRecord:
DenominationRecord)
-
/**
* Create a new refresh session.
*/
diff --git
a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Denomination.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Denomination.kt
new file mode 100644
index 0000000..88a81fd
--- /dev/null
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Denomination.kt
@@ -0,0 +1,234 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin.exchange
+
+import kotlinx.serialization.Serializable
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Base32Crockford
+import net.taler.wallet.kotlin.Duration
+import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+
+/**
+ * Denomination as found in the /keys response from the exchange.
+ */
+@Serializable
+internal data class Denomination(
+ /**
+ * Value of one coin of the denomination.
+ */
+ val value: Amount,
+
+ /**
+ * Public signing key of the denomination.
+ */
+ val denom_pub: String,
+
+ /**
+ * Fee for withdrawing.
+ */
+ val fee_withdraw: Amount,
+
+ /**
+ * Fee for depositing.
+ */
+ val fee_deposit: Amount,
+
+ /**
+ * Fee for refreshing.
+ */
+ val fee_refresh: Amount,
+
+ /**
+ * Fee for refunding.
+ */
+ val fee_refund: Amount,
+
+ /**
+ * Start date from which withdraw is allowed.
+ */
+ val stamp_start: Timestamp,
+
+ /**
+ * End date for withdrawing.
+ */
+ val stamp_expire_withdraw: Timestamp,
+
+ /**
+ * Expiration date after which the exchange can forget about
+ * the currency.
+ */
+ val stamp_expire_legal: Timestamp,
+
+ /**
+ * Date after which the coins of this denomination can't be
+ * deposited anymore.
+ */
+ val stamp_expire_deposit: Timestamp,
+
+ /**
+ * Signature over the denomination information by the exchange's master
+ * signing key.
+ */
+ val master_sig: String
+) {
+ fun toDenominationRecord(
+ baseUrl: String,
+ denomPubHash: ByteArray,
+ isOffered: Boolean,
+ isRevoked: Boolean,
+ status: DenominationStatus
+ ): DenominationRecord =
+ DenominationRecord(
+ denomPub = denom_pub,
+ denomPubHash = Base32Crockford.encode(denomPubHash),
+ exchangeBaseUrl = baseUrl,
+ feeDeposit = fee_deposit,
+ feeRefresh = fee_refresh,
+ feeRefund = fee_refund,
+ feeWithdraw = fee_withdraw,
+ isOffered = isOffered,
+ isRevoked = isRevoked,
+ masterSig = master_sig,
+ stampExpireDeposit = stamp_expire_deposit,
+ stampExpireLegal = stamp_expire_legal,
+ stampExpireWithdraw = stamp_expire_withdraw,
+ stampStart = stamp_start,
+ status = status,
+ value = value
+ )
+}
+
+enum class DenominationStatus {
+ /**
+ * Verification was delayed.
+ */
+ Unverified,
+
+ /**
+ * Verified as valid.
+ */
+ VerifiedGood,
+
+ /**
+ * Verified as invalid.
+ */
+ VerifiedBad
+}
+
+data class DenominationRecord(
+ /**
+ * Value of one coin of the denomination.
+ */
+ val value: Amount,
+ /**
+ * The denomination public key.
+ */
+ val denomPub: String,
+ /**
+ * Hash of the denomination public key.
+ * Stored in the database for faster lookups.
+ */
+ val denomPubHash: String,
+ /**
+ * Fee for withdrawing.
+ */
+ val feeWithdraw: Amount,
+ /**
+ * Fee for depositing.
+ */
+ val feeDeposit: Amount,
+ /**
+ * Fee for refreshing.
+ */
+ val feeRefresh: Amount,
+ /**
+ * Fee for refunding.
+ */
+ val feeRefund: Amount,
+ /**
+ * Validity start date of the denomination.
+ */
+ val stampStart: Timestamp,
+ /**
+ * Date after which the currency can't be withdrawn anymore.
+ */
+ val stampExpireWithdraw: Timestamp,
+ /**
+ * Date after the denomination officially doesn't exist anymore.
+ */
+ val stampExpireLegal: Timestamp,
+ /**
+ * Data after which coins of this denomination can't be deposited anymore.
+ */
+ val stampExpireDeposit: Timestamp,
+ /**
+ * Signature by the exchange's master key over the denomination
+ * information.
+ */
+ val masterSig: String,
+ /**
+ * Did we verify the signature on the denomination?
+ */
+ val status: DenominationStatus,
+ /**
+ * Was this denomination still offered by the exchange the last time
+ * we checked?
+ * Only false when the exchange redacts a previously published
denomination.
+ */
+ val isOffered: Boolean,
+ /**
+ * Did the exchange revoke the denomination?
+ * When this field is set to true in the database, the same transaction
+ * should also mark all affected coins as revoked.
+ */
+ val isRevoked: Boolean,
+ /**
+ * Base URL of the exchange.
+ */
+ val exchangeBaseUrl: String
+) {
+ fun isWithdrawable(now: Timestamp = Timestamp.now()): Boolean {
+ if (isRevoked) return false // can not use revoked denomination
+ if (status != Unverified && status != VerifiedGood) return false //
verified to be bad
+ if (now < stampStart) return false // denomination has not yet started
+ val lastPossibleWithdraw = stampExpireWithdraw - Duration(50 * 1000)
+ if ((lastPossibleWithdraw - now).ms == 0L) return false //
denomination has expired
+ return true
+ }
+}
+
+data class DenominationSelectionInfo(
+ val totalCoinValue: Amount,
+ val totalWithdrawCost: Amount,
+ val selectedDenominations: List<SelectedDenomination>
+) {
+ fun getEarliestDepositExpiry(): Timestamp {
+ if (selectedDenominations.isEmpty()) return Timestamp(
+ Timestamp.NEVER
+ )
+ var earliest =
selectedDenominations[0].denominationRecord.stampExpireDeposit
+ for (i in 1 until selectedDenominations.size) {
+ val stampExpireDeposit =
selectedDenominations[i].denominationRecord.stampExpireDeposit
+ if (stampExpireDeposit < earliest) earliest = stampExpireDeposit
+ }
+ return earliest
+ }
+}
+
+data class SelectedDenomination(val count: Int, val denominationRecord:
DenominationRecord)
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
index c8a89ef..e4a99b7 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
@@ -53,7 +53,8 @@ internal class Exchange(
) {
companion object {
- const val PROTOCOL_VERSION = "7:0:0"
+ private const val PROTOCOL_VERSION = "7:0:0"
+ fun getVersionMatch(version: String) =
compareVersions(PROTOCOL_VERSION, version)
fun normalizeUrl(exchangeBaseUrl: String): String {
var url = exchangeBaseUrl
if (!url.startsWith("http")) url = "http://$url"
@@ -103,7 +104,7 @@ internal class Exchange(
throw Error("Exchange doesn't offer any denominations")
}
// check if the exchange version is compatible
- val versionMatch = compareVersions(PROTOCOL_VERSION, keys.version)
+ val versionMatch = getVersionMatch(keys.version)
if (versionMatch == null || !versionMatch.compatible) {
throw Error("Exchange protocol version not compatible with wallet")
}
diff --git
a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
index 38d85ec..9bfd649 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
@@ -16,7 +16,6 @@
package net.taler.wallet.kotlin.exchange
-import net.taler.wallet.kotlin.Amount
import net.taler.wallet.kotlin.Timestamp
/**
@@ -86,6 +85,9 @@ data class ExchangeRecord(
init {
check(baseUrl == Exchange.normalizeUrl(baseUrl)) { "Base URL was not
normalized" }
}
+
+ val termsOfServiceAccepted: Boolean
+ get() = termsOfServiceAcceptedTimestamp != null &&
termsOfServiceAcceptedEtag == termsOfServiceLastEtag
}
/**
@@ -129,6 +131,7 @@ data class ExchangeWireInfo(
val accounts: List<ExchangeBankAccount>
)
+// TODO is this class needed?
data class ExchangeBankAccount(
val paytoUri: String
)
@@ -146,93 +149,3 @@ sealed class ExchangeUpdateReason(val value: String) {
object Forced : ExchangeUpdateReason("forced")
object Scheduled : ExchangeUpdateReason("scheduled")
}
-
-data class DenominationRecord(
- /**
- * Value of one coin of the denomination.
- */
- val value: Amount,
- /**
- * The denomination public key.
- */
- val denomPub: String,
- /**
- * Hash of the denomination public key.
- * Stored in the database for faster lookups.
- */
- val denomPubHash: String,
- /**
- * Fee for withdrawing.
- */
- val feeWithdraw: Amount,
- /**
- * Fee for depositing.
- */
- val feeDeposit: Amount,
- /**
- * Fee for refreshing.
- */
- val feeRefresh: Amount,
- /**
- * Fee for refunding.
- */
- val feeRefund: Amount,
- /**
- * Validity start date of the denomination.
- */
- val stampStart: Timestamp,
- /**
- * Date after which the currency can't be withdrawn anymore.
- */
- val stampExpireWithdraw: Timestamp,
- /**
- * Date after the denomination officially doesn't exist anymore.
- */
- val stampExpireLegal: Timestamp,
- /**
- * Data after which coins of this denomination can't be deposited anymore.
- */
- val stampExpireDeposit: Timestamp,
- /**
- * Signature by the exchange's master key over the denomination
- * information.
- */
- val masterSig: String,
- /**
- * Did we verify the signature on the denomination?
- */
- val status: DenominationStatus,
- /**
- * Was this denomination still offered by the exchange the last time
- * we checked?
- * Only false when the exchange redacts a previously published
denomination.
- */
- val isOffered: Boolean,
- /**
- * Did the exchange revoke the denomination?
- * When this field is set to true in the database, the same transaction
- * should also mark all affected coins as revoked.
- */
- val isRevoked: Boolean,
- /**
- * Base URL of the exchange.
- */
- val exchangeBaseUrl: String
-)
-
-enum class DenominationStatus {
- /**
- * Verification was delayed.
- */
- Unverified,
-
- /**
- * Verified as valid.
- */
- VerifiedGood,
-
- /**
- * Verified as invalid.
- */
- VerifiedBad
-}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
index 016f957..54806f9 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
@@ -19,8 +19,6 @@ package net.taler.wallet.kotlin.exchange
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import kotlinx.serialization.Serializable
-import net.taler.wallet.kotlin.Amount
-import net.taler.wallet.kotlin.Base32Crockford
import net.taler.wallet.kotlin.Timestamp
/**
@@ -86,95 +84,6 @@ data class SigningKey(
val master_sig: String
)
-/**
- * Denomination as found in the /keys response from the exchange.
- */
-@Serializable
-internal data class Denomination(
- /**
- * Value of one coin of the denomination.
- */
- val value: Amount,
-
- /**
- * Public signing key of the denomination.
- */
- val denom_pub: String,
-
- /**
- * Fee for withdrawing.
- */
- val fee_withdraw: Amount,
-
- /**
- * Fee for depositing.
- */
- val fee_deposit: Amount,
-
- /**
- * Fee for refreshing.
- */
- val fee_refresh: Amount,
-
- /**
- * Fee for refunding.
- */
- val fee_refund: Amount,
-
- /**
- * Start date from which withdraw is allowed.
- */
- val stamp_start: Timestamp,
-
- /**
- * End date for withdrawing.
- */
- val stamp_expire_withdraw: Timestamp,
-
- /**
- * Expiration date after which the exchange can forget about
- * the currency.
- */
- val stamp_expire_legal: Timestamp,
-
- /**
- * Date after which the coins of this denomination can't be
- * deposited anymore.
- */
- val stamp_expire_deposit: Timestamp,
-
- /**
- * Signature over the denomination information by the exchange's master
- * signing key.
- */
- val master_sig: String
-) {
- fun toDenominationRecord(
- baseUrl: String,
- denomPubHash: ByteArray,
- isOffered: Boolean,
- isRevoked: Boolean,
- status: DenominationStatus
- ): DenominationRecord = DenominationRecord(
- denomPub = denom_pub,
- denomPubHash = Base32Crockford.encode(denomPubHash),
- exchangeBaseUrl = baseUrl,
- feeDeposit = fee_deposit,
- feeRefresh = fee_refresh,
- feeRefund = fee_refund,
- feeWithdraw = fee_withdraw,
- isOffered = isOffered,
- isRevoked = isRevoked,
- masterSig = master_sig,
- stampExpireDeposit = stamp_expire_deposit,
- stampExpireLegal = stamp_expire_legal,
- stampExpireWithdraw = stamp_expire_withdraw,
- stampStart = stamp_start,
- status = status,
- value = value
- )
-}
-
/**
* Element of the payback list that the
* exchange gives us in /keys.
diff --git
a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
index b73688c..1fa2822 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
@@ -23,22 +23,29 @@ import kotlinx.serialization.Serializable
import net.taler.wallet.kotlin.Amount
import net.taler.wallet.kotlin.Db
import net.taler.wallet.kotlin.DbFactory
-import net.taler.wallet.kotlin.Duration
import net.taler.wallet.kotlin.TalerUri.parseWithdrawUri
import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.VersionMatchResult
+import net.taler.wallet.kotlin.crypto.Crypto
import net.taler.wallet.kotlin.crypto.CryptoFactory
-import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo
-import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination
import net.taler.wallet.kotlin.crypto.Signature
import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.DenominationSelectionInfo
import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+import net.taler.wallet.kotlin.exchange.Exchange
+import net.taler.wallet.kotlin.exchange.ExchangeRecord
+import net.taler.wallet.kotlin.exchange.ExchangeWireInfo
+import net.taler.wallet.kotlin.exchange.SelectedDenomination
import net.taler.wallet.kotlin.getDefaultHttpClient
internal class Withdraw(
private val httpClient: HttpClient = getDefaultHttpClient(),
private val db: Db = DbFactory().openDb(),
- private val signature: Signature = Signature(CryptoFactory.getCrypto())
+ private val crypto: Crypto = CryptoFactory.getCrypto(),
+ private val signature: Signature = Signature(crypto),
+ private val exchange: Exchange = Exchange(crypto, signature, httpClient,
db = db)
) {
data class BankDetails(
@@ -86,6 +93,123 @@ internal class Withdraw(
return response.toBankDetails(url)
}
+ /**
+ * Information about what will happen when creating a reserve.
+ *
+ * Sent to the wallet frontend to be rendered and shown to the user.
+ */
+ data class WithdrawalDetails(
+ /**
+ * Exchange that the reserve will be created at.
+ */
+ // TODO we probably don't need to include our internal exchange record
in here
+ val exchange: ExchangeRecord,
+
+ /**
+ * Selected denominations for withdraw.
+ */
+ val selectedDenominations: DenominationSelectionInfo,
+
+ /**
+ * Fees for withdraw.
+ */
+ val withdrawFee: Amount,
+
+ /**
+ * Remaining balance that is too small to be withdrawn.
+ */
+ val overhead: Amount,
+
+ /**
+ * The earliest deposit expiration of the selected coins.
+ */
+ // TODO what is this needed for?
+ val earliestDepositExpiration: Timestamp,
+
+ /**
+ * Number of currently offered denominations.
+ */
+ // TODO what is this needed for?
+ val numOfferedDenoms: Int
+ ) {
+ init {
+ check(exchange.details != null)
+ check(exchange.wireInfo != null)
+ }
+
+ /**
+ * Filtered wire info to send to the bank.
+ */
+ val exchangeWireAccounts: List<String> get() =
exchange.wireInfo!!.accounts.map { it.paytoUri }
+
+ /**
+ * Wire fees from the exchange.
+ */
+ val wireFees: ExchangeWireInfo get() = exchange.wireInfo!!
+
+ /**
+ * Did the user already accept the current terms of service for the
exchange?
+ */
+ val termsOfServiceAccepted: Boolean get() =
exchange.termsOfServiceAccepted
+
+ /**
+ * Result of checking the wallet's version against the exchange's
version.
+ */
+ val versionMatch: VersionMatchResult?
+ get() =
Exchange.getVersionMatch(exchange.details!!.protocolVersion)
+
+ }
+
+ internal suspend fun getWithdrawalDetails(exchangeBaseUrl: String, amount:
Amount): WithdrawalDetails {
+ val exchange = exchange.updateFromUrl(exchangeBaseUrl)
+ check(exchange.details != null)
+ check(exchange.wireInfo != null)
+ val selectedDenominations = selectDenominations(exchange, amount)
+ val possibleDenominations =
db.getDenominationsByBaseUrl(exchangeBaseUrl).filter { it.isOffered }
+ // TODO determine trust and audit status
+ return WithdrawalDetails(
+ exchange = exchange,
+ selectedDenominations = selectedDenominations,
+ withdrawFee = selectedDenominations.totalWithdrawCost -
selectedDenominations.totalCoinValue,
+ overhead = amount - selectedDenominations.totalWithdrawCost,
+ earliestDepositExpiration =
selectedDenominations.getEarliestDepositExpiry(),
+ numOfferedDenoms = possibleDenominations.size
+ )
+ }
+
+ /**
+ * Get a list of denominations to withdraw from the given exchange for the
given amount,
+ * making sure that all denominations' signatures are verified.
+ */
+ internal suspend fun selectDenominations(exchange: ExchangeRecord, amount:
Amount): DenominationSelectionInfo {
+ val exchangeDetails = exchange.details ?: throw Error("Exchange
$exchange details not available.")
+
+ val possibleDenominations = getPossibleDenominations(exchange.baseUrl)
+ val selectedDenominations = getDenominationSelection(amount,
possibleDenominations)
+ // TODO consider validating denominations before writing them into the
DB
+ for (selectedDenomination in
selectedDenominations.selectedDenominations) {
+ var denomination = selectedDenomination.denominationRecord
+ if (denomination.status == Unverified) {
+ val valid = signature.verifyDenominationRecord(denomination,
exchangeDetails.masterPublicKey)
+ denomination = if (!valid) {
+ denomination.copy(status = VerifiedBad)
+ } else {
+ denomination.copy(status = VerifiedGood)
+ }
+ db.put(denomination)
+ }
+ if (denomination.status == VerifiedBad) throw Error("Exchange
$exchange has bad denomination.")
+ }
+ return selectedDenominations
+ }
+
+ suspend fun getPossibleDenominations(exchangeBaseUrl: String):
List<DenominationRecord> {
+ return db.getDenominationsByBaseUrl(exchangeBaseUrl).filter {
denomination ->
+ (denomination.status == Unverified || denomination.status ==
VerifiedGood) &&
+ !denomination.isRevoked
+ }
+ }
+
/**
* Get a list of denominations (with repetitions possible)
* whose total value is as close as possible to the available amount, but
never larger.
@@ -98,7 +222,8 @@ internal class Withdraw(
var totalWithdrawCost = Amount.zero(amount.currency)
// denominations need to be sorted, so we try the highest ones first
- val denominations =
denoms.filter(this::isWithdrawableDenomination).sortedByDescending { it.value }
+ val now = Timestamp.now()
+ val denominations = denoms.filter { it.isWithdrawable(now)
}.sortedByDescending { it.value }
var remainingAmount = amount.copy()
val zero = Amount.zero(amount.currency)
for (d in denominations) {
@@ -125,15 +250,4 @@ internal class Withdraw(
)
}
- // TODO move into DenominationRecord?
- fun isWithdrawableDenomination(d: DenominationRecord): Boolean {
- if (d.isRevoked) return false // can not use revoked denomination
- if (d.status != Unverified && d.status != VerifiedGood) return false
// verified to be bad
- val now = Timestamp.now()
- if (now < d.stampStart) return false // denomination has not yet
started
- val lastPossibleWithdraw = d.stampExpireWithdraw - Duration(50 * 1000)
- if ((lastPossibleWithdraw - now).ms == 0L) return false //
denomination has expired
- return true
- }
-
}
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
index 32a8d88..ab4770d 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
@@ -16,8 +16,8 @@
package net.taler.wallet.kotlin
-import net.taler.wallet.kotlin.Denominations.denomination10
-import net.taler.wallet.kotlin.Denominations.denomination5
+import net.taler.wallet.kotlin.exchange.Denominations.denomination10
+import net.taler.wallet.kotlin.exchange.Denominations.denomination5
import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
import net.taler.wallet.kotlin.exchange.ExchangeRecord
diff --git
a/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/DenominationTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/DenominationTest.kt
new file mode 100644
index 0000000..f48c97d
--- /dev/null
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/DenominationTest.kt
@@ -0,0 +1,91 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin.exchange
+
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.Timestamp.Companion.NEVER
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+import net.taler.wallet.kotlin.exchange.Denominations.denomination1
+import net.taler.wallet.kotlin.exchange.Denominations.denomination10
+import net.taler.wallet.kotlin.exchange.Denominations.denomination2
+import net.taler.wallet.kotlin.exchange.Denominations.denomination5
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class DenominationTest {
+
+ @Test
+ fun testGetEarliestDepositExpiry() {
+ // empty selection info never expires
+ val infoEmpty = DenominationSelectionInfo(
+ totalCoinValue = Amount.zero("TESTKUDOS"),
+ totalWithdrawCost = Amount.zero("TESTKUDOS"),
+ selectedDenominations = emptyList()
+ )
+ assertEquals(Timestamp(NEVER), infoEmpty.getEarliestDepositExpiry())
+
+ // earliest expiry of single denomination is that of the denomination
+ val info1 = infoEmpty.copy(
+ selectedDenominations = listOf(SelectedDenomination(1,
denomination10))
+ )
+ assertEquals(denomination10.stampExpireDeposit,
info1.getEarliestDepositExpiry())
+
+ // denomination that expires earlier gets selected
+ val info2 = infoEmpty.copy(
+ selectedDenominations = listOf(
+ SelectedDenomination(3, denomination5.copy(stampExpireDeposit
= Timestamp(42))),
+ SelectedDenomination(2, denomination2.copy(stampExpireDeposit
= Timestamp(2))),
+ SelectedDenomination(1, denomination1.copy(stampExpireDeposit
= Timestamp(1)))
+ )
+ )
+ assertEquals(Timestamp(1), info2.getEarliestDepositExpiry())
+
+ // denomination that expires at all is earlier than the one that never
expires
+ val info3 = infoEmpty.copy(
+ selectedDenominations = listOf(
+ SelectedDenomination(2, denomination2.copy(stampExpireDeposit
= Timestamp(NEVER))),
+ SelectedDenomination(1, denomination1.copy(stampExpireDeposit
= Timestamp(1)))
+ )
+ )
+ assertEquals(Timestamp(1), info3.getEarliestDepositExpiry())
+ }
+
+ @Test
+ fun testIsWithdrawableDenomination() {
+ // denomination is withdrawable
+ assertTrue(denomination1.isWithdrawable())
+ // denomination is withdrawable when VerifiedGood
+ assertTrue(denomination1.copy(status = VerifiedGood).isWithdrawable())
+ // fails with VerifiedBad
+ assertFalse(denomination1.copy(status = VerifiedBad).isWithdrawable())
+ // fails when revoked
+ assertFalse(denomination1.copy(isRevoked = true).isWithdrawable())
+ // fails when not started
+ assertFalse(denomination1.copy(stampStart =
Timestamp(Timestamp.now().ms + 9999)).isWithdrawable())
+ // fails when expired
+ assertFalse(denomination1.copy(stampExpireWithdraw =
Timestamp.now()).isWithdrawable())
+ // fails when almost expired
+ assertFalse(denomination1.copy(stampExpireWithdraw =
Timestamp(Timestamp.now().ms + 5000)).isWithdrawable())
+ // succeeds when not quite expired
+ assertTrue(denomination1.copy(stampExpireWithdraw =
Timestamp(Timestamp.now().ms + 51000)).isWithdrawable())
+ }
+
+}
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/Denominations.kt
similarity index 97%
rename from src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt
rename to
src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/Denominations.kt
index 2049cf4..8cfd7fe 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/Denominations.kt
@@ -14,16 +14,18 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.wallet.kotlin
+package net.taler.wallet.kotlin.exchange
-import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Timestamp
import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
object Denominations {
private val validStart = Timestamp.now()
- private val validExpireWithdraw = Timestamp(Timestamp.now().ms + 1000L *
60L * 60L * 24L * 365L)
+ private val validExpireWithdraw =
+ Timestamp(Timestamp.now().ms + 1000L * 60L * 60L * 24L * 365L)
val denomination10 = DenominationRecord(
denomPub =
"020000X0X3G1EBB22XJ4HD6R8545R294TMCMA13ZRW7R101KJENFGTNTZSPGA0XP898FJEVHY4SJTC0SM264K0Y7Q6E24S35JSFZXD6VAJDJX8FCERBTNFV5DZR8V4GV7DAD062CPZBEVGNDEJQCTHVFJP84QWVPYJFNZSS3EJEK3WKJVG5EM3X2JPM1C97AB26VSZXWNYNC2CNJN7KG2001",
denomPubHash =
"7GB2YKDWKQ3DS2GA9XCVXVPMPJQA9M7Q0DFDHCX5M71J4E2PEHAJK3QF3KTJTWJA33KG0BX6XX0TTRMMZ8CEBM4GSE2N5FSV7GYRGH0",
diff --git
a/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
index 03b629e..413deef 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
@@ -17,33 +17,41 @@
package net.taler.wallet.kotlin.operations
import net.taler.wallet.kotlin.Amount
-import net.taler.wallet.kotlin.Denominations.denomination0d01
-import net.taler.wallet.kotlin.Denominations.denomination0d1
-import net.taler.wallet.kotlin.Denominations.denomination1
-import net.taler.wallet.kotlin.Denominations.denomination10
-import net.taler.wallet.kotlin.Denominations.denomination2
-import net.taler.wallet.kotlin.Denominations.denomination4
-import net.taler.wallet.kotlin.Denominations.denomination5
-import net.taler.wallet.kotlin.Denominations.denomination8
-import net.taler.wallet.kotlin.Timestamp
-import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo
-import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination
-import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
-import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+import net.taler.wallet.kotlin.exchange.DenominationSelectionInfo
+import net.taler.wallet.kotlin.exchange.Denominations.denomination0d01
+import net.taler.wallet.kotlin.exchange.Denominations.denomination0d1
+import net.taler.wallet.kotlin.exchange.Denominations.denomination1
+import net.taler.wallet.kotlin.exchange.Denominations.denomination10
+import net.taler.wallet.kotlin.exchange.Denominations.denomination2
+import net.taler.wallet.kotlin.exchange.Denominations.denomination4
+import net.taler.wallet.kotlin.exchange.Denominations.denomination5
+import net.taler.wallet.kotlin.exchange.Denominations.denomination8
+import net.taler.wallet.kotlin.exchange.SelectedDenomination
import net.taler.wallet.kotlin.getMockHttpClient
import net.taler.wallet.kotlin.giveJsonResponse
import net.taler.wallet.kotlin.operations.Withdraw.BankDetails
import net.taler.wallet.kotlin.runCoroutine
+import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
internal class WithdrawTest {
private val httpClient = getMockHttpClient()
private val withdraw = Withdraw(httpClient)
+ @Ignore // live test that requires internet connectivity and a working
exchange
+ @Test
+ fun testLiveUpdate() {
+ runCoroutine {
+ val withdraw = Withdraw() // use own instance without mocked HTTP
client
+ val url = "http://exchange.test.taler.net/"
+ val amount = Amount("TESTKUDOS", 5, 0)
+ val details = withdraw.getWithdrawalDetails(url, amount)
+ assertEquals(url, details.exchange.baseUrl)
+ }
+ }
+
@Test
fun getBankWithdrawalInfo() {
val bankDetails = BankDetails(
@@ -74,26 +82,6 @@ internal class WithdrawTest {
}
}
- @Test
- fun testIsWithdrawableDenomination() {
- // denomination is withdrawable
- assertTrue(withdraw.isWithdrawableDenomination(denomination1))
- // denomination is withdrawable when VerifiedGood
-
assertTrue(withdraw.isWithdrawableDenomination(denomination1.copy(status =
VerifiedGood)))
- // fails with VerifiedBad
-
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(status =
VerifiedBad)))
- // fails when revoked
-
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(isRevoked =
true)))
- // fails when not started
-
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampStart =
Timestamp(Timestamp.now().ms + 9999))))
- // fails when expired
-
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
= Timestamp.now())))
- // fails when almost expired
-
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
= Timestamp(Timestamp.now().ms + 5000))))
- // succeeds when not quite expired
-
assertTrue(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
= Timestamp(Timestamp.now().ms + 51000))))
- }
-
@Test
fun testGetDenominationSelection() {
val allDenominations = listOf(
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.