[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-wallet-kotlin] branch master updated: Finish prototype of adding/
From: |
gnunet |
Subject: |
[taler-wallet-kotlin] branch master updated: Finish prototype of adding/updating an exchange |
Date: |
Mon, 13 Jul 2020 22:09:30 +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.
The following commit(s) were added to refs/heads/master by this push:
new 5f03889 Finish prototype of adding/updating an exchange
5f03889 is described below
commit 5f03889e649271347229af777ec1a025a6210f23
Author: Torsten Grote <t@grobox.de>
AuthorDate: Mon Jul 13 17:01:35 2020 -0300
Finish prototype of adding/updating an exchange
---
.idea/jarRepositories.xml | 5 +
build.gradle | 10 +-
.../kotlin/net/taler/wallet/kotlin/Db.kt | 34 +++-
.../kotlin/net/taler/wallet/kotlin/Types.kt | 23 ---
.../kotlin/net/taler/wallet/kotlin/Utils.kt | 6 +
.../net/taler/wallet/kotlin/crypto/Signature.kt | 2 +-
.../net/taler/wallet/kotlin/exchange/Exchange.kt | 108 +++++++++++--
.../taler/wallet/kotlin/exchange/ExchangeRecord.kt | 24 +--
.../net/taler/wallet/kotlin/exchange/Keys.kt | 13 +-
.../net/taler/wallet/kotlin/exchange/Wire.kt | 79 ++++++++++
.../taler/wallet/kotlin/crypto/SignatureTest.kt | 2 +-
.../exchange/{ExchangeTest.kt => KeysTest.kt} | 5 +-
.../taler/wallet/kotlin/exchange/UpdateTest.kt} | 32 ++--
.../net/taler/wallet/kotlin/exchange/WireTest.kt | 175 +++++++++++++++++++++
14 files changed, 442 insertions(+), 76 deletions(-)
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index b3e9cbd..b9b962c 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -21,5 +21,10 @@
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
+ <remote-repository>
+ <option name="id" value="maven" />
+ <option name="name" value="maven" />
+ <option name="url" value="https://dl.bintray.com/terl/lazysodium-maven"
/>
+ </remote-repository>
</component>
</project>
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index c173b78..007a4bd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -66,6 +66,7 @@ kotlin {
implementation kotlin('stdlib-common')
implementation
"org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutines_version"
implementation "io.ktor:ktor-client-core:$ktor_version"
+ implementation "io.ktor:ktor-client-logging:$ktor_version"
implementation
"io.ktor:ktor-client-serialization:$ktor_version"
implementation "com.soywiz.korlibs.klock:klock:1.11.12"
}
@@ -82,10 +83,13 @@ kotlin {
implementation kotlin('stdlib-jdk8')
implementation
"org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "io.ktor:ktor-client-android:$ktor_version"
+ implementation "io.ktor:ktor-client-logging-jvm:$ktor_version"
implementation
"io.ktor:ktor-client-serialization-jvm:$ktor_version"
// TODO Android
// implementation
"com.goterl.lazycode:lazysodium-android:4.1.1@aar"
- implementation "com.goterl.lazycode:lazysodium-java:4.2.6"
+ implementation("com.goterl.lazycode:lazysodium-java:4.2.6") {
+ exclude group: "org.slf4j"
+ }
implementation 'net.java.dev.jna:jna:5.5.0@aar'
}
}
@@ -94,6 +98,7 @@ kotlin {
implementation kotlin('test')
implementation kotlin('test-junit')
api "io.ktor:ktor-client-mock-jvm:$ktor_version"
+ implementation "org.slf4j:slf4j-simple:1.7.30"
}
}
jsMain {
@@ -101,12 +106,14 @@ kotlin {
implementation kotlin('stdlib-js')
implementation
"org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutines_version"
implementation "io.ktor:ktor-client-js:$ktor_version"
+ implementation "io.ktor:ktor-client-logging-js:$ktor_version"
implementation
"io.ktor:ktor-client-serialization-js:$ktor_version"
// bug: https://github.com/ktorio/ktor/issues/1822
api npm("text-encoding", '0.7.0') // work-around for above bug
api npm("bufferutil", '4.0.1') // work-around for above bug
api npm("utf-8-validate", '5.0.2') // work-around for above bug
api npm("abort-controller", '3.0.0') // work-around for above
bug
+ api npm("node-fetch", '2.6.0') // work-around for above bug
api npm("fs", '*') // work-around for above bug
implementation npm('tweetnacl', '1.0.3')
@@ -124,6 +131,7 @@ kotlin {
dependencies {
implementation
"org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutines_version"
implementation "io.ktor:ktor-client-curl:$ktor_version"
+ implementation
"io.ktor:ktor-client-logging-native:$ktor_version"
implementation
"io.ktor:ktor-client-serialization-native:$ktor_version"
}
}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
index 303c526..14a2f98 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
@@ -16,6 +16,8 @@
package net.taler.wallet.kotlin
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import net.taler.wallet.kotlin.exchange.DenominationRecord
import net.taler.wallet.kotlin.exchange.ExchangeRecord
@@ -25,35 +27,51 @@ internal interface Db {
suspend fun getExchangeByBaseUrl(baseUrl: String): ExchangeRecord?
suspend fun deleteExchangeByBaseUrl(baseUrl: String)
suspend fun put(denomination: DenominationRecord)
+ suspend fun <T> transaction(function: suspend Db.() -> T): T
}
internal expect class DbFactory() {
fun openDb(): Db
}
-internal class FakeDb: Db {
+internal class FakeDb : Db {
- private val exchanges = HashMap<String, ExchangeRecord>()
- private val denominations = HashMap<String, DenominationRecord>()
+ private data class Data(
+ val exchanges: HashMap<String, ExchangeRecord> = HashMap(),
+ val denominations: HashMap<String, DenominationRecord> = HashMap()
+ )
+
+ private var data = Data()
+ private val mutex = Mutex(false)
override suspend fun put(exchange: ExchangeRecord) {
- exchanges[exchange.baseUrl] = exchange
+ data.exchanges[exchange.baseUrl] = exchange
}
override suspend fun listExchanges(): List<ExchangeRecord> {
- return exchanges.values.toList()
+ return data.exchanges.values.toList()
}
override suspend fun getExchangeByBaseUrl(baseUrl: String):
ExchangeRecord? {
- return exchanges[baseUrl]
+ return data.exchanges[baseUrl]
}
override suspend fun deleteExchangeByBaseUrl(baseUrl: String) {
- exchanges.remove(baseUrl)
+ data.exchanges.remove(baseUrl)
}
override suspend fun put(denomination: DenominationRecord) {
- denominations[denomination.exchangeBaseUrl] = denomination
+ data.denominations[denomination.exchangeBaseUrl] = denomination
+ }
+
+ override suspend fun <T> transaction(function: suspend Db.() -> T): T =
mutex.withLock("transaction") {
+ val dataCopy = data.copy()
+ return@withLock try {
+ function()
+ } catch (e: Throwable) {
+ data = dataCopy
+ throw e
+ }
}
}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
index 2365795..04b17e7 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
@@ -16,29 +16,6 @@
package net.taler.wallet.kotlin
-data class WireFee(
- /**
- * Fee for wire transfers.
- */
- val wireFee: Amount,
- /**
- * Fees to close and refund a reserve.
- */
- val closingFee: Amount,
- /**
- * Start date of the fee.
- */
- val startStamp: Timestamp,
- /**
- * End date of the fee.
- */
- val endStamp: Timestamp,
- /**
- * Signature made by the exchange master key.
- */
- val signature: String
-)
-
class CoinRecord(
/**
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
index aa3fd91..2549195 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
@@ -19,6 +19,8 @@ package net.taler.wallet.kotlin
import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.client.features.logging.LogLevel
+import io.ktor.client.features.logging.Logging
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
@@ -27,6 +29,10 @@ fun getDefaultHttpClient(): HttpClient = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(Json(getJsonConfiguration()))
}
+ install(Logging) {
+// level = LogLevel.HEADERS
+ level = LogLevel.NONE
+ }
}
@OptIn(UnstableDefault::class)
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt
index ee1dff4..9b06756 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt
@@ -17,9 +17,9 @@
package net.taler.wallet.kotlin.crypto
import net.taler.wallet.kotlin.Base32Crockford
-import net.taler.wallet.kotlin.WireFee
import net.taler.wallet.kotlin.crypto.CryptoImpl.Companion.toByteArray
import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.WireFee
internal class Signature(private val crypto: Crypto) {
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 4124c30..c8a89ef 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
@@ -17,7 +17,13 @@
package net.taler.wallet.kotlin.exchange
import io.ktor.client.HttpClient
+import io.ktor.client.request.accept
import io.ktor.client.request.get
+import io.ktor.client.statement.HttpResponse
+import io.ktor.client.statement.readText
+import io.ktor.http.ContentType
+import io.ktor.http.HttpHeaders
+import io.ktor.http.HttpStatusCode
import net.taler.wallet.kotlin.Amount
import net.taler.wallet.kotlin.Base32Crockford
import net.taler.wallet.kotlin.Db
@@ -26,15 +32,23 @@ import net.taler.wallet.kotlin.Timestamp
import net.taler.wallet.kotlin.compareVersions
import net.taler.wallet.kotlin.crypto.Crypto
import net.taler.wallet.kotlin.crypto.CryptoFactory
+import net.taler.wallet.kotlin.crypto.Signature
import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
import net.taler.wallet.kotlin.exchange.ExchangeUpdateReason.Initial
import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchKeys
+import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchTerms
import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchWire
+import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FinalizeUpdate
+import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.Finished
import net.taler.wallet.kotlin.getDefaultHttpClient
internal class Exchange(
private val crypto: Crypto = CryptoFactory.getCrypto(),
+ private val signature: Signature = Signature(crypto),
private val httpClient: HttpClient = getDefaultHttpClient(),
+ // using the default Http client adds a json Accept header to each
request, so we need a different one
+ // because the exchange is returning XML when it doesn't exactly match a
mime type.
+ private val httpNoJsonClient: HttpClient = HttpClient(),
private val db: Db = DbFactory().openDb()
) {
@@ -49,6 +63,9 @@ internal class Exchange(
}
}
+ /**
+ * Update or add exchange DB entry by fetching the /keys, /wire and /terms
information.
+ */
suspend fun updateFromUrl(baseUrl: String): ExchangeRecord {
val now = Timestamp.now()
val url = normalizeUrl(baseUrl)
@@ -59,9 +76,17 @@ internal class Exchange(
updateStarted = now,
updateReason = Initial
).also { db.put(it) }
- record = updateKeys(record)
- // TODO update wire
- // TODO update ToS
+ val recordBeforeUpdate = record.copy()
+
+ record = updateKeys(record) // TODO add denominations in transaction
at the end
+ record = updateWireInfo(record)
+ record = updateTermsOfService(record)
+ record = finalizeUpdate(record)
+ db.transaction {
+ val dbRecord = getExchangeByBaseUrl(record.baseUrl)
+ if (dbRecord != recordBeforeUpdate) throw Error("Concurrent
modification of $dbRecord")
+ put(record)
+ }
return record
}
@@ -71,7 +96,7 @@ internal class Exchange(
* Exceptions thrown in this method must be caught and reported in the
pending operations.
*/
internal suspend fun updateKeys(record: ExchangeRecord): ExchangeRecord {
- val keys: Keys = fetchKeys(record.baseUrl)
+ val keys: Keys = Keys.fetch(httpClient, record.baseUrl)
// check if there are denominations offered
// TODO provide more error information for catcher
if (keys.denoms.isEmpty()) {
@@ -106,15 +131,6 @@ internal class Exchange(
return updatedRecord
}
- /**
- * Fetch an exchange's /keys with the given normalized base URL.
- *
- * Visible for testing.
- */
- internal suspend fun fetchKeys(baseUrl: String): Keys {
- return httpClient.get("${baseUrl}keys")
- }
-
/**
* Turn an exchange's denominations from /keys into [DenominationRecord]s
*
@@ -135,6 +151,72 @@ internal class Exchange(
)
}
+ /**
+ * Fetch wire information for an exchange and store it in the database.
+ */
+ internal suspend fun updateWireInfo(record: ExchangeRecord):
ExchangeRecord {
+ if (record.updateStatus != FetchWire) {
+ throw Error("Unexpected updateStatus: ${record.updateStatus},
expected: $FetchWire")
+ }
+ if (record.details == null) throw Error("Invalid exchange state")
+ val wire = Wire.fetch(httpClient, record.baseUrl)
+ // check account signatures
+ for (a in wire.accounts) {
+ val valid = signature.verifyWireAccount(
+ paytoUri = a.paytoUri,
+ signature = a.masterSig,
+ masterPub = record.details.masterPublicKey
+ )
+ if (!valid) throw Error("Exchange wire account signature invalid")
+ }
+ // check fee signatures
+ for (fee in wire.fees) {
+ val wireMethod = fee.key
+ val wireFees = fee.value
+ for (wireFee in wireFees) {
+ val valid = signature.verifyWireFee(
+ type = wireMethod,
+ wireFee = wireFee,
+ masterPub = record.details.masterPublicKey
+ )
+ if (!valid) throw Error("Exchange wire fee signature invalid")
+ checkCurrency(record.details.currency, wireFee.wireFee)
+ checkCurrency(record.details.currency, wireFee.closingFee)
+ }
+ }
+ val wireInfo = ExchangeWireInfo(
+ accounts = wire.accounts.map { ExchangeBankAccount(it.paytoUri) },
+ feesForType = wire.fees
+ )
+ return record.copy(updateStatus = FetchTerms, wireInfo = wireInfo)
+ }
+
+ /**
+ * Fetch wire information for an exchange and store it in the database.
+ */
+ internal suspend fun updateTermsOfService(record: ExchangeRecord):
ExchangeRecord {
+ if (record.updateStatus != FetchTerms) {
+ throw Error("Unexpected updateStatus: ${record.updateStatus},
expected: $FetchTerms")
+ }
+ val response: HttpResponse =
httpNoJsonClient.get("${record.baseUrl}terms") {
+ accept(ContentType.Text.Plain)
+ }
+ if (response.status != HttpStatusCode.OK) {
+ throw Error("/terms response has unexpected status code
(${response.status.value})")
+ }
+ val text = response.readText()
+ val eTag = response.headers[HttpHeaders.ETag]
+ return record.copy(updateStatus = FinalizeUpdate, termsOfServiceText =
text, termsOfServiceLastEtag = eTag)
+ }
+
+ internal fun finalizeUpdate(record: ExchangeRecord): ExchangeRecord {
+ if (record.updateStatus != FinalizeUpdate) {
+ throw Error("Unexpected updateStatus: ${record.updateStatus},
expected: $FinalizeUpdate")
+ }
+ // TODO store an event log for this update (exchangeUpdatedEvents)
+ return record.copy(updateStatus = Finished, addComplete = true)
+ }
+
private fun checkCurrency(currency: String, amount: Amount) {
if (currency != amount.currency) throw Error("Expected currency
$currency, but found ${amount.currency}")
}
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 d882249..38d85ec 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
@@ -18,7 +18,6 @@ package net.taler.wallet.kotlin.exchange
import net.taler.wallet.kotlin.Amount
import net.taler.wallet.kotlin.Timestamp
-import net.taler.wallet.kotlin.WireFee
/**
* Exchange record as stored in the wallet's database.
@@ -29,6 +28,11 @@ data class ExchangeRecord(
*/
val baseUrl: String,
+ /**
+ * Did we finish adding the exchange?
+ */
+ val addComplete: Boolean = false,
+
/**
* Was the exchange added as a built-in exchange?
*/
@@ -125,22 +129,22 @@ data class ExchangeWireInfo(
val accounts: List<ExchangeBankAccount>
)
-data class ExchangeBankAccount(
+data class ExchangeBankAccount(
val paytoUri: String
)
sealed class ExchangeUpdateStatus(val value: String) {
- object FetchKeys: ExchangeUpdateStatus("fetch-keys")
- object FetchWire: ExchangeUpdateStatus("fetch-wire")
- object FetchTerms: ExchangeUpdateStatus("fetch-terms")
- object FinalizeUpdate: ExchangeUpdateStatus("finalize-update")
- object Finished: ExchangeUpdateStatus("finished")
+ object FetchKeys : ExchangeUpdateStatus("fetch-keys")
+ object FetchWire : ExchangeUpdateStatus("fetch-wire")
+ object FetchTerms : ExchangeUpdateStatus("fetch-terms")
+ object FinalizeUpdate : ExchangeUpdateStatus("finalize-update")
+ object Finished : ExchangeUpdateStatus("finished")
}
sealed class ExchangeUpdateReason(val value: String) {
- object Initial: ExchangeUpdateReason("initial")
- object Forced: ExchangeUpdateReason("forced")
- object Scheduled: ExchangeUpdateReason("scheduled")
+ object Initial : ExchangeUpdateReason("initial")
+ object Forced : ExchangeUpdateReason("forced")
+ object Scheduled : ExchangeUpdateReason("scheduled")
}
data class DenominationRecord(
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 c7ea966..016f957 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
@@ -16,6 +16,8 @@
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
@@ -61,7 +63,16 @@ internal data class Keys(
* Protocol version.
*/
val version: String
-)
+) {
+ companion object {
+ /**
+ * Fetch an exchange's /keys with the given normalized base URL.
+ */
+ suspend fun fetch(httpClient: HttpClient, exchangeBaseUrl: String):
Keys {
+ return httpClient.get("${exchangeBaseUrl}keys")
+ }
+ }
+}
/**
* Structure of one exchange signing key in the /keys response.
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Wire.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Wire.kt
new file mode 100644
index 0000000..c8fae88
--- /dev/null
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Wire.kt
@@ -0,0 +1,79 @@
+/*
+ * 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 io.ktor.client.HttpClient
+import io.ktor.client.request.get
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Timestamp
+
+@Serializable
+internal data class Wire(
+ val accounts: List<AccountInfo>,
+ val fees: Map<String, List<WireFee>>
+) {
+ companion object {
+ /**
+ * Fetch an exchange's /wire with the given normalized base URL.
+ */
+ suspend fun fetch(httpClient: HttpClient, exchangeBaseUrl: String):
Wire {
+ return httpClient.get("${exchangeBaseUrl}wire")
+ }
+ }
+}
+
+@Serializable
+data class AccountInfo(
+ @SerialName("payto_uri")
+ val paytoUri: String,
+ @SerialName("master_sig")
+ val masterSig: String
+)
+
+/**
+ * Wire fees as announced by the exchange.
+ */
+@Serializable
+data class WireFee(
+ /**
+ * Fee for wire transfers.
+ */
+ @SerialName("wire_fee")
+ val wireFee: Amount,
+ /**
+ * Fees to close and refund a reserve.
+ */
+ @SerialName("closing_fee")
+ val closingFee: Amount,
+ /**
+ * Start date of the fee.
+ */
+ @SerialName("start_date")
+ val startStamp: Timestamp,
+ /**
+ * End date of the fee.
+ */
+ @SerialName("end_date")
+ val endStamp: Timestamp,
+ /**
+ * Signature made by the exchange master key.
+ */
+ @SerialName("sig")
+ val signature: String
+)
diff --git
a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt
index 02d9b1d..1306c14 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt
@@ -19,11 +19,11 @@ package net.taler.wallet.kotlin.crypto
import net.taler.wallet.kotlin.Amount
import net.taler.wallet.kotlin.Base32Crockford
import net.taler.wallet.kotlin.Timestamp
-import net.taler.wallet.kotlin.WireFee
import net.taler.wallet.kotlin.crypto.Signature.PurposeBuilder
import net.taler.wallet.kotlin.exchange.DenominationRecord
import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
+import net.taler.wallet.kotlin.exchange.WireFee
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git
a/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/KeysTest.kt
similarity index 99%
rename from
src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt
rename to src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/KeysTest.kt
index e7a2c48..a6b0c98 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/KeysTest.kt
@@ -24,10 +24,9 @@ import net.taler.wallet.kotlin.runCoroutine
import kotlin.test.Test
import kotlin.test.assertEquals
-class ExchangeTest {
+class KeysTest {
private val httpClient = getMockHttpClient()
- private val exchange = Exchange(httpClient = httpClient)
@Test
fun testFetchKeys() {
@@ -308,7 +307,7 @@ class ExchangeTest {
}""".trimIndent()
}
runCoroutine {
- val keys = exchange.fetchKeys("https://exchange.test.taler.net/")
+ val keys = Keys.fetch(httpClient,
"https://exchange.test.taler.net/")
assertEquals(expectedKeys, keys)
}
}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/UpdateTest.kt
similarity index 55%
copy from src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
copy to src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/UpdateTest.kt
index aa3fd91..271dc09 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/UpdateTest.kt
@@ -14,22 +14,24 @@
* 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 io.ktor.client.HttpClient
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.json.serializer.KotlinxSerializer
-import kotlinx.serialization.UnstableDefault
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonConfiguration
+import net.taler.wallet.kotlin.runCoroutine
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertTrue
-fun getDefaultHttpClient(): HttpClient = HttpClient {
- install(JsonFeature) {
- serializer = KotlinxSerializer(Json(getJsonConfiguration()))
+class UpdateTest {
+
+ private val exchange = Exchange()
+
+ @Ignore // live test that requires internet connectivity
+ @Test
+ fun testLiveUpdate() {
+ runCoroutine {
+ val record =
exchange.updateFromUrl("http://exchange.test.taler.net/")
+ assertTrue(record.addComplete)
+ }
}
-}
-@OptIn(UnstableDefault::class)
-internal fun getJsonConfiguration() = JsonConfiguration(
- ignoreUnknownKeys = true
-)
+}
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/WireTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/WireTest.kt
new file mode 100644
index 0000000..d09b44b
--- /dev/null
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/WireTest.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.getMockHttpClient
+import net.taler.wallet.kotlin.giveJsonResponse
+import net.taler.wallet.kotlin.runCoroutine
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class WireTest {
+
+ private val httpClient = getMockHttpClient()
+
+ @Test
+ fun testFetchWire() {
+ val expectedWire = Wire(
+ accounts = listOf(
+ AccountInfo(
+ paytoUri =
"payto://x-taler-bank/bank.test.taler.net/Exchange",
+ masterSig =
"5DMYMQCEFWG7B21RAX8XQ585V689K8DSSR065F04E2JK6G9AF1WM8EVDCHHBMVWRY3P02EWEE4M6YVKPY6B43H2CPCWHDP13RM1WR10"
+ )
+ ),
+ fees = mapOf(
+ "x-taler-bank" to listOf(
+ WireFee(
+ wireFee = Amount.fromJSONString("TESTKUDOS:0.04"),
+ closingFee = Amount.fromJSONString("TESTKUDOS:0.01"),
+ startStamp = Timestamp(1577833200000),
+ endStamp = Timestamp(1609455600000),
+ signature =
"9DS6TXPTM8ZBBTJS9VCRSD9FVS56ZY9EVWCF4HDA3Y2DNWSVMGS7XXPWE295EZ3E98KVV1SWDJ11CP0A0VDSRDZTM6RD2RPRG19ZA2G"
+ ),
+ WireFee(
+ wireFee = Amount.fromJSONString("TESTKUDOS:0.04"),
+ closingFee = Amount.fromJSONString("TESTKUDOS:0.01"),
+ startStamp = Timestamp(1609455600000),
+ endStamp = Timestamp(1640991600000),
+ signature =
"81852REBNR3ZRQHKQ2FPT6CPACED0MA0CW4V9CPDS3NP2JX6X8BE5YE5W1AKR9XPASEXSKQH6FEXHP7VJB64XWA7FDGH5DKCD3Q700G"
+ ),
+ WireFee(
+ wireFee = Amount.fromJSONString("TESTKUDOS:0.05"),
+ closingFee = Amount.fromJSONString("TESTKUDOS:0.01"),
+ startStamp = Timestamp(1640991600000),
+ endStamp = Timestamp(1672527600000),
+ signature =
"REYMSGH4QBNF339Q8TD5VJMMY6BV7KFTC1Y69YD69Y9E8Z5HXGNAKCQKT490MHBSF48894YADT1ATGDMSRZAQJJFVXF6HX9JEYDT61G"
+ ),
+ WireFee(
+ wireFee = Amount.fromJSONString("TESTKUDOS:0.06"),
+ closingFee = Amount.fromJSONString("TESTKUDOS:0.01"),
+ startStamp = Timestamp(1672527600000),
+ endStamp = Timestamp(1704063600000),
+ signature =
"BXB47D936XT7XDHGA3VA3461CY1GMQWFPVMSBY01N5SN6PBCGYRS8HSY19FJ0P5HVX3TGS9TAHY9X7RP4BQHPM4DMMS30TJ0EKG5A3G"
+ ),
+ WireFee(
+ wireFee = Amount.fromJSONString("TESTKUDOS:0.07"),
+ closingFee = Amount.fromJSONString("TESTKUDOS:0.01"),
+ startStamp = Timestamp(1704063600000),
+ endStamp = Timestamp(1735686000000),
+ signature =
"RFF1KV54BH9TJ8KBE8YEY8DM0R468PZYGW82G16P97EDHNN3XZVN4KK5E9CBZQ730WPJT0RKR3TTYPBWGTR0YQ064XZZDHJHHZN1418"
+ ),
+ WireFee(
+ wireFee = Amount.fromJSONString("TESTKUDOS:0.08"),
+ closingFee = Amount.fromJSONString("TESTKUDOS:0.01"),
+ startStamp = Timestamp(1735686000000),
+ endStamp = Timestamp(1767222000000),
+ signature =
"Q89VKJ54MF3DVG0NKK4N6VB96NCT0PRSTNBJ0SSB42SQTHB10JC68XJSDM6PRBBPEJ8CHDE9VVRZWW20VFSZFDTRA332JKDSBBFWY1G"
+ )
+ )
+ )
+ )
+ httpClient.giveJsonResponse("https://exchange.test.taler.net/wire") {
+ """{
+ "accounts": [
+ {
+ "payto_uri":
"payto://x-taler-bank/bank.test.taler.net/Exchange",
+ "master_sig":
"5DMYMQCEFWG7B21RAX8XQ585V689K8DSSR065F04E2JK6G9AF1WM8EVDCHHBMVWRY3P02EWEE4M6YVKPY6B43H2CPCWHDP13RM1WR10"
+ }
+ ],
+ "fees": {
+ "x-taler-bank": [
+ {
+ "wire_fee": "TESTKUDOS:0.04",
+ "closing_fee": "TESTKUDOS:0.01",
+ "start_date": {
+ "t_ms": 1577833200000
+ },
+ "end_date": {
+ "t_ms": 1609455600000
+ },
+ "sig":
"9DS6TXPTM8ZBBTJS9VCRSD9FVS56ZY9EVWCF4HDA3Y2DNWSVMGS7XXPWE295EZ3E98KVV1SWDJ11CP0A0VDSRDZTM6RD2RPRG19ZA2G"
+ },
+ {
+ "wire_fee": "TESTKUDOS:0.04",
+ "closing_fee": "TESTKUDOS:0.01",
+ "start_date": {
+ "t_ms": 1609455600000
+ },
+ "end_date": {
+ "t_ms": 1640991600000
+ },
+ "sig":
"81852REBNR3ZRQHKQ2FPT6CPACED0MA0CW4V9CPDS3NP2JX6X8BE5YE5W1AKR9XPASEXSKQH6FEXHP7VJB64XWA7FDGH5DKCD3Q700G"
+ },
+ {
+ "wire_fee": "TESTKUDOS:0.05",
+ "closing_fee": "TESTKUDOS:0.01",
+ "start_date": {
+ "t_ms": 1640991600000
+ },
+ "end_date": {
+ "t_ms": 1672527600000
+ },
+ "sig":
"REYMSGH4QBNF339Q8TD5VJMMY6BV7KFTC1Y69YD69Y9E8Z5HXGNAKCQKT490MHBSF48894YADT1ATGDMSRZAQJJFVXF6HX9JEYDT61G"
+ },
+ {
+ "wire_fee": "TESTKUDOS:0.06",
+ "closing_fee": "TESTKUDOS:0.01",
+ "start_date": {
+ "t_ms": 1672527600000
+ },
+ "end_date": {
+ "t_ms": 1704063600000
+ },
+ "sig":
"BXB47D936XT7XDHGA3VA3461CY1GMQWFPVMSBY01N5SN6PBCGYRS8HSY19FJ0P5HVX3TGS9TAHY9X7RP4BQHPM4DMMS30TJ0EKG5A3G"
+ },
+ {
+ "wire_fee": "TESTKUDOS:0.07",
+ "closing_fee": "TESTKUDOS:0.01",
+ "start_date": {
+ "t_ms": 1704063600000
+ },
+ "end_date": {
+ "t_ms": 1735686000000
+ },
+ "sig":
"RFF1KV54BH9TJ8KBE8YEY8DM0R468PZYGW82G16P97EDHNN3XZVN4KK5E9CBZQ730WPJT0RKR3TTYPBWGTR0YQ064XZZDHJHHZN1418"
+ },
+ {
+ "wire_fee": "TESTKUDOS:0.08",
+ "closing_fee": "TESTKUDOS:0.01",
+ "start_date": {
+ "t_ms": 1735686000000
+ },
+ "end_date": {
+ "t_ms": 1767222000000
+ },
+ "sig":
"Q89VKJ54MF3DVG0NKK4N6VB96NCT0PRSTNBJ0SSB42SQTHB10JC68XJSDM6PRBBPEJ8CHDE9VVRZWW20VFSZFDTRA332JKDSBBFWY1G"
+ }
+ ]
+ },
+ "master_public_key":
"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG"
+ }""".trimIndent()
+ }
+
+ runCoroutine {
+ val wire = Wire.fetch(httpClient,
"https://exchange.test.taler.net/")
+ assertEquals(expectedWire, wire)
+ }
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-wallet-kotlin] branch master updated: Finish prototype of adding/updating an exchange,
gnunet <=