gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]