commit 639007225869545654ca90c44859c63a37656b8b
parent 7c58aac506a51c27ddf0505957c1232d437277ba
Author: Antoine A <>
Date: Wed, 18 Jun 2025 14:16:17 +0200
Merge remote-tracking branch 'origin/dev/antoine/api-v2'
Diffstat:
49 files changed, 2877 insertions(+), 4831 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "addr2line"
@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "adler2"
-version = "2.0.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
@@ -27,6 +27,12 @@ dependencies = [
]
[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -49,9 +55,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
-version = "0.6.18"
+version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -64,40 +70,52 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.10"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
-version = "0.2.6"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
-version = "1.1.2"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
-version = "3.0.7"
+version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
- "once_cell",
+ "once_cell_polyfill",
"windows-sys 0.59.0",
]
[[package]]
+name = "anyhow"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
name = "async-trait"
version = "0.1.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -105,20 +123,83 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
]
[[package]]
name = "autocfg"
-version = "1.4.0"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "axum"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
+dependencies = [
+ "axum-core",
+ "bytes",
+ "form_urlencoded",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
[[package]]
name = "backtrace"
-version = "0.3.74"
+version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if",
@@ -126,14 +207,18 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
- "windows-targets",
+ "windows-targets 0.52.6",
]
[[package]]
-name = "base32"
-version = "0.5.1"
+name = "base58ck"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076"
+checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f"
+dependencies = [
+ "bitcoin-internals",
+ "bitcoin_hashes",
+]
[[package]]
name = "base64"
@@ -142,10 +227,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
-name = "bech32"
-version = "0.10.0-beta"
+name = "base64ct"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea"
+checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
[[package]]
name = "bech32"
@@ -155,12 +240,15 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
[[package]]
name = "bitcoin"
-version = "0.31.2"
+version = "0.32.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae"
+checksum = "ad8929a18b8e33ea6b3c09297b687baaa71fb1b97353243a3f1029fad5c59c5b"
dependencies = [
- "bech32 0.10.0-beta",
+ "base58ck",
+ "bech32",
"bitcoin-internals",
+ "bitcoin-io",
+ "bitcoin-units",
"bitcoin_hashes",
"hex-conservative",
"hex_lit",
@@ -170,29 +258,48 @@ dependencies = [
[[package]]
name = "bitcoin-internals"
-version = "0.2.0"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
+checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2"
dependencies = [
"serde",
]
[[package]]
-name = "bitcoin_hashes"
-version = "0.13.0"
+name = "bitcoin-io"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf"
+
+[[package]]
+name = "bitcoin-units"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
+checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2"
dependencies = [
"bitcoin-internals",
+ "serde",
+]
+
+[[package]]
+name = "bitcoin_hashes"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
+dependencies = [
+ "bitcoin-io",
"hex-conservative",
"serde",
]
[[package]]
name = "bitflags"
-version = "2.9.0"
+version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+dependencies = [
+ "serde",
+]
[[package]]
name = "block-buffer"
@@ -207,10 +314,9 @@ dependencies = [
name = "btc-wire"
version = "0.1.0"
dependencies = [
- "bech32 0.11.0",
+ "bech32",
"bitcoin",
"clap",
- "clap_lex",
"common",
"const-hex",
"criterion",
@@ -219,14 +325,14 @@ dependencies = [
"serde",
"serde_json",
"serde_repr",
- "thiserror 1.0.69",
+ "thiserror",
]
[[package]]
name = "bumpalo"
-version = "3.17.0"
+version = "3.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
[[package]]
name = "byteorder"
@@ -248,24 +354,24 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
-version = "1.2.17"
+version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
+checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "chrono"
-version = "0.4.40"
+version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -303,9 +409,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.34"
+version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff"
+checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -313,9 +419,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.34"
+version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489"
+checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
@@ -325,27 +431,27 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.32"
+version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
+checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
name = "clap_lex"
-version = "0.7.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "clap_mangen"
-version = "0.2.26"
+version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "724842fa9b144f9b89b3f3d371a89f3455eea660361d13a554f68f8ae5d6c13a"
+checksum = "fc33c849748320656a90832f54a5eeecaa598e92557fb5dedebc3355746d31e4"
dependencies = [
"clap",
"roff",
@@ -353,9 +459,9 @@ dependencies = [
[[package]]
name = "color-backtrace"
-version = "0.6.1"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "150fd80a270c0671379f388c8204deb6a746bb4eac8a6c03fe2460b2c0127ea0"
+checksum = "2123a5984bd52ca861c66f66a9ab9883b27115c607f801f86c1bc2a84eb69f0f"
dependencies = [
"backtrace",
"termcolor",
@@ -363,31 +469,45 @@ dependencies = [
[[package]]
name = "colorchoice"
-version = "1.0.3"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "common"
version = "0.1.0"
dependencies = [
- "base32",
+ "bitcoin",
+ "const-hex",
+ "ethereum-types",
"exponential-backoff",
"flexi_logger",
"log",
"postgres",
- "rand 0.8.5",
+ "rand 0.9.1",
"rust-ini",
"serde",
"serde_json",
"serde_with",
- "thiserror 1.0.69",
+ "sqlx",
+ "taler-api",
+ "taler-common",
+ "thiserror",
"uri-pack",
"url",
"zeroize",
]
[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "console"
version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -402,9 +522,9 @@ dependencies = [
[[package]]
name = "const-hex"
-version = "1.14.0"
+version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c"
+checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -414,6 +534,12 @@ dependencies = [
]
[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -428,7 +554,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
"once_cell",
"tiny-keccak",
]
@@ -453,7 +579,7 @@ dependencies = [
"cookie",
"document-features",
"idna",
- "indexmap 2.8.0",
+ "indexmap 2.9.0",
"log",
"serde",
"serde_derive",
@@ -478,6 +604,21 @@ dependencies = [
]
[[package]]
+name = "crc"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
+
+[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -542,6 +683,15 @@ dependencies = [
]
[[package]]
+name = "crossbeam-queue"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -585,6 +735,32 @@ dependencies = [
]
[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -605,7 +781,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -616,54 +792,45 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
-name = "data-encoding"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
-
-[[package]]
-name = "deadpool"
-version = "0.12.2"
+name = "dashmap"
+version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ed5957ff93768adf7a65ab167a17835c3d2c3c50d084fe305174c112f468e2f"
+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
- "deadpool-runtime",
- "num_cpus",
- "tokio",
+ "cfg-if",
+ "crossbeam-utils",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
]
[[package]]
-name = "deadpool-postgres"
-version = "0.14.1"
+name = "data-encoding"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d697d376cbfa018c23eb4caab1fd1883dd9c906a8c034e8d9a3cb06a7e0bef9"
-dependencies = [
- "async-trait",
- "deadpool",
- "getrandom 0.2.15",
- "tokio",
- "tokio-postgres",
- "tracing",
-]
+checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
-name = "deadpool-runtime"
-version = "0.1.4"
+name = "der"
+version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
- "tokio",
+ "const-oid",
+ "pem-rfc7468",
+ "zeroize",
]
[[package]]
name = "deranged"
-version = "0.4.1"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
"serde",
@@ -676,6 +843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
+ "const-oid",
"crypto-common",
"subtle",
]
@@ -688,7 +856,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -710,10 +878,46 @@ dependencies = [
]
[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "sha2",
+ "subtle",
+]
+
+[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+dependencies = [
+ "serde",
+]
[[package]]
name = "encode_unicode"
@@ -739,27 +943,37 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
-version = "0.3.10"
+version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
+checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if",
+ "home",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "eth-wire"
version = "0.1.0"
dependencies = [
"clap",
- "clap_lex",
"common",
"const-hex",
"ethereum-types",
"serde",
"serde_json",
"serde_repr",
- "thiserror 1.0.69",
+ "thiserror",
]
[[package]]
@@ -788,6 +1002,17 @@ dependencies = [
]
[[package]]
+name = "event-listener"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
name = "exponential-backoff"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -809,6 +1034,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
name = "fixed-hash"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -821,9 +1052,9 @@ dependencies = [
[[package]]
name = "flate2"
-version = "1.1.0"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
+checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -837,7 +1068,18 @@ checksum = "88a5a6882b2e137c4f2664562995865084eb5a00611fba30c582ef10354c4ad8"
dependencies = [
"chrono",
"log",
- "thiserror 2.0.12",
+ "thiserror",
+]
+
+[[package]]
+name = "flume"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spin",
]
[[package]]
@@ -847,6 +1089,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -872,6 +1120,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -879,7 +1155,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -901,9 +1177,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
+ "futures-io",
"futures-macro",
"futures-sink",
"futures-task",
+ "memchr",
"pin-project-lite",
"pin-utils",
"slab",
@@ -921,22 +1199,20 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
- "js-sys",
"libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
- "wasm-bindgen",
+ "wasi 0.11.1+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
@@ -951,10 +1227,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
+name = "glob"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
+
+[[package]]
name = "half"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
dependencies = [
"cfg-if",
"crunchy",
@@ -974,27 +1256,35 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
-version = "0.15.2"
+version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
[[package]]
-name = "heck"
-version = "0.5.0"
+name = "hashlink"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown 0.15.4",
+]
+
[[package]]
-name = "hermit-abi"
-version = "0.3.9"
+name = "heck"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
-version = "0.5.0"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "hex"
@@ -1004,9 +1294,12 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-conservative"
-version = "0.1.2"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20"
+checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd"
+dependencies = [
+ "arrayvec",
+]
[[package]]
name = "hex_lit"
@@ -1015,6 +1308,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1024,14 +1326,12 @@ dependencies = [
]
[[package]]
-name = "http"
-version = "0.2.12"
+name = "home"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
- "bytes",
- "fnv",
- "itoa",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1047,12 +1347,24 @@ dependencies = [
[[package]]
name = "http-body"
-version = "0.4.6"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
- "http 0.2.12",
+ "futures-core",
+ "http",
+ "http-body",
"pin-project-lite",
]
@@ -1070,38 +1382,37 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
-version = "0.14.32"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
+checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
dependencies = [
"bytes",
"futures-channel",
- "futures-core",
"futures-util",
- "http 0.2.12",
+ "http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
- "socket2",
+ "smallvec",
"tokio",
- "tower-service",
- "tracing",
- "want",
]
[[package]]
-name = "hyperlocal"
-version = "0.8.0"
+name = "hyper-util"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c"
+checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
dependencies = [
- "futures-util",
- "hex",
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
"hyper",
- "pin-project",
+ "pin-project-lite",
"tokio",
+ "tower-service",
]
[[package]]
@@ -1130,21 +1441,22 @@ dependencies = [
[[package]]
name = "icu_collections"
-version = "1.5.0"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
+ "potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
-name = "icu_locid"
-version = "1.5.0"
+name = "icu_locale_core"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
@@ -1154,30 +1466,10 @@ dependencies = [
]
[[package]]
-name = "icu_locid_transform"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
-dependencies = [
- "displaydoc",
- "icu_locid",
- "icu_locid_transform_data",
- "icu_provider",
- "tinystr",
- "zerovec",
-]
-
-[[package]]
-name = "icu_locid_transform_data"
-version = "1.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d"
-
-[[package]]
name = "icu_normalizer"
-version = "1.5.0"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
@@ -1185,68 +1477,55 @@ dependencies = [
"icu_properties",
"icu_provider",
"smallvec",
- "utf16_iter",
- "utf8_iter",
- "write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
-version = "1.5.1"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
-version = "1.5.1"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
- "icu_locid_transform",
+ "icu_locale_core",
"icu_properties_data",
"icu_provider",
- "tinystr",
+ "potential_utf",
+ "zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
-version = "1.5.1"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
-version = "1.5.0"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
- "icu_locid",
- "icu_provider_macros",
+ "icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
+ "zerotrie",
"zerovec",
]
[[package]]
-name = "icu_provider_macros"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.100",
-]
-
-[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1265,9 +1544,9 @@ dependencies = [
[[package]]
name = "idna_adapter"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
@@ -1295,12 +1574,12 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.8.0"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
+checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
- "hashbrown 0.15.2",
+ "hashbrown 0.15.4",
"serde",
]
@@ -1324,19 +1603,20 @@ dependencies = [
"bitcoin",
"btc-wire",
"clap",
- "clap_lex",
"clap_mangen",
"color-backtrace",
"common",
+ "const-hex",
"eth-wire",
"ethereum-types",
"fastrand",
- "hex",
"indicatif",
"libdeflater",
"owo-colors",
"rust-ini",
"signal-child",
+ "taler-api",
+ "taler-common",
"tempfile",
"thread-local-panic-hook",
"ureq",
@@ -1348,7 +1628,7 @@ version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
- "hermit-abi 0.5.0",
+ "hermit-abi",
"libc",
"windows-sys 0.59.0",
]
@@ -1375,6 +1655,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
+name = "jiff"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
+dependencies = [
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1389,36 +1694,55 @@ name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
[[package]]
name = "libc"
-version = "0.2.171"
+version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
+checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "libdeflate-sys"
-version = "1.23.1"
+version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38b72ad3fbf5ac78f2df7b36075e48adf2459b57c150b9e63937d0204d0f9cd7"
+checksum = "805824325366c44599dfeb62850fe3c7d7b3e3d75f9ab46785bc7dba3676815c"
dependencies = [
"cc",
]
[[package]]
name = "libdeflater"
-version = "1.23.1"
+version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "013344b17f9dceddff4872559ae19378bd8ee0479eccdd266d2dd2e894b4792f"
+checksum = "b270bcc7e9d6dce967a504a55b1b0444f966aa9184e8605b531bc0492abb30bb"
dependencies = [
"libdeflate-sys",
]
[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "libsqlite3-sys"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+dependencies = [
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "linux-raw-sys"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "listenfd"
@@ -1433,9 +1757,9 @@ dependencies = [
[[package]]
name = "litemap"
-version = "0.7.5"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "litrs"
@@ -1445,9 +1769,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "lock_api"
-version = "0.4.12"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
@@ -1460,6 +1784,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
+name = "matchit"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
+
+[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1471,28 +1801,61 @@ dependencies = [
[[package]]
name = "memchr"
-version = "2.7.4"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
-version = "0.8.5"
+version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
-version = "1.0.3"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.52.0",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+dependencies = [
+ "byteorder",
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand 0.8.5",
+ "smallvec",
+ "zeroize",
]
[[package]]
@@ -1502,22 +1865,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
-name = "num-traits"
-version = "0.2.19"
+name = "num-integer"
+version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
+ "num-integer",
+ "num-traits",
]
[[package]]
-name = "num_cpus"
-version = "1.16.0"
+name = "num-traits"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
- "hermit-abi 0.3.9",
- "libc",
+ "autocfg",
+ "libm",
]
[[package]]
@@ -1542,6 +1916,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
+name = "once_cell_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[package]]
name = "oorandom"
version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1558,16 +1938,28 @@ dependencies = [
]
[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
name = "owo-colors"
-version = "4.2.0"
+version = "4.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec"
+
+[[package]]
+name = "parking"
+version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
-version = "0.12.3"
+version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -1575,15 +1967,24 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.10"
+version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
- "windows-targets",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
]
[[package]]
@@ -1611,36 +2012,43 @@ dependencies = [
]
[[package]]
-name = "pin-project"
-version = "1.1.10"
+name = "pin-project-lite"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
-dependencies = [
- "pin-project-internal",
-]
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
-name = "pin-project-internal"
-version = "1.1.10"
+name = "pkcs1"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.100",
+ "der",
+ "pkcs8",
+ "spki",
]
[[package]]
-name = "pin-project-lite"
-version = "0.2.16"
+name = "pkcs8"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
[[package]]
-name = "pin-utils"
-version = "0.1.0"
+name = "pkg-config"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plotters"
@@ -1672,9 +2080,18 @@ dependencies = [
[[package]]
name = "portable-atomic"
-version = "1.11.0"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
+dependencies = [
+ "portable-atomic",
+]
[[package]]
name = "postgres"
@@ -1703,7 +2120,7 @@ dependencies = [
"hmac",
"md-5",
"memchr",
- "rand 0.9.0",
+ "rand 0.9.1",
"sha2",
"stringprep",
]
@@ -1720,6 +2137,15 @@ dependencies = [
]
[[package]]
+name = "potential_utf"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1747,24 +2173,24 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.94"
+version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proptest"
-version = "1.6.0"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50"
+checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f"
dependencies = [
"bitflags",
"lazy_static",
"num-traits",
- "rand 0.8.5",
- "rand_chacha 0.3.1",
+ "rand 0.9.1",
+ "rand_chacha 0.9.0",
"rand_xorshift",
"regex-syntax",
"unarray",
@@ -1783,13 +2209,13 @@ dependencies = [
[[package]]
name = "quickcheck_macros"
-version = "1.0.0"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
+checksum = "f71ee38b42f8459a88d3362be6f9b841ad2d5421844f61eb1c59c11bff3ac14a"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn",
]
[[package]]
@@ -1803,9 +2229,9 @@ dependencies = [
[[package]]
name = "r-efi"
-version = "5.2.0"
+version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
@@ -1820,13 +2246,12 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
- "zerocopy",
]
[[package]]
@@ -1855,7 +2280,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
]
[[package]]
@@ -1864,16 +2289,16 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
- "getrandom 0.3.2",
+ "getrandom 0.3.3",
]
[[package]]
name = "rand_xorshift"
-version = "0.3.0"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
+checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
- "rand_core 0.6.4",
+ "rand_core 0.9.3",
]
[[package]]
@@ -1898,14 +2323,34 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.5.10"
+version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
+checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [
"bitflags",
]
[[package]]
+name = "ref-cast"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1942,7 +2387,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
@@ -1955,6 +2400,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]]
+name = "rsa"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "rust-ini"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1967,9 +2432,9 @@ dependencies = [
[[package]]
name = "rustc-demangle"
-version = "0.1.24"
+version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]]
name = "rustc-hex"
@@ -1978,12 +2443,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
-name = "rustix"
-version = "1.0.3"
+name = "rustc_version"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
- "bitflags",
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags",
"errno",
"libc",
"linux-raw-sys",
@@ -1992,9 +2466,9 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.25"
+version = "0.23.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
+checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
dependencies = [
"log",
"once_cell",
@@ -2016,15 +2490,18 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "zeroize",
+]
[[package]]
name = "rustls-webpki"
-version = "0.103.1"
+version = "0.103.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
+checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
dependencies = [
"ring",
"rustls-pki-types",
@@ -2033,9 +2510,9 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.20"
+version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
@@ -2053,6 +2530,18 @@ dependencies = [
]
[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2060,9 +2549,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "secp256k1"
-version = "0.28.2"
+version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
+checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
dependencies = [
"bitcoin_hashes",
"secp256k1-sys",
@@ -2071,14 +2560,20 @@ dependencies = [
[[package]]
name = "secp256k1-sys"
-version = "0.9.2"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb"
+checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9"
dependencies = [
"cc",
]
[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2095,7 +2590,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -2111,6 +2606,16 @@ dependencies = [
]
[[package]]
+name = "serde_path_to_error"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
name = "serde_repr"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2118,7 +2623,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -2135,15 +2640,16 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.12.0"
+version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
+checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
- "indexmap 2.8.0",
+ "indexmap 2.9.0",
+ "schemars",
"serde",
"serde_derive",
"serde_json",
@@ -2153,21 +2659,32 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.12.0"
+version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
+checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77"
dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
]
[[package]]
name = "sha2"
-version = "0.10.8"
+version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -2175,6 +2692,15 @@ dependencies = [
]
[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2187,6 +2713,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3184fa464a0128cbcc353100ae752a848bc0067dd5715a50550f31570051150"
[[package]]
+name = "signal-hook-registry"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core 0.6.4",
+]
+
+[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2194,30 +2739,243 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
-version = "0.4.9"
+version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
-dependencies = [
- "autocfg",
-]
+checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
[[package]]
name = "smallvec"
-version = "1.14.0"
+version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+dependencies = [
+ "serde",
+]
[[package]]
name = "socket2"
-version = "0.5.9"
+version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "sqlx"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
+dependencies = [
+ "base64",
+ "bytes",
+ "crc",
+ "crossbeam-queue",
+ "either",
+ "event-listener",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashbrown 0.15.4",
+ "hashlink",
+ "indexmap 2.9.0",
+ "log",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rustls",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "thiserror",
+ "time",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "url",
+ "webpki-roots 0.26.11",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
+dependencies = [
+ "dotenvy",
+ "either",
+ "heck",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2",
+ "sqlx-core",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+ "syn",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "sqlx-mysql"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
+dependencies = [
+ "atoi",
+ "base64",
+ "bitflags",
+ "byteorder",
+ "bytes",
+ "crc",
+ "digest",
+ "dotenvy",
+ "either",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "generic-array",
+ "hex",
+ "hkdf",
+ "hmac",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rand 0.8.5",
+ "rsa",
+ "serde",
+ "sha1",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror",
+ "time",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
+dependencies = [
+ "atoi",
+ "base64",
+ "bitflags",
+ "byteorder",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac",
+ "home",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror",
+ "time",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-sqlite"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
+dependencies = [
+ "atoi",
+ "flume",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-intrusive",
+ "futures-util",
+ "libsqlite3-sys",
+ "log",
+ "percent-encoding",
+ "serde",
+ "serde_urlencoded",
+ "sqlx-core",
+ "thiserror",
+ "time",
+ "tracing",
+ "url",
+]
+
+[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2254,9 +3012,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "1.0.109"
+version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",
@@ -2264,35 +3022,77 @@ dependencies = [
]
[[package]]
-name = "syn"
-version = "2.0.100"
+name = "sync_wrapper"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "synstructure"
-version = "0.13.1"
+version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
+]
+
+[[package]]
+name = "taler-api"
+version = "0.0.0"
+source = "git+https://git.taler.net/taler-rust.git/#9d2bcbf4d1fe2e4efb2a024966077efeaddec0ae"
+dependencies = [
+ "axum",
+ "dashmap",
+ "ed25519-dalek",
+ "http-body-util",
+ "libdeflater",
+ "listenfd",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "sqlx",
+ "taler-common",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "taler-common"
+version = "0.0.0"
+source = "git+https://git.taler.net/taler-rust.git/#9d2bcbf4d1fe2e4efb2a024966077efeaddec0ae"
+dependencies = [
+ "anyhow",
+ "clap",
+ "fastrand",
+ "glob",
+ "indexmap 2.9.0",
+ "jiff",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "serde_with",
+ "sqlx",
+ "tempfile",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+ "url",
]
[[package]]
name = "tempfile"
-version = "3.19.1"
+version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
+checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [
"fastrand",
- "getrandom 0.3.2",
+ "getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.59.0",
@@ -2309,31 +3109,11 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
-dependencies = [
- "thiserror-impl 1.0.69",
-]
-
-[[package]]
-name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
- "thiserror-impl 2.0.12",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.100",
+ "thiserror-impl",
]
[[package]]
@@ -2344,7 +3124,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -2354,6 +3134,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70399498abd3ec85f99a2f2d765c8638588e20361678af93a9f47de96719743"
[[package]]
+name = "thread_local"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "time"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2395,9 +3184,9 @@ dependencies = [
[[package]]
name = "tinystr"
-version = "0.7.6"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
dependencies = [
"displaydoc",
"zerovec",
@@ -2430,15 +3219,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.44.1"
+version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
+checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"pin-project-lite",
+ "signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
@@ -2452,7 +3242,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -2474,7 +3264,7 @@ dependencies = [
"pin-project-lite",
"postgres-protocol",
"postgres-types",
- "rand 0.9.0",
+ "rand 0.9.1",
"socket2",
"tokio",
"tokio-util",
@@ -2482,10 +3272,21 @@ dependencies = [
]
[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
name = "tokio-util"
-version = "0.7.14"
+version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
+checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
dependencies = [
"bytes",
"futures-core",
@@ -2495,6 +3296,28 @@ dependencies = [
]
[[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2506,6 +3329,7 @@ version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
+ "log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -2513,35 +3337,55 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.28"
+version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
+checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
name = "tracing-core"
-version = "0.1.33"
+version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [
"once_cell",
+ "valuable",
]
[[package]]
-name = "trim-in-place"
-version = "0.1.7"
+name = "tracing-log"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
[[package]]
-name = "try-lock"
-version = "0.2.5"
+name = "tracing-subscriber"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
+dependencies = [
+ "nu-ansi-term",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "trim-in-place"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
[[package]]
name = "typenum"
@@ -2596,9 +3440,9 @@ checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unicode-width"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]]
name = "untrusted"
@@ -2608,9 +3452,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
-version = "3.0.10"
+version = "3.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0351ca625c7b41a8e4f9bb6c5d9755f67f62c2187ebedecacd9974674b271d"
+checksum = "9f0fde9bc91026e381155f8c67cb354bcd35260b2f4a29bcc84639f762760c39"
dependencies = [
"base64",
"cookie_store",
@@ -2624,17 +3468,17 @@ dependencies = [
"serde_json",
"ureq-proto",
"utf-8",
- "webpki-roots",
+ "webpki-roots 0.26.11",
]
[[package]]
name = "ureq-proto"
-version = "0.3.5"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae239d0a3341aebc94259414d1dc67cfce87d41cbebc816772c91b77902fafa4"
+checksum = "59db78ad1923f2b1be62b6da81fe80b173605ca0d57f85da2e005382adf693f7"
dependencies = [
"base64",
- "http 1.3.1",
+ "http",
"httparse",
"log",
]
@@ -2649,7 +3493,7 @@ dependencies = [
"quickcheck",
"quickcheck_macros",
"serde_json",
- "thiserror 1.0.69",
+ "thiserror",
"url",
]
@@ -2672,12 +3516,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
-name = "utf16_iter"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
-
-[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2691,9 +3529,25 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.16.0"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
@@ -2712,19 +3566,10 @@ dependencies = [
]
[[package]]
-name = "want"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
-dependencies = [
- "try-lock",
-]
-
-[[package]]
name = "wasi"
-version = "0.11.0+wasi-snapshot-preview1"
+version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
@@ -2763,7 +3608,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
"wasm-bindgen-shared",
]
@@ -2785,7 +3630,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2821,9 +3666,18 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "0.26.8"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.0",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
+checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb"
dependencies = [
"rustls-pki-types",
]
@@ -2872,9 +3726,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
-version = "0.61.0"
+version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
@@ -2891,7 +3745,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -2902,40 +3756,49 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
name = "windows-link"
-version = "0.1.1"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-result"
-version = "0.3.2"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
-version = "0.4.0"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "windows-targets",
+ "windows-targets 0.52.6",
]
[[package]]
@@ -2944,7 +3807,22 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
- "windows-targets",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
]
[[package]]
@@ -2953,30 +3831,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
@@ -2989,24 +3885,48 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
@@ -3015,23 +3935,17 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
name = "wire-gateway"
version = "0.1.0"
dependencies = [
+ "axum",
"bitcoin",
"clap",
- "clap_lex",
"common",
- "deadpool-postgres",
"ethereum-types",
- "hyper",
- "hyperlocal",
- "listenfd",
- "miniz_oxide",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "serde_with",
- "thiserror 1.0.69",
+ "sqlx",
+ "taler-api",
+ "taler-common",
+ "thiserror",
+ "time",
"tokio",
- "tokio-postgres",
]
[[package]]
@@ -3044,22 +3958,16 @@ dependencies = [
]
[[package]]
-name = "write16"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
-
-[[package]]
name = "writeable"
-version = "0.5.5"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yoke"
-version = "0.7.5"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
@@ -3069,34 +3977,34 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.7.5"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
"synstructure",
]
[[package]]
name = "zerocopy"
-version = "0.8.24"
+version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
+checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.24"
+version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
+checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
[[package]]
@@ -3116,7 +4024,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
"synstructure",
]
@@ -3127,10 +4035,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
name = "zerovec"
-version = "0.10.4"
+version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
@@ -3139,11 +4058,11 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.10.3"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn",
]
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,13 +1,45 @@
[workspace]
-resolver = "2"
+resolver = "3"
members = [
"wire-gateway",
"btc-wire",
"eth-wire",
"uri-pack",
"common",
- "instrumentation"
+ "instrumentation",
]
+[workspace.package]
+edition = "2024"
+authors = ["Taler Systems SA <deb@taler.net>"]
+homepage = "https://taler.net/"
+repository = "https://git.taler.net/depolymerization.git"
+license-file = "LICENSE"
+
[profile.dev]
-debug = true
-\ No newline at end of file
+debug = true
+
+[workspace.dependencies]
+thiserror = "2.0.4"
+serde_json = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+tokio = { version = "1.42", features = ["macros"] }
+axum = "0.8.1"
+sqlx = { version = "0.8", features = [
+ "postgres",
+ "runtime-tokio-rustls",
+ "tls-rustls-ring",
+ "time",
+], default-features = false }
+url = { version = "2.2", features = ["serde"] }
+taler-common = { git = "https://git.taler.net/taler-rust.git/" }
+taler-api = { git = "https://git.taler.net/taler-rust.git/" }
+bitcoin = { version = "0.32.5", features = [
+ "std",
+ "serde",
+], default-features = false }
+ethereum-types = { version = "0.15.1", default-features = false, features = [
+ "serialize",
+] }
+hex = { package = "const-hex", version = "1.9.1" }
+clap = { version = "4.5", features = ["derive"] }
diff --git a/README.md b/README.md
@@ -38,7 +38,7 @@ Depolymerizer require:
#### Bitcoin
-[Bitcoind](https://bitcoincore.org/) version 28.0 is expected
+[Bitcoind](https://bitcoincore.org/) version 28.1 is expected
#### Ethereum
diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml
@@ -1,9 +1,11 @@
[package]
name = "btc-wire"
version = "0.1.0"
-edition = "2021"
-license = "AGPL-3.0-or-later"
-rust-version = "1.72.1"
+edition.workspace = true
+authors.workspace = true
+homepage.workspace = true
+repository.workspace = true
+license-file.workspace = true
[features]
# Enable random failures
@@ -11,28 +13,24 @@ fail = []
[dependencies]
# Typed bitcoin rpc types
-bitcoin = { version = "0.31.2", features = [
- "std",
- "serde",
-], default-features = false }
+bitcoin.workspace = true
# Cli args parser
-clap = { version = "4.5.20", features = ["derive"] }
-clap_lex = "0.7.0"
+clap.workspace = true
# Bech32 encoding and decoding
bech32 = "0.11.0"
# Serialization library
-serde = { version = "1.0.188", features = ["derive"] }
-serde_json = "1.0.107"
+serde.workspace = true
+serde_json.workspace = true
serde_repr = "0.1.16"
# Error macros
-thiserror = "1.0.49"
+thiserror.workspace = true
data-encoding = "2.4.0"
# Common lib
common = { path = "../common" }
# Ini parser
rust-ini = "0.21.0"
# Hexadecimal encoding
-hex = { package = "const-hex", version = "1.9.1" }
+hex.workspace = true
[dev-dependencies]
# statistics-driven micro-benchmarks
diff --git a/btc-wire/benches/metadata.rs b/btc-wire/benches/metadata.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -15,7 +15,7 @@
*/
use btc_wire::segwit::{decode_segwit_msg, encode_segwit_key, rand_addresses};
use common::rand_slice;
-use criterion::{criterion_group, criterion_main, Criterion};
+use criterion::{Criterion, criterion_group, criterion_main};
fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("SegWit addresses");
diff --git a/btc-wire/src/bin/segwit-demo.rs b/btc-wire/src/bin/segwit-demo.rs
@@ -2,12 +2,9 @@ use std::str::FromStr;
use bech32::Hrp;
use bitcoin::{Address, Amount, Network};
-use btc_wire::segwit::decode_segwit_msg;
+use btc_wire::{guess_network, segwit::decode_segwit_msg};
use btc_wire::{rpc_utils, segwit::encode_segwit_addr};
-use common::{
- base32::{self, Alphabet},
- rand_slice,
-};
+use common::{rand_slice, taler_common::types::base32};
pub fn main() {
let address = Address::from_str("tb1qhxrhccqexg0dv4nltgkuw4fg2ce7muplmjsn0v")
@@ -28,10 +25,7 @@ pub fn main() {
);
println!("\nâ…¡ - Generate fake segwit addresses");
- let decoded: [u8; 32] = base32::decode(Alphabet::Crockford, reserve_pub)
- .unwrap()
- .try_into()
- .unwrap();
+ let decoded: [u8; 32] = base32::decode(reserve_pub.as_bytes()).unwrap();
println!("Decode reserve public key: 0x{}", hex::encode(&decoded[..]));
let prefix: [u8; 4] = rand_slice();
println!("Generate random prefix 0x{}", hex::encode(prefix));
@@ -55,7 +49,7 @@ pub fn main() {
hex::encode(&second_half)
);
// bech32: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
- let hrp = match address.network() {
+ let hrp = match guess_network(&address) {
Network::Bitcoin => "bc",
Network::Testnet | Network::Signet => "tb",
Network::Regtest => "bcrt",
@@ -72,7 +66,9 @@ pub fn main() {
println!("\nâ…¢ - Send to many");
let minimum = rpc_utils::segwit_min_amount().to_btc();
println!("Send a single bitcoin transaction with the three addresses as recipient as follow:");
- println!("\nIn bitcoincore wallet use 'Add Recipient' button to add two additional recipient and copy adresses and amounts");
+ println!(
+ "\nIn bitcoincore wallet use 'Add Recipient' button to add two additional recipient and copy adresses and amounts"
+ );
let first = Address::from_str(&first).unwrap().assume_checked();
let second = Address::from_str(&second).unwrap().assume_checked();
for (address, amount) in [(&address, btc), (&first, minimum), (&second, minimum)] {
diff --git a/btc-wire/src/btc_config.rs b/btc-wire/src/btc_config.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -22,7 +22,7 @@ use std::{
use bitcoin::Network;
use common::{
currency::CurrencyBtc,
- log::{fail, OrFail},
+ log::{OrFail, fail},
};
use crate::{
diff --git a/btc-wire/src/lib.rs b/btc-wire/src/lib.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -16,14 +16,16 @@
use std::path::{Path, PathBuf};
use std::str::FromStr;
-use bitcoin::{hashes::hex::FromHex, Address, Amount, Network, Txid};
+use bitcoin::{Address, Amount, Network, Txid, hashes::hex::FromHex};
use btc_config::BitcoinConfig;
-use common::api_common::Amount as TalerAmount;
-use common::config::TalerConfig;
-use common::currency::{Currency, CurrencyBtc};
-use common::log::{fail, OrFail};
-use common::postgres;
-use common::url::Url;
+use common::{
+ config::TalerConfig,
+ currency::{Currency, CurrencyBtc},
+ log::{OrFail, fail},
+ postgres,
+ taler_common::{api_common::EddsaPublicKey, types::amount::Amount as TalerAmount},
+ url::Url,
+};
use rpc::{Category, Rpc, Transaction};
use rpc_utils::{default_data_dir, segwit_min_amount, sender_address};
use segwit::{decode_segwit_msg, encode_segwit_key};
@@ -60,7 +62,8 @@ impl Rpc {
amount: &Amount,
metadata: &[u8; 32],
) -> rpc::Result<Txid> {
- let hrp = match to.network() {
+ let network = guess_network(to);
+ let hrp = match network {
Network::Bitcoin => bech32::hrp::BC,
Network::Testnet | Network::Signet => bech32::hrp::TB,
Network::Regtest => bech32::hrp::BCRT,
@@ -68,14 +71,8 @@ impl Rpc {
};
let addresses = encode_segwit_key(hrp, metadata);
let addresses = [
- Address::from_str(&addresses[0])
- .unwrap()
- .require_network(*to.network())
- .unwrap(),
- Address::from_str(&addresses[1])
- .unwrap()
- .require_network(*to.network())
- .unwrap(),
+ Address::from_str(&addresses[0]).unwrap().assume_checked(),
+ Address::from_str(&addresses[1]).unwrap().assume_checked(),
];
let mut recipients = vec![(to, amount)];
let min = segwit_min_amount();
@@ -87,7 +84,7 @@ impl Rpc {
pub fn get_tx_segwit_key(
&mut self,
id: &Txid,
- ) -> Result<(Transaction, [u8; 32]), GetSegwitErr> {
+ ) -> Result<(Transaction, EddsaPublicKey), GetSegwitErr> {
let full = self.get_tx(id)?;
let addresses: Vec<String> = full
@@ -230,3 +227,18 @@ fn check_network_currency(network: Network, currency: CurrencyBtc) {
))
}
}
+
+pub fn guess_network(address: &Address) -> Network {
+ let addr = address.as_unchecked();
+ for network in [
+ Network::Bitcoin,
+ Network::Regtest,
+ Network::Signet,
+ Network::Regtest,
+ ] {
+ if addr.is_valid_for_network(network) {
+ return network;
+ }
+ }
+ unreachable!()
+}
diff --git a/btc-wire/src/loops.rs b/btc-wire/src/loops.rs
@@ -25,7 +25,7 @@ pub mod worker;
#[derive(Debug, thiserror::Error)]
pub enum LoopError {
- #[error("RPC {0}")]
+ #[error(transparent)]
Rpc(#[from] rpc::Error),
#[error("DB {0}")]
DB(#[from] postgres::Error),
diff --git a/btc-wire/src/loops/worker.rs b/btc-wire/src/loops/worker.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -13,40 +13,36 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use std::{
- collections::HashMap,
- fmt::Write,
- time::{Duration, SystemTime},
-};
+use std::{collections::HashMap, fmt::Write, time::SystemTime};
-use bitcoin::{hashes::Hash, Amount as BtcAmount, BlockHash, Txid};
+use bitcoin::{Amount as BtcAmount, BlockHash, Txid, hashes::Hash};
use btc_wire::{
+ GetOpReturnErr, GetSegwitErr,
rpc::{self, AutoRpcWallet, Category, ErrorCode, Rpc, Transaction},
rpc_utils::sender_address,
- taler_utils::{btc_payto_url, btc_to_taler},
- GetOpReturnErr, GetSegwitErr,
+ taler_utils::btc_to_taler,
};
use common::{
- api_common::base32,
log::{
- log::{error, info, warn},
OrFail,
+ log::{error, info, warn},
},
metadata::OutMetadata,
postgres,
reconnect::AutoReconnectDb,
- sql::{sql_array, sql_url},
+ sql::{sql_base_32, sql_url},
status::{BounceStatus, DebitStatus},
+ taler_common::{api_common::ShortHashCode, types::timestamp::Timestamp},
};
-use postgres::{fallible_iterator::FallibleIterator, Client};
+use postgres::{Client, fallible_iterator::FallibleIterator};
use crate::{
+ WireState,
fail_point::fail_point,
sql::{sql_addr, sql_btc_amount, sql_txid},
- WireState,
};
-use super::{analysis::analysis, LoopError, LoopResult};
+use super::{LoopError, LoopResult, analysis::analysis};
/// Synchronize local db with blockchain and perform transactions
pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, mut state: WireState) {
@@ -120,12 +116,8 @@ pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, mut state: WireSt
&id.as_byte_array().as_slice(),
],
)?;
- info!(
- ">> (bump) {} replace {} with {}",
- base32(row.get(0)),
- id,
- bump.txid
- );
+ let wtid: ShortHashCode = sql_base_32(&row, 0);
+ info!(">> (bump) {wtid} replace {id} with {}", bump.txid);
}
// Send requested bounce
@@ -135,7 +127,7 @@ pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, mut state: WireSt
Ok(())
})();
if let Err(e) = result {
- error!("worker: {}", e);
+ error!("worker: {e}");
// When we catch an error, we sometimes want to retry immediately (eg. reconnect to RPC or DB).
// Bitcoin error codes are generic. We need to match the msg to get precise ones. Some errors
// can resolve themselves when a new block is mined (new fees, new transactions). Our simple
@@ -144,6 +136,7 @@ pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, mut state: WireSt
e,
LoopError::Rpc(rpc::Error::RPC { .. } | rpc::Error::Bitcoin(_))
| LoopError::Concurrency
+ | LoopError::Injected(_)
);
} else {
skip_notification = false;
@@ -306,17 +299,10 @@ fn sync_chain_removed(
if !blocking_bounce.is_empty() || !blocking_debit.is_empty() {
let mut buf = "The following transaction have been removed from the blockchain, bitcoin backing is compromised until the transaction reappear:".to_string();
for (key, id, addr) in blocking_debit {
- write!(
- &mut buf,
- "\n\tcredit {} in {} from {}",
- base32(&key),
- id,
- addr
- )
- .unwrap();
+ write!(&mut buf, "\n\tcredit {key} in {id} from {addr}",).unwrap();
}
for (id, bounced) in blocking_bounce {
- write!(&mut buf, "\n\tbounced {} in {}", id, bounced).unwrap();
+ write!(&mut buf, "\n\tbounced {id} in {bounced}").unwrap();
}
error!("{}", buf);
Ok(false)
@@ -337,27 +323,20 @@ fn sync_chain_incoming_confirmed(
// Store transactions in database
let debit_addr = sender_address(rpc, &full)?;
let credit_addr = full.details[0].address.clone().unwrap().assume_checked();
- let date = SystemTime::UNIX_EPOCH + Duration::from_secs(full.time);
let amount = btc_to_taler(&full.amount, state.currency);
- let nb = db.execute("INSERT INTO tx_in (_date, amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (reserve_pub) DO NOTHING ", &[
- &date, &amount.to_string(), &reserve_pub.as_slice(), &btc_payto_url(&debit_addr).as_ref(), &btc_payto_url(&credit_addr).as_ref()
+ let nb = db.execute("INSERT INTO tx_in (received, amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, ($2, $3)::taler_amount, $4, $5, $6) ON CONFLICT (reserve_pub) DO NOTHING ", &[
+ &((full.time * 1000000) as i64), &(amount.val as i64), &(amount.frac as i32), &reserve_pub.as_slice(), &debit_addr.to_string(), &credit_addr.to_string()
])?;
if nb > 0 {
- info!(
- "<< {} {} in {} from {}",
- amount,
- base32(&reserve_pub),
- id,
- debit_addr
- );
+ info!("<< {amount} {reserve_pub} in {id} from {debit_addr}");
}
}
Err(err) => match err {
GetSegwitErr::Decode(_) => {
// If encoding is wrong request a bounce
db.execute(
- "INSERT INTO bounce (bounced) VALUES ($1) ON CONFLICT (bounced) DO NOTHING",
- &[&id.as_byte_array().as_slice()],
+ "INSERT INTO bounce (created, bounced) VALUES ($1, $2) ON CONFLICT (bounced) DO NOTHING",
+ &[&Timestamp::now().as_sql_micros(), &id.as_byte_array().as_slice()],
)?;
}
GetSegwitErr::RPC(e) => return Err(e.into()),
@@ -370,7 +349,7 @@ fn sync_chain_incoming_confirmed(
fn sync_chain_debit(
id: &Txid,
full: &Transaction,
- wtid: &[u8; 32],
+ wtid: &ShortHashCode,
rpc: &mut Rpc,
db: &mut Client,
confirmations: i32,
@@ -390,7 +369,7 @@ fn sync_chain_debit(
],
)?;
if nb_row > 0 {
- warn!(">> (conflict) {} in {id} to {credit_addr}", base32(wtid));
+ warn!(">> (conflict) {wtid} in {id} to {credit_addr}");
}
}
} else {
@@ -401,7 +380,7 @@ fn sync_chain_debit(
)?;
if let Some(row) = row {
// If already in database, sync status
- let row_id: i32 = row.get(0);
+ let row_id: i64 = row.get(0);
let status: i16 = row.get(1);
match DebitStatus::try_from(status as u8).unwrap() {
DebitStatus::Requested => {
@@ -415,10 +394,7 @@ fn sync_chain_debit(
],
)?;
if nb_row > 0 {
- warn!(
- ">> (recovered) {amount} {} in {id} to {credit_addr}",
- base32(wtid)
- );
+ warn!(">> (recovered) {amount} {wtid} in {id} to {credit_addr}");
}
}
DebitStatus::Sent => {
@@ -433,7 +409,7 @@ fn sync_chain_debit(
],
)?;
if nb_row > 0 {
- info!(">> (recovered) {} replace {txid} with {id}", base32(wtid),);
+ info!(">> (recovered) {wtid} replace {txid} with {id}",);
}
}
}
@@ -442,16 +418,12 @@ fn sync_chain_debit(
} else {
// Else add to database
let debit_addr = sender_address(rpc, full)?;
- let date = SystemTime::UNIX_EPOCH + Duration::from_secs(full.time);
let nb = db.execute(
- "INSERT INTO tx_out (_date, amount, wtid, debit_acc, credit_acc, exchange_url, status, txid, request_uid) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (wtid) DO NOTHING",
- &[&date, &amount.to_string(), &wtid.as_slice(), &btc_payto_url(&debit_addr).as_ref(), &btc_payto_url(&credit_addr).as_ref(), &state.base_url.as_ref(), &(DebitStatus::Sent as i16), &id.as_byte_array().as_slice(), &None::<&[u8]>],
+ "INSERT INTO tx_out (created, amount, wtid, debit_acc, credit_acc, exchange_url, status, txid, request_uid) VALUES ($1, ($2, $3)::taler_amount, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (wtid) DO NOTHING",
+ &[&((full.time*1000000) as i64), &(amount.val as i64), &(amount.frac as i32), &wtid.as_slice(), &debit_addr.to_string(), &credit_addr.to_string(), &state.base_url.as_ref(), &(DebitStatus::Sent as i16), &id.as_byte_array().as_slice(), &None::<&[u8]>],
)?;
if nb > 0 {
- warn!(
- ">> (onchain) {amount} {} in {id} to {credit_addr}",
- base32(wtid)
- );
+ warn!(">> (onchain) {amount} {wtid} in {id} to {credit_addr}",);
}
}
@@ -488,7 +460,7 @@ fn sync_chain_bounce(
],
)?;
if nb_row > 0 {
- warn!("|| (conflict) {} in {}", &bounced, &id);
+ warn!("|| (conflict) {bounced} in {id}");
}
} else {
// Get previous bounce
@@ -498,7 +470,7 @@ fn sync_chain_bounce(
)?;
if let Some(row) = row {
// If already in database, sync status
- let row_id: i32 = row.get(0);
+ let row_id: i64 = row.get(0);
let status: i16 = row.get(1);
match BounceStatus::try_from(status as u8).unwrap() {
BounceStatus::Requested => {
@@ -512,23 +484,22 @@ fn sync_chain_bounce(
],
)?;
if nb_row > 0 {
- warn!("|| (recovered) {} in {}", &bounced, &id);
+ warn!("|| (recovered) {bounced} in {id}");
}
}
- BounceStatus::Ignored => error!(
- "watcher: ignored bounce {} found in chain at {}",
- bounced, id
- ),
+ BounceStatus::Ignored => {
+ error!("watcher: ignored bounce {bounced} found in chain at {id}")
+ }
BounceStatus::Sent => { /* Status is correct */ }
}
} else {
// Else add to database
let nb = db.execute(
- "INSERT INTO bounce (bounced, txid, status) VALUES ($1, $2, $3) ON CONFLICT (txid) DO NOTHING",
- &[&bounced.as_byte_array().as_slice(), &id.as_byte_array().as_slice(), &(BounceStatus::Sent as i16)],
+ "INSERT INTO bounce (created, bounced, txid, status) VALUES ($1, $2, $3, $4) ON CONFLICT (txid) DO NOTHING",
+ &[&Timestamp::now().as_sql_micros(), &bounced.as_byte_array().as_slice(), &id.as_byte_array().as_slice(), &(BounceStatus::Sent as i16)],
)?;
if nb > 0 {
- warn!("|| (onchain) {} in {}", &bounced, &id);
+ warn!("|| (onchain) {bounced} in {id}");
}
}
}
@@ -556,7 +527,7 @@ fn sync_chain_outgoing(
sync_chain_bounce(id, &Txid::from_byte_array(bounced), db, confirmations)?
}
},
- Ok((_, Err(e))) => warn!("send: decode-info {} - {}", id, e),
+ Ok((_, Err(e))) => warn!("send: decode-info {id} - {e}"),
Err(e) => match e {
GetOpReturnErr::MissingOpReturn => { /* Ignore */ }
GetOpReturnErr::RPC(e) => return Err(e)?,
@@ -569,16 +540,19 @@ fn sync_chain_outgoing(
fn debit(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool> {
// We rely on the advisory lock to ensure we are the only one sending transactions
let row = db.query_opt(
- "SELECT id, amount, wtid, credit_acc, exchange_url FROM tx_out WHERE status=$1 ORDER BY _date LIMIT 1",
+ "SELECT id, (amount).val, (amount).frac, wtid, credit_acc, exchange_url FROM tx_out WHERE status=$1 ORDER BY created LIMIT 1",
&[&(DebitStatus::Requested as i16)],
)?;
if let Some(row) = &row {
- let id: i32 = row.get(0);
+ let id: i64 = row.get(0);
let amount = sql_btc_amount(row, 1, state.currency);
- let wtid: [u8; 32] = sql_array(row, 2);
- let addr = sql_addr(row, 3);
- let url = sql_url(row, 4);
- let metadata = OutMetadata::Debit { wtid, url };
+ let wtid: ShortHashCode = sql_base_32(row, 3);
+ let addr = sql_addr(row, 4);
+ let url = sql_url(row, 5);
+ let metadata = OutMetadata::Debit {
+ wtid: wtid.clone(),
+ url,
+ };
let tx_id = rpc.send(
&addr,
@@ -596,7 +570,7 @@ fn debit(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool>
],
)?;
let amount = btc_to_taler(&amount.to_signed().unwrap(), state.currency);
- info!(">> {} {} in {} to {}", amount, base32(&wtid), tx_id, addr);
+ info!(">> {amount} {wtid} in {tx_id} to {addr}");
}
Ok(row.is_some())
}
@@ -605,11 +579,11 @@ fn debit(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool>
fn bounce(db: &mut Client, rpc: &mut Rpc, fee: &BtcAmount) -> LoopResult<bool> {
// We rely on the advisory lock to ensure we are the only one sending transactions
let row = db.query_opt(
- "SELECT id, bounced FROM bounce WHERE status=$1 ORDER BY _date LIMIT 1",
+ "SELECT id, bounced FROM bounce WHERE status=$1 ORDER BY created LIMIT 1",
&[&(BounceStatus::Requested as i16)],
)?;
if let Some(row) = &row {
- let id: i32 = row.get(0);
+ let id: i64 = row.get(0);
let bounced: Txid = sql_txid(row, 1);
let metadata = OutMetadata::Bounce {
bounced: *bounced.as_byte_array(),
@@ -630,7 +604,7 @@ fn bounce(db: &mut Client, rpc: &mut Rpc, fee: &BtcAmount) -> LoopResult<bool> {
&id,
],
)?;
- info!("|| {} in {}", &bounced, &it);
+ info!("|| {bounced} in {it}");
}
Err(err) => match err {
rpc::Error::RPC {
@@ -641,7 +615,7 @@ fn bounce(db: &mut Client, rpc: &mut Rpc, fee: &BtcAmount) -> LoopResult<bool> {
"UPDATE bounce SET status=$1 WHERE id=$2",
&[&(BounceStatus::Ignored as i16), &id],
)?;
- info!("|| (ignore) {} because {}", &bounced, msg);
+ info!("|| (ignore) {bounced} because {msg}");
}
e => Err(e)?,
},
diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs
@@ -1,174 +1,176 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-use bitcoin::{hashes::Hash, Network};
-use btc_wire::{
- btc_config::{BitcoinConfig, WIRE_WALLET_NAME},
- load_taler_config,
- rpc::{self, auto_rpc_common, auto_rpc_wallet, ErrorCode, Rpc},
- WireState,
-};
-use clap::Parser;
-use common::{
- log::{log::info, OrFail},
- named_spawn, password,
- postgres::NoTls,
- reconnect::auto_reconnect_db,
-};
-use loops::LoopResult;
-use std::path::PathBuf;
-
-use crate::loops::{watcher::watcher, worker::worker};
-
-mod fail_point;
-mod loops;
-mod sql;
-
-/// Taler wire for bitcoincore
-#[derive(clap::Parser, Debug)]
-struct Args {
- /// Override default configuration file path
- #[clap(global = true, short, long)]
- config: Option<PathBuf>,
- #[clap(subcommand)]
- init: Option<Init>,
-}
-
-#[derive(clap::Subcommand, Debug)]
-enum Init {
- /// Initialize database schema and state
- Initdb,
- /// Generate bitcoin wallet and initialize state
- Initwallet,
-}
-
-/// TODO support external signer https://github.com/bitcoin/bitcoin/blob/master/doc/external-signer.md
-
-fn main() {
- common::log::init();
- let args = Args::parse();
-
- match args.init {
- Some(cmd) => init(args.config, cmd).or_fail(|e| format!("{}", e)),
- None => run(args.config),
- }
-}
-
-fn init(config: Option<PathBuf>, init: Init) -> LoopResult<()> {
- // Parse taler config
- let (taler_config, path, currency) = load_taler_config(config.as_deref());
- // Connect to database
- let mut db = taler_config.db_config().connect(NoTls)?;
- // Parse bitcoin config
- let btc_conf =
- BitcoinConfig::load(path, currency).or_fail(|e| format!("bitcoin config: {}", e));
- // Connect to bitcoin node
- let mut rpc = Rpc::common(&btc_conf).or_fail(|e| format!("rpc connect: {}", e));
- match init {
- Init::Initdb => {
- let mut tx = db.transaction()?;
- // Load schema
- tx.batch_execute(include_str!("../../db/btc.sql"))?;
- // Init status to true
- tx
- .execute(
- "INSERT INTO state (name, value) VALUES ('status', $1) ON CONFLICT (name) DO NOTHING",
- &[&[1u8].as_slice()],
- )?;
- // Init last_hash if not already set
- let genesis_hash = rpc.get_genesis()?;
- tx
- .execute(
- "INSERT INTO state (name, value) VALUES ('last_hash', $1) ON CONFLICT (name) DO NOTHING",
- &[&genesis_hash.as_byte_array().as_slice()],
- )?;
- tx.commit()?;
- println!("Database initialised");
- }
- Init::Initwallet => {
- // Create wallet
- let passwd = password();
- let created = match rpc.create_wallet(WIRE_WALLET_NAME, &passwd) {
- Err(rpc::Error::RPC {
- code: ErrorCode::RpcWalletError,
- ..
- }) => false,
- Err(e) => panic!("{}", e),
- Ok(_) => true,
- };
-
- rpc.load_wallet(WIRE_WALLET_NAME).ok();
-
- // Load previous address
- // TODO Use address label instead of the database ?
- let prev_addr = db.query_opt("SELECT value FROM state WHERE name = 'addr'", &[])?;
- let addr = if let Some(row) = prev_addr {
- String::from_utf8(row.get(0)).unwrap()
- } else {
- // Or generate a new one
- let new = Rpc::wallet(&btc_conf, WIRE_WALLET_NAME)
- .or_fail(|e| format!("rpc connect: {}", e))
- .gen_addr()?;
- db.execute(
- "INSERT INTO state (name, value) VALUES ('addr', $1)",
- &[&new.to_string().as_bytes()],
- )?;
- new.to_string()
- };
-
- if created {
- println!("Created new wallet");
- } else {
- println!("Found already existing wallet")
- }
- println!("You must backup the generated key file and your chosen password, more info there: https://github.com/bitcoin/bitcoin/blob/master/doc/managing-wallets.md#14-backing-up-the-wallet");
- println!("Public address is {}", &addr);
- println!("Add the following line into taler.conf:");
- println!("[depolymerizer-bitcoin]");
- println!("PAYTO = payto://bitcoin/{}", addr);
- }
- }
- Ok(())
-}
-
-fn run(config: Option<PathBuf>) {
- let state = WireState::load_taler_config(config.as_deref());
-
- #[cfg(feature = "fail")]
- if state.btc_config.network == Network::Regtest {
- common::log::log::warn!("Running with random failures");
- } else {
- common::log::log::error!("Running with random failures is unsuitable for production");
- std::process::exit(1);
- }
- let chain_name = match state.btc_config.network {
- Network::Bitcoin => "main",
- Network::Testnet => "test",
- Network::Signet => "signet",
- Network::Regtest => "regtest",
- _ => unreachable!(),
- };
- info!("Running on {} chain", chain_name);
- // TODO Check wire wallet own config PAYTO address
-
- let rpc_watcher = auto_rpc_common(state.btc_config.clone());
- let rpc_worker = auto_rpc_wallet(state.btc_config.clone(), WIRE_WALLET_NAME);
-
- let db_watcher = auto_reconnect_db(state.db_config.clone());
- let db_worker = auto_reconnect_db(state.db_config.clone());
- named_spawn("watcher", move || watcher(rpc_watcher, db_watcher));
- worker(rpc_worker, db_worker, state);
- info!("btc-wire stopped");
-}
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+use bitcoin::{Network, hashes::Hash};
+use btc_wire::{
+ WireState,
+ btc_config::{BitcoinConfig, WIRE_WALLET_NAME},
+ load_taler_config,
+ rpc::{self, ErrorCode, Rpc, auto_rpc_common, auto_rpc_wallet},
+};
+use clap::Parser;
+use common::{
+ log::{OrFail, log::info},
+ named_spawn, password,
+ postgres::NoTls,
+ reconnect::auto_reconnect_db,
+};
+use loops::LoopResult;
+use std::path::PathBuf;
+
+use crate::loops::{watcher::watcher, worker::worker};
+
+mod fail_point;
+mod loops;
+mod sql;
+
+/// Taler wire for bitcoincore
+#[derive(clap::Parser, Debug)]
+struct Args {
+ /// Override default configuration file path
+ #[clap(global = true, short, long)]
+ config: Option<PathBuf>,
+ #[clap(subcommand)]
+ init: Option<Init>,
+}
+
+#[derive(clap::Subcommand, Debug)]
+enum Init {
+ /// Initialize database schema and state
+ Initdb,
+ /// Generate bitcoin wallet and initialize state
+ Initwallet,
+}
+
+/// TODO support external signer https://github.com/bitcoin/bitcoin/blob/master/doc/external-signer.md
+
+fn main() {
+ common::log::init();
+ let args = Args::parse();
+
+ match args.init {
+ Some(cmd) => init(args.config, cmd).or_fail(|e| format!("{}", e)),
+ None => run(args.config),
+ }
+}
+
+fn init(config: Option<PathBuf>, init: Init) -> LoopResult<()> {
+ // Parse taler config
+ let (taler_config, path, currency) = load_taler_config(config.as_deref());
+ // Connect to database
+ let mut db = taler_config.db_config().connect(NoTls)?;
+ // Parse bitcoin config
+ let btc_conf =
+ BitcoinConfig::load(path, currency).or_fail(|e| format!("bitcoin config: {}", e));
+ // Connect to bitcoin node
+ let mut rpc = Rpc::common(&btc_conf).or_fail(|e| format!("rpc connect: {}", e));
+ match init {
+ Init::Initdb => {
+ let mut tx = db.transaction()?;
+ // Load schema
+ tx.batch_execute(include_str!("../../db/btc.sql"))?;
+ // Init status to true
+ tx
+ .execute(
+ "INSERT INTO state (name, value) VALUES ('status', $1) ON CONFLICT (name) DO NOTHING",
+ &[&[1u8].as_slice()],
+ )?;
+ // Init last_hash if not already set
+ let genesis_hash = rpc.get_genesis()?;
+ tx
+ .execute(
+ "INSERT INTO state (name, value) VALUES ('last_hash', $1) ON CONFLICT (name) DO NOTHING",
+ &[&genesis_hash.as_byte_array().as_slice()],
+ )?;
+ tx.commit()?;
+ println!("Database initialised");
+ }
+ Init::Initwallet => {
+ // Create wallet
+ let passwd = password();
+ let created = match rpc.create_wallet(WIRE_WALLET_NAME, &passwd) {
+ Err(rpc::Error::RPC {
+ code: ErrorCode::RpcWalletError,
+ ..
+ }) => false,
+ Err(e) => panic!("{}", e),
+ Ok(_) => true,
+ };
+
+ rpc.load_wallet(WIRE_WALLET_NAME).ok();
+
+ // Load previous address
+ // TODO Use address label instead of the database ?
+ let prev_addr = db.query_opt("SELECT value FROM state WHERE name = 'addr'", &[])?;
+ let addr = if let Some(row) = prev_addr {
+ String::from_utf8(row.get(0)).unwrap()
+ } else {
+ // Or generate a new one
+ let new = Rpc::wallet(&btc_conf, WIRE_WALLET_NAME)
+ .or_fail(|e| format!("rpc connect: {}", e))
+ .gen_addr()?;
+ db.execute(
+ "INSERT INTO state (name, value) VALUES ('addr', $1)",
+ &[&new.to_string().as_bytes()],
+ )?;
+ new.to_string()
+ };
+
+ if created {
+ println!("Created new wallet");
+ } else {
+ println!("Found already existing wallet")
+ }
+ println!(
+ "You must backup the generated key file and your chosen password, more info there: https://github.com/bitcoin/bitcoin/blob/master/doc/managing-wallets.md#14-backing-up-the-wallet"
+ );
+ println!("Public address is {}", &addr);
+ println!("Add the following line into taler.conf:");
+ println!("[depolymerizer-bitcoin]");
+ println!("PAYTO = payto://bitcoin/{}", addr);
+ }
+ }
+ Ok(())
+}
+
+fn run(config: Option<PathBuf>) {
+ let state = WireState::load_taler_config(config.as_deref());
+
+ #[cfg(feature = "fail")]
+ if state.btc_config.network == Network::Regtest {
+ common::log::log::warn!("Running with random failures");
+ } else {
+ common::log::log::error!("Running with random failures is unsuitable for production");
+ std::process::exit(1);
+ }
+ let chain_name = match state.btc_config.network {
+ Network::Bitcoin => "main",
+ Network::Testnet => "test",
+ Network::Signet => "signet",
+ Network::Regtest => "regtest",
+ _ => unreachable!(),
+ };
+ info!("Running on {chain_name} chain");
+ // TODO Check wire wallet own config PAYTO address
+
+ let rpc_watcher = auto_rpc_common(state.btc_config.clone());
+ let rpc_worker = auto_rpc_wallet(state.btc_config.clone(), WIRE_WALLET_NAME);
+
+ let db_watcher = auto_reconnect_db(state.db_config.clone());
+ let db_worker = auto_reconnect_db(state.db_config.clone());
+ named_spawn("watcher", move || watcher(rpc_watcher, db_watcher));
+ worker(rpc_worker, db_worker, state);
+ info!("btc-wire stopped");
+}
diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -25,10 +25,10 @@
//!
//! bitcoincore RPC documentation: <https://bitcoincore.org/en/doc/23.0.0/>
-use bitcoin::{address::NetworkUnchecked, Address, Amount, BlockHash, SignedAmount, Txid};
+use bitcoin::{Address, Amount, BlockHash, SignedAmount, Txid, address::NetworkUnchecked};
use common::{log::log::error, password, reconnect::AutoReconnect};
use data_encoding::BASE64;
-use serde_json::{json, Value};
+use serde_json::{Value, json};
use std::{
fmt::Debug,
io::{self, BufRead, BufReader, Write},
@@ -99,7 +99,7 @@ struct RpcError {
#[derive(Debug, thiserror::Error)]
pub enum Error {
- #[error("{0:?}")]
+ #[error("IO: {0:?}")]
Transport(#[from] std::io::Error),
#[error("RPC: {code:?} - {msg}")]
RPC { code: ErrorCode, msg: String },
diff --git a/btc-wire/src/segwit.rs b/btc-wire/src/segwit.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -14,9 +14,11 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
use bech32::Hrp;
-use common::{rand::rngs::OsRng, rand_slice};
+use common::{rand::rngs::ThreadRng, rand_slice, taler_common::api_common::EddsaPublicKey};
use std::cmp::Ordering;
+/// TODO use segwit v1 to only use a single address
+
/// Encode metadata into a segwit address
pub fn encode_segwit_addr(hrp: Hrp, metada: &[u8; 20]) -> String {
bech32::segwit::encode_v0(hrp, metada).unwrap()
@@ -67,7 +69,9 @@ pub enum DecodeSegWitErr {
}
/// Decode a 32B key into from adresses
-pub fn decode_segwit_msg(segwit_addrs: &[impl AsRef<str>]) -> Result<[u8; 32], DecodeSegWitErr> {
+pub fn decode_segwit_msg(
+ segwit_addrs: &[impl AsRef<str>],
+) -> Result<EddsaPublicKey, DecodeSegWitErr> {
// Extract parts from every addresses
let decoded: Vec<(bool, [u8; 4], [u8; 16])> = segwit_addrs
.iter()
@@ -111,7 +115,7 @@ pub fn decode_segwit_msg(segwit_addrs: &[impl AsRef<str>]) -> Result<[u8; 32], D
for (is_first, _, half) in matches {
key[*is_first as usize * 16..][..16].copy_from_slice(half);
}
- Ok(key)
+ Ok(EddsaPublicKey::from(key))
}
Ordering::Greater => Err(DecodeSegWitErr::PrefixCollision),
Ordering::Less => Err(DecodeSegWitErr::MissingSegWitAddress),
@@ -129,25 +133,26 @@ pub fn rand_addresses(hrp: Hrp, key: &[u8; 32]) -> Vec<String> {
let mut addresses = encode_segwit_key(hrp, key).to_vec();
addresses.append(&mut rng_address);
- addresses.shuffle(&mut OsRng);
+ addresses.shuffle(&mut ThreadRng::default());
addresses
}
#[cfg(test)]
mod test {
use common::{
- rand::{prelude::SliceRandom, rngs::OsRng},
- rand_slice,
+ rand::{prelude::SliceRandom, rngs::ThreadRng},
+ taler_common::types::base32::Base32,
};
use crate::segwit::{decode_segwit_msg, encode_segwit_key, rand_addresses};
#[test]
fn test_shuffle() {
+ let mut rng = ThreadRng::default();
for _ in 0..1000 {
- let key = rand_slice();
+ let key = Base32::rand();
let mut addresses = encode_segwit_key(bech32::hrp::TB, &key);
- addresses.shuffle(&mut OsRng);
+ addresses.shuffle(&mut rng);
let decoded =
decode_segwit_msg(&addresses.iter().map(|s| s.as_str()).collect::<Vec<&str>>())
.unwrap();
@@ -158,7 +163,7 @@ mod test {
#[test]
fn test_shuffle_many() {
for _ in 0..1000 {
- let key = rand_slice();
+ let key = Base32::rand();
let addresses = rand_addresses(bech32::hrp::TB, &key);
let decoded =
decode_segwit_msg(&addresses.iter().map(|s| s.as_str()).collect::<Vec<&str>>())
diff --git a/btc-wire/src/sql.rs b/btc-wire/src/sql.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -14,17 +14,19 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use bitcoin::{hashes::Hash, Address, Amount as BtcAmount, Txid};
+use std::str::FromStr as _;
+
+use bitcoin::{Address, Amount as BtcAmount, Txid, hashes::Hash};
use common::currency::CurrencyBtc;
use common::log::OrFail;
use common::postgres::Row;
-use common::sql::{sql_amount, sql_url};
+use common::sql::sql_amount;
-use btc_wire::taler_utils::{btc_payto_addr, taler_to_btc};
+use btc_wire::taler_utils::taler_to_btc;
/// Bitcoin amount from sql
pub fn sql_btc_amount(row: &Row, idx: usize, currency: CurrencyBtc) -> BtcAmount {
- let amount = sql_amount(row, idx);
+ let amount = sql_amount(row, idx, currency.to_str());
taler_to_btc(&amount, currency).or_fail(|_| {
format!(
"Database invariant: expected an bitcoin amount got {}",
@@ -35,13 +37,10 @@ pub fn sql_btc_amount(row: &Row, idx: usize, currency: CurrencyBtc) -> BtcAmount
/// Bitcoin address from sql
pub fn sql_addr(row: &Row, idx: usize) -> Address {
- let url = sql_url(row, idx);
- btc_payto_addr(&url).or_fail(|_| {
- format!(
- "Database invariant: expected an bitcoin payto url got {}",
- url
- )
- })
+ let str = row.get(idx);
+ Address::from_str(str)
+ .or_fail(|_| format!("Database invariant: expected an bitcoin address got {str}"))
+ .assume_checked()
}
/// Bitcoin transaction id from sql
diff --git a/btc-wire/src/taler_utils.rs b/btc-wire/src/taler_utils.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -15,29 +15,11 @@
*/
//! Utils function to convert taler API types to bitcoin API types
-use bitcoin::{Address, Amount as BtcAmount, SignedAmount};
-use common::api_common::Amount;
-use common::currency::CurrencyBtc;
-use common::url::Url;
-use std::str::FromStr;
-
-/// Generate a payto uri from a btc address
-pub fn btc_payto_url(addr: &Address) -> Url {
- Url::from_str(&format!("payto://bitcoin/{}", addr)).unwrap()
-}
-
-/// Extract a btc address from a payto uri
-pub fn btc_payto_addr(url: &Url) -> Result<Address, String> {
- if url.domain() != Some("bitcoin") {
- return Err(format!(
- "Expected domain 'bitcoin' got '{}'",
- url.domain().unwrap_or_default()
- ));
- }
- let str = url.path().trim_start_matches('/');
- let addr = Address::from_str(str).map_err(|e| e.to_string())?;
- Ok(addr.assume_checked())
-}
+use bitcoin::{Amount as BtcAmount, SignedAmount};
+use common::{
+ currency::CurrencyBtc,
+ taler_common::types::amount::{Amount, FRAC_BASE},
+};
/// Transform a btc amount into a taler amount
pub fn btc_to_taler(amount: &SignedAmount, currency: CurrencyBtc) -> Amount {
@@ -52,7 +34,7 @@ pub fn btc_to_taler(amount: &SignedAmount, currency: CurrencyBtc) -> Amount {
/// Transform a taler amount into a btc amount
pub fn taler_to_btc(amount: &Amount, currency: CurrencyBtc) -> Result<BtcAmount, String> {
- if amount.currency != currency.to_str() {
+ if amount.currency.as_ref() != currency.to_str() {
return Err(format!(
"expected currency {} got {}",
currency.to_str(),
@@ -60,6 +42,6 @@ pub fn taler_to_btc(amount: &Amount, currency: CurrencyBtc) -> Result<BtcAmount,
));
}
- let sat = amount.value * 100_000_000 + amount.fraction as u64;
+ let sat = amount.val * FRAC_BASE as u64 + amount.frac as u64;
Ok(BtcAmount::from_sat(sat))
}
diff --git a/common/Cargo.toml b/common/Cargo.toml
@@ -1,25 +1,23 @@
[package]
name = "common"
version = "0.1.0"
-edition = "2021"
-license = "AGPL-3.0-or-later"
-rust-version = "1.72.1"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+edition.workspace = true
+authors.workspace = true
+homepage.workspace = true
+repository.workspace = true
+license-file.workspace = true
[dependencies]
# Serialization framework
-serde = { version = "1.0.188", features = ["derive"] }
+serde.workspace = true
# Serialization helper
serde_with = "3.3.0"
# JSON serialization
-serde_json = "1.0.107"
+serde_json.workspace = true
# Url format
-url = { version = "2.4.1", features = ["serde"] }
-# Crockford’s base32
-base32 = "0.5.0"
+url.workspace = true
# Error macros
-thiserror = "1.0.49"
+thiserror.workspace = true
# Ini files
rust-ini = "0.21.0"
# Logging
@@ -28,10 +26,16 @@ log = "0.4.20"
# Postgres client
postgres = "0.19.7"
# Secure random
-rand = { version = "0.8.5", features = ["getrandom"] }
+rand = { version = "0.9.0" }
# Securely zero memory
zeroize = "1.6.0"
# Optimized uri binary format
uri-pack = { path = "../uri-pack" }
# Exponential backoff generator
exponential-backoff = "1.2.0"
+taler-common.workspace = true
+taler-api.workspace = true
+sqlx.workspace = true
+bitcoin.workspace = true
+ethereum-types.workspace = true
+hex.workspace = true
diff --git a/common/src/api_common.rs b/common/src/api_common.rs
@@ -1,348 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-use std::{
- fmt::Display,
- num::ParseIntError,
- ops::Deref,
- str::FromStr,
- time::{Duration, SystemTime},
-};
-
-use serde::{de::Error, ser::SerializeStruct, Deserialize, Deserializer};
-use serde_json::Value;
-
-/// <https://docs.taler.net/core/api-common.html#tsref-type-ErrorDetail>
-#[derive(Debug, Clone, serde::Serialize)]
-pub struct ErrorDetail {
- pub code: i64,
- pub hint: Option<String>,
- pub detail: Option<String>,
- pub parameter: Option<String>,
- pub path: Option<String>,
- pub offset: Option<String>,
- pub index: Option<String>,
- pub object: Option<String>,
- pub currency: Option<String>,
- pub type_expected: Option<String>,
- pub type_actual: Option<String>,
-}
-
-/// <https://docs.taler.net/core/api-common.html#tsref-type-Timestamp>
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Timestamp {
- Never,
- Time(SystemTime),
-}
-
-#[derive(serde::Serialize, serde::Deserialize)]
-struct TimestampImpl {
- t_s: Value,
-}
-
-impl Timestamp {
- pub fn now() -> Self {
- Self::Time(SystemTime::now())
- }
-}
-
-impl<'de> Deserialize<'de> for Timestamp {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let tmp = TimestampImpl::deserialize(deserializer)?;
- match tmp.t_s {
- Value::Number(s) => {
- if let Some(since_epoch_s) = s.as_u64() {
- Ok(Self::Time(
- SystemTime::UNIX_EPOCH + Duration::from_secs(since_epoch_s),
- ))
- } else {
- Err(Error::custom("Expected epoch time"))
- }
- }
- Value::String(str) if str == "never" => Ok(Self::Never),
- _ => Err(Error::custom("Expected epoch time or 'never'")),
- }
- }
-}
-
-impl serde::Serialize for Timestamp {
- fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- let mut se_struct = se.serialize_struct("Timestamp", 1)?;
- match self {
- Timestamp::Never => se_struct.serialize_field("t_s", "never")?,
- Timestamp::Time(time) => se_struct.serialize_field(
- "t_s",
- &time
- .duration_since(SystemTime::UNIX_EPOCH)
- .unwrap()
- .as_secs(),
- )?,
- };
-
- se_struct.end()
- }
-}
-
-impl From<SystemTime> for Timestamp {
- fn from(time: SystemTime) -> Self {
- Self::Time(time)
- }
-}
-
-/// <https://docs.taler.net/core/api-common.html#tsref-type-SafeUint64>
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
-pub struct SafeU64(u64);
-
-#[derive(Debug, thiserror::Error)]
-#[error("{0} unsafe, {0} > (2^53 - 1)")]
-pub struct UnsafeUint64(u64);
-
-impl TryFrom<u64> for SafeU64 {
- type Error = UnsafeUint64;
-
- fn try_from(nb: u64) -> Result<Self, Self::Error> {
- if nb < (1 << 53) - 1 {
- Ok(SafeU64(nb))
- } else {
- Err(UnsafeUint64(nb))
- }
- }
-}
-
-impl<'de> Deserialize<'de> for SafeU64 {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- SafeU64::try_from(u64::deserialize(deserializer)?).map_err(D::Error::custom)
- }
-}
-
-#[derive(Debug, thiserror::Error)]
-pub enum ParseSafeUint64Error {
- #[error(transparent)]
- Unsafe(#[from] UnsafeUint64),
- #[error(transparent)]
- Format(#[from] ParseIntError),
-}
-
-/// <https://docs.taler.net/core/api-common.html#tsref-type-Amount>
-#[derive(
- Debug, Clone, PartialEq, Eq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
-)]
-pub struct Amount {
- pub currency: String,
- pub value: u64,
- pub fraction: u32,
-}
-
-impl Amount {
- pub fn new(currency: impl Into<String>, value: u64, fraction: u32) -> Self {
- Self {
- currency: currency.into(),
- value,
- fraction,
- }
- }
-}
-
-#[derive(Debug, thiserror::Error)]
-pub enum ParseAmountError {
- #[error("Invalid amount format")]
- FormatAmount,
- #[error("Amount overflow")]
- AmountOverflow,
- #[error(transparent)]
- Format(#[from] ParseIntError),
-}
-
-impl FromStr for Amount {
- type Err = ParseAmountError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let (currency, amount) = s
- .trim()
- .split_once(':')
- .ok_or(ParseAmountError::FormatAmount)?;
- if currency.len() > 12 {
- return Err(ParseAmountError::FormatAmount);
- }
- let (value, fraction) = amount.split_once('.').unwrap_or((amount, ""));
-
- let value: u64 = value.parse().map_err(|_| ParseAmountError::FormatAmount)?;
- if value > 1 << 52 {
- return Err(ParseAmountError::FormatAmount);
- }
-
- if fraction.len() > 8 {
- return Err(ParseAmountError::FormatAmount);
- }
- let fraction: u32 = if fraction.is_empty() {
- 0
- } else {
- fraction
- .parse::<u32>()
- .map_err(|_| ParseAmountError::FormatAmount)?
- * 10_u32.pow((8 - fraction.len()) as u32)
- };
-
- Ok(Self {
- currency: currency.to_string(),
- value,
- fraction,
- })
- }
-}
-
-impl Display for Amount {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!(
- "{}:{}.{:08}",
- self.currency, self.value, self.fraction
- ))
- }
-}
-
-#[test]
-fn test_amount() {
- const TALER_AMOUNT_FRAC_BASE: u32 = 100000000;
- // https://git.taler.net/exchange.git/tree/src/util/test_amount.c
-
- const INVALID_AMOUNTS: [&str; 6] = [
- "EUR:4a", // non-numeric,
- "EUR:4.4a", // non-numeric
- "EUR:4.a4", // non-numeric
- ":4.a4", // no currency
- "EUR:4.123456789", // precision to high
- "EUR:1234567890123456789012345678901234567890123456789012345678901234567890", // value to big
- ];
-
- for str in INVALID_AMOUNTS {
- let amount = Amount::from_str(str);
- assert!(amount.is_err(), "invalid {} got {:?}", str, amount);
- }
-
- let valid_amounts: Vec<(&str, Amount)> = vec![
- ("EUR:4", Amount::new("EUR", 4, 0)), // without fraction
- (
- "eur:0.02",
- Amount::new("eur", 0, TALER_AMOUNT_FRAC_BASE / 100 * 2),
- ), // leading zero fraction
- (
- " eur:4.12",
- Amount::new("eur", 4, TALER_AMOUNT_FRAC_BASE / 100 * 12),
- ), // leading space and fraction
- (
- " *LOCAL:4444.1000",
- Amount::new("*LOCAL", 4444, TALER_AMOUNT_FRAC_BASE / 10),
- ), // local currency
- ];
- for (str, goal) in valid_amounts {
- let amount = Amount::from_str(str);
- assert!(amount.is_ok(), "Valid {} got {:?}", str, amount);
- assert_eq!(
- *amount.as_ref().unwrap(),
- goal,
- "Expected {:?} got {:?} for {}",
- goal,
- amount,
- str
- );
- let amount = amount.unwrap();
- let str = amount.to_string();
- assert_eq!(amount, Amount::from_str(&str).unwrap(), "{:?}", str);
- }
-}
-
-#[derive(Debug, thiserror::Error)]
-pub enum ParseBase32Error {
- #[error("Invalid Crockford’s base32 format")]
- Format,
- #[error("Invalid length: expected {0} bytes got {1}")]
- Length(usize, usize),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub struct Base32<const L: usize>([u8; L]);
-
-impl<const L: usize> From<[u8; L]> for Base32<L> {
- fn from(array: [u8; L]) -> Self {
- Self(array)
- }
-}
-
-impl<const L: usize> Deref for Base32<L> {
- type Target = [u8; L];
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl<const L: usize> FromStr for Base32<L> {
- type Err = ParseBase32Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let bytes =
- base32::decode(base32::Alphabet::Crockford, s).ok_or(ParseBase32Error::Format)?;
- let exact: [u8; L] = bytes
- .try_into()
- .map_err(|vec: Vec<u8>| ParseBase32Error::Length(L, vec.len()))?;
- Ok(Self(exact))
- }
-}
-
-pub fn base32(bytes: &[u8]) -> String {
- base32::encode(base32::Alphabet::Crockford, bytes)
-}
-
-impl<const L: usize> Display for Base32<L> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_str(&base32(&self.0))
- }
-}
-
-impl<const L: usize> serde::Serialize for Base32<L> {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- serializer.serialize_str(&self.to_string())
- }
-}
-
-impl<'de, const L: usize> Deserialize<'de> for Base32<L> {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- Base32::from_str(&String::deserialize(deserializer)?).map_err(D::Error::custom)
- }
-}
-
-/// EdDSA and ECDHE public keys always point on Curve25519
-/// and represented using the standard 256 bits Ed25519 compact format,
-/// converted to Crockford Base32.
-pub type EddsaPublicKey = Base32<32>;
-/// 64-byte hash code
-pub type HashCode = Base32<64>;
-/// 32-bytes hash code
-pub type ShortHashCode = Base32<32>;
diff --git a/common/src/api_wire.rs b/common/src/api_wire.rs
@@ -1,101 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-
-/// Type for the Taler Wire Gateway HTTP API <https://docs.taler.net/core/api-bank-wire.html#taler-wire-gateway-http-api>
-use url::Url;
-
-use crate::api_common::{Amount, EddsaPublicKey, HashCode, SafeU64, ShortHashCode, Timestamp};
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
-pub struct WireConfig {
- pub name: &'static str,
- pub version: &'static str,
- pub currency: &'static str,
-}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
-pub struct TransferResponse {
- pub timestamp: Timestamp,
- pub row_id: SafeU64,
-}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
-pub struct TransferRequest {
- pub request_uid: HashCode,
- pub amount: Amount,
- pub exchange_base_url: Url,
- pub wtid: ShortHashCode,
- pub credit_account: Url,
-}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
-pub struct OutgoingHistory {
- pub outgoing_transactions: Vec<OutgoingBankTransaction>,
- pub debit_account: Url,
-}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
-pub struct OutgoingBankTransaction {
- pub row_id: SafeU64,
- pub date: Timestamp,
- pub amount: Amount,
- pub credit_account: Url,
- pub wtid: ShortHashCode,
- pub exchange_base_url: Url,
-}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
-pub struct IncomingHistory {
- pub credit_account: Url,
- pub incoming_transactions: Vec<IncomingBankTransaction>,
-}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
-#[serde(tag = "type")]
-pub enum IncomingBankTransaction {
- #[serde(rename = "RESERVE")]
- IncomingReserveTransaction {
- row_id: SafeU64,
- date: Timestamp,
- amount: Amount,
- debit_account: Url,
- reserve_pub: EddsaPublicKey,
- },
- #[serde(rename = "WAD")]
- IncomingWadTransaction {
- // TODO not yet supported
- },
-}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
-pub struct AddIncomingRequest {
- pub amount: Amount,
- pub reserve_pub: EddsaPublicKey,
- pub debit_account: Url,
-}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
-pub struct AddIncomingResponse {
- pub row_id: SafeU64,
- pub timestamp: Timestamp,
-}
-
-#[derive(Debug, Clone, serde::Deserialize)]
-pub struct HistoryParams {
- pub start: Option<u64>,
- pub delta: i64,
- pub long_pool_ms: Option<u64>,
-}
diff --git a/common/src/config.rs b/common/src/config.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -13,31 +13,38 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use ini::{Ini, Properties};
+use sqlx::postgres::PgConnectOptions;
use std::{
+ fs::Permissions,
+ net::SocketAddr,
+ os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
process::Command,
str::FromStr,
};
+use taler_api::{Serve, auth::AuthMethod};
+use taler_common::{
+ config::{Config, Section, parser::ConfigErr},
+ types::payto::PaytoURI,
+};
use url::Url;
use crate::{
currency::Currency,
- log::{fail, OrFail},
+ log::{OrFail, fail},
};
-pub use ini;
// Depolymerizer taler config
pub struct TalerConfig {
- conf: Ini,
+ cfg: Config,
section_name: &'static str,
pub currency: Currency,
}
impl TalerConfig {
pub fn load(file: Option<&Path>) -> Self {
- // Load config using taler-config
- let mut cmd = Command::new("taler-config");
+ // Load config using taler-exchange-config
+ let mut cmd = Command::new("taler-exchange-config");
cmd.arg("-d");
if let Some(path) = file {
cmd.arg("-c");
@@ -45,18 +52,18 @@ impl TalerConfig {
}
let output = cmd
.output()
- .or_fail(|e| format!("Failed to execute taler-config: {}", e));
+ .or_fail(|e| format!("Failed to execute taler-exchange-config: {}", e));
if !output.status.success() {
fail(format_args!(
- "taler-config failure:\n{}",
+ "taler-exchange-config failure:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
+
// Parse ini config
- let conf = ini::Ini::load_from_str(&String::from_utf8_lossy(&output.stdout))
+ let conf = Config::from_mem(&String::from_utf8_lossy(&output.stdout))
.or_fail(|e| format!("config format: {}", e));
- let taler = section(&conf, "taler");
- let currency = required(taler, "CURRENCY", string);
+ let currency = conf.section("taler").str("currency").require().unwrap();
let currency = Currency::from_str(¤cy)
.or_fail(|_| format!("config CURRENCY={} is an unsupported currency", currency));
let section_name = match currency {
@@ -65,18 +72,22 @@ impl TalerConfig {
};
Self {
- conf,
+ cfg: conf,
section_name,
currency,
}
}
- fn section(&self) -> &Properties {
- section(&self.conf, self.section_name)
+ fn section(&self) -> Section {
+ self.cfg.section(self.section_name)
}
fn non_zero_option(&self, name: &str) -> Option<u32> {
- nb(self.section(), name).filter(|nb| *nb != 0)
+ self.section()
+ .number::<u32>(name)
+ .opt()
+ .unwrap()
+ .filter(|it| *it != 0)
}
}
@@ -84,25 +95,36 @@ impl TalerConfig {
/* ----- Common ----- */
pub fn db_config(&self) -> postgres::Config {
- required(self.section(), "DB_URL", postgres)
+ self.section()
+ .parse("Postgres", "DB_URL")
+ .require()
+ .unwrap()
}
pub fn base_url(&self) -> Url {
- required(section(&self.conf, "exchange"), "BASE_URL", url)
+ self.cfg
+ .section("exchange")
+ .url("BASE_URL")
+ .require()
+ .unwrap()
}
/* ----- Wire Gateway ----- */
- pub fn payto(&self) -> Url {
- required(self.section(), "PAYTO", url)
+ pub fn payto(&self) -> PaytoURI {
+ self.section().payto("PAYTO").require().unwrap()
}
pub fn port(&self) -> u16 {
- nb(self.section(), "PORT").unwrap_or(8080)
+ self.section().number("PORT").default(8080).unwrap()
}
pub fn unix_path(&self) -> Option<PathBuf> {
- path(self.section(), "UNIXPATH")
+ self.section()
+ .path("UNIXPATH")
+ .opt()
+ .unwrap()
+ .map(|e| e.into())
}
pub fn http_lifetime(&self) -> Option<u32> {
@@ -110,25 +132,31 @@ impl TalerConfig {
}
pub fn auth_method(&self) -> AuthMethod {
- let section = self.section();
- match required(section, "AUTH_METHOD", string).as_str() {
+ let sect = self.section();
+ let kind = sect
+ .value("auth method", "AUTH_METHOD", |s| match s {
+ "none" | "basic" => Ok(s),
+ unknown => Err(format!(
+ "unknown config auth method AUTH_METHOD={unknown} expected 'none' or 'basic'"
+ )),
+ })
+ .require()
+ .unwrap();
+ match kind {
"none" => AuthMethod::None,
- "basic" => AuthMethod::Basic(required(section, "AUTH_TOKEN", string)),
- it => fail(format!(
- "unknown config auth method AUTH_METHOD={} expected 'none' or 'basic'",
- it
- )),
+ "basic" => AuthMethod::Basic(sect.str("AUTH_TOKEN").require().unwrap()),
+ _ => unreachable!(),
}
}
/* ----- Wire Common ----- */
pub fn confirmation(&self) -> Option<u16> {
- nb(self.section(), "CONFIRMATION")
+ self.section().number("CONFIRMATION").opt().unwrap()
}
pub fn bounce_fee(&self) -> Option<String> {
- string(self.section(), "BOUNCE_FEE")
+ self.section().str("BOUNCE_FEE").opt().unwrap()
}
pub fn wire_lifetime(&self) -> Option<u32> {
@@ -142,62 +170,55 @@ impl TalerConfig {
/* ----- Custom ----- */
pub fn path(&self, name: &str) -> Option<PathBuf> {
- path(self.section(), name)
+ self.section().path(name).opt().unwrap().map(|e| e.into())
}
}
-/* ----- Auth Method ----- */
-
-pub enum AuthMethod {
- Basic(String),
- None,
-}
-
-/* ----- Helper parsing functions ----- */
-
-pub fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties {
- ini.section(Some(name))
- .or_fail(|_| format!("missing config section {}", name))
-}
-
-pub fn required<T>(
- properties: &Properties,
- name: &str,
- lambda: fn(properties: &Properties, name: &str) -> Option<T>,
-) -> T {
- expect_config(lambda(properties, name), name)
-}
-
-pub fn expect_config<T>(value: Option<T>, name: &str) -> T {
- value.or_fail(|_| format!("missing config value {}", name))
-}
-
-pub fn string(properties: &Properties, name: &str) -> Option<String> {
- properties.get(name).map(|s| s.to_string())
-}
-
-pub fn path(properties: &Properties, name: &str) -> Option<PathBuf> {
- properties.get(name).map(|s| {
- PathBuf::from_str(s).or_fail(|_| format!("config {}={} is not a valid path", name, s))
- })
-}
-
-pub fn nb<T: FromStr>(properties: &Properties, name: &str) -> Option<T> {
- properties.get(name).map(|s| {
- s.parse()
- .or_fail(|_| format!("config {}={} is not a number", name, s))
- })
+pub struct WireGatewayCfg {
+ pub auth: AuthMethod,
+ pub http_lifetime: Option<u32>,
+ pub db: PgConnectOptions,
+ pub payto: PaytoURI,
+ pub currency: Currency,
+ pub serve: Serve,
}
-pub fn url(properties: &Properties, name: &str) -> Option<Url> {
- properties.get(name).map(|s| {
- Url::parse(s).or_fail(|e| format!("config {}={} is not a valid url: {}", name, s, e))
- })
-}
+impl WireGatewayCfg {
+ pub fn parse(path: Option<&Path>) -> Result<Self, ConfigErr> {
+ let tmp = TalerConfig::load(path);
+ let sect = tmp.section();
+ let auth = {
+ let kind = sect
+ .value("auth method", "AUTH_METHOD", |s| match s {
+ "none" | "basic" => Ok(s),
+ unknown => Err(format!(
+ "unknown config auth method AUTH_METHOD={unknown} expected 'none' or 'basic'"
+ )),
+ })
+ .require()?;
+ match kind {
+ "none" => AuthMethod::None,
+ "basic" => AuthMethod::Basic(sect.str("AUTH_TOKEN").require()?),
+ _ => unreachable!(),
+ }
+ };
+ let serve = if let Some(path) = sect.path("UNIXPATH").opt()? {
+ Serve::Unix {
+ path,
+ permission: Permissions::from_mode(0o660),
+ }
+ } else {
+ let port = sect.number("PORT").default(8080)?;
+ Serve::Tcp(SocketAddr::from(([0, 0, 0, 0], port)))
+ };
-pub fn postgres(properties: &Properties, name: &str) -> Option<postgres::Config> {
- properties.get(name).map(|s| {
- postgres::Config::from_str(s)
- .or_fail(|e| format!("config {}={} is not a valid postgres url: {}", name, s, e))
- })
+ Ok(Self {
+ auth,
+ http_lifetime: tmp.non_zero_option("HTTP_LIFETIME"),
+ db: sect.postgres("DB_URL").require()?,
+ payto: sect.payto("PAYTO").require()?,
+ currency: tmp.currency,
+ serve,
+ })
+ }
}
diff --git a/common/src/error_codes.rs b/common/src/error_codes.rs
@@ -1,2169 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/// Error codes used by GNU Taler
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-#[allow(non_camel_case_types, dead_code)]
-#[repr(u32)]
-pub enum ErrorCode {
- /**
- Special code to indicate success (no error).
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- NONE = 0,
- /**
- A non-integer error code was returned in the JSON response.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- INVALID = 1,
- /**
- The response we got from the server was not even in JSON format.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_INVALID_RESPONSE = 10,
- /**
- An operation timed out.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_TIMEOUT = 11,
- /**
- The version string given does not follow the expected CURRENT:REVISION:AGE Format.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_VERSION_MALFORMED = 12,
- /**
- The service responded with a reply that was in JSON but did not satsify the protocol. Note that invalid cryptographic signatures should have signature-specific error codes.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_REPLY_MALFORMED = 13,
- /**
- There is an error in the client-side configuration, for example the base URL specified is malformed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_CONFIGURATION_INVALID = 14,
- /**
- The HTTP method used is invalid for this endpoint.
- Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_METHOD_INVALID = 20,
- /**
- There is no endpoint defined for the URL provided by the client.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_ENDPOINT_UNKNOWN = 21,
- /**
- The JSON in the client's request was malformed (generic parse error).
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_JSON_INVALID = 22,
- /**
- Some of the HTTP headers provided by the client caused the server to not be able to handle the request.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_HTTP_HEADERS_MALFORMED = 23,
- /**
- The payto:// URI provided by the client is malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PAYTO_URI_MALFORMED = 24,
- /**
- A required parameter in the request was missing.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PARAMETER_MISSING = 25,
- /**
- A parameter in the request was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PARAMETER_MALFORMED = 26,
- /**
- The currencies involved in the operation do not match.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_CURRENCY_MISMATCH = 30,
- /**
- The URI is longer than the longest URI the HTTP server is willing to parse.
- Returned with an HTTP status code of #MHD_HTTP_URI_TOO_LONG (414).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_URI_TOO_LONG = 31,
- /**
- The body is too large to be permissible for the endpoint.
- Returned with an HTTP status code of #MHD_HTTP_PAYLOAD_TOO_LARGE (413).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_UPLOAD_EXCEEDS_LIMIT = 32,
- /**
- The service failed initialize its connection to the database.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_SETUP_FAILED = 50,
- /**
- The service encountered an error event to just start the database transaction.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_START_FAILED = 51,
- /**
- The service failed to store information in its database.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_STORE_FAILED = 52,
- /**
- The service failed to fetch information from its database.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_FETCH_FAILED = 53,
- /**
- The service encountered an error event to commit the database transaction (hard, unrecoverable error).
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_COMMIT_FAILED = 54,
- /**
- The service encountered an error event to commit the database transaction, even after repeatedly retrying it there was always a conflicting transaction. (This indicates a repeated serialization error; should only happen if some client maliciously tries to create conflicting concurrent transactions.)
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_SOFT_FAILURE = 55,
- /**
- The service's database is inconsistent and violates service-internal invariants.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_DB_INVARIANT_FAILURE = 56,
- /**
- The HTTP server experienced an internal invariant failure (bug).
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_INTERNAL_INVARIANT_FAILURE = 60,
- /**
- The service could not compute a cryptographic hash over some JSON value.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_FAILED_COMPUTE_JSON_HASH = 61,
- /**
- The HTTP server had insufficient memory to parse the request.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_PARSER_OUT_OF_MEMORY = 70,
- /**
- The HTTP server failed to allocate memory.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_ALLOCATION_FAILURE = 71,
- /**
- The HTTP server failed to allocate memory for building JSON reply.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_JSON_ALLOCATION_FAILURE = 72,
- /**
- The HTTP server failed to allocate memory for making a CURL request.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- GENERIC_CURL_ALLOCATION_FAILURE = 73,
- /**
- Exchange is badly configured and thus cannot operate.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_BAD_CONFIGURATION = 1000,
- /**
- Operation specified unknown for this endpoint.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_OPERATION_UNKNOWN = 1001,
- /**
- The number of segments included in the URI does not match the number of segments expected by the endpoint.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS = 1002,
- /**
- The same coin was already used with a different denomination previously.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY = 1003,
- /**
- The public key of given to a "/coins/" endpoint of the exchange was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB = 1004,
- /**
- The exchange is not aware of the denomination key the wallet requested for the operation.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN = 1005,
- /**
- The signature of the denomination key over the coin is not valid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DENOMINATION_SIGNATURE_INVALID = 1006,
- /**
- The exchange failed to perform the operation as it could not find the private keys. This is a problem with the exchange setup, not with the client's request.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_KEYS_MISSING = 1007,
- /**
- Validity period of the denomination lies in the future.
- Returned with an HTTP status code of #MHD_HTTP_PRECONDITION_FAILED (412).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE = 1008,
- /**
- Denomination key of the coin is past its expiration time for the requested operation.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_DENOMINATION_EXPIRED = 1009,
- /**
- Denomination key of the coin has been revoked.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_DENOMINATION_REVOKED = 1010,
- /**
- An operation where the exchange interacted with a security module timed out.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_GENERIC_SECMOD_TIMEOUT = 1011,
- /**
- The exchange did not find information about the specified transaction in the database.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSITS_GET_NOT_FOUND = 1100,
- /**
- The wire hash of given to a "/deposits/" handler was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE = 1101,
- /**
- The merchant key of given to a "/deposits/" handler was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB = 1102,
- /**
- The hash of the contract terms given to a "/deposits/" handler was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS = 1103,
- /**
- The coin public key of given to a "/deposits/" handler was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB = 1104,
- /**
- The signature returned by the exchange in a /deposits/ request was malformed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE = 1105,
- /**
- The signature of the merchant is invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID = 1106,
- /**
- The given reserve does not have sufficient funds to admit the requested withdraw operation at this time. The response includes the current "balance" of the reserve as well as the transaction "history" that lead to this balance.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS = 1150,
- /**
- The exchange has no information about the "reserve_pub" that was given.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WITHDRAW_RESERVE_UNKNOWN = 1151,
- /**
- The amount to withdraw together with the fee exceeds the numeric range for Taler amounts. This is not a client failure, as the coin value and fees come from the exchange's configuration.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW = 1152,
- /**
- The exchange failed to create the signature using the denomination key.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WITHDRAW_SIGNATURE_FAILED = 1153,
- /**
- The signature of the reserve is not valid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID = 1154,
- /**
- When computing the reserve history, we ended up with a negative overall balance, which should be impossible.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WITHDRAW_HISTORY_ERROR_INSUFFICIENT_FUNDS = 1155,
- /**
- Withdraw period of the coin to be withdrawn is in the past.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WITHDRAW_DENOMINATION_KEY_LOST = 1158,
- /**
- The client failed to unblind the blind signature.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WITHDRAW_UNBLIND_FAILURE = 1159,
- /**
- The respective coin did not have sufficient residual value for the /deposit operation (i.e. due to double spending). The "history" in the response provides the transaction history of the coin proving this fact.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS = 1200,
- /**
- The signature made by the coin over the deposit permission is not valid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID = 1205,
- /**
- The stated value of the coin after the deposit fee is subtracted would be negative.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE = 1207,
- /**
- The stated refund deadline is after the wire deadline.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE = 1208,
- /**
- The exchange failed to canonicalize and hash the given wire format. For example, the merchant failed to provide the "salt" or a valid payto:// URI in the wire details. Note that while the exchange will do some basic sanity checking on the wire details, it cannot warrant that the banking system will ultimately be able to route to the specified address, even if this check passed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSIT_INVALID_WIRE_FORMAT_JSON = 1210,
- /**
- The hash of the given wire address does not match the wire hash specified in the proposal data.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSIT_INVALID_WIRE_FORMAT_CONTRACT_HASH_CONFLICT = 1211,
- /**
- The signature provided by the exchange is not valid.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE = 1221,
- /**
- The deposited amount is smaller than the deposit fee, which would result in a negative contribution.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT = 1222,
- /**
- The reserve status was requested using a unknown key.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_RESERVES_GET_STATUS_UNKNOWN = 1250,
- /**
- The respective coin did not have sufficient residual value for the /refresh/melt operation. The "history" in this response provdes the "residual_value" of the coin, which may be less than its "original_value".
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MELT_INSUFFICIENT_FUNDS = 1300,
- /**
- The exchange had an internal error reconstructing the transaction history of the coin that was being melted.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MELT_COIN_HISTORY_COMPUTATION_FAILED = 1301,
- /**
- The exchange encountered melt fees exceeding the melted coin's contribution.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION = 1302,
- /**
- The signature made with the coin to be melted is invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MELT_COIN_SIGNATURE_INVALID = 1303,
- /**
- The exchange failed to obtain the transaction history of the given coin from the database while generating an insufficient funds errors.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS = 1304,
- /**
- The denomination of the given coin has past its expiration date and it is also not a valid zombie (that is, was not refreshed with the fresh coin being subjected to recoup).
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE = 1305,
- /**
- The signature returned by the exchange in a melt request was malformed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE = 1306,
- /**
- The provided transfer keys do not match up with the original commitment. Information about the original commitment is included in the response.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_COMMITMENT_VIOLATION = 1353,
- /**
- Failed to produce the blinded signatures over the coins to be returned.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_SIGNING_ERROR = 1354,
- /**
- The exchange is unaware of the refresh session specified in the request.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN = 1355,
- /**
- The size of the cut-and-choose dimension of the private transfer keys request does not match #TALER_CNC_KAPPA - 1.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID = 1356,
- /**
- The number of coins to be created in refresh exceeds the limits of the exchange. private transfer keys request does not match #TALER_CNC_KAPPA - 1.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE = 1357,
- /**
- The number of envelopes given does not match the number of denomination keys given.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH = 1358,
- /**
- The exchange encountered a numeric overflow totaling up the cost for the refresh operation.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW = 1359,
- /**
- The exchange's cost calculation shows that the melt amount is below the costs of the transaction.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_AMOUNT_INSUFFICIENT = 1360,
- /**
- The signature made with the coin over the link data is invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID = 1361,
- /**
- The refresh session hash given to a /refreshes/ handler was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_INVALID_RCH = 1362,
- /**
- Operation specified invalid for this endpoint.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFRESHES_REVEAL_OPERATION_INVALID = 1363,
- /**
- The coin specified in the link request is unknown to the exchange.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_LINK_COIN_UNKNOWN = 1400,
- /**
- The public key of given to a /transfers/ handler was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_TRANSFERS_GET_WTID_MALFORMED = 1450,
- /**
- The exchange did not find information about the specified wire transfer identifier in the database.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND = 1451,
- /**
- The exchange did not find information about the wire transfer fees it charged.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND = 1452,
- /**
- The exchange found a wire fee that was above the total transfer value (and thus could not have been charged).
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT = 1453,
- /**
- The exchange knows literally nothing about the coin we were asked to refund. But without a transaction history, we cannot issue a refund. This is kind-of OK, the owner should just refresh it directly without executing the refund.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_COIN_NOT_FOUND = 1500,
- /**
- We could not process the refund request as the coin's transaction history does not permit the requested refund because then refunds would exceed the deposit amount. The "history" in the response proves this.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT = 1501,
- /**
- The exchange knows about the coin we were asked to refund, but not about the specific /deposit operation. Hence, we cannot issue a refund (as we do not know if this merchant public key is authorized to do a refund).
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_DEPOSIT_NOT_FOUND = 1502,
- /**
- The exchange can no longer refund the customer/coin as the money was already transferred (paid out) to the merchant. (It should be past the refund deadline.)
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_MERCHANT_ALREADY_PAID = 1503,
- /**
- The refund fee specified for the request is lower than the refund fee charged by the exchange for the given denomination key of the refunded coin.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_FEE_TOO_LOW = 1504,
- /**
- The refunded amount is smaller than the refund fee, which would result in a negative refund.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_FEE_ABOVE_AMOUNT = 1505,
- /**
- The signature of the merchant is invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID = 1506,
- /**
- Merchant backend failed to create the refund confirmation signature.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_MERCHANT_SIGNING_FAILED = 1507,
- /**
- The signature returned by the exchange in a refund request was malformed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE = 1508,
- /**
- The failure proof returned by the exchange is incorrect.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE = 1509,
- /**
- Conflicting refund granted before with different amount but same refund transaction ID.
- Returned with an HTTP status code of #MHD_HTTP_FAILED_DEPENDENCY (424).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_REFUND_INCONSISTENT_AMOUNT = 1510,
- /**
- The given coin signature is invalid for the request.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_RECOUP_SIGNATURE_INVALID = 1550,
- /**
- The exchange could not find the corresponding withdraw operation. The request is denied.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND = 1551,
- /**
- The coin's remaining balance is zero. The request is denied.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_RECOUP_COIN_BALANCE_ZERO = 1552,
- /**
- The exchange failed to reproduce the coin's blinding.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_RECOUP_BLINDING_FAILED = 1553,
- /**
- The coin's remaining balance is zero. The request is denied.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_RECOUP_COIN_BALANCE_NEGATIVE = 1554,
- /**
- The coin's denomination has not been revoked yet.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_RECOUP_NOT_ELIGIBLE = 1555,
- /**
- This exchange does not allow clients to request /keys for times other than the current (exchange) time.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_KEYS_TIMETRAVEL_FORBIDDEN = 1600,
- /**
- A signature in the server's response was malformed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WIRE_SIGNATURE_INVALID = 1650,
- /**
- No bank accounts are enabled for the exchange. The administrator should enable-account using the taler-exchange-offline tool.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED = 1651,
- /**
- The payto:// URI stored in the exchange database for its bank account is malformed.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED = 1652,
- /**
- No wire fees are configured for an enabled wire method of the exchange. The administrator must set the wire-fee using the taler-exchange-offline tool.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_WIRE_FEES_NOT_CONFIGURED = 1653,
- /**
- The exchange failed to talk to the process responsible for its private denomination keys.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE = 1700,
- /**
- The response from the denomination key helper process was malformed.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DENOMINATION_HELPER_BUG = 1701,
- /**
- The helper refuses to sign with the key, because it is too early: the validity period has not yet started.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_DENOMINATION_HELPER_TOO_EARLY = 1702,
- /**
- The exchange failed to talk to the process responsible for its private signing keys.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE = 1750,
- /**
- The response from the online signing key helper process was malformed.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_SIGNKEY_HELPER_BUG = 1751,
- /**
- The helper refuses to sign with the key, because it is too early: the validity period has not yet started.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_SIGNKEY_HELPER_TOO_EARLY = 1752,
- /**
- The auditor that was supposed to be disabled is unknown to this exchange.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_AUDITOR_NOT_FOUND = 1800,
- /**
- The exchange has a more recently signed conflicting instruction and is thus refusing the current change (replay detected).
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT = 1801,
- /**
- The signature to add or enable the auditor does not validate.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_AUDITOR_ADD_SIGNATURE_INVALID = 1802,
- /**
- The signature to disable the auditor does not validate.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_AUDITOR_DEL_SIGNATURE_INVALID = 1803,
- /**
- The signature to revoke the denomination does not validate.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_DENOMINATION_REVOKE_SIGNATURE_INVALID = 1804,
- /**
- The signature to revoke the online signing key does not validate.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_SIGNKEY_REVOKE_SIGNATURE_INVALID = 1805,
- /**
- The exchange has a more recently signed conflicting instruction and is thus refusing the current change (replay detected).
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT = 1806,
- /**
- The signingkey specified is unknown to the exchange.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN = 1807,
- /**
- The signature to publish wire account does not validate.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_WIRE_DETAILS_SIGNATURE_INVALID = 1808,
- /**
- The signature to add the wire account does not validate.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_WIRE_ADD_SIGNATURE_INVALID = 1809,
- /**
- The signature to disable the wire account does not validate.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_WIRE_DEL_SIGNATURE_INVALID = 1810,
- /**
- The wire account to be disabled is unknown to the exchange.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_WIRE_NOT_FOUND = 1811,
- /**
- The signature to affirm wire fees does not validate.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_WIRE_FEE_SIGNATURE_INVALID = 1812,
- /**
- The signature conflicts with a previous signature affirming different fees.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_WIRE_FEE_MISMATCH = 1813,
- /**
- The signature affirming the denomination key is invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID = 1814,
- /**
- The signature affirming the signing key is invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID = 1815,
- /**
- The auditor signature over the denomination meta data is invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_AUDITORS_AUDITOR_SIGNATURE_INVALID = 1900,
- /**
- The auditor that was specified is unknown to this exchange.
- Returned with an HTTP status code of #MHD_HTTP_PRECONDITION_FAILED (412).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_AUDITORS_AUDITOR_UNKNOWN = 1901,
- /**
- The auditor that was specified is no longer used by this exchange.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_AUDITORS_AUDITOR_INACTIVE = 1902,
- /**
- The signature affirming the wallet's KYC request was invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_KYC_WALLET_SIGNATURE_INVALID = 1925,
- /**
- The exchange received an unexpected malformed response from its KYC backend.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE = 1926,
- /**
- The backend signaled an unexpected failure.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_KYC_PROOF_BACKEND_ERROR = 1927,
- /**
- The backend signaled an authorization failure.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_KYC_PROOF_BACKEND_AUTHORIZATION_FAILED = 1928,
- /**
- The payto-URI hash did not match. Hence the request was denied.
- Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
- (A value of 0 indicates that the error is generated client-side).
- */
- EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED = 1930,
- /**
- The backend could not find the merchant instance specified in the request.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_INSTANCE_UNKNOWN = 2000,
- /**
- The start and end-times in the wire fee structure leave a hole. This is not allowed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE = 2001,
- /**
- The reserve key of given to a /reserves/ handler was malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_RESERVE_PUB_MALFORMED = 2002,
- /**
- The backend could not locate a required template to generate an HTML reply.
- Returned with an HTTP status code of #MHD_HTTP_NOT_ACCEPTABLE (406).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_FAILED_TO_LOAD_TEMPLATE = 2003,
- /**
- The backend could not expand the template to generate an HTML reply.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_FAILED_TO_EXPAND_TEMPLATE = 2004,
- /**
- The proposal is not known to the backend.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_ORDER_UNKNOWN = 2005,
- /**
- The order provided to the backend could not be completed, because a product to be completed via inventory data is not actually in our inventory.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_PRODUCT_UNKNOWN = 2006,
- /**
- The tip ID is unknown. This could happen if the tip has expired.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_TIP_ID_UNKNOWN = 2007,
- /**
- The contract obtained from the merchant backend was malformed.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID = 2008,
- /**
- The order we found does not match the provided contract hash.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER = 2009,
- /**
- The exchange failed to provide a valid response to the merchant's /keys request.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE = 2010,
- /**
- The exchange failed to respond to the merchant on time.
- Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_EXCHANGE_TIMEOUT = 2011,
- /**
- The merchant failed to talk to the exchange.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE = 2012,
- /**
- The exchange returned a maformed response.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_EXCHANGE_REPLY_MALFORMED = 2013,
- /**
- The exchange returned an unexpected response status.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS = 2014,
- /**
- The merchant refused the request due to lack of authorization.
- Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_UNAUTHORIZED = 2015,
- /**
- The merchant instance specified in the request was deleted.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_INSTANCE_DELETED = 2016,
- /**
- The backend could not find the inbound wire transfer specified in the request.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GENERIC_TRANSFER_UNKNOWN = 2017,
- /**
- The exchange failed to provide a valid answer to the tracking request, thus those details are not in the response.
- Returned with an HTTP status code of #MHD_HTTP_OK (200).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GET_ORDERS_EXCHANGE_TRACKING_FAILURE = 2100,
- /**
- The merchant backend failed to construct the request for tracking to the exchange, thus tracking details are not in the response.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GET_ORDERS_ID_EXCHANGE_REQUEST_FAILURE = 2103,
- /**
- The merchant backend failed trying to contact the exchange for tracking details, thus those details are not in the response.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GET_ORDERS_ID_EXCHANGE_LOOKUP_START_FAILURE = 2104,
- /**
- The claim token used to authenticate the client is invalid for this order.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GET_ORDERS_ID_INVALID_TOKEN = 2105,
- /**
- The contract terms hash used to authenticate the client is invalid for this order.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH = 2106,
- /**
- The exchange responded saying that funds were insufficient (for example, due to double-spending).
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS = 2150,
- /**
- The denomination key used for payment is not listed among the denomination keys of the exchange.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND = 2151,
- /**
- The denomination key used for payment is not audited by an auditor approved by the merchant.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_AUDITOR_FAILURE = 2152,
- /**
- There was an integer overflow totaling up the amounts or deposit fees in the payment.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW = 2153,
- /**
- The deposit fees exceed the total value of the payment.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT = 2154,
- /**
- After considering deposit and wire fees, the payment is insufficient to satisfy the required amount for the contract. The client should revisit the logic used to calculate fees it must cover.
- Returned with an HTTP status code of #MHD_HTTP_NOT_ACCEPTABLE (406).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES = 2155,
- /**
- Even if we do not consider deposit and wire fees, the payment is insufficient to satisfy the required amount for the contract.
- Returned with an HTTP status code of #MHD_HTTP_NOT_ACCEPTABLE (406).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT = 2156,
- /**
- The signature over the contract of one of the coins was invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_COIN_SIGNATURE_INVALID = 2157,
- /**
- When we tried to find information about the exchange to issue the deposit, we failed. This usually only happens if the merchant backend is somehow unable to get its own HTTP client logic to work.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED = 2158,
- /**
- The refund deadline in the contract is after the transfer deadline.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE = 2159,
- /**
- The payment is too late, the offer has expired.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED = 2161,
- /**
- The "merchant" field is missing in the proposal data. This is an internal error as the proposal is from the merchant's own database at this point.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING = 2162,
- /**
- Failed to locate merchant's account information matching the wire hash given in the proposal.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN = 2163,
- /**
- The deposit time for the denomination has expired.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED = 2165,
- /**
- The exchange of the deposited coin charges a wire fee that could not be added to the total (total amount too high).
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED = 2166,
- /**
- The contract was not fully paid because of refunds. Note that clients MAY treat this as paid if, for example, contracts must be executed despite of refunds.
- Returned with an HTTP status code of #MHD_HTTP_PAYMENT_REQUIRED (402).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_REFUNDED = 2167,
- /**
- According to our database, we have refunded more than we were paid (which should not be possible).
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS = 2168,
- /**
- Legacy stuff. Remove me with protocol v1.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- DEAD_QQQ_PAY_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE = 2169,
- /**
- The payment failed at the exchange.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_FAILED = 2170,
- /**
- The contract hash does not match the given order ID.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAID_CONTRACT_HASH_MISMATCH = 2200,
- /**
- The signature of the merchant is not valid for the given contract hash.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_PAID_COIN_SIGNATURE_INVALID = 2201,
- /**
- The merchant failed to send the exchange the refund request.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_REFUND_FAILED = 2251,
- /**
- The merchant failed to find the exchange to process the lookup.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_LOOKUP_FAILED = 2252,
- /**
- The merchant could not find the contract.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND = 2253,
- /**
- The payment was already completed and thus cannot be aborted anymore.
- Returned with an HTTP status code of #MHD_HTTP_PRECONDITION_FAILED (412).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE = 2254,
- /**
- The hash provided by the wallet does not match the order.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH = 2255,
- /**
- The array of coins cannot be empty.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY = 2256,
- /**
- We could not claim the order because the backend is unaware of it.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND = 2300,
- /**
- We could not claim the order because someone else claimed it first.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED = 2301,
- /**
- The client-side experienced an internal failure.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_CLAIM_CLIENT_INTERNAL_FAILURE = 2302,
- /**
- The backend failed to sign the refund request.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_POST_ORDERS_ID_REFUND_SIGNATURE_FAILED = 2350,
- /**
- The client failed to unblind the signature returned by the merchant.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_TIP_PICKUP_UNBLIND_FAILURE = 2400,
- /**
- The exchange returned a failure code for the withdraw operation.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_TIP_PICKUP_EXCHANGE_ERROR = 2403,
- /**
- The merchant failed to add up the amounts to compute the pick up value.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_TIP_PICKUP_SUMMATION_FAILED = 2404,
- /**
- The tip expired.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_TIP_PICKUP_HAS_EXPIRED = 2405,
- /**
- The requested withdraw amount exceeds the amount remaining to be picked up.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING = 2406,
- /**
- The merchant did not find the specified denomination key in the exchange's key set.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN = 2407,
- /**
- The backend lacks a wire transfer method configuration option for the given instance. Thus, this instance is unavailable (not findable for creating new orders).
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE = 2500,
- /**
- The proposal had no timestamp and the backend failed to obtain the local time. Likely to be an internal error.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME = 2501,
- /**
- The order provided to the backend could not be parsed, some required fields were missing or ill-formed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR = 2502,
- /**
- The backend encountered an error: the proposal already exists.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS = 2503,
- /**
- The request is invalid: the wire deadline is before the refund deadline.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE = 2504,
- /**
- The request is invalid: a delivery date was given, but it is in the past.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST = 2505,
- /**
- One of the paths to forget is malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_SYNTAX_INCORRECT = 2510,
- /**
- One of the paths to forget was not marked as forgettable.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_NOT_FORGETTABLE = 2511,
- /**
- The order provided to the backend could not be deleted, our offer is still valid and awaiting payment.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_DELETE_ORDERS_AWAITING_PAYMENT = 2520,
- /**
- The amount to be refunded is inconsistent: either is lower than the previous amount being awarded, or it is too big to be paid back. In this second case, the fault stays on the business dept. side.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_INCONSISTENT_AMOUNT = 2530,
- /**
- The frontend gave an unpaid order id to issue the refund to.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID = 2531,
- /**
- The refund delay was set to 0 and thus no refunds are allowed for this order.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_NOT_ALLOWED_BY_CONTRACT = 2532,
- /**
- The exchange says it does not know this transfer.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TRANSFERS_EXCHANGE_UNKNOWN = 2550,
- /**
- We internally failed to execute the /track/transfer request.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TRANSFERS_REQUEST_ERROR = 2551,
- /**
- The amount transferred differs between what was submitted and what the exchange claimed.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS = 2552,
- /**
- The exchange gave conflicting information about a coin which has been wire transferred.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS = 2553,
- /**
- The exchange charged a different wire fee than what it originally advertised, and it is higher.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE = 2554,
- /**
- We did not find the account that the transfer was made to.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TRANSFERS_ACCOUNT_NOT_FOUND = 2555,
- /**
- The backend could not delete the transfer as the echange already replied to our inquiry about it and we have integrated the result.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_DELETE_TRANSFERS_ALREADY_CONFIRMED = 2556,
- /**
- The backend was previously informed about a wire transfer with the same ID but a different amount. Multiple wire transfers with the same ID are not allowed. If the new amount is correct, the old transfer should first be deleted.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_SUBMISSION = 2557,
- /**
- The merchant backend cannot create an instance under the given identifier as one already exists. Use PATCH to modify the existing entry.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS = 2600,
- /**
- The merchant backend cannot create an instance because the authentication configuration field is malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH = 2601,
- /**
- The merchant backend cannot update an instance's authentication settings because the provided authentication settings are malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_INSTANCE_AUTH_BAD_AUTH = 2602,
- /**
- The merchant backend cannot create an instance under the given identifier, the previous one was deleted but must be purged first.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED = 2603,
- /**
- The merchant backend cannot update an instance under the given identifier, the previous one was deleted but must be purged first.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED = 2625,
- /**
- The product ID exists.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS = 2650,
- /**
- The update would have reduced the total amount of product lost, which is not allowed.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED = 2660,
- /**
- The update would have mean that more stocks were lost than what remains from total inventory after sales, which is not allowed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS = 2661,
- /**
- The update would have reduced the total amount of product in stock, which is not allowed.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED = 2662,
- /**
- The update would have reduced the total amount of product sold, which is not allowed.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED = 2663,
- /**
- The lock request is for more products than we have left (unlocked) in stock.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_PRODUCTS_LOCK_INSUFFICIENT_STOCKS = 2670,
- /**
- The deletion request is for a product that is locked.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_DELETE_PRODUCTS_CONFLICTING_LOCK = 2680,
- /**
- The requested wire method is not supported by the exchange.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD = 2700,
- /**
- The reserve could not be deleted because it is unknown.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_DELETE_RESERVES_NO_SUCH_RESERVE = 2710,
- /**
- The reserve that was used to fund the tips has expired.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED = 2750,
- /**
- The reserve that was used to fund the tips was not found in the DB.
- Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE (503).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_UNKNOWN = 2751,
- /**
- The backend knows the instance that was supposed to support the tip, and it was configured for tipping. However, the funds remaining are insufficient to cover the tip, and the merchant should top up the reserve.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS = 2752,
- /**
- The backend failed to find a reserve needed to authorize the tip.
- Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE (503).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND = 2753,
- /**
- The merchant backend encountered a failure in computing the deposit total.
- Returned with an HTTP status code of #MHD_HTTP_OK (200).
- (A value of 0 indicates that the error is generated client-side).
- */
- MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE = 2800,
- /**
- The signature from the exchange on the deposit confirmation is invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID = 3100,
- /**
- The exchange key used for the signature on the deposit confirmation was revoked.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- AUDITOR_EXCHANGE_SIGNING_KEY_REVOKED = 3101,
- /**
- Wire transfer attempted with credit and debit party being the same bank account.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_SAME_ACCOUNT = 5101,
- /**
- Wire transfer impossible, due to financial limitation of the party that attempted the payment.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_UNALLOWED_DEBIT = 5102,
- /**
- Negative number was used (as value and/or fraction) to initiate a Amount object.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_NEGATIVE_NUMBER_AMOUNT = 5103,
- /**
- A number too big was used (as value and/or fraction) to initiate a amount object.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_NUMBER_TOO_BIG = 5104,
- /**
- Could not login for the requested operation.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_LOGIN_FAILED = 5105,
- /**
- The bank account referenced in the requested operation was not found. Returned along "400 Not found".
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_UNKNOWN_ACCOUNT = 5106,
- /**
- The transaction referenced in the requested operation (typically a reject operation), was not found.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_TRANSACTION_NOT_FOUND = 5107,
- /**
- Bank received a malformed amount string.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_BAD_FORMAT_AMOUNT = 5108,
- /**
- The client does not own the account credited by the transaction which is to be rejected, so it has no rights do reject it. To be returned along HTTP 403 Forbidden.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_REJECT_NO_RIGHTS = 5109,
- /**
- This error code is returned when no known exception types captured the exception, and comes along with a 500 Internal Server Error.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_UNMANAGED_EXCEPTION = 5110,
- /**
- This error code is used for all those exceptions that do not really need a specific error code to return to the client, but need to signal the middleware that the bank is not responding with 500 Internal Server Error. Used for example when a client is trying to register with a unavailable username.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_SOFT_EXCEPTION = 5111,
- /**
- The request UID for a request to transfer funds has already been used, but with different details for the transfer.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_TRANSFER_REQUEST_UID_REUSED = 5112,
- /**
- The withdrawal operation already has a reserve selected. The current request conflicts with the existing selection.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT = 5113,
- /**
- The wire transfer subject duplicates an existing reserve public key. But wire transfer subjects must be unique.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- BANK_DUPLICATE_RESERVE_PUB_SUBJECT = 5114,
- /**
- The sync service failed find the account in its database.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_ACCOUNT_UNKNOWN = 6100,
- /**
- The SHA-512 hash provided in the If-None-Match header is malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_BAD_IF_NONE_MATCH = 6101,
- /**
- The SHA-512 hash provided in the If-Match header is malformed or missing.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_BAD_IF_MATCH = 6102,
- /**
- The signature provided in the "Sync-Signature" header is malformed or missing.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_BAD_SYNC_SIGNATURE = 6103,
- /**
- The signature provided in the "Sync-Signature" header does not match the account, old or new Etags.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_INVALID_SIGNATURE = 6104,
- /**
- The "Content-length" field for the upload is not a number.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_MALFORMED_CONTENT_LENGTH = 6105,
- /**
- The "Content-length" field for the upload is too big based on the server's terms of service.
- Returned with an HTTP status code of #MHD_HTTP_PAYLOAD_TOO_LARGE (413).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_EXCESSIVE_CONTENT_LENGTH = 6106,
- /**
- The server is out of memory to handle the upload. Trying again later may succeed.
- Returned with an HTTP status code of #MHD_HTTP_PAYLOAD_TOO_LARGE (413).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_OUT_OF_MEMORY_ON_CONTENT_LENGTH = 6107,
- /**
- The uploaded data does not match the Etag.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_INVALID_UPLOAD = 6108,
- /**
- HTTP server experienced a timeout while awaiting promised payment.
- Returned with an HTTP status code of #MHD_HTTP_REQUEST_TIMEOUT (408).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_PAYMENT_GENERIC_TIMEOUT = 6109,
- /**
- Sync could not setup the payment request with its own backend.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_PAYMENT_CREATE_BACKEND_ERROR = 6110,
- /**
- The sync service failed find the backup to be updated in its database.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_PREVIOUS_BACKUP_UNKNOWN = 6111,
- /**
- The "Content-length" field for the upload is missing.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- SYNC_MISSING_CONTENT_LENGTH = 6112,
- /**
- The wallet does not implement a version of the exchange protocol that is compatible with the protocol version of the exchange.
- Returned with an HTTP status code of #MHD_HTTP_NOT_IMPLEMENTED (501).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE = 7000,
- /**
- The wallet encountered an unexpected exception. This is likely a bug in the wallet implementation.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_UNEXPECTED_EXCEPTION = 7001,
- /**
- The wallet received a response from a server, but the response can't be parsed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_RECEIVED_MALFORMED_RESPONSE = 7002,
- /**
- The wallet tried to make a network request, but it received no response.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_NETWORK_ERROR = 7003,
- /**
- The wallet tried to make a network request, but it was throttled.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_HTTP_REQUEST_THROTTLED = 7004,
- /**
- The wallet made a request to a service, but received an error response it does not know how to handle.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_UNEXPECTED_REQUEST_ERROR = 7005,
- /**
- The denominations offered by the exchange are insufficient. Likely the exchange is badly configured or not maintained.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT = 7006,
- /**
- The wallet does not support the operation requested by a client.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_CORE_API_OPERATION_UNKNOWN = 7007,
- /**
- The given taler://pay URI is invalid.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_INVALID_TALER_PAY_URI = 7008,
- /**
- The signature on a coin by the exchange's denomination key is invalid after unblinding it.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_EXCHANGE_COIN_SIGNATURE_INVALID = 7009,
- /**
- The exchange does not know about the reserve (yet), and thus withdrawal can't progress.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN_AT_EXCHANGE = 7010,
- /**
- The wallet core service is not available.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_CORE_NOT_AVAILABLE = 7011,
- /**
- The bank has aborted a withdrawal operation, and thus a withdrawal can't complete.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK = 7012,
- /**
- An HTTP request made by the wallet timed out.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_HTTP_REQUEST_GENERIC_TIMEOUT = 7013,
- /**
- The order has already been claimed by another wallet.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_ORDER_ALREADY_CLAIMED = 7014,
- /**
- A group of withdrawal operations (typically for the same reserve at the same exchange) has errors and will be tried again later.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_WITHDRAWAL_GROUP_INCOMPLETE = 7015,
- /**
- The signature on a coin by the exchange's denomination key (obtained through the merchant via tipping) is invalid after unblinding it.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_TIPPING_COIN_SIGNATURE_INVALID = 7016,
- /**
- The wallet does not implement a version of the bank integration API that is compatible with the version offered by the bank.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE = 7017,
- /**
- The wallet processed a taler://pay URI, but the merchant base URL in the downloaded contract terms does not match the merchant base URL derived from the URI.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH = 7018,
- /**
- The merchant's signature on the contract terms is invalid.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_CONTRACT_TERMS_SIGNATURE_INVALID = 7019,
- /**
- The contract terms given by the merchant are malformed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- WALLET_CONTRACT_TERMS_MALFORMED = 7020,
- /**
- We encountered a timeout with our payment backend.
- Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_GENERIC_BACKEND_TIMEOUT = 8000,
- /**
- The backend requested payment, but the request is malformed.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST = 8001,
- /**
- The backend got an unexpected reply from the payment processor.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_GENERIC_BACKEND_ERROR = 8002,
- /**
- The "Content-length" field for the upload is missing.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_GENERIC_MISSING_CONTENT_LENGTH = 8003,
- /**
- The "Content-length" field for the upload is malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_GENERIC_MALFORMED_CONTENT_LENGTH = 8004,
- /**
- The backend failed to setup an order with the payment processor.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_GENERIC_ORDER_CREATE_BACKEND_ERROR = 8005,
- /**
- The backend was not authorized to check for payment with the payment processor.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED = 8006,
- /**
- The backend could not check payment status with the payment processor.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_GENERIC_PAYMENT_CHECK_START_FAILED = 8007,
- /**
- The truth public key is unknown to the provider.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_UNKNOWN = 8108,
- /**
- The authorization method used by the truth is no longer supported by the provider.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED = 8109,
- /**
- The client needs to respond to the challenge.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED = 8110,
- /**
- The client's response to the challenge was invalid.
- Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_CHALLENGE_FAILED = 8111,
- /**
- The service is unaware of having issued a challenge.
- Returned with an HTTP status code of #MHD_HTTP_GONE (410).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_CHALLENGE_UNKNOWN = 8112,
- /**
- A challenge is already active, the service is thus not issuing a new one.
- Returned with an HTTP status code of #MHD_HTTP_ALREADY_REPORTED (208).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_CHALLENGE_ACTIVE = 8113,
- /**
- The backend failed to initiate the authorization process.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED = 8114,
- /**
- The authorization succeeded, but the key share is no longer available.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_KEY_SHARE_GONE = 8115,
- /**
- The backend forgot the order we asked the client to pay for
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_ORDER_DISAPPEARED = 8116,
- /**
- The backend itself reported a bad exchange interaction.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD = 8117,
- /**
- The backend reported a payment status we did not expect.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS = 8118,
- /**
- The backend failed to setup the order for payment.
- Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR = 8119,
- /**
- The decryption of the truth object failed with the provided key.
- Returned with an HTTP status code of #MHD_HTTP_EXPECTATION_FAILED (417).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_DECRYPTION_FAILED = 8120,
- /**
- The request rate is too high. The server is refusing requests to guard against brute-force attacks.
- Returned with an HTTP status code of #MHD_HTTP_TOO_MANY_REQUESTS (429).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_RATE_LIMITED = 8121,
- /**
- The authentication process did not yet complete. The user should try again later.
- Returned with an HTTP status code of #MHD_HTTP_REQUEST_TIMEOUT (408).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_AUTH_TIMEOUT = 8122,
- /**
- The backend failed to store the truth because the UUID is already in use.
- Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_UPLOAD_UUID_EXISTS = 8150,
- /**
- The backend failed to store the truth because the authorization method is not supported.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TRUTH_UPLOAD_METHOD_NOT_SUPPORTED = 8151,
- /**
- The provided phone number is not an acceptable number.
- Returned with an HTTP status code of #MHD_HTTP_EXPECTATION_FAILED (417).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_SMS_PHONE_INVALID = 8200,
- /**
- Failed to run the SMS transmission helper process.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_SMS_HELPER_EXEC_FAILED = 8201,
- /**
- Provider failed to send SMS. Helper terminated with a non-successful result.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_SMS_HELPER_COMMAND_FAILED = 8202,
- /**
- The provided email address is not an acceptable address.
- Returned with an HTTP status code of #MHD_HTTP_EXPECTATION_FAILED (417).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_EMAIL_INVALID = 8210,
- /**
- Failed to run the E-mail transmission helper process.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_EMAIL_HELPER_EXEC_FAILED = 8211,
- /**
- Provider failed to send E-mail. Helper terminated with a non-successful result.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_EMAIL_HELPER_COMMAND_FAILED = 8212,
- /**
- The provided postal address is not an acceptable address.
- Returned with an HTTP status code of #MHD_HTTP_EXPECTATION_FAILED (417).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POST_INVALID = 8220,
- /**
- Failed to run the mail transmission helper process.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POST_HELPER_EXEC_FAILED = 8221,
- /**
- Provider failed to send mail. Helper terminated with a non-successful result.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POST_HELPER_COMMAND_FAILED = 8222,
- /**
- The provided IBAN address is not an acceptable IBAN.
- Returned with an HTTP status code of #MHD_HTTP_EXPECTATION_FAILED (417).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_IBAN_INVALID = 8230,
- /**
- The backend did not find a TOTP key in the data provided.
- Returned with an HTTP status code of #MHD_HTTP_EXPECTATION_FAILED (417).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TOTP_KEY_MISSING = 8240,
- /**
- The key provided does not satisfy the format restrictions for an Anastasis TOTP key.
- Returned with an HTTP status code of #MHD_HTTP_EXPECTATION_FAILED (417).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_TOTP_KEY_INVALID = 8241,
- /**
- The given if-none-match header is malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POLICY_BAD_IF_NONE_MATCH = 8301,
- /**
- The server is out of memory to handle the upload. Trying again later may succeed.
- Returned with an HTTP status code of #MHD_HTTP_PAYLOAD_TOO_LARGE (413).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POLICY_OUT_OF_MEMORY_ON_CONTENT_LENGTH = 8304,
- /**
- The signature provided in the "Anastasis-Policy-Signature" header is malformed or missing.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POLICY_BAD_SIGNATURE = 8305,
- /**
- The given if-match header is malformed.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POLICY_BAD_IF_MATCH = 8306,
- /**
- The uploaded data does not match the Etag.
- Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POLICY_INVALID_UPLOAD = 8307,
- /**
- The provider is unaware of the requested policy.
- Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_POLICY_NOT_FOUND = 8350,
- /**
- The given action is invalid for the current state of the reducer.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_ACTION_INVALID = 8400,
- /**
- The given state of the reducer is invalid.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_STATE_INVALID = 8401,
- /**
- The given input to the reducer is invalid.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_INPUT_INVALID = 8402,
- /**
- The selected authentication method does not work for the Anastasis provider.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_AUTHENTICATION_METHOD_NOT_SUPPORTED = 8403,
- /**
- The given input and action do not work for the current state.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE = 8404,
- /**
- We experienced an unexpected failure interacting with the backend.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_BACKEND_FAILURE = 8405,
- /**
- The contents of a resource file did not match our expectations.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_RESOURCE_MALFORMED = 8406,
- /**
- A required resource file is missing.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_RESOURCE_MISSING = 8407,
- /**
- An input did not match the regular expression.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_INPUT_REGEX_FAILED = 8408,
- /**
- An input did not match the custom validation logic.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED = 8409,
- /**
- Our attempts to download the recovery document failed with all providers. Most likely the personal information you entered differs from the information you provided during the backup process and you should go back to the previous step. Alternatively, if you used a backup provider that is unknown to this application, you should add that provider manually.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED = 8410,
- /**
- Anastasis provider reported a fatal failure.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED = 8411,
- /**
- Anastasis provider failed to respond to the configuration request.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED = 8412,
- /**
- The policy we downloaded is malformed. Must have been a client error while creating the backup.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_POLICY_MALFORMED = 8413,
- /**
- We failed to obtain the policy, likely due to a network issue.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_NETWORK_FAILED = 8414,
- /**
- The recovered secret did not match the required syntax.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_SECRET_MALFORMED = 8415,
- /**
- The challenge data provided is too large for the available providers.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_CHALLENGE_DATA_TOO_BIG = 8416,
- /**
- The provided core secret is too large for some of the providers.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_SECRET_TOO_BIG = 8417,
- /**
- The provider returned in invalid configuration.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG = 8418,
- /**
- The reducer encountered an internal error, likely a bug that needs to be reported.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- ANASTASIS_REDUCER_INTERNAL_ERROR = 8419,
- /**
- A generic error happened in the LibEuFin nexus. See the enclose details JSON for more information.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- LIBEUFIN_NEXUS_GENERIC_ERROR = 9000,
- /**
- An uncaught exception happened in the LibEuFin nexus service.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- LIBEUFIN_NEXUS_UNCAUGHT_EXCEPTION = 9001,
- /**
- A generic error happened in the LibEuFin sandbox. See the enclose details JSON for more information.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- LIBEUFIN_SANDBOX_GENERIC_ERROR = 9500,
- /**
- An uncaught exception happened in the LibEuFin sandbox service.
- Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
- (A value of 0 indicates that the error is generated client-side).
- */
- LIBEUFIN_SANDBOX_UNCAUGHT_EXCEPTION = 9501,
- /**
- End of error code range.
- Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
- (A value of 0 indicates that the error is generated client-side).
- */
- END = 9999,
-}
diff --git a/common/src/lib.rs b/common/src/lib.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -16,21 +16,19 @@
use std::{process::exit, thread::JoinHandle};
use ::log::error;
-use rand::{rngs::OsRng, RngCore};
+use rand::{RngCore, rngs::ThreadRng};
use zeroize::Zeroizing;
-pub use base32;
pub use postgres;
pub use rand;
+pub use taler_common;
pub use url;
-pub mod api_common;
-pub mod api_wire;
pub mod config;
pub mod currency;
-pub mod error_codes;
pub mod log;
pub mod metadata;
+pub mod payto;
pub mod reconnect;
pub mod sql;
pub mod status;
@@ -38,7 +36,7 @@ pub mod status;
/// Secure random slice generator using getrandom
pub fn rand_slice<const N: usize>() -> [u8; N] {
let mut slice = [0; N];
- OsRng.fill_bytes(&mut slice);
+ ThreadRng::default().fill_bytes(&mut slice);
slice
}
diff --git a/common/src/metadata.rs b/common/src/metadata.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -16,6 +16,7 @@
use std::fmt::Debug;
+use taler_common::api_common::{EddsaPublicKey, ShortHashCode};
use url::Url;
#[derive(Debug, Clone, thiserror::Error)]
@@ -37,7 +38,7 @@ pub enum EncodeErr {
/// Encoded metadata for outgoing transaction
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OutMetadata {
- Debit { wtid: [u8; 32], url: Url },
+ Debit { wtid: ShortHashCode, url: Url },
Bounce { bounced: [u8; 32] },
}
@@ -55,7 +56,7 @@ impl OutMetadata {
scheme => return Err(EncodeErr::UnsupportedScheme(scheme.to_string())),
};
buffer.push(scheme_id);
- buffer.extend_from_slice(wtid);
+ buffer.extend_from_slice(wtid.as_slice());
let parts = format!("{}{}", url.domain().unwrap_or(""), url.path());
let packed = uri_pack::pack_uri(&parts).unwrap();
buffer.extend_from_slice(&packed);
@@ -105,7 +106,7 @@ impl OutMetadata {
/// Encoded metadata for incoming transaction
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InMetadata {
- Credit { reserve_pub: [u8; 32] },
+ Credit { reserve_pub: EddsaPublicKey },
}
impl InMetadata {
@@ -114,7 +115,7 @@ impl InMetadata {
match self {
InMetadata::Credit { reserve_pub } => {
buffer.push(0);
- buffer.extend_from_slice(reserve_pub);
+ buffer.extend_from_slice(reserve_pub.as_slice());
}
}
buffer
@@ -141,6 +142,7 @@ impl InMetadata {
#[cfg(test)]
mod test {
+ use taler_common::api_common::{EddsaPublicKey, ShortHashCode};
use url::Url;
use crate::{
@@ -152,7 +154,7 @@ mod test {
fn decode_encode_credit() {
for _ in 0..4 {
let metadata = InMetadata::Credit {
- reserve_pub: rand_slice(),
+ reserve_pub: EddsaPublicKey::rand(),
};
let encoded = metadata.encode();
let decoded = InMetadata::decode(&encoded).unwrap();
@@ -169,7 +171,7 @@ mod test {
"http://git.taler.net/depolymerization.git/",
];
for url in urls {
- let wtid = rand_slice();
+ let wtid = ShortHashCode::rand();
let url = Url::parse(url).unwrap();
let metadata = OutMetadata::Debit { wtid, url };
let encoded = metadata.encode().unwrap();
@@ -183,7 +185,7 @@ mod test {
let url = "https+wtf://git.taler.net";
let url = Url::parse(url).unwrap();
let metadata = OutMetadata::Debit {
- wtid: rand_slice(),
+ wtid: ShortHashCode::rand(),
url,
};
let encoded = metadata.encode();
diff --git a/common/src/payto.rs b/common/src/payto.rs
@@ -0,0 +1,92 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+
+use std::str::FromStr;
+
+use taler_common::types::payto::{PaytoErr, PaytoImpl, PaytoURI};
+
+const BITCOIN: &str = "bitcoin";
+const ETHEREUM: &str = "ethereum";
+
+pub struct BtcAccount(pub bitcoin::Address);
+
+#[derive(Debug, thiserror::Error)]
+pub enum BtcErr {
+ #[error("missing bitcoin address in path")]
+ MissingAddr,
+ #[error(transparent)]
+ Addr(#[from] bitcoin::address::ParseError),
+}
+
+impl PaytoImpl for BtcAccount {
+ fn as_payto(&self) -> PaytoURI {
+ PaytoURI::from_parts(BITCOIN, format_args!("/{}", self.0))
+ }
+
+ fn parse(uri: &PaytoURI) -> Result<Self, PaytoErr> {
+ let url = uri.as_ref();
+ if url.domain() != Some(BITCOIN) {
+ return Err(PaytoErr::UnsupportedKind(
+ BITCOIN,
+ url.domain().unwrap_or_default().to_owned(),
+ ));
+ }
+ let Some(mut segments) = url.path_segments() else {
+ return Err(PaytoErr::custom(BtcErr::MissingAddr));
+ };
+ let Some(addr) = segments.next() else {
+ return Err(PaytoErr::custom(BtcErr::MissingAddr));
+ };
+ let addr =
+ bitcoin::Address::from_str(addr).map_err(|e| PaytoErr::custom(BtcErr::Addr(e)))?;
+ Ok(Self(addr.assume_checked()))
+ }
+}
+
+pub struct EthAccount(pub ethereum_types::Address);
+
+#[derive(Debug, thiserror::Error)]
+pub enum EthErr {
+ #[error("missing ethereum address in path")]
+ MissingAddr,
+ #[error("malformed ethereum address")]
+ Addr,
+}
+
+impl PaytoImpl for EthAccount {
+ fn as_payto(&self) -> PaytoURI {
+ PaytoURI::from_parts(ETHEREUM, format_args!("/{}", hex::encode(self.0)))
+ }
+
+ fn parse(uri: &PaytoURI) -> Result<Self, PaytoErr> {
+ let url = uri.as_ref();
+ if url.domain() != Some(ETHEREUM) {
+ return Err(PaytoErr::UnsupportedKind(
+ BITCOIN,
+ url.domain().unwrap_or_default().to_owned(),
+ ));
+ }
+ let Some(mut segments) = url.path_segments() else {
+ return Err(PaytoErr::custom(EthErr::MissingAddr));
+ };
+ let Some(addr) = segments.next() else {
+ return Err(PaytoErr::custom(EthErr::MissingAddr));
+ };
+ let addr =
+ ethereum_types::Address::from_str(addr).map_err(|_| PaytoErr::custom(EthErr::Addr))?;
+ Ok(Self(addr))
+ }
+}
diff --git a/common/src/sql.rs b/common/src/sql.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -17,12 +17,13 @@
use std::str::FromStr;
use postgres::Row;
+use taler_common::{
+ api_common::SafeU64,
+ types::{amount::Amount, base32::Base32, payto::PaytoURI},
+};
use url::Url;
-use crate::{
- api_common::{Amount, SafeU64},
- log::OrFail,
-};
+use crate::log::OrFail;
/// URL from sql
pub fn sql_url(row: &Row, idx: usize) -> Url {
@@ -30,10 +31,17 @@ pub fn sql_url(row: &Row, idx: usize) -> Url {
Url::from_str(str).or_fail(|_| format!("Database invariant: expected an url got {}", str))
}
-/// Ethereum amount from sql
-pub fn sql_amount(row: &Row, idx: usize) -> Amount {
+/// Payto from sql
+pub fn sql_payto(row: &Row, idx: usize) -> PaytoURI {
let str: &str = row.get(idx);
- Amount::from_str(str).or_fail(|_| format!("Database invariant: expected an amount got {}", str))
+ PaytoURI::from_str(str).or_fail(|_| format!("Database invariant: expected a payto got {}", str))
+}
+
+/// Ethereum amount from sql
+pub fn sql_amount(row: &Row, idx: usize, currency: &str) -> Amount {
+ let val: i64 = row.get(idx);
+ let frac: i32 = row.get(idx + 1);
+ Amount::new(currency, val as u64, frac as u32)
}
/// Byte array from sql
@@ -48,8 +56,20 @@ pub fn sql_array<const N: usize>(row: &Row, idx: usize) -> [u8; N] {
})
}
+/// Base32 from sql
+pub fn sql_base_32<const N: usize>(row: &Row, idx: usize) -> Base32<N> {
+ let slice: &[u8] = row.get(idx);
+ slice.try_into().or_fail(|_| {
+ format!(
+ "Database invariant: expected a base32 byte array of {}B for {}B",
+ N,
+ slice.len()
+ )
+ })
+}
+
/// Safe safe u64 from sql
pub fn sql_safe_u64(row: &Row, idx: usize) -> SafeU64 {
- let id: i32 = row.get(idx);
+ let id: i64 = row.get(idx);
SafeU64::try_from(id as u64).unwrap()
}
diff --git a/db/btc.sql b/db/btc.sql
@@ -1,38 +1,42 @@
+CREATE TYPE taler_amount AS (val INT8, frac INT4);
+COMMENT ON TYPE taler_amount
+ IS 'Stores an amount, fraction is in units of 1/100000000 of the base value';
+
-- Key value state
CREATE TABLE state (
- name TEXT PRIMARY KEY,
+ name TEXT NOT NULL PRIMARY KEY,
value BYTEA NOT NULL
);
-- Incoming transactions
CREATE TABLE tx_in (
- id SERIAL PRIMARY KEY,
- _date TIMESTAMP NOT NULL DEFAULT now(),
- amount TEXT NOT NULL,
- reserve_pub BYTEA NOT NULL UNIQUE,
+ id INT8 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
+ received INT8 NOT NULL,
+ amount taler_amount NOT NULL,
+ reserve_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(reserve_pub)=32),
debit_acc TEXT NOT NULL,
credit_acc TEXT NOT NULL
);
-- Outgoing transactions
CREATE TABLE tx_out (
- id SERIAL PRIMARY KEY,
- _date TIMESTAMP NOT NULL DEFAULT now(),
- amount TEXT NOT NULL,
- wtid BYTEA NOT NULL UNIQUE,
+ id INT8 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
+ created INT8 NOT NULL,
+ amount taler_amount NOT NULL,
+ wtid BYTEA NOT NULL UNIQUE CHECK (LENGTH(wtid)=32),
debit_acc TEXT NOT NULL,
credit_acc TEXT NOT NULL,
exchange_url TEXT NOT NULL,
- request_uid BYTEA UNIQUE,
+ request_uid BYTEA UNIQUE CHECK (LENGTH(request_uid)=64),
status SMALLINT NOT NULL DEFAULT 0,
- txid BYTEA UNIQUE
+ txid BYTEA UNIQUE CHECK (LENGTH(txid)=32)
);
-- Bounced transaction
CREATE TABLE bounce (
- id SERIAL PRIMARY KEY,
+ id INT8 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
bounced BYTEA UNIQUE NOT NULL,
- txid BYTEA UNIQUE,
- _date TIMESTAMP NOT NULL DEFAULT now(),
+ txid BYTEA UNIQUE CHECK (LENGTH(txid)=32),
+ created INT8 NOT NULL,
status SMALLINT NOT NULL DEFAULT 0
)
\ No newline at end of file
diff --git a/db/common.sql b/db/common.sql
@@ -1,27 +1,31 @@
+CREATE TYPE taler_amount AS (val INT8, frac INT4);
+COMMENT ON TYPE taler_amount
+ IS 'Stores an amount, fraction is in units of 1/100000000 of the base value';
+
-- Key value state
CREATE TABLE state (
- name TEXT PRIMARY KEY,
+ name TEXT NOT NULL PRIMARY KEY,
value BYTEA NOT NULL
);
-- Incoming transactions
CREATE TABLE tx_in (
- id SERIAL PRIMARY KEY,
- _date TIMESTAMP NOT NULL DEFAULT now(),
- amount TEXT NOT NULL,
- reserve_pub BYTEA NOT NULL UNIQUE,
+ id INT8 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
+ created INT8 NOT NULL DEFAULT now(),
+ amount taler_amount NOT NULL,
+ reserve_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(reserve_pub)=32),
debit_acc TEXT NOT NULL,
credit_acc TEXT NOT NULL
);
-- Outgoing transactions
CREATE TABLE tx_out (
- id SERIAL PRIMARY KEY,
- _date TIMESTAMP NOT NULL DEFAULT now(),
- amount TEXT NOT NULL,
- wtid BYTEA NOT NULL UNIQUE,
+ id INT8 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
+ created INT8 NOT NULL DEFAULT now(),
+ amount taler_amount NOT NULL,
+ wtid BYTEA NOT NULL UNIQUE CHECK (LENGTH(wtid)=32),
debit_acc TEXT NOT NULL,
credit_acc TEXT NOT NULL,
exchange_url TEXT NOT NULL,
- request_uid BYTEA UNIQUE
+ request_uid BYTEA UNIQUE CHECK (LENGTH(request_uid)=64)
);
\ No newline at end of file
diff --git a/db/eth.sql b/db/eth.sql
@@ -1,39 +1,43 @@
+CREATE TYPE taler_amount AS (val INT8, frac INT4);
+COMMENT ON TYPE taler_amount
+ IS 'Stores an amount, fraction is in units of 1/100000000 of the base value';
+
-- Key value state
CREATE TABLE state (
- name TEXT PRIMARY KEY,
+ name TEXT NOT NULL PRIMARY KEY,
value BYTEA NOT NULL
);
-- Incoming transactions
CREATE TABLE tx_in (
- id SERIAL PRIMARY KEY,
- _date TIMESTAMP NOT NULL DEFAULT now(),
- amount TEXT NOT NULL,
- reserve_pub BYTEA NOT NULL UNIQUE,
- debit_acc TEXT NOT NULL,
- credit_acc TEXT NOT NULL
+ id INT8 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
+ received INT8 NOT NULL,
+ amount taler_amount NOT NULL,
+ reserve_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(reserve_pub)=32),
+ debit_acc BYTEA NOT NULL CHECK (LENGTH(debit_acc)=20),
+ credit_acc BYTEA NOT NULL CHECK (LENGTH(credit_acc)=20)
);
-- Outgoing transactions
CREATE TABLE tx_out (
- id SERIAL PRIMARY KEY,
- _date TIMESTAMP NOT NULL DEFAULT now(),
- amount TEXT NOT NULL,
- wtid BYTEA NOT NULL UNIQUE,
- debit_acc TEXT NOT NULL,
- credit_acc TEXT NOT NULL,
+ id INT8 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
+ created INT8 NOT NULL,
+ amount taler_amount NOT NULL,
+ wtid BYTEA NOT NULL UNIQUE CHECK (LENGTH(wtid)=32),
+ debit_acc BYTEA NOT NULL CHECK (LENGTH(debit_acc)=20),
+ credit_acc BYTEA NOT NULL CHECK (LENGTH(credit_acc)=20),
exchange_url TEXT NOT NULL,
- request_uid BYTEA UNIQUE,
+ request_uid BYTEA UNIQUE CHECK (LENGTH(request_uid)=64),
status SMALLINT NOT NULL DEFAULT 0,
- txid BYTEA UNIQUE,
- sent TIMESTAMP DEFAULT NULL
+ txid BYTEA UNIQUE CHECK (LENGTH(txid)=32),
+ sent INT8 DEFAULT NULL
);
-- Bounced transaction
CREATE TABLE bounce (
- id SERIAL PRIMARY KEY,
- bounced BYTEA UNIQUE NOT NULL,
- txid BYTEA UNIQUE,
- _date TIMESTAMP NOT NULL DEFAULT now(),
+ id INT8 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
+ bounced BYTEA UNIQUE NOT NULL CHECK (LENGTH(bounced)=32),
+ txid BYTEA UNIQUE CHECK (LENGTH(txid)=32),
+ created INT8 NOT NULL,
status SMALLINT NOT NULL DEFAULT 0
)
\ No newline at end of file
diff --git a/eth-wire/Cargo.toml b/eth-wire/Cargo.toml
@@ -1,9 +1,11 @@
[package]
name = "eth-wire"
version = "0.1.0"
-edition = "2021"
-license = "AGPL-3.0-or-later"
-rust-version = "1.72.1"
+edition.workspace = true
+authors.workspace = true
+homepage.workspace = true
+repository.workspace = true
+license-file.workspace = true
[features]
# Enable random failures
@@ -11,19 +13,16 @@ fail = []
[dependencies]
# Cli args
-clap = { version = "4.4.6", features = ["derive"] }
-clap_lex = "0.7.0"
+clap.workspace = true
# Serialization library
-serde = { version = "1.0.188", features = ["derive"] }
-serde_json = "1.0.107"
+serde.workspace = true
+serde_json.workspace = true
serde_repr = "0.1.16"
# Hexadecimal encoding
-hex = { package = "const-hex", version = "1.9.1" }
+hex.workspace = true
# Ethereum serializable types
-ethereum-types = { version = "0.15.1", default-features = false, features = [
- "serialize",
-] }
+ethereum-types.workspace = true
# Error macros
-thiserror = "1.0.49"
+thiserror.workspace = true
# Common lib
common = { path = "../common" }
diff --git a/eth-wire/src/lib.rs b/eth-wire/src/lib.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -21,19 +21,23 @@ use std::{
};
use common::{
- api_common::Amount,
config::TalerConfig,
currency::{Currency, CurrencyEth},
- log::{fail, OrFail},
+ log::{OrFail, fail},
metadata::{InMetadata, OutMetadata},
+ payto::EthAccount,
postgres,
+ taler_common::{
+ api_common::{EddsaPublicKey, ShortHashCode},
+ types::{amount::Amount, payto::PaytoImpl},
+ },
url::Url,
};
-use ethereum_types::{Address, H160, H256, U256, U64};
-use rpc::{hex::Hex, Rpc, RpcClient, RpcStream, Transaction};
+use ethereum_types::{Address, H256, U64, U256};
+use rpc::{Rpc, RpcClient, RpcStream, Transaction, hex::Hex};
use rpc_utils::default_data_dir;
use serde::de::DeserializeOwned;
-use taler_util::{eth_payto_addr, taler_to_eth};
+use taler_util::taler_to_eth;
pub mod rpc;
mod rpc_utils;
@@ -47,7 +51,7 @@ pub trait RpcExtended: RpcClient {
from: Address,
to: Address,
value: U256,
- reserve_pub: [u8; 32],
+ reserve_pub: EddsaPublicKey,
) -> rpc::Result<H256> {
let metadata = InMetadata::Credit { reserve_pub };
self.send_transaction(&rpc::TransactionRequest {
@@ -66,7 +70,7 @@ pub trait RpcExtended: RpcClient {
from: Address,
to: Address,
value: U256,
- wtid: [u8; 32],
+ wtid: ShortHashCode,
url: Url,
) -> rpc::Result<H256> {
let metadata = OutMetadata::Debit { wtid, url };
@@ -226,13 +230,12 @@ const DEFAULT_BOUNCE_FEE: &str = "0.00001";
pub struct WireState {
pub confirmation: u32,
pub max_confirmations: u32,
- pub address: H160,
pub bounce_fee: U256,
pub ipc_path: PathBuf,
pub lifetime: Option<u32>,
pub bump_delay: Option<u32>,
pub base_url: Url,
- pub payto: Url,
+ pub account: EthAccount,
pub db_config: postgres::Config,
pub currency: CurrencyEth,
}
@@ -241,18 +244,17 @@ impl WireState {
pub fn load_taler_config(file: Option<&Path>) -> Self {
let (taler_config, ipc_path, currency) = load_taler_config(file);
let init_confirmation = taler_config.confirmation().unwrap_or(DEFAULT_CONFIRMATION) as u32;
- let payto = taler_config.payto();
+ let account = EthAccount::parse(&taler_config.payto()).unwrap();
Self {
confirmation: init_confirmation,
max_confirmations: init_confirmation * 2,
- address: eth_payto_addr(&payto).unwrap(),
ipc_path,
bounce_fee: config_bounce_fee(&taler_config.bounce_fee(), currency),
lifetime: taler_config.wire_lifetime(),
bump_delay: taler_config.bump_delay(),
base_url: taler_config.base_url(),
db_config: taler_config.db_config(),
- payto,
+ account,
currency,
}
}
diff --git a/eth-wire/src/loops/worker.rs b/eth-wire/src/loops/worker.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -13,32 +13,32 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use std::{fmt::Write, time::SystemTime};
+use std::fmt::Write;
use common::{
- api_common::base32,
log::log::{error, info, warn},
metadata::{InMetadata, OutMetadata},
- postgres::{fallible_iterator::FallibleIterator, Client},
+ postgres::{Client, fallible_iterator::FallibleIterator},
reconnect::AutoReconnectDb,
- sql::{sql_array, sql_url},
+ sql::{sql_array, sql_base_32, sql_url},
status::{BounceStatus, DebitStatus},
+ taler_common::{api_common::ShortHashCode, types::timestamp::Timestamp},
};
use eth_wire::{
- rpc::{self, AutoRpcWallet, Rpc, RpcClient, Transaction, TransactionRequest},
- taler_util::{eth_payto_url, eth_to_taler},
ListSinceSync, RpcExtended, SyncState, SyncTransaction,
+ rpc::{self, AutoRpcWallet, Rpc, RpcClient, Transaction, TransactionRequest},
+ taler_util::eth_to_taler,
};
use ethereum_types::{Address, H256, U256};
use crate::{
+ WireState,
fail_point::fail_point,
loops::LoopError,
sql::{sql_addr, sql_eth_amount, sql_hash},
- WireState,
};
-use super::{analysis::analysis, LoopResult};
+use super::{LoopResult, analysis::analysis};
pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, mut state: WireState) {
let mut lifetime = state.lifetime;
@@ -95,7 +95,7 @@ pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, mut state: WireSt
let sync_state = SyncState::from_bytes(&sql_array(&row, 0));
// Get changes
- let list = rpc.list_since_sync(&state.address, sync_state, state.confirmation)?;
+ let list = rpc.list_since_sync(&state.account.0, sync_state, state.confirmation)?;
// Perform analysis
state.confirmation =
@@ -125,7 +125,9 @@ pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, mut state: WireSt
// approach is to wait for the next loop when an RPC error is caught to prevent endless logged errors.
skip_notification = !matches!(
e,
- LoopError::Rpc(rpc::Error::RPC { .. }) | LoopError::Concurrency
+ LoopError::Rpc(rpc::Error::RPC { .. })
+ | LoopError::Concurrency
+ | LoopError::Injected(_)
);
} else {
skip_notification = false;
@@ -144,14 +146,15 @@ fn sync_chain(
let conf_delay = state.confirmation;
// Check if a confirmed incoming transaction have been removed by a blockchain reorganization
- let new_status = sync_chain_removed(&list.txs, &list.removed, db, &state.address, conf_delay)?;
+ let new_status =
+ sync_chain_removed(&list.txs, &list.removed, db, &state.account.0, conf_delay)?;
// Sync status with database
if *status != new_status {
let mut tx = db.transaction()?;
tx.execute(
"UPDATE state SET value=$1 WHERE name='status'",
- &[&[new_status as u8].as_ref()],
+ &[&[new_status as u8].as_slice()],
)?;
tx.execute("NOTIFY status", &[])?;
tx.commit()?;
@@ -166,16 +169,16 @@ fn sync_chain(
for sync_tx in list.txs {
let tx = &sync_tx.tx;
- if tx.to == Some(state.address) && sync_tx.confirmations >= conf_delay {
+ if tx.to == Some(state.account.0) && sync_tx.confirmations >= conf_delay {
sync_chain_incoming_confirmed(tx, db, state)?;
- } else if tx.from == Some(state.address) {
+ } else if tx.from == Some(state.account.0) {
sync_chain_outgoing(&sync_tx, db, state)?;
}
}
db.execute(
"UPDATE state SET value=$1 WHERE name='sync'",
- &[&list.state.to_bytes().as_ref()],
+ &[&list.state.to_bytes().as_slice()],
)?;
Ok(true)
}
@@ -216,7 +219,7 @@ fn sync_chain_removed(
if db
.query_opt(
"SELECT 1 FROM tx_in WHERE reserve_pub=$1",
- &[&reserve_pub.as_ref()],
+ &[&reserve_pub.as_slice()],
)?
.is_some()
{
@@ -244,8 +247,7 @@ fn sync_chain_removed(
for (key, id, addr) in blocking_credit {
write!(
&mut buf,
- "\n\tcredit {} in {} from {}",
- base32(&key),
+ "\n\tcredit {key} in {} from {}",
hex::encode(id),
hex::encode(addr)
)
@@ -276,17 +278,14 @@ fn sync_chain_incoming_confirmed(
match InMetadata::decode(&tx.input) {
Ok(metadata) => match metadata {
InMetadata::Credit { reserve_pub } => {
- let date = SystemTime::now();
let amount = eth_to_taler(&tx.value, state.currency);
let credit_addr = tx.from.expect("Not coinbase");
- let nb = db.execute("INSERT INTO tx_in (_date, amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (reserve_pub) DO NOTHING ", &[
- &date, &amount.to_string(), &reserve_pub.as_ref(), ð_payto_url(&credit_addr).as_ref(), &state.payto.as_ref()
+ let nb = db.execute("INSERT INTO tx_in (received, amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, ($2, $3)::taler_amount, $4, $5, $6) ON CONFLICT (reserve_pub) DO NOTHING ", &[
+ &Timestamp::now().as_sql_micros(), &(amount.val as i64), &(amount.frac as i32), &reserve_pub.as_slice(), &credit_addr.as_bytes(), &state.account.0.as_bytes()
])?;
if nb > 0 {
info!(
- "<< {} {} in {} from {}",
- amount,
- base32(&reserve_pub),
+ "<< {amount} {reserve_pub} in {} from {}",
hex::encode(tx.hash),
hex::encode(credit_addr),
);
@@ -296,8 +295,8 @@ fn sync_chain_incoming_confirmed(
Err(_) => {
// If encoding is wrong request a bounce
db.execute(
- "INSERT INTO bounce (bounced) VALUES ($1) ON CONFLICT (bounced) DO NOTHING",
- &[&tx.hash.as_ref()],
+ "INSERT INTO bounce (created, bounced) VALUES ($1, $2) ON CONFLICT (bounced) DO NOTHING",
+ &[&Timestamp::now().as_sql_micros(), &tx.hash.as_ref()],
)?;
}
}
@@ -315,13 +314,13 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState)
// Get previous out tx
let row = db.query_opt(
"SELECT id, status, sent FROM tx_out WHERE wtid=$1 FOR UPDATE",
- &[&wtid.as_ref()],
+ &[&wtid.as_slice()],
)?;
if let Some(row) = row {
// If already in database, sync status
- let row_id: i32 = row.get(0);
+ let row_id: i64 = row.get(0);
let status: i16 = row.get(1);
- let sent: Option<SystemTime> = row.get(2);
+ let sent: Option<i64> = row.get(2);
let expected_status = DebitStatus::Sent as i16;
let expected_send = sent.filter(|_| *confirmations == 0);
@@ -339,9 +338,7 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState)
match DebitStatus::try_from(status as u8).unwrap() {
DebitStatus::Requested => {
warn!(
- ">> (recovered) {} {} in {} to {}",
- amount,
- base32(&wtid),
+ ">> (recovered) {amount} {wtid} in {} to {}",
hex::encode(tx.hash),
hex::encode(credit_addr)
);
@@ -352,16 +349,13 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState)
}
} else {
// Else add to database
- let date = SystemTime::now();
let nb = db.execute(
- "INSERT INTO tx_out (_date, amount, wtid, debit_acc, credit_acc, exchange_url, status, txid, request_uid) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (wtid) DO NOTHING",
- &[&date, &amount.to_string(), &wtid.as_ref(), ð_payto_url(&state.address).as_ref(), ð_payto_url(&credit_addr).as_ref(), &state.base_url.as_ref(), &(DebitStatus::Sent as i16), &tx.hash.as_ref(), &None::<&[u8]>],
+ "INSERT INTO tx_out (created, amount, wtid, debit_acc, credit_acc, exchange_url, status, txid, request_uid) VALUES ($1, ($2, $3)::taler_amount, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (wtid) DO NOTHING",
+ &[&Timestamp::now().as_sql_micros(), &(amount.val as i64), &(amount.frac as i32), &wtid.as_slice(), &state.account.0.as_bytes(), &credit_addr.as_bytes(), &state.base_url.as_ref(), &(DebitStatus::Sent as i16), &tx.hash.as_ref(), &None::<&[u8]>],
)?;
if nb > 0 {
warn!(
- ">> (onchain) {} {} in {} to {}",
- amount,
- base32(&wtid),
+ ">> (onchain) {amount} {wtid} in {} to {}",
hex::encode(tx.hash),
hex::encode(credit_addr)
);
@@ -377,7 +371,7 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState)
)?;
if let Some(row) = row {
// If already in database, sync status
- let row_id: i32 = row.get(0);
+ let row_id: i64 = row.get(0);
let status: i16 = row.get(1);
match BounceStatus::try_from(status as u8).unwrap() {
BounceStatus::Requested => {
@@ -408,8 +402,8 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState)
} else {
// Else add to database
let nb = db.execute(
- "INSERT INTO bounce (bounced, txid, status) VALUES ($1, $2, $3) ON CONFLICT (txid) DO NOTHING",
- &[&bounced.as_ref(), &tx.hash.as_ref(), &(BounceStatus::Sent as i16)],
+ "INSERT INTO bounce (created, bounced, txid, status) VALUES ($1, $2, $3, $4) ON CONFLICT (txid) DO NOTHING",
+ &[&Timestamp::now().as_sql_micros(), &bounced.as_ref(), &tx.hash.as_ref(), &(BounceStatus::Sent as i16)],
)?;
if nb > 0 {
warn!(
@@ -430,26 +424,30 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState)
fn debit(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool> {
// We rely on the advisory lock to ensure we are the only one sending transactions
let row = db.query_opt(
-"SELECT id, amount, wtid, credit_acc, exchange_url FROM tx_out WHERE status=$1 ORDER BY _date LIMIT 1",
+"SELECT id, (amount).val, (amount).frac, wtid, credit_acc, exchange_url FROM tx_out WHERE status=$1 ORDER BY created LIMIT 1",
&[&(DebitStatus::Requested as i16)],
)?;
if let Some(row) = &row {
- let id: i32 = row.get(0);
+ let id: i64 = row.get(0);
let amount = sql_eth_amount(row, 1, state.currency);
- let wtid: [u8; 32] = sql_array(row, 2);
- let addr = sql_addr(row, 3);
- let url = sql_url(row, 4);
- let tx_id = rpc.debit(state.address, addr, amount, wtid, url)?;
+ let wtid: ShortHashCode = sql_base_32(row, 3);
+ let addr = sql_addr(row, 4);
+ let url = sql_url(row, 5);
+ let now = Timestamp::now();
+ let tx_id = rpc.debit(state.account.0, addr, amount, wtid.clone(), url)?;
fail_point("(injected) fail debit", 0.3)?;
db.execute(
- "UPDATE tx_out SET status=$1, txid=$2, sent=now() WHERE id=$3",
- &[&(DebitStatus::Sent as i16), &tx_id.as_ref(), &id],
+ "UPDATE tx_out SET status=$1, txid=$2, sent=$3 WHERE id=$4",
+ &[
+ &(DebitStatus::Sent as i16),
+ &tx_id.as_ref(),
+ &now.as_sql_micros(),
+ &id,
+ ],
)?;
let amount = eth_to_taler(&amount, state.currency);
info!(
- ">> {} {} in {} to {}",
- amount,
- base32(&wtid),
+ ">> {amount} {wtid} in {} to {}",
hex::encode(tx_id),
hex::encode(addr)
);
@@ -460,13 +458,15 @@ fn debit(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool>
/// Bump a stuck transaction, return false if no more stuck transactions are found
fn bump(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool> {
if let Some(delay) = state.bump_delay {
+ let now = Timestamp::now().as_sql_micros();
// We rely on the advisory lock to ensure we are the only one sending transactions
let row = db.query_opt(
- "SELECT id, txid FROM tx_out WHERE status=$1 AND EXTRACT(EPOCH FROM (now() - sent)) > $2 ORDER BY _date LIMIT 1",
- &[&(DebitStatus::Sent as i16), &(delay as f64)],
+ "SELECT id, txid FROM tx_out WHERE status=$1 AND $2 - sent > $3 ORDER BY created LIMIT 1",
+ &[&(DebitStatus::Sent as i16), &now, &((delay * 1000000) as i64)],
)?;
if let Some(row) = &row {
- let id: i32 = row.get(0);
+ let now = Timestamp::now();
+ let id: i64 = row.get(0);
let txid = sql_hash(row, 1);
let tx = rpc.get_transaction(&txid)?.expect("Bump existing tx");
rpc.send_transaction(&TransactionRequest {
@@ -478,10 +478,11 @@ fn bump(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool> {
nonce: Some(tx.nonce),
})?;
let row = db.query_one(
- "UPDATE tx_out SET sent=now() WHERE id=$1 RETURNING wtid",
- &[&id],
+ "UPDATE tx_out SET sent=$1 WHERE id=$2 RETURNING wtid",
+ &[&now.as_sql_micros(), &id],
)?;
- info!(">> (bump) {} in {}", base32(row.get(0)), hex::encode(txid));
+ let wtid: ShortHashCode = sql_base_32(&row, 0);
+ info!(">> (bump) {wtid} in {}", hex::encode(txid));
}
Ok(row.is_some())
} else {
@@ -493,11 +494,11 @@ fn bump(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool> {
fn bounce(db: &mut Client, rpc: &mut Rpc, fee: U256) -> LoopResult<bool> {
// We rely on the advisory lock to ensure we are the only one sending transactions
let row = db.query_opt(
- "SELECT id, bounced FROM bounce WHERE status=$1 ORDER BY _date LIMIT 1",
+ "SELECT id, bounced FROM bounce WHERE status=$1 ORDER BY created LIMIT 1",
&[&(BounceStatus::Requested as i16)],
)?;
if let Some(row) = &row {
- let id: i32 = row.get(0);
+ let id: i64 = row.get(0);
let bounced: H256 = sql_hash(row, 1);
let bounce = rpc.bounce(bounced, fee)?;
diff --git a/eth-wire/src/main.rs b/eth-wire/src/main.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -18,18 +18,17 @@ use std::path::PathBuf;
use clap::Parser;
use common::{
- log::{log::info, OrFail},
+ log::{OrFail, log::info},
named_spawn, password,
postgres::NoTls,
reconnect::auto_reconnect_db,
};
use eth_wire::{
- load_taler_config,
- rpc::{auto_rpc_common, auto_rpc_wallet, Rpc, RpcClient},
- SyncState, WireState,
+ SyncState, WireState, load_taler_config,
+ rpc::{Rpc, RpcClient, auto_rpc_common, auto_rpc_wallet},
};
use ethereum_types::H160;
-use loops::{watcher::watcher, worker::worker, LoopResult};
+use loops::{LoopResult, watcher::watcher, worker::worker};
mod fail_point;
mod loops;
@@ -80,7 +79,7 @@ fn init(config: Option<PathBuf>, init: Init) -> LoopResult<()> {
tx
.execute(
"INSERT INTO state (name, value) VALUES ('status', $1) ON CONFLICT (name) DO NOTHING",
- &[&[1u8].as_ref()],
+ &[&[1u8].as_slice()],
)?;
// Init sync if not already set
let block = rpc.earliest_block()?;
@@ -91,7 +90,7 @@ fn init(config: Option<PathBuf>, init: Init) -> LoopResult<()> {
};
tx.execute(
"INSERT INTO state (name, value) VALUES ('sync', $1) ON CONFLICT (name) DO NOTHING",
- &[&state.to_bytes().as_ref()],
+ &[&state.to_bytes().as_slice()],
)?;
tx.commit()?;
println!("Database initialised");
@@ -117,7 +116,7 @@ fn init(config: Option<PathBuf>, init: Init) -> LoopResult<()> {
)?;
let nb_row = db.execute(
"UPDATE state SET value=$1 WHERE name='sync'",
- &[&state.to_bytes().as_ref()],
+ &[&state.to_bytes().as_slice()],
)?;
if nb_row > 0 {
println!("Skipped {} previous block", state.conf_height);
@@ -132,7 +131,9 @@ fn init(config: Option<PathBuf>, init: Init) -> LoopResult<()> {
};
let addr = hex::encode(addr.as_bytes());
- println!("You must backup the generated key file and your chosen password, more info there: https://geth.ethereum.org/docs/install-and-build/backup-restore");
+ println!(
+ "You must backup the generated key file and your chosen password, more info there: https://geth.ethereum.org/docs/install-and-build/backup-restore"
+ );
println!("Public address is {}", &addr);
println!("Add the following line into taler.conf:");
println!("[depolymerizer-ethereum]");
@@ -145,7 +146,7 @@ fn init(config: Option<PathBuf>, init: Init) -> LoopResult<()> {
fn run(config: Option<PathBuf>) {
let state = WireState::load_taler_config(config.as_deref());
- let rpc_worker = auto_rpc_wallet(state.ipc_path.clone(), state.address);
+ let rpc_worker = auto_rpc_wallet(state.ipc_path.clone(), state.account.0);
let rpc_watcher = auto_rpc_common(state.ipc_path.clone());
let db_watcher = auto_reconnect_db(state.db_config.clone());
diff --git a/eth-wire/src/rpc.rs b/eth-wire/src/rpc.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -20,7 +20,7 @@
//! make our code more compatible with future deprecation
use common::{log::log::error, password, reconnect::AutoReconnect, url::Url};
-use ethereum_types::{Address, H160, H256, U256, U64};
+use ethereum_types::{Address, H160, H256, U64, U256};
use serde::de::DeserializeOwned;
use std::{
fmt::Debug,
@@ -300,7 +300,7 @@ impl<N: Debug + DeserializeOwned> RpcClient for RpcStream<'_, N> {
self.buff.push(n.result);
}
NotificationOrResponse::Response(response) => {
- return self.rpc.handle_response(response)
+ return self.rpc.handle_response(response);
}
}
}
@@ -517,8 +517,8 @@ pub mod hex {
};
use serde::{
- de::{Error, Unexpected, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
+ de::{Error, Unexpected, Visitor},
};
/// Raw bytes wrapper
@@ -559,7 +559,7 @@ pub mod hex {
struct BytesVisitor;
- impl<'a> Visitor<'a> for BytesVisitor {
+ impl Visitor<'_> for BytesVisitor {
type Value = Hex;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
diff --git a/eth-wire/src/sql.rs b/eth-wire/src/sql.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -17,14 +17,14 @@ use common::{
currency::CurrencyEth,
log::OrFail,
postgres::Row,
- sql::{sql_amount, sql_array, sql_url},
+ sql::{sql_amount, sql_array},
};
-use eth_wire::taler_util::{eth_payto_addr, taler_to_eth};
+use eth_wire::taler_util::taler_to_eth;
use ethereum_types::{H160, H256, U256};
/// Ethereum amount from sql
pub fn sql_eth_amount(row: &Row, idx: usize, currency: CurrencyEth) -> U256 {
- let amount = sql_amount(row, idx);
+ let amount = sql_amount(row, idx, currency.to_str());
taler_to_eth(&amount, currency).or_fail(|_| {
format!(
"Database invariant: expected an ethereum amount got {}",
@@ -35,13 +35,8 @@ pub fn sql_eth_amount(row: &Row, idx: usize, currency: CurrencyEth) -> U256 {
/// Ethereum address from sql
pub fn sql_addr(row: &Row, idx: usize) -> H160 {
- let url = sql_url(row, idx);
- eth_payto_addr(&url).or_fail(|_| {
- format!(
- "Database invariant: expected an ethereum payto url got {}",
- url
- )
- })
+ let array: [u8; 20] = sql_array(row, idx);
+ H160::from_slice(&array)
}
/// Ethereum hash from sql
diff --git a/eth-wire/src/taler_util.rs b/eth-wire/src/taler_util.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -13,34 +13,15 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use std::str::FromStr;
-use common::{api_common::Amount, currency::CurrencyEth, url::Url};
-use ethereum_types::{Address, U256};
+use common::{
+ currency::CurrencyEth,
+ taler_common::types::amount::{Amount, FRAC_BASE},
+};
+use ethereum_types::U256;
pub const WEI: u64 = 1_000_000_000_000_000_000;
-pub const TRUNC: u64 = 10_000_000_000;
-
-/// Generate a payto uri from an eth address
-pub fn eth_payto_url(addr: &Address) -> Url {
- Url::from_str(&format!(
- "payto://ethereum/{}",
- hex::encode(addr.as_bytes())
- ))
- .unwrap()
-}
-
-/// Extract an eth address from a payto uri
-pub fn eth_payto_addr(url: &Url) -> Result<Address, String> {
- if url.domain() != Some("ethereum") {
- return Err(format!(
- "Expected domain 'ethereum' got '{}'",
- url.domain().unwrap_or_default()
- ));
- }
- let str = url.path().trim_start_matches('/');
- Address::from_str(str).map_err(|e| e.to_string())
-}
+pub const TRUNC: u64 = WEI / FRAC_BASE as u64;
/// Transform a eth amount into a taler amount
pub fn eth_to_taler(amount: &U256, currency: CurrencyEth) -> Amount {
@@ -53,7 +34,7 @@ pub fn eth_to_taler(amount: &U256, currency: CurrencyEth) -> Amount {
/// Transform a eth amount into a btc amount
pub fn taler_to_eth(amount: &Amount, currency: CurrencyEth) -> Result<U256, String> {
- if amount.currency != currency.to_str() {
+ if amount.currency.as_ref() != currency.to_str() {
return Err(format!(
"expected currency {} got {}",
currency.to_str(),
@@ -61,5 +42,5 @@ pub fn taler_to_eth(amount: &Amount, currency: CurrencyEth) -> Result<U256, Stri
));
}
- Ok(U256::from(amount.value) * WEI + U256::from(amount.fraction) * TRUNC)
+ Ok(U256::from(amount.val) * WEI + U256::from(amount.frac) * TRUNC)
}
diff --git a/instrumentation/Cargo.toml b/instrumentation/Cargo.toml
@@ -1,22 +1,23 @@
[package]
name = "instrumentation"
version = "0.1.0"
-edition = "2021"
-license = "AGPL-3.0-or-later"
-rust-version = "1.72.1"
+edition.workspace = true
+authors.workspace = true
+homepage.workspace = true
+repository.workspace = true
+license-file.workspace = true
[dependencies]
# Cli args parser
-clap = { version = "4.4.6", features = ["derive"] }
-clap_lex = "0.7.0"
+clap.workspace = true
common = { path = "../common" }
# Bitcoin
btc-wire = { path = "../btc-wire" }
-bitcoin = { version = "0.31.2", default-features = false, features = ["std"] }
+bitcoin.workspace = true
# Ethereum
eth-wire = { path = "../eth-wire" }
-ethereum-types = { version = "0.15.1", default-features = false }
-hex = "0.4.3"
+ethereum-types.workspace = true
+hex.workspace = true
# Wire Gateway
ureq = { version = "3.0.0", features = ["json"] }
# In memory deflate library
@@ -28,7 +29,7 @@ fastrand = "2.0.1"
# terminal color
owo-colors = "4.0.0"
# Better backtrace
-color-backtrace = "0.6.0"
+color-backtrace = "0.7.0"
# Send signal to child processes
signal-child = "1.0.5"
# Edit toml files
@@ -36,6 +37,8 @@ rust-ini = "0.21.0"
# Progress reporting
indicatif = "0.17.7"
thread-local-panic-hook = "0.1.0"
+taler-common.workspace = true
+taler-api.workspace = true
[build-dependencies]
clap_mangen = "0.2.14"
diff --git a/instrumentation/src/btc.rs b/instrumentation/src/btc.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022-2024 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -22,21 +22,31 @@ use std::{
time::Duration,
};
-use bitcoin::{hashes::Hash, Address, Amount, BlockHash, Network, SignedAmount, Txid};
+use bitcoin::{Address, Amount, BlockHash, Network, SignedAmount, Txid, hashes::Hash};
use btc_wire::{
+ WireState,
btc_config::BitcoinConfig,
rpc::{self, Category, ErrorCode, Rpc},
rpc_utils::{self, segwit_min_amount},
- taler_utils::{btc_payto_url, btc_to_taler},
- WireState,
+ taler_utils::btc_to_taler,
+};
+use common::{
+ currency::CurrencyBtc,
+ metadata::OutMetadata,
+ payto::BtcAccount,
+ postgres::NoTls,
+ taler_common::{
+ api_common::{EddsaPublicKey, ShortHashCode},
+ types::base32::Base32,
+ },
};
-use common::{currency::CurrencyBtc, metadata::OutMetadata, postgres::NoTls, rand_slice};
use indicatif::ProgressBar;
+use taler_common::types::payto::Payto;
use tempfile::TempDir;
use crate::utils::{
- check_incoming, check_outgoing, cmd_redirect, cmd_redirect_ok, print_now, retry, retry_opt,
- transfer, unused_port, ChildGuard, TalerCtx, TestCtx,
+ ChildGuard, TalerCtx, TestCtx, check_incoming, check_outgoing, cmd_redirect, cmd_redirect_ok,
+ print_now, retry, retry_opt, transfer, unused_port,
};
pub const CLIENT: &str = "client";
@@ -125,7 +135,7 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
let taler_test_amount = btc_to_taler(&test_amount.to_signed().unwrap(), state.currency);
println!("Send transaction");
- let reserve_pub_key = rand_slice();
+ let reserve_pub_key = Base32::rand();
let credit_id = client_rpc
.send_segwit_key(&wire_addr, &test_amount, &reserve_pub_key)
.unwrap();
@@ -195,12 +205,12 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
));
println!("Get back some money");
- let wtid = rand_slice();
+ let wtid = Base32::rand();
transfer(
base_url,
&wtid,
&state.base_url,
- btc_payto_url(&client_addr),
+ Payto::new(BtcAccount(client_addr)).as_payto(),
&taler_test_amount,
);
wait_for_pending(&mut since, &mut client_rpc, &mut wire_rpc);
@@ -423,18 +433,18 @@ impl BtcCtx {
/* ----- Transaction ------ */
- pub fn credit(&mut self, amount: Amount, metadata: [u8; 32]) {
+ pub fn credit(&mut self, amount: Amount, metadata: &EddsaPublicKey) {
self.client_rpc
- .send_segwit_key(&self.wire_addr, &amount, &metadata)
+ .send_segwit_key(&self.wire_addr, &amount, metadata)
.unwrap();
}
- pub fn debit(&mut self, amount: Amount, metadata: [u8; 32]) {
+ pub fn debit(&mut self, amount: Amount, metadata: &ShortHashCode) {
transfer(
&self.ctx.gateway_url,
- &metadata,
+ metadata,
&self.state.base_url,
- btc_payto_url(&self.client_addr),
+ Payto::new(BtcAccount(self.client_addr.clone())).as_payto(),
&btc_to_taler(&amount.to_signed().unwrap(), self.state.currency),
)
}
@@ -514,12 +524,12 @@ impl BtcCtx {
/* ----- Wire Gateway ----- */
- pub fn expect_credits(&self, txs: &[([u8; 32], Amount)]) {
+ pub fn expect_credits(&self, txs: &[(EddsaPublicKey, Amount)]) {
let txs: Vec<_> = txs
.iter()
.map(|(metadata, amount)| {
(
- *metadata,
+ metadata.clone(),
btc_to_taler(&amount.to_signed().unwrap(), self.state.currency),
)
})
@@ -527,12 +537,12 @@ impl BtcCtx {
self.ctx.expect_credits(&txs)
}
- pub fn expect_debits(&self, txs: &[([u8; 32], Amount)]) {
+ pub fn expect_debits(&self, txs: &[(ShortHashCode, Amount)]) {
let txs: Vec<_> = txs
.iter()
.map(|(metadata, amount)| {
(
- *metadata,
+ metadata.clone(),
btc_to_taler(&amount.to_signed().unwrap(), self.state.currency),
)
})
@@ -552,9 +562,9 @@ pub fn wire(ctx: TestCtx) {
let mut balance = ctx.wire_balance();
let mut txs = Vec::new();
for n in 10..100 {
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = Amount::from_sat(n * 1000);
- ctx.credit(amount, metadata);
+ ctx.credit(amount, &metadata);
txs.push((metadata, amount));
balance += amount;
ctx.next_block();
@@ -569,10 +579,10 @@ pub fn wire(ctx: TestCtx) {
let mut balance = ctx.client_balance();
let mut txs = Vec::new();
for n in 10..100 {
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = Amount::from_sat(n * 100);
balance += amount;
- ctx.debit(amount, metadata);
+ ctx.debit(amount, &metadata);
txs.push((metadata, amount));
}
ctx.next_block();
@@ -601,17 +611,20 @@ pub fn lifetime(ctx: TestCtx) {
ctx.step("Check lifetime");
// Start up
retry(|| ctx.wire_running() && ctx.gateway_running());
- // Consume lifetime
- for _ in 0..=ctx.taler_conf.wire_lifetime().unwrap() {
- ctx.credit(segwit_min_amount(), rand_slice());
+ // Consume wire lifetime
+ for _ in 0..=ctx.taler_conf.wire_lifetime().unwrap() + 2 {
+ ctx.credit(segwit_min_amount(), &Base32::rand());
ctx.next_block();
+ std::thread::sleep(Duration::from_millis(200));
}
+ retry(|| !ctx.wire_running());
+ // Consume gateway lifetime
for _ in 0..=ctx.taler_conf.http_lifetime().unwrap() {
- ctx.debit(segwit_min_amount(), rand_slice());
+ ctx.debit(segwit_min_amount(), &Base32::rand());
ctx.next_block();
}
// End down
- retry(|| !ctx.wire_running() && !ctx.gateway_running());
+ retry(|| !ctx.gateway_running());
}
/// Check the capacity of wire-gateway and btc-wire to recover from database and node loss
@@ -624,9 +637,9 @@ pub fn reconnect(ctx: TestCtx) {
ctx.step("With DB");
{
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = Amount::from_sat(42000);
- ctx.credit(amount, metadata);
+ ctx.credit(amount, &metadata);
credits.push((metadata, amount));
ctx.next_block();
ctx.next_conf();
@@ -637,21 +650,21 @@ pub fn reconnect(ctx: TestCtx) {
{
ctx.stop_db();
ctx.malformed_credit(&Amount::from_sat(24000));
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = Amount::from_sat(40000);
- ctx.credit(amount, metadata);
+ ctx.credit(amount, &metadata);
credits.push((metadata, amount));
ctx.stop_node();
- ctx.expect_error();
+ ctx.expect_gateway_down();
}
ctx.step("Reconnect DB");
{
ctx.resume_db();
ctx.resume_node(&[]);
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = Amount::from_sat(2000);
- ctx.debit(amount, metadata);
+ ctx.debit(amount, &metadata);
debits.push((metadata, amount));
ctx.next_block();
sleep(Duration::from_secs(3));
@@ -685,9 +698,9 @@ pub fn stress(ctx: TestCtx) {
{
let mut balance = ctx.wire_balance();
for n in 10..30 {
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = Amount::from_sat(n * 1000);
- ctx.credit(amount, metadata);
+ ctx.credit(amount, &metadata);
credits.push((metadata, amount));
balance += amount;
ctx.next_block();
@@ -701,10 +714,10 @@ pub fn stress(ctx: TestCtx) {
{
let mut balance = ctx.client_balance();
for n in 10..30 {
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = Amount::from_sat(n * 100);
balance += amount;
- ctx.debit(amount, metadata);
+ ctx.debit(amount, &metadata);
debits.push((metadata, amount));
}
ctx.next_block();
@@ -744,14 +757,14 @@ pub fn conflict(tctx: TestCtx) {
{
// Perform credit
let amount = Amount::from_sat(4200000);
- ctx.credit(amount, rand_slice());
+ ctx.credit(amount, &Base32::rand());
ctx.next_conf();
ctx.expect_wire_balance(amount, true);
let client = ctx.client_balance();
let wire = ctx.wire_balance();
// Perform debit
- ctx.debit(Amount::from_sat(400000), rand_slice());
+ ctx.debit(Amount::from_sat(400000), &Base32::rand());
retry(|| ctx.wire_balance() < wire);
// Abandon pending transaction
@@ -761,7 +774,7 @@ pub fn conflict(tctx: TestCtx) {
ctx.expect_wire_balance(wire, false);
// Generate conflict
- ctx.debit(Amount::from_sat(500000), rand_slice());
+ ctx.debit(Amount::from_sat(500000), &Base32::rand());
retry(|| ctx.wire_balance() < wire);
// Resend conflicting transaction
@@ -774,7 +787,7 @@ pub fn conflict(tctx: TestCtx) {
ctx.step("Setup");
drop(ctx);
let mut ctx = BtcCtx::setup(&tctx, "taler_btc.conf", false);
- ctx.credit(Amount::from_sat(3000000), rand_slice());
+ ctx.credit(Amount::from_sat(3000000), &Base32::rand());
ctx.next_block();
ctx.step("Conflict bounce");
@@ -794,7 +807,7 @@ pub fn conflict(tctx: TestCtx) {
// Generate conflict
let amount = Amount::from_sat(50000);
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
retry(|| ctx.wire_balance() < (wire + bounce_amount));
// Resend conflicting transaction
@@ -818,7 +831,7 @@ pub fn reorg(ctx: TestCtx) {
// Perform credits
let before = ctx.wire_balance();
for n in 10..21 {
- ctx.credit(Amount::from_sat(n * 10000), rand_slice());
+ ctx.credit(Amount::from_sat(n * 10000), &Base32::rand());
ctx.next_block();
}
let after = ctx.wire_balance();
@@ -845,7 +858,7 @@ pub fn reorg(ctx: TestCtx) {
let mut after = ctx.client_balance();
for n in 10..21 {
let amount = Amount::from_sat(n * 100);
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
after += amount;
}
ctx.next_block();
@@ -914,7 +927,7 @@ pub fn hell(ctx: TestCtx) {
ctx.restart_node(&["-minrelaytxfee=0.001"]);
ctx.abandon_client();
let amount = Amount::from_sat(54000);
- ctx.credit(amount, rand_slice());
+ ctx.credit(amount, &Base32::rand());
ctx.expect_wire_balance(amount, true);
// Check btc-wire suspend operation
@@ -927,7 +940,7 @@ pub fn hell(ctx: TestCtx) {
step(&ctx, "Handle reorg conflicting incoming credit", |ctx| {
let amount = Amount::from_sat(420000);
- ctx.credit(amount, rand_slice());
+ ctx.credit(amount, &Base32::rand());
ctx.next_conf();
ctx.expect_wire_balance(amount, true);
});
@@ -953,7 +966,7 @@ pub fn analysis(ctx: TestCtx) {
// Perform credit
let before = ctx.wire_balance();
- ctx.credit(Amount::from_sat(42000), rand_slice());
+ ctx.credit(Amount::from_sat(42000), &Base32::rand());
ctx.next_conf();
let after = ctx.wire_balance();
@@ -974,7 +987,7 @@ pub fn analysis(ctx: TestCtx) {
// Perform credit
let before = ctx.wire_balance();
- ctx.credit(Amount::from_sat(42000), rand_slice());
+ ctx.credit(Amount::from_sat(42000), &Base32::rand());
ctx.next_conf();
// Perform fork and check btc-wire learned from previous attack
@@ -992,7 +1005,7 @@ pub fn bumpfee(tctx: TestCtx) {
// Perform credits to allow wire to perform debits latter
for n in 10..13 {
- ctx.credit(Amount::from_sat(n * 100000), rand_slice());
+ ctx.credit(Amount::from_sat(n * 100000), &Base32::rand());
ctx.next_block();
}
ctx.next_conf();
@@ -1003,7 +1016,7 @@ pub fn bumpfee(tctx: TestCtx) {
let mut client = ctx.client_balance();
let wire = ctx.wire_balance();
let amount = Amount::from_sat(40000);
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
retry(|| ctx.wire_balance() < wire);
// Bump min relay fee making the previous debit stuck
@@ -1023,7 +1036,7 @@ pub fn bumpfee(tctx: TestCtx) {
let mut client = ctx.client_balance();
let wire = ctx.wire_balance();
let amount = Amount::from_sat(40000);
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
retry(|| ctx.wire_balance() < wire);
// Bump min relay fee and fork making the previous debit stuck and problematic
@@ -1040,7 +1053,7 @@ pub fn bumpfee(tctx: TestCtx) {
// Perform credits to allow wire to perform debits latter
for n in 10..61 {
- ctx.credit(Amount::from_sat(n * 100000), rand_slice());
+ ctx.credit(Amount::from_sat(n * 100000), &Base32::rand());
ctx.next_block();
}
ctx.next_conf();
@@ -1057,7 +1070,7 @@ pub fn bumpfee(tctx: TestCtx) {
for n in 10..31 {
let amount = Amount::from_sat(n * 10000);
total_amount += amount;
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
}
retry(|| ctx.wire_balance() < wire - total_amount);
@@ -1076,7 +1089,7 @@ pub fn maxfee(ctx: TestCtx) {
// Perform credits to allow wire to perform debits latter
for n in 10..31 {
- ctx.credit(Amount::from_sat(n * 100000), rand_slice());
+ ctx.credit(Amount::from_sat(n * 100000), &Base32::rand());
ctx.next_block();
}
ctx.next_conf();
@@ -1094,7 +1107,7 @@ pub fn maxfee(ctx: TestCtx) {
for n in 10..31 {
let amount = Amount::from_sat(n * 10000);
total_amount += amount;
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
}
ctx.mine(2);
diff --git a/instrumentation/src/eth.rs b/instrumentation/src/eth.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022-2024 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -21,17 +21,25 @@ use std::{
time::Duration,
};
-use common::{metadata::OutMetadata, postgres::NoTls, rand_slice};
+use common::{
+ metadata::OutMetadata,
+ payto::EthAccount,
+ postgres::NoTls,
+ taler_common::{
+ api_common::{EddsaPublicKey, ShortHashCode},
+ types::{base32::Base32, payto::Payto},
+ },
+};
use eth_wire::{
- rpc::{hex::Hex, Rpc, RpcClient, TransactionRequest},
- taler_util::{eth_payto_url, eth_to_taler, TRUNC},
RpcExtended, SyncState, WireState,
+ rpc::{Rpc, RpcClient, TransactionRequest, hex::Hex},
+ taler_util::{TRUNC, eth_to_taler},
};
use ethereum_types::{H160, H256, U256};
use crate::utils::{
- check_incoming, check_outgoing, cmd_out, cmd_redirect, cmd_redirect_ok, print_now, retry,
- retry_opt, transfer, unused_port, ChildGuard, TalerCtx, TestCtx,
+ ChildGuard, TalerCtx, TestCtx, check_incoming, check_outgoing, cmd_out, cmd_redirect,
+ cmd_redirect_ok, print_now, retry, retry_opt, transfer, unused_port,
};
fn wait_for_pending(rpc: &mut Rpc) {
@@ -61,7 +69,7 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
.unwrap()
.into_iter()
.skip(1) // Skip etherbase if dev network
- .find(|addr| addr != &state.address) // Skip wire
+ .find(|addr| addr != &state.account.0) // Skip wire
.unwrap_or_else(|| rpc.new_account("password").unwrap()); // Else create account
rpc.unlock_account(&client_addr, "password").unwrap();
@@ -83,7 +91,7 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
wait_for_pending(&mut rpc);
// Load balances
let client_balance = rpc.get_balance_latest(&client_addr).unwrap();
- let wire_balance = rpc.get_balance_latest(&state.address).unwrap();
+ let wire_balance = rpc.get_balance_latest(&state.account.0).unwrap();
// Start sync state
let latest = rpc.latest_block().unwrap();
let mut sync_state = SyncState {
@@ -93,14 +101,19 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
};
println!("Send transaction");
- let reserve_pub_key = rand_slice();
+ let reserve_pub_key = Base32::rand();
let credit_id = rpc
- .credit(client_addr, state.address, test_amount, reserve_pub_key)
+ .credit(
+ client_addr,
+ state.account.0,
+ test_amount,
+ reserve_pub_key.clone(),
+ )
.unwrap();
let zero_id = rpc
.send_transaction(&TransactionRequest {
from: client_addr,
- to: state.address,
+ to: state.account.0,
value: U256::zero(),
gas_price: None,
data: Hex(vec![]),
@@ -110,7 +123,7 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
let bounce_id = rpc
.send_transaction(&TransactionRequest {
from: client_addr,
- to: state.address,
+ to: state.account.0,
value: test_amount,
gas_price: None,
data: Hex(vec![]),
@@ -121,11 +134,13 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
let bounce = {
let mut rpc = rpc.subscribe_new_head().unwrap();
'l: loop {
- let list = rpc.list_since_sync(&state.address, sync_state, 0).unwrap();
+ let list = rpc
+ .list_since_sync(&state.account.0, sync_state, 0)
+ .unwrap();
sync_state = list.state;
for sync_tx in list.txs {
let tx = sync_tx.tx;
- if tx.to.unwrap() == client_addr && tx.from.unwrap() == state.address {
+ if tx.to.unwrap() == client_addr && tx.from.unwrap() == state.account.0 {
let metadata = OutMetadata::decode(&tx.input).unwrap();
match metadata {
OutMetadata::Debit { .. } => {}
@@ -149,7 +164,7 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
println!("Check balance");
let new_client_balance = rpc.get_balance_latest(&client_addr).unwrap();
- let new_wire_balance = rpc.get_balance_latest(&state.address).unwrap();
+ let new_wire_balance = rpc.get_balance_latest(&state.account.0).unwrap();
let client_sent_amount_cost = test_amount * U256::from(2u8);
let client_sent_fees_cost = [credit_id, zero_id, bounce_id]
.into_iter()
@@ -177,12 +192,12 @@ pub fn online_test(config: Option<&Path>, base_url: &str) {
));
println!("Get back some money");
- let wtid = rand_slice();
+ let wtid = Base32::rand();
transfer(
base_url,
&wtid,
&state.base_url,
- eth_payto_url(&client_addr),
+ Payto::new(EthAccount(client_addr)).as_payto(),
&taler_test_amount,
);
wait_for_pending(&mut rpc);
@@ -469,7 +484,7 @@ impl EthCtx {
"import",
path,
],
- &self.ctx.log("geth"),
+ self.ctx.log("geth"),
"import chain",
);
self.resume_node(&[]);
@@ -493,7 +508,7 @@ impl EthCtx {
"--rpc.enabledeprecatedpersonal",
];
args.extend_from_slice(additional_args);
- self.node = cmd_redirect("geth", &args, &self.ctx.log("geth"));
+ self.node = cmd_redirect("geth", &args, self.ctx.log("geth"));
self.rpc = retry_opt(|| Rpc::new(&self.ctx.wire_dir).ok());
for addr in [&self.wire_addr, &self.client_addr, &self.reserve_addr] {
self.rpc.unlock_account(addr, &self.passwd).unwrap();
@@ -506,18 +521,23 @@ impl EthCtx {
/* ----- Transaction ------ */
- pub fn credit(&mut self, amount: U256, metadata: [u8; 32]) {
+ pub fn credit(&mut self, amount: U256, reserve_pub: &EddsaPublicKey) {
self.rpc
- .credit(self.client_addr, self.wire_addr, amount, metadata)
+ .credit(
+ self.client_addr,
+ self.wire_addr,
+ amount,
+ reserve_pub.clone(),
+ )
.unwrap();
}
- pub fn debit(&mut self, amount: U256, metadata: [u8; 32]) {
+ pub fn debit(&mut self, amount: U256, wtid: &ShortHashCode) {
transfer(
&self.ctx.gateway_url,
- &metadata,
+ wtid,
&self.state.base_url,
- eth_payto_url(&self.client_addr),
+ Payto::new(EthAccount(self.client_addr)).as_payto(),
ð_to_taler(&amount, self.state.currency),
)
}
@@ -619,18 +639,18 @@ impl EthCtx {
/* ----- Wire Gateway ----- */
- pub fn expect_credits(&self, txs: &[([u8; 32], U256)]) {
+ pub fn expect_credits(&self, txs: &[(EddsaPublicKey, U256)]) {
let txs: Vec<_> = txs
.iter()
- .map(|(metadata, amount)| (*metadata, eth_to_taler(amount, self.state.currency)))
+ .map(|(metadata, amount)| (metadata.clone(), eth_to_taler(amount, self.state.currency)))
.collect();
self.ctx.expect_credits(&txs)
}
- pub fn expect_debits(&self, txs: &[([u8; 32], U256)]) {
+ pub fn expect_debits(&self, txs: &[(ShortHashCode, U256)]) {
let txs: Vec<_> = txs
.iter()
- .map(|(metadata, amount)| (*metadata, eth_to_taler(amount, self.state.currency)))
+ .map(|(metadata, amount)| (metadata.clone(), eth_to_taler(amount, self.state.currency)))
.collect();
self.ctx.expect_debits(&self.state.base_url, &txs)
}
@@ -647,10 +667,10 @@ pub fn wire(ctx: TestCtx) {
let mut balance = ctx.wire_balance();
let mut txs = Vec::new();
for n in 10..100 {
- let metadata = rand_slice();
+ let reserve_pub = Base32::rand();
let amount = ctx.amount(n * 1000);
- ctx.credit(amount, metadata);
- txs.push((metadata, amount));
+ ctx.credit(amount, &reserve_pub);
+ txs.push((reserve_pub, amount));
balance += amount;
}
ctx.next_conf();
@@ -663,10 +683,10 @@ pub fn wire(ctx: TestCtx) {
let mut balance = ctx.client_balance();
let mut txs = Vec::new();
for n in 10..100 {
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = ctx.amount(n * 100);
balance += amount;
- ctx.debit(amount, metadata);
+ ctx.debit(amount, &metadata);
txs.push((metadata, amount));
}
ctx.next_block();
@@ -696,11 +716,11 @@ pub fn lifetime(ctx: TestCtx) {
retry(|| ctx.wire_running() && ctx.gateway_running());
// Consume lifetime
for n in 0..=ctx.taler_conf.wire_lifetime().unwrap() {
- ctx.credit(ctx.amount(n * 1000), rand_slice());
+ ctx.credit(ctx.amount(n * 1000), &Base32::rand());
ctx.next_block();
}
for n in 0..=ctx.taler_conf.http_lifetime().unwrap() {
- ctx.debit(ctx.amount(n * 1000), rand_slice());
+ ctx.debit(ctx.amount(n * 1000), &Base32::rand());
}
// End down
retry(|| !ctx.wire_running() && !ctx.gateway_running());
@@ -716,9 +736,9 @@ pub fn reconnect(ctx: TestCtx) {
ctx.step("With DB");
{
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = ctx.amount(42000);
- ctx.credit(amount, metadata);
+ ctx.credit(amount, &metadata);
credits.push((metadata, amount));
ctx.next_block();
ctx.next_conf();
@@ -729,21 +749,21 @@ pub fn reconnect(ctx: TestCtx) {
{
ctx.stop_db();
ctx.malformed_credit(ctx.amount(24000));
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = ctx.amount(4000);
- ctx.credit(amount, metadata);
+ ctx.credit(amount, &metadata);
credits.push((metadata, amount));
ctx.stop_node();
- ctx.expect_error();
+ ctx.expect_gateway_down();
}
ctx.step("Reconnect DB");
{
ctx.resume_db();
ctx.resume_node(&[]);
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = ctx.amount(2000);
- ctx.debit(amount, metadata);
+ ctx.debit(amount, &metadata);
debits.push((metadata, amount));
ctx.next_block();
sleep(Duration::from_secs(3));
@@ -781,9 +801,9 @@ pub fn stress(ctx: TestCtx) {
{
let mut balance = ctx.wire_balance();
for n in 10..30 {
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = ctx.amount(n * 1000);
- ctx.credit(amount, metadata);
+ ctx.credit(amount, &metadata);
credits.push((metadata, amount));
balance += amount;
}
@@ -796,10 +816,10 @@ pub fn stress(ctx: TestCtx) {
{
let mut balance = ctx.client_balance();
for n in 10..30 {
- let metadata = rand_slice();
+ let metadata = Base32::rand();
let amount = ctx.amount(n * 100);
balance += amount;
- ctx.debit(amount, metadata);
+ ctx.debit(amount, &metadata);
debits.push((metadata, amount));
}
ctx.next_block();
@@ -842,7 +862,7 @@ pub fn reorg(ctx: TestCtx) {
// Perform credits
let before = ctx.wire_balance();
for n in 10..21 {
- ctx.credit(ctx.amount(n * 10000), rand_slice());
+ ctx.credit(ctx.amount(n * 10000), &Base32::rand());
}
ctx.next_conf();
let after = ctx.wire_balance();
@@ -869,7 +889,7 @@ pub fn reorg(ctx: TestCtx) {
let mut after = ctx.client_balance();
for n in 10..21 {
let amount = ctx.amount(n * 100);
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
after += amount;
}
ctx.next_block();
@@ -938,7 +958,7 @@ pub fn hell(ctx: TestCtx) {
ctx.restart_node(&["--miner.gasprice", "1000"]);
ctx.abandon();
let amount = ctx.amount(54000);
- ctx.credit(amount, rand_slice());
+ ctx.credit(amount, &Base32::rand());
ctx.expect_wire_balance(amount, true);
// Check eth-wire suspend operation
@@ -951,7 +971,7 @@ pub fn hell(ctx: TestCtx) {
step(&ctx, "Handle reorg conflicting incoming credit", |ctx| {
let amount = ctx.amount(420000);
- ctx.credit(amount, rand_slice());
+ ctx.credit(amount, &Base32::rand());
ctx.next_conf();
ctx.expect_wire_balance(amount, true);
});
@@ -976,7 +996,7 @@ pub fn analysis(ctx: TestCtx) {
// Perform credit
let before = ctx.wire_balance();
- ctx.credit(ctx.amount(42000), rand_slice());
+ ctx.credit(ctx.amount(42000), &Base32::rand());
ctx.next_conf();
let after = ctx.wire_balance();
@@ -996,7 +1016,7 @@ pub fn analysis(ctx: TestCtx) {
// Perform credit
let before = ctx.wire_balance();
- ctx.credit(ctx.amount(42000), rand_slice());
+ ctx.credit(ctx.amount(42000), &Base32::rand());
ctx.next_conf();
// Perform fork and check eth-wire learned from previous attack
@@ -1013,7 +1033,7 @@ pub fn bumpfee(tctx: TestCtx) {
let mut ctx = EthCtx::setup(&tctx, "taler_eth_bump.conf", false);
// Perform credits to allow wire to perform debits latter
- ctx.credit(ctx.amount(90000000), rand_slice());
+ ctx.credit(ctx.amount(90000000), &Base32::rand());
ctx.next_conf();
ctx.step("Bump fee");
@@ -1022,7 +1042,7 @@ pub fn bumpfee(tctx: TestCtx) {
let mut client = ctx.client_balance();
let wire = ctx.wire_balance();
let amount = ctx.amount(40000);
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
retry(|| ctx.wire_balance_pending() < wire);
// Bump min relay fee making the previous debit stuck
@@ -1042,7 +1062,7 @@ pub fn bumpfee(tctx: TestCtx) {
let mut client = ctx.client_balance();
let wire = ctx.wire_balance();
let amount = ctx.amount(40000);
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
retry(|| ctx.wire_balance_pending() < wire);
// Bump min relay fee and fork making the previous debit stuck and problematic
@@ -1059,7 +1079,7 @@ pub fn bumpfee(tctx: TestCtx) {
let mut ctx = EthCtx::setup(&tctx, "taler_eth_bump.conf", true);
// Perform credit to allow wire to perform debits latter
- ctx.credit(ctx.amount(9000000), rand_slice());
+ ctx.credit(ctx.amount(9000000), &Base32::rand());
ctx.next_conf();
ctx.step("Bump fee stress");
@@ -1074,7 +1094,7 @@ pub fn bumpfee(tctx: TestCtx) {
for n in 10..31 {
let amount = ctx.amount(n * 10000);
total_amount += amount;
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
}
retry(|| ctx.wire_balance_pending() < wire - total_amount);
@@ -1092,7 +1112,7 @@ pub fn maxfee(ctx: TestCtx) {
let mut ctx = EthCtx::setup(&ctx, "taler_eth.conf", false);
// Perform credit to allow wire to perform debits latter
- ctx.credit(ctx.amount(9000000), rand_slice());
+ ctx.credit(ctx.amount(9000000), &Base32::rand());
ctx.next_conf();
let client = ctx.client_balance();
@@ -1108,7 +1128,7 @@ pub fn maxfee(ctx: TestCtx) {
for n in 10..31 {
let amount = ctx.amount(n * 10000);
total_amount += amount;
- ctx.debit(amount, rand_slice());
+ ctx.debit(amount, &Base32::rand());
}
sleep(Duration::from_secs(3));
diff --git a/instrumentation/src/gateway.rs b/instrumentation/src/gateway.rs
@@ -16,38 +16,32 @@
use std::str::FromStr;
-use btc_wire::taler_utils::btc_payto_url;
use common::{
- api_common::{Amount as TalerAmount, Base32},
- api_wire::TransferRequest,
- rand_slice,
- url::Url,
+ payto::BtcAccount,
+ taler_common::{
+ api_wire::TransferRequest,
+ types::{
+ amount::Amount,
+ base32::Base32,
+ payto::{PaytoURI, payto},
+ },
+ },
};
use libdeflater::{CompressionLvl, Compressor};
-use ureq::{http::Response, Body};
+use taler_common::types::payto::Payto;
use crate::{
btc::BtcCtx,
- utils::{cmd_out, cmd_redirect_ok, gateway_error, TestCtx},
+ utils::{TestCtx, cmd_out, cmd_redirect_ok, gateway_error, http_code},
};
-fn client_transfer(gateway_url: &str, payto_url: &str, amount: &str) -> String {
+fn client_transfer(gateway_url: &str, payto_url: &PaytoURI, amount: &str) -> String {
cmd_out(
"taler-exchange-wire-gateway-client",
- &["-b", gateway_url, "-C", payto_url, "-a", amount],
+ &["-b", gateway_url, "-C", payto_url.raw(), "-a", amount],
)
}
-fn http_code(response: Result<Response<Body>, ureq::Error>) -> u16 {
- match response {
- Ok(resp) => resp.status().as_u16(),
- Err(err) => match err {
- ureq::Error::StatusCode(err) => err,
- e => unreachable!("{e}"),
- },
- }
-}
-
/// Test wire-gateway conformance to documentation and its security
pub fn api(ctx: TestCtx) {
ctx.step("Setup");
@@ -65,7 +59,9 @@ pub fn api(ctx: TestCtx) {
"-b",
&ctx.gateway_url,
"-D",
- btc_payto_url(&ctx.client_addr).as_ref(),
+ Payto::new(BtcAccount(ctx.client_addr.clone()))
+ .as_payto()
+ .raw(),
"-a",
&amount,
],
@@ -88,7 +84,7 @@ pub fn api(ctx: TestCtx) {
let amount = format!("{}:0.0000{}", ctx.taler_conf.currency.to_str(), n);
client_transfer(
&ctx.gateway_url,
- btc_payto_url(&ctx.client_addr).as_ref(),
+ &Payto::new(BtcAccount(ctx.client_addr.clone())).as_payto(),
&amount,
);
amounts.push(amount);
@@ -113,33 +109,33 @@ pub fn api(ctx: TestCtx) {
}
let amount = &format!("{}:0.00042", ctx.taler_conf.currency.to_str());
- let payto = btc_payto_url(&ctx.client_addr).to_string();
+ let btc_payto = Payto::new(BtcAccount(ctx.client_addr.clone())).as_payto();
ctx.step("Request format");
{
// Bad payto_url
for url in [
- "http://bitcoin/$CLIENT",
+ //"http://bitcoin/$CLIENT",
"payto://btc/$CLIENT",
"payto://bitcoin/$CLIENT?id=admin",
"payto://bitcoin/$CLIENT#admin",
"payto://bitcoin/42$CLIENT",
] {
let url = url.replace("$CLIENT", &ctx.client_addr.to_string());
- let result = client_transfer(&ctx.gateway_url, &url, amount);
+ let result = client_transfer(&ctx.gateway_url, &payto(url), amount);
assert!(result.contains("(400/24)"));
}
// Bad transaction amount
- let result = client_transfer(&ctx.gateway_url, &payto, "ATC:0.00042");
- assert!(result.contains("(400/26)"));
+ let result = client_transfer(&ctx.gateway_url, &btc_payto, "ATC:0.00042");
+ assert!(result.contains("(400/30)"));
// Bad history delta
for delta in [
- "incoming",
- "outgoing",
+ "incoming?offset=-1",
+ "outgoing?offset=-1",
"incoming?delta=0",
- "outgoing?delta=0;",
+ "outgoing?delta=0",
] {
let code =
http_code(ureq::get(&format!("{}history/{}", ctx.gateway_url, delta)).call());
@@ -150,11 +146,11 @@ pub fn api(ctx: TestCtx) {
ctx.step("Transfer idempotence");
{
let mut request = TransferRequest {
- request_uid: Base32::from(rand_slice()),
- amount: TalerAmount::from_str(amount).unwrap(),
+ request_uid: Base32::rand(),
+ amount: Amount::from_str(amount).unwrap(),
exchange_base_url: ctx.taler_conf.base_url(),
- wtid: Base32::from(rand_slice()),
- credit_account: Url::from_str(&payto).unwrap(),
+ wtid: Base32::rand(),
+ credit_account: btc_payto,
};
// Same
assert_eq!(
@@ -166,7 +162,7 @@ pub fn api(ctx: TestCtx) {
200
);
// Collision
- request.amount.fraction += 42;
+ request.amount.frac += 42;
assert_eq!(
http_code(ureq::post(&format!("{}transfer", ctx.gateway_url)).send_json(&request)),
409
@@ -186,7 +182,7 @@ pub fn api(ctx: TestCtx) {
assert_eq!(
http_code(
ureq::post(&format!("{}transfer", ctx.gateway_url))
- .query("Content-Length", "1024")
+ .header("Content-Length", "1024")
.send_json(&big_hello)
),
400
@@ -202,7 +198,7 @@ pub fn api(ctx: TestCtx) {
assert_eq!(
http_code(
ureq::post(&format!("{}transfer", ctx.gateway_url))
- .query("Content-Encoding", "deflate")
+ .header("Content-Encoding", "deflate")
.send(&compressed)
),
400
@@ -232,7 +228,9 @@ pub fn auth(ctx: TestCtx) {
"-s",
"exchange-accountcredentials-admin",
"-C",
- btc_payto_url(&ctx.client_addr).as_ref(),
+ Payto::new(BtcAccount(ctx.client_addr.clone()))
+ .as_payto()
+ .raw(),
"-a",
&format!("{}:0.00042", ctx.taler_conf.currency.to_str()),
],
diff --git a/instrumentation/src/main.rs b/instrumentation/src/main.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022-2024 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -29,7 +29,7 @@ use owo_colors::OwoColorize;
use thread_local_panic_hook::set_hook;
use utils::TestDb;
-use crate::utils::{try_cmd_redirect, TestCtx};
+use crate::utils::{TestCtx, try_cmd_redirect};
mod btc;
mod eth;
@@ -92,7 +92,9 @@ pub fn main() {
// Generate password
let pwd: String = (0..30).map(|_| fastrand::alphanumeric()).collect();
- std::env::set_var("PASSWORD", pwd);
+ unsafe {
+ std::env::set_var("PASSWORD", pwd);
+ }
// Run tests
let m = MultiProgress::new();
diff --git a/instrumentation/src/utils.rs b/instrumentation/src/utils.rs
@@ -28,14 +28,17 @@ use std::{
};
use common::{
- api_common::{Amount, Base32},
- api_wire::{IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferRequest},
config::TalerConfig,
- rand_slice,
+ taler_common::{
+ api_common::{EddsaPublicKey, ShortHashCode},
+ api_wire::{IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferRequest},
+ types::{amount::Amount, base32::Base32, payto::PaytoURI},
+ },
url::Url,
};
use indicatif::ProgressBar;
use tempfile::TempDir;
+use ureq::http::Response;
pub fn print_now(disp: impl Display) {
print!("{}", disp);
@@ -43,9 +46,9 @@ pub fn print_now(disp: impl Display) {
}
#[must_use]
-pub fn check_incoming(base_url: &str, txs: &[([u8; 32], Amount)]) -> bool {
+pub fn check_incoming(base_url: &str, txs: &[(EddsaPublicKey, Amount)]) -> bool {
let mut res = ureq::get(&format!("{}history/incoming", base_url))
- .query("delta", &format!("-{}", txs.len()))
+ .query("delta", format!("-{}", txs.len()))
.call()
.unwrap();
if txs.is_empty() {
@@ -59,35 +62,34 @@ pub fn check_incoming(base_url: &str, txs: &[([u8; 32], Amount)]) -> bool {
history.incoming_transactions.len() == txs.len()
&& txs.iter().all(|(reserve_pub_key, taler_amount)| {
history.incoming_transactions.iter().any(|h| {
- matches!(
- h,
- IncomingBankTransaction::IncomingReserveTransaction {
- reserve_pub,
- amount,
- ..
- } if reserve_pub == &Base32::from(*reserve_pub_key) && amount == taler_amount
- )
- })
+ matches!(
+ h,
+ IncomingBankTransaction::Reserve {
+ reserve_pub,
+ amount,
+ ..
+ } if reserve_pub == reserve_pub_key && amount == taler_amount
+ )
+ })
})
}
}
-pub fn gateway_error(path: &str, error: u16) {
- let err = ureq::get(path).call().unwrap_err();
- match err {
- ureq::Error::StatusCode(nb) => assert_eq!(nb, error),
- e => unreachable!("{e}"),
+pub fn http_code<T>(response: Result<Response<T>, ureq::Error>) -> u16 {
+ match response {
+ Ok(resp) => resp.status().as_u16(),
+ Err(err) => match err {
+ ureq::Error::StatusCode(err) => err,
+ _ => unreachable!(),
+ },
}
}
-#[must_use]
-pub fn check_gateway_error(base_url: &str) -> bool {
- matches!(
- ureq::get(&format!("{}history/incoming", base_url))
- .query("delta", "-5")
- .call(),
- Err(ureq::Error::StatusCode(504))
- )
+pub fn gateway_error(path: &str, error: u16) {
+ assert!(matches!(
+ ureq::get(path).call(),
+ Err(ureq::Error::StatusCode(err)) if err == error
+ ))
}
#[must_use]
@@ -96,7 +98,7 @@ pub fn check_gateway_down(base_url: &str) -> bool {
ureq::get(&format!("{}history/incoming", base_url))
.query("delta", "-5")
.call(),
- Err(ureq::Error::StatusCode(502))
+ Err(ureq::Error::StatusCode(504 | 502))
)
}
@@ -105,10 +107,16 @@ pub fn check_gateway_up(base_url: &str) -> bool {
ureq::get(&format!("{}config", base_url)).call().is_ok()
}
-pub fn transfer(base_url: &str, wtid: &[u8; 32], url: &Url, credit_account: Url, amount: &Amount) {
+pub fn transfer(
+ base_url: &str,
+ wtid: &[u8; 32],
+ url: &Url,
+ credit_account: PaytoURI,
+ amount: &Amount,
+) {
ureq::post(&format!("{}transfer", base_url))
.send_json(TransferRequest {
- request_uid: Base32::from(rand_slice()),
+ request_uid: Base32::rand(),
amount: amount.clone(),
exchange_base_url: url.clone(),
wtid: Base32::from(*wtid),
@@ -118,9 +126,9 @@ pub fn transfer(base_url: &str, wtid: &[u8; 32], url: &Url, credit_account: Url,
}
#[must_use]
-pub fn check_outgoing(base_url: &str, url: &Url, txs: &[([u8; 32], Amount)]) -> bool {
+pub fn check_outgoing(base_url: &str, url: &Url, txs: &[(ShortHashCode, Amount)]) -> bool {
let mut res = ureq::get(&format!("{}history/outgoing", base_url))
- .query("delta", &format!("-{}", txs.len()))
+ .query("delta", format!("-{}", txs.len()))
.call()
.unwrap();
if txs.is_empty() {
@@ -133,11 +141,10 @@ pub fn check_outgoing(base_url: &str, url: &Url, txs: &[([u8; 32], Amount)]) ->
history.outgoing_transactions.len() == txs.len()
&& txs.iter().all(|(wtid, amount)| {
- history.outgoing_transactions.iter().any(|h| {
- h.wtid == Base32::from(*wtid)
- && &h.exchange_base_url == url
- && &h.amount == amount
- })
+ history
+ .outgoing_transactions
+ .iter()
+ .any(|h| h.wtid == *wtid && &h.exchange_base_url == url && &h.amount == amount)
})
}
}
@@ -316,13 +323,7 @@ impl TalerCtx {
.with_section(section)
.set("CONF_PATH", wire_dir.to_string_lossy())
.set("IPC_PATH", wire_dir.to_string_lossy())
- .set(
- "DB_URL",
- format!(
- "postgres://localhost:{}/{}?user=postgres&password=password",
- ctx.db.port, ctx.name
- ),
- )
+ .set("DB_URL", ctx.db.postgres_uri(&ctx.name))
.set("PORT", gateway_port.to_string());
config.write_to_file(&conf).unwrap();
@@ -409,18 +410,14 @@ impl TalerCtx {
/* ----- Wire Gateway -----*/
- pub fn expect_credits(&self, txs: &[([u8; 32], Amount)]) {
+ pub fn expect_credits(&self, txs: &[(EddsaPublicKey, Amount)]) {
retry(|| check_incoming(&self.gateway_url, txs))
}
- pub fn expect_debits(&self, base_url: &Url, txs: &[([u8; 32], Amount)]) {
+ pub fn expect_debits(&self, base_url: &Url, txs: &[(ShortHashCode, Amount)]) {
retry(|| check_outgoing(&self.gateway_url, base_url, txs))
}
- pub fn expect_error(&self) {
- retry(|| check_gateway_error(&self.gateway_url));
- }
-
pub fn expect_gateway_up(&self) {
retry(|| check_gateway_up(&self.gateway_url));
}
@@ -454,14 +451,12 @@ pub fn unused_port() -> u16 {
pub struct TestDb {
pub dir: TempDir,
- pub port: u16,
_db: ChildGuard,
}
impl TestDb {
pub fn new() -> Self {
let dir = TempDir::new().unwrap();
- let port = unused_port();
let log = "log/postgres.log";
// Init databases files
@@ -476,7 +471,7 @@ impl TestDb {
dir.path().join("postgresql.conf"),
format!(
"
- port={port}
+ listen_addresses=''
unix_socket_directories='{}'
fsync=off
synchronous_commit=off
@@ -491,20 +486,23 @@ impl TestDb {
&["-D", dir.path().to_string_lossy().as_ref()],
log,
);
- retry(|| {
- Self::execute_sql(
- port,
- "CREATE ROLE postgres LOGIN SUPERUSER PASSWORD 'password'",
- )
- });
+ let tmp = Self { dir, _db: db };
+ // Wait for postgres to start
+ retry(|| tmp.execute_sql("SELECT true"));
+ tmp
+ }
- Self { dir, port, _db: db }
+ pub fn postgres_uri(&self, database: &str) -> String {
+ format!(
+ "postgres:///{database}?host={}",
+ self.dir.path().to_string_lossy().as_ref()
+ )
}
- fn execute_sql(port: u16, sql: &str) -> bool {
+ fn execute_sql(&self, sql: &str) -> bool {
let mut psql = ChildGuard(
Command::new("psql")
- .arg(format!("postgres://localhost:{}/postgres", port))
+ .arg(self.postgres_uri("postgres"))
.stderr(Stdio::null())
.stdout(
std::fs::File::options()
@@ -527,34 +525,27 @@ impl TestDb {
}
pub fn create_db(&self, name: &str) {
- Self::execute_sql(
- self.port,
- &format!(
- "
+ self.execute_sql(&format!(
+ "
DROP DATABASE IF EXISTS {name};
CREATE DATABASE {name};
"
- ),
- );
+ ));
}
pub fn stop_db(&self, name: &str) {
- Self::execute_sql(
- self.port,
- &format!(
- "
+ self.execute_sql(&format!(
+ "
UPDATE pg_database SET datallowconn=false WHERE datname='{name}';
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname='{name}' AND pid <> pg_backend_pid();
"
- ),
- );
+ ));
}
pub fn resume_db(&self, name: &str) {
- Self::execute_sql(
- self.port,
- &format!("UPDATE pg_database SET datallowconn=true WHERE datname='{name}';"),
- );
+ self.execute_sql(&format!(
+ "UPDATE pg_database SET datallowconn=true WHERE datname='{name}';"
+ ));
}
}
diff --git a/script/prepare.sh b/script/prepare.sh
@@ -16,25 +16,30 @@ function cleanup() {
trap cleanup EXIT
+BTC_VER="28.1"
+ETH_VER="1.13.5-916d6a44"
+
echo "â… - Find installed postgres version"
PG_VER=`pg_config --version | egrep -o '[0-9]{1,}' | head -1`
echo "Found version $PG_VER"
-echo "â…¡ - Install bitcoind version 28.8"
+echo "â…¡ - Install bitcoind version $BTC_VER"
cd $DIR
-curl -L https://bitcoincore.org/bin/bitcoin-core-28.0/bitcoin-28.0-x86_64-linux-gnu.tar.gz -o btc.tar.gz
+ARCH=`uname -m`
+curl -L https://bitcoincore.org/bin/bitcoin-core-$BTC_VER/bitcoin-$BTC_VER-$ARCH-linux-gnu.tar.gz -o btc.tar.gz
tar xvzf btc.tar.gz
rm -rfv ~/bitcoin
mkdir -pv ~/bitcoin
-mv -v bitcoin-28.0/* ~/bitcoin
+mv -v bitcoin-$BTC_VER/* ~/bitcoin
-echo "â…¢ - Install Go Ethereum (Geth) v1.13.5"
+echo "â…¢ - Install Go Ethereum (Geth) v$ETH_VER"
cd $DIR
-curl -L https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.13.5-916d6a44.tar.gz -o geth.tar.gz
+ARCH=`dpkg --print-architecture`
+curl -L https://gethstore.blob.core.windows.net/builds/geth-linux-$ARCH-$ETH_VER.tar.gz -o geth.tar.gz
tar xvzf geth.tar.gz
rm -rfv ~/geth
mkdir -pv ~/geth
-mv -v geth-linux-amd64-1.13.5-916d6a44/* ~/geth
+mv -v geth-linux-$ARCH-$ETH_VER/* ~/geth
echo "â…¤ - PATH"
diff --git a/uri-pack/Cargo.toml b/uri-pack/Cargo.toml
@@ -1,21 +1,22 @@
[package]
name = "uri-pack"
version = "0.1.0"
-edition = "2021"
-license = "AGPL-3.0-or-later"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+edition.workspace = true
+authors.workspace = true
+homepage.workspace = true
+repository.workspace = true
+license-file.workspace = true
[dependencies]
# Error macros
-thiserror = "1.0.49"
+thiserror.workspace = true
csv = "1.3.0"
[dev-dependencies]
# Json parser
-serde_json = "1.0.107"
+serde_json.workspace = true
# Url parser
-url = "2.4.1"
+url.workspace = true
# statistics-driven micro-benchmarks
criterion = "0.5.1"
# Fast insecure random
diff --git a/uri-pack/benches/pack.rs b/uri-pack/benches/pack.rs
@@ -1,80 +1,80 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
-use uri_pack::{pack_uri, unpack_uri};
-
-/// Ascii char that can be packed into 5 bits
-pub(crate) const PACKED: [u8; 30] = [
- b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
- b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'.', b'/', b'-', b'%',
-];
-
-fn rand_compat(size: usize) -> String {
- String::from_utf8(
- std::iter::repeat_with(|| fastrand::u8(b'!'..=b'~'))
- .take(size)
- .collect(),
- )
- .unwrap()
-}
-
-fn rand_simple(size: usize) -> String {
- String::from_utf8(
- std::iter::repeat_with(|| PACKED[fastrand::usize(..PACKED.len())])
- .take(size)
- .collect(),
- )
- .unwrap()
-}
-
-fn criterion_benchmark(c: &mut Criterion) {
- let mut group = c.benchmark_group("Uri");
- for size in [50, 500, 4048].iter() {
- group.throughput(Throughput::Bytes(*size as u64));
- group.bench_with_input(BenchmarkId::new("pack rand", size), size, |b, &size| {
- b.iter_batched(
- || rand_compat(size),
- |uri| pack_uri(&uri).unwrap(),
- criterion::BatchSize::SmallInput,
- )
- });
- group.bench_with_input(BenchmarkId::new("unpack rand", size), size, |b, &size| {
- b.iter_batched(
- || pack_uri(&rand_compat(size)).unwrap(),
- |packed| unpack_uri(&packed),
- criterion::BatchSize::SmallInput,
- )
- });
- group.bench_with_input(BenchmarkId::new("pack simple", size), size, |b, &size| {
- b.iter_batched(
- || rand_simple(size),
- |uri| pack_uri(&uri).unwrap(),
- criterion::BatchSize::SmallInput,
- )
- });
- group.bench_with_input(BenchmarkId::new("unpack simple", size), size, |b, &size| {
- b.iter_batched(
- || pack_uri(&rand_simple(size)).unwrap(),
- |packed| unpack_uri(&packed),
- criterion::BatchSize::SmallInput,
- )
- });
- }
- group.finish();
-}
-
-criterion_group!(benches, criterion_benchmark);
-criterion_main!(benches);
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
+use uri_pack::{pack_uri, unpack_uri};
+
+/// Ascii char that can be packed into 5 bits
+pub(crate) const PACKED: [u8; 30] = [
+ b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
+ b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'.', b'/', b'-', b'%',
+];
+
+fn rand_compat(size: usize) -> String {
+ String::from_utf8(
+ std::iter::repeat_with(|| fastrand::u8(b'!'..=b'~'))
+ .take(size)
+ .collect(),
+ )
+ .unwrap()
+}
+
+fn rand_simple(size: usize) -> String {
+ String::from_utf8(
+ std::iter::repeat_with(|| PACKED[fastrand::usize(..PACKED.len())])
+ .take(size)
+ .collect(),
+ )
+ .unwrap()
+}
+
+fn criterion_benchmark(c: &mut Criterion) {
+ let mut group = c.benchmark_group("Uri");
+ for size in [50, 500, 4048].iter() {
+ group.throughput(Throughput::Bytes(*size as u64));
+ group.bench_with_input(BenchmarkId::new("pack rand", size), size, |b, &size| {
+ b.iter_batched(
+ || rand_compat(size),
+ |uri| pack_uri(&uri).unwrap(),
+ criterion::BatchSize::SmallInput,
+ )
+ });
+ group.bench_with_input(BenchmarkId::new("unpack rand", size), size, |b, &size| {
+ b.iter_batched(
+ || pack_uri(&rand_compat(size)).unwrap(),
+ |packed| unpack_uri(&packed),
+ criterion::BatchSize::SmallInput,
+ )
+ });
+ group.bench_with_input(BenchmarkId::new("pack simple", size), size, |b, &size| {
+ b.iter_batched(
+ || rand_simple(size),
+ |uri| pack_uri(&uri).unwrap(),
+ criterion::BatchSize::SmallInput,
+ )
+ });
+ group.bench_with_input(BenchmarkId::new("unpack simple", size), size, |b, &size| {
+ b.iter_batched(
+ || pack_uri(&rand_simple(size)).unwrap(),
+ |packed| unpack_uri(&packed),
+ criterion::BatchSize::SmallInput,
+ )
+ });
+ }
+ group.finish();
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/uri-pack/src/lib.rs b/uri-pack/src/lib.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -93,7 +93,7 @@ pub enum DecodeErr {
/// Pack an uri string into an optimized binary format
pub fn pack_uri(uri: &str) -> Result<Vec<u8>, EncodeErr> {
- let len = uri.as_bytes().len();
+ let len = uri.len();
let mut vec = Vec::with_capacity(len);
if let Some(char) = uri.as_bytes().iter().find(|c| !supported_ascii(c)) {
@@ -200,7 +200,7 @@ mod test {
use serde_json::Value;
- use crate::{pack_ascii, pack_uri, supported_ascii, unpack_ascii, unpack_uri, EXTENDED};
+ use crate::{EXTENDED, pack_ascii, pack_uri, supported_ascii, unpack_ascii, unpack_uri};
/// Ascii char that can be packed into 5 bits
const PACKED: [u8; 30] = [
diff --git a/uri-pack/src/main.rs b/uri-pack/src/main.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -32,7 +32,7 @@ fn main() {
for ascii in domain.as_bytes() {
ascii_counter[*ascii as usize] += 1;
}
- let before = domain.as_bytes().len();
+ let before = domain.len();
let after = pack_uri(domain).unwrap().len();
before_len += before;
after_len += after;
diff --git a/wire-gateway/Cargo.toml b/wire-gateway/Cargo.toml
@@ -1,43 +1,31 @@
[package]
name = "wire-gateway"
version = "0.1.0"
-edition = "2021"
-license = "AGPL-3.0-or-later"
-rust-version = "1.72.1"
+edition.workspace = true
+authors.workspace = true
+homepage.workspace = true
+repository.workspace = true
+license-file.workspace = true
[dependencies]
-# Http library
-hyper = { version = "0.14.30", features = ["http1", "server", "runtime"] }
-# Hyper compat lib for unix domain socket
-hyperlocal = "0.8.0"
+axum.workspace = true
+taler-api.workspace = true
+taler-common.workspace = true
# Async runtime
-tokio = { version = "1.32.0", features = ["net", "macros", "rt-multi-thread"] }
-# Serialization framework
-serde = { version = "1.0.188", features = ["derive"] }
-# Serialization helper
-serde_with = "3.3.0"
-# JSON serialization
-serde_json = "1.0.91"
-# Url query serialization
-serde_urlencoded = "0.7.1"
+tokio = { workspace = true, features = ["net", "macros", "rt-multi-thread"] }
# Error macros
-thiserror = "1.0.38"
-# Deflate compression
-miniz_oxide = "0.8.0"
+thiserror.workspace = true
# Async postgres client
-tokio-postgres = { version = "0.7.7" }
-deadpool-postgres = "0.14.0"
-# Socket activation
-listenfd = "1.0.0"
+sqlx.workspace = true
# Common lib
common = { path = "../common" }
# Bitcoin types
-bitcoin = { version = "0.31.2" }
+bitcoin.workspace = true
# Ethereum types
-ethereum-types = { version = "0.15.1", default-features = false }
+ethereum-types.workspace = true
# Cli args parser
-clap = { version = "4.4.6", features = ["derive"] }
-clap_lex = "0.7.0"
+clap.workspace = true
+time = "0.3"
[features]
# Enable test admin endpoint
diff --git a/wire-gateway/src/error.rs b/wire-gateway/src/error.rs
@@ -1,128 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-use common::{api_common::ErrorDetail, error_codes::ErrorCode};
-use hyper::{header, Body, Response, StatusCode};
-
-/// Generic http error
-#[derive(Debug)]
-pub struct ServerError {
- pub status: StatusCode,
- content: Content,
- pub msg: String,
-}
-
-#[derive(Debug)]
-enum Content {
- None,
- Json(Vec<u8>),
-}
-
-impl ServerError {
- fn new(status: StatusCode, content: Content, msg: String) -> Self {
- Self {
- status,
- content,
- msg,
- }
- }
-
- pub fn response(self) -> (Response<Body>, String) {
- let builder = Response::builder().status(self.status);
- let result = match self.content {
- Content::None => builder.body(Body::empty()),
- Content::Json(it) => builder
- .header(header::CONTENT_TYPE, "application/json")
- .body(Body::from(it)),
- };
- (result.unwrap(), self.msg)
- }
-
- pub fn unexpected<E: std::error::Error>(e: E) -> Self {
- Self::new(
- StatusCode::INTERNAL_SERVER_ERROR,
- Content::None,
- format!("unexpected: {}", e),
- )
- }
-
- fn detail(status: StatusCode, code: ErrorCode, msg: String) -> Self {
- let detail = ErrorDetail {
- code: code as i64,
- hint: None,
- detail: None,
- parameter: None,
- path: None,
- offset: None,
- index: None,
- object: None,
- currency: None,
- type_expected: None,
- type_actual: None,
- };
- match serde_json::to_vec(&detail) {
- Ok(json) => Self::new(status, Content::Json(json), msg),
- Err(e) => Self::unexpected(e),
- }
- }
-
- pub fn status(status: StatusCode) -> Self {
- Self::new(
- status,
- Content::None,
- status.canonical_reason().unwrap_or("").to_string(),
- )
- }
-
- pub fn code(status: StatusCode, code: ErrorCode) -> Self {
- Self::detail(
- status,
- code,
- format!(
- "standard {}: {}",
- code as i64,
- status.canonical_reason().unwrap_or("")
- ),
- )
- }
-
- pub fn catch_code<E: std::error::Error>(e: E, status: StatusCode, code: ErrorCode) -> Self {
- Self::detail(status, code, format!("standard {}: {}", code as i64, e))
- }
-}
-
-impl From<tokio_postgres::Error> for ServerError {
- fn from(e: tokio_postgres::Error) -> Self {
- ServerError::catch_code(
- e,
- StatusCode::BAD_GATEWAY,
- ErrorCode::GENERIC_DB_FETCH_FAILED,
- )
- }
-}
-pub trait CatchResult<T: Sized> {
- fn unexpected(self) -> Result<T, ServerError>;
- fn catch_code(self, status: StatusCode, code: ErrorCode) -> Result<T, ServerError>;
-}
-
-impl<T, E: std::error::Error> CatchResult<T> for Result<T, E> {
- fn unexpected(self) -> Result<T, ServerError> {
- self.map_err(|e| ServerError::unexpected(e))
- }
-
- fn catch_code(self, status: StatusCode, code: ErrorCode) -> Result<T, ServerError> {
- self.map_err(|e| ServerError::catch_code(e, status, code))
- }
-}
diff --git a/wire-gateway/src/json.rs b/wire-gateway/src/json.rs
@@ -1,103 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-use hyper::{body::HttpBody, header, http::request::Parts, Body, Response, StatusCode};
-use miniz_oxide::inflate::TINFLStatus;
-
-const MAX_PAYLOAD_SIZE: u64 = 4 * 1024; // 4kB
-
-#[derive(Debug, thiserror::Error)]
-pub enum ParseBodyError {
- #[error(transparent)]
- Body(#[from] hyper::Error),
- #[error(transparent)]
- Json(#[from] serde_json::Error),
- #[error("deflate decompression error")]
- Deflate,
- #[error("body is suspiciously big")]
- SuspiciousBody,
- #[error("decompression is suspiciously big")]
- SuspiciousCompression,
-}
-
-/// Parse json body, perform security check and decompression
-pub async fn parse_body<J: serde::de::DeserializeOwned>(
- parts: &Parts,
- body: Body,
-) -> Result<J, ParseBodyError> {
- // Check announced body size
- if body.size_hint().upper().unwrap_or(u64::MAX) > MAX_PAYLOAD_SIZE {
- return Err(ParseBodyError::SuspiciousBody);
- }
- // Read body
- let bytes = hyper::body::to_bytes(body).await?;
-
- // Decompress if necessary
- if parts
- .headers
- .get(header::CONTENT_ENCODING)
- .map(|it| it == "deflate")
- .unwrap_or(false)
- {
- let decompressed = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(
- &bytes,
- MAX_PAYLOAD_SIZE as usize,
- )
- .map_err(|s| match s.status {
- TINFLStatus::HasMoreOutput => ParseBodyError::SuspiciousCompression,
- _ => ParseBodyError::Deflate,
- })?;
- // Parse json
- Ok(serde_json::from_slice(&decompressed)?)
- } else {
- // Parse json
- Ok(serde_json::from_slice(&bytes)?)
- }
-}
-
-#[derive(Debug, thiserror::Error)]
-pub enum EncodeBodyError {
- #[error(transparent)]
- Json(#[from] serde_json::Error),
-}
-
-pub fn encode_body<J: serde::Serialize>(
- parts: &Parts,
- status: StatusCode,
- json: &J,
-) -> Result<Response<Body>, EncodeBodyError> {
- let json = serde_json::to_vec(json)?;
- if parts
- .headers
- .get(header::ACCEPT_ENCODING)
- .and_then(|it| it.to_str().ok())
- .map(|str| str.contains("deflate"))
- .unwrap_or(false)
- {
- let compressed = miniz_oxide::deflate::compress_to_vec_zlib(&json, 6);
- Ok(Response::builder()
- .status(status)
- .header(header::CONTENT_TYPE, "application/json")
- .header(header::CONTENT_ENCODING, "deflate")
- .body(Body::from(compressed))
- .unwrap())
- } else {
- Ok(Response::builder()
- .status(status)
- .header(header::CONTENT_TYPE, "application/json")
- .body(Body::from(json))
- .unwrap())
- }
-}
diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs
@@ -13,82 +13,292 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+use axum::{
+ extract::{Request, State},
+ http::StatusCode,
+ middleware::{self, Next},
+ response::{IntoResponse, Response},
+};
+use bitcoin::address::NetworkUnchecked;
use clap::Parser;
use common::{
- api_common::{ShortHashCode, Timestamp},
- api_wire::{
- HistoryParams, IncomingBankTransaction, IncomingHistory, OutgoingBankTransaction,
- OutgoingHistory, TransferRequest, TransferResponse, WireConfig,
- },
- config::{AuthMethod, TalerConfig},
+ config::WireGatewayCfg,
currency::Currency,
- error_codes::ErrorCode,
log::{
- fail,
- log::{error, info, log, Level},
+ OrFail,
+ log::{error, info},
},
- postgres::{self, fallible_iterator::FallibleIterator},
- sql::{sql_amount, sql_array, sql_safe_u64, sql_url},
- url::Url,
-};
-use deadpool_postgres::{Pool, Runtime};
-use error::{CatchResult, ServerError};
-use hyper::{
- http::request::Parts,
- service::{make_service_fn, service_fn},
- Body, Method, Response, Server, StatusCode,
+ payto::{BtcAccount, EthAccount},
};
-use json::{encode_body, parse_body};
-use listenfd::ListenFd;
+use sqlx::{PgPool, QueryBuilder, postgres::PgListener};
+use sqlx::{Row, postgres::PgRow};
use std::{
- convert::Infallible,
path::PathBuf,
- str::FromStr,
- sync::atomic::{AtomicBool, AtomicU32, Ordering},
- time::{Duration, Instant},
+ str::FromStr as _,
+ sync::{
+ Arc,
+ atomic::{AtomicBool, Ordering},
+ },
+ time::Duration,
+};
+use taler_api::{
+ api::{
+ TalerApi, TalerRouter,
+ wire::{self, WireGateway},
+ },
+ db::{BindHelper, TypeHelper, page},
+ error::{ApiResult, failure, failure_status},
};
-use tokio::sync::Notify;
-use tokio_postgres::{config::Host, NoTls};
+use taler_common::{
+ api_params::{History, Page},
+ api_wire::{
+ AddIncomingRequest, AddIncomingResponse, AddKycauthRequest, AddKycauthResponse,
+ IncomingBankTransaction, IncomingHistory, OutgoingBankTransaction, OutgoingHistory,
+ TransferList, TransferRequest, TransferResponse, TransferState, TransferStatus,
+ },
+ error_code::ErrorCode,
+ types::{
+ payto::{Payto, PaytoURI},
+ timestamp::Timestamp,
+ },
+};
+use tokio::time::sleep;
-mod error;
-mod json;
+pub enum Address<'a> {
+ BTC(&'a str),
+ ETH([u8; 20]),
+}
struct ServerState {
- pool: Pool,
- db_config: postgres::Config,
- payto: Url,
+ pool: PgPool,
+ payto: PaytoURI,
currency: Currency,
- notify: Notify,
- lifetime: Option<AtomicU32>,
status: AtomicBool,
- auth: AuthMethod,
}
impl ServerState {
- /// Decrease lifetime, triggering graceful shutdown when reach lifetime end
- pub fn step(&self) {
- if let Some(lifetime) = &self.lifetime {
- let mut current = lifetime.load(Ordering::Relaxed);
- loop {
- if current == 0 {
- self.notify.notify_one();
+ pub fn sql_payto(&self, row: &PgRow, idx: usize) -> sqlx::Result<PaytoURI> {
+ Ok(match self.currency {
+ Currency::ETH(_) => {
+ let it: [u8; 20] = row.try_get(idx)?;
+ let addr = ethereum_types::Address::from_slice(&it);
+ Payto::new(EthAccount(addr))
+ .as_payto()
+ .as_full_payto("Ethereum User")
+ }
+ Currency::BTC(_) => {
+ let addr = row
+ .try_get_parse::<_, _, bitcoin::Address<NetworkUnchecked>>(idx)?
+ .assume_checked();
+ Payto::new(BtcAccount(addr))
+ .as_payto()
+ .as_full_payto("Bitcoin User")
+ }
+ })
+ }
+
+ pub fn payto_addr<'a>(&self, payto: &'a PaytoURI) -> Result<Address<'a>, String> {
+ let url = payto.as_ref();
+ Ok(match self.currency {
+ Currency::ETH(_) => {
+ if url.domain() != Some("ethereum") {
+ return Err(format!(
+ "Expected domain 'ethereum' got '{}'",
+ url.domain().unwrap_or_default()
+ ));
}
- match lifetime.compare_exchange_weak(
- current,
- current - 1,
- Ordering::SeqCst,
- Ordering::Relaxed,
- ) {
- Ok(_) => break,
- Err(new) => current = new,
+ let str = url.path().trim_start_matches('/');
+ Address::ETH(
+ ethereum_types::Address::from_str(str)
+ .map_err(|e| e.to_string())?
+ .to_fixed_bytes(),
+ )
+ }
+ Currency::BTC(_) => {
+ if url.domain() != Some("bitcoin") {
+ return Err(format!(
+ "Expected domain 'bitcoin' got '{}'",
+ url.domain().unwrap_or_default()
+ ));
}
+ let str = url.path().trim_start_matches('/');
+ bitcoin::Address::from_str(str).map_err(|e| e.to_string())?;
+ Address::BTC(str)
}
+ })
+ }
+}
+
+impl TalerApi for ServerState {
+ fn currency(&self) -> &str {
+ self.currency.to_str()
+ }
+
+ fn implementation(&self) -> Option<&str> {
+ None
+ }
+}
+
+impl WireGateway for ServerState {
+ async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> {
+ if !check_payto(&req.credit_account, self.currency) {
+ return Err(failure(ErrorCode::GENERIC_PAYTO_URI_MALFORMED, "bad payto"));
}
+
+ // Handle idempotence, check previous transaction with the same request_uid
+ let row = sqlx::query("SELECT (amount).val, (amount).frac, exchange_url, wtid, credit_acc, id, created FROM tx_out WHERE request_uid = $1").bind(req.request_uid.as_slice())
+ .fetch_optional(&self.pool)
+ .await?;
+ if let Some(r) = row {
+ // TODO store names?
+ let prev = TransferRequest {
+ request_uid: req.request_uid.clone(),
+ amount: r.try_get_amount_i(0, self.currency())?,
+ exchange_base_url: r.try_get_url(2)?,
+ wtid: r.try_get_base32(3)?,
+ credit_account: self.sql_payto(&r, 4)?,
+ };
+ if prev == req {
+ // Idempotence
+ return Ok(TransferResponse {
+ row_id: r.try_get_safeu64(5)?,
+ timestamp: r.try_get_timestamp(6)?,
+ });
+ } else {
+ return Err(failure(
+ ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED,
+ format!("Request UID {} already used", req.request_uid),
+ ));
+ }
+ }
+
+ let timestamp = Timestamp::now();
+ let q = sqlx::query(
+ "INSERT INTO tx_out (created, amount, wtid, debit_acc, credit_acc, exchange_url, request_uid) VALUES ($1, ($2, $3)::taler_amount, $4, $5, $6, $7, $8) RETURNING id"
+ )
+ .bind_timestamp(&Timestamp::now())
+ .bind_amount(&req.amount)
+ .bind(req.wtid.as_slice());
+ let q = match self.payto_addr(&self.payto).unwrap() {
+ Address::BTC(a) => q.bind(a),
+ Address::ETH(a) => q.bind(a),
+ };
+ let q = match self.payto_addr(&req.credit_account).unwrap() {
+ Address::BTC(a) => q.bind(a),
+ Address::ETH(a) => q.bind(a),
+ };
+ let r = q
+ .bind(req.exchange_base_url.as_str())
+ .bind(req.request_uid.as_slice())
+ .fetch_one(&self.pool)
+ .await?;
+ sqlx::query("NOTIFY new_tx").execute(&self.pool).await?;
+
+ Ok(TransferResponse {
+ timestamp,
+ row_id: r.try_get_safeu64(0)?,
+ })
+ }
+
+ async fn transfer_page(
+ &self,
+ _page: Page,
+ _status: Option<TransferState>,
+ ) -> ApiResult<TransferList> {
+ unimplemented!("depolymerization does not supports transfer details API")
+ }
+
+ async fn transfer_by_id(&self, _id: u64) -> ApiResult<Option<TransferStatus>> {
+ unimplemented!("depolymerization does not supports transfer details API")
+ }
+
+ async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> {
+ let outgoing_transactions = page(&self.pool, "id", ¶ms.page, || QueryBuilder::new(
+ "SELECT id, created, (amount).val, (amount).frac, wtid, credit_acc, exchange_url FROM tx_out WHERE"
+ ), |r| {
+ Ok(OutgoingBankTransaction {
+ row_id: r.try_get_safeu64(0)?,
+ date: r.try_get_timestamp(1)?,
+ amount: r.try_get_amount_i(2, self.currency())?,
+ wtid: r.try_get_base32(4)?,
+ credit_account: self.sql_payto(&r, 5)?,
+ exchange_base_url: r.try_get_url(6)?,
+ })
+ }).await?;
+ Ok(OutgoingHistory {
+ debit_account: self.payto.clone(),
+ outgoing_transactions,
+ })
+ }
+
+ async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> {
+ let incoming_transactions = page(
+ &self.pool,
+ "id",
+ ¶ms.page,
+ || {
+ QueryBuilder::new(
+ "SELECT id, received, (amount).val, (amount).frac, reserve_pub, debit_acc FROM tx_in WHERE"
+ )
+ },
+ |r| {
+ Ok(IncomingBankTransaction::Reserve {
+ row_id: r.try_get_safeu64(0)?,
+ date: r.try_get_timestamp(1)?,
+ amount: r.try_get_amount_i(2, self.currency())?,
+ reserve_pub: r.try_get_base32(4)?,
+ debit_account: self.sql_payto(&r, 5)?,
+ })
+ },
+ )
+ .await?;
+ Ok(IncomingHistory {
+ credit_account: self.payto.clone(),
+ incoming_transactions,
+ })
}
- pub async fn shutdown_signal(&self) {
- self.notify.notified().await;
- info!("Reach end of lifetime");
+ async fn add_incoming_reserve(
+ &self,
+ req: AddIncomingRequest,
+ ) -> ApiResult<AddIncomingResponse> {
+ let timestamp = Timestamp::now();
+ let r = sqlx::query("INSERT INTO tx_in (received, amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, ($2, $3)::taler_amount, $4, $5, $6) RETURNING id")
+ .bind_timestamp(&Timestamp::now())
+ .bind_amount(&req.amount)
+ .bind(req.reserve_pub.as_slice())
+ .bind(req.debit_account.raw())
+ .bind("payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj")
+ .fetch_one(&self.pool).await?;
+ Ok(AddIncomingResponse {
+ timestamp,
+ row_id: r.try_get_safeu64(0)?,
+ })
+ }
+
+ async fn add_incoming_kyc(&self, _req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> {
+ unimplemented!("depolymerization does not supports KYC")
+ }
+
+ fn support_account_check(&self) -> bool {
+ false
+ }
+}
+
+async fn status_middleware(
+ State(state): State<Arc<ServerState>>,
+ request: Request,
+ next: Next,
+) -> Response {
+ if !state.status.load(Ordering::Relaxed) {
+ failure_status(
+ ErrorCode::GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Currency backing is compromised until the transaction reappear",
+ StatusCode::BAD_GATEWAY,
+ )
+ .into_response()
+ } else {
+ next.run(request).await
}
}
@@ -103,470 +313,88 @@ struct Args {
#[tokio::main]
async fn main() {
common::log::init();
- let args = Args::parse();
- let taler_config = TalerConfig::load(args.config.as_deref());
-
#[cfg(feature = "test")]
common::log::log::warn!("Running with test admin endpoint unsuitable for production");
+ let args = Args::parse();
+
+ let config = WireGatewayCfg::parse(args.config.as_deref()).or_fail(|e| e.to_string());
+
// Parse postgres url
- let db_config = taler_config.db_config();
- // TODO find a way to clean this ugly mess
- let mut cfg = deadpool_postgres::Config::new();
- cfg.user = db_config.get_user().map(|it| it.to_string());
- cfg.password = db_config
- .get_password()
- .map(|it| String::from_utf8(it.to_vec()).unwrap());
- cfg.dbname = db_config.get_dbname().map(|it| it.to_string());
- cfg.options = db_config.get_options().map(|it| it.to_string());
- cfg.host = Some(
- db_config
- .get_hosts()
- .iter()
- .map(|it| match it {
- Host::Tcp(it) => it.to_string(),
- #[cfg(unix)]
- Host::Unix(it) => it.to_str().unwrap().to_string(),
- })
- .collect(),
- );
- cfg.ports = Some(db_config.get_ports().to_vec());
- cfg.application_name = db_config.get_application_name().map(|it| it.to_string());
- cfg.connect_timeout = db_config.get_connect_timeout().cloned();
-
- let pool = cfg.create_pool(Some(Runtime::Tokio1), NoTls).unwrap();
- let payto = taler_config.payto();
- let state = ServerState {
+ let pool = PgPool::connect_with(config.db).await.unwrap();
+
+ let state = Arc::new(ServerState {
pool,
- notify: Notify::new(),
- lifetime: taler_config.http_lifetime().map(AtomicU32::new),
status: AtomicBool::new(true),
- db_config,
- payto,
- currency: taler_config.currency,
- auth: taler_config.auth_method(),
- };
- let state: &'static ServerState = Box::leak(Box::new(state));
- std::thread::spawn(move || status_watcher(state));
- let service = service_fn(move |req| async move {
- state.step();
- let start = Instant::now();
- let (parts, body) = req.into_parts();
- let (response, msg) = match router(&parts, body, state).await {
- Ok(resp) => (resp, String::new()),
- Err(err) => err.response(),
- };
- let status = response.status().as_u16();
- let level = if status >= 500 {
- Level::Error
- } else if status >= 400 {
- Level::Warn
- } else {
- Level::Info
- };
- log!(
- level,
- "{} {} {}ms {} - {}",
- parts.method,
- status,
- start.elapsed().as_millis(),
- parts.uri.path(),
- msg
- );
- Ok::<Response<Body>, Infallible>(response)
+ payto: config.payto,
+ currency: config.currency,
});
- let make_service = make_service_fn(move |_| async move { Ok::<_, Infallible>(service) });
- let make_service_unix = make_service_fn(move |_| async move { Ok::<_, Infallible>(service) });
-
- let mut listenfd = ListenFd::from_env();
-
- if let Some(listener) = listenfd.take_tcp_listener(0).unwrap() {
- info!(
- "Server listening on activated socket {}",
- listener.local_addr().unwrap()
- );
- let server = Server::from_tcp(listener)
- .unwrap()
- .serve(make_service)
- .with_graceful_shutdown(state.shutdown_signal());
- if let Err(e) = server.await {
- error!("server: {}", e);
- }
- } else if let Some(path) = taler_config.unix_path() {
- use hyperlocal::UnixServerExt;
- info!("Server listening on unix domain socket {:?}", path);
- if let Err(err) = std::fs::remove_file(&path) {
- if err.kind() != std::io::ErrorKind::NotFound {
- fail(format_args!("{}", err));
- }
- }
- let server = Server::bind_unix(path)
- .unwrap()
- .serve(make_service_unix)
- .with_graceful_shutdown(state.shutdown_signal());
- if let Err(e) = server.await {
- error!("server: {}", e);
- }
- } else {
- let addr = ([0, 0, 0, 0], taler_config.port()).into();
- info!("Server listening on http://{}", &addr);
- let server = Server::bind(&addr)
- .serve(make_service)
- .with_graceful_shutdown(state.shutdown_signal());
- if let Err(e) = server.await {
- error!("server: {}", e);
- }
- };
+
+ tokio::spawn(status_watcher(state.clone()));
+ wire::router(state.clone())
+ .auth(config.auth)
+ .layer(middleware::from_fn_with_state(
+ state.clone(),
+ status_middleware,
+ ))
+ .serve(config.serve, config.http_lifetime)
+ .await
+ .unwrap();
info!("wire-gateway stopped");
}
-/// Check if an url if a valid payto url for the configured currency
-fn check_payto(url: &Url, currency: Currency) -> bool {
+/// Check if a payto is valid the configured currency
+fn check_payto(payto: &PaytoURI, currency: Currency) -> bool {
match currency {
- Currency::ETH(_) => check_pay_to_eth(url),
- Currency::BTC(_) => check_pay_to_btc(url),
+ Currency::ETH(_) => check_pay_to_eth(payto),
+ Currency::BTC(_) => check_pay_to_btc(payto),
}
}
/// Check if an url is a valid bitcoin payto url
-fn check_pay_to_btc(url: &Url) -> bool {
- return url.domain() == Some("bitcoin")
- && url.scheme() == "payto"
+fn check_pay_to_btc(payto: &PaytoURI) -> bool {
+ let url = payto.as_ref();
+ url.domain() == Some("bitcoin")
&& url.username() == ""
&& url.password().is_none()
&& url.query().is_none()
&& url.fragment().is_none()
- && bitcoin::Address::from_str(url.path().trim_start_matches('/')).is_ok();
+ && bitcoin::Address::from_str(url.path().trim_start_matches('/')).is_ok()
}
/// Check if an url is a valid ethereum payto url
-fn check_pay_to_eth(url: &Url) -> bool {
- return url.domain() == Some("ethereum")
- && url.scheme() == "payto"
+fn check_pay_to_eth(payto: &PaytoURI) -> bool {
+ let url = payto.as_ref();
+ url.domain() == Some("ethereum")
&& url.username() == ""
&& url.password().is_none()
&& url.query().is_none()
&& url.fragment().is_none()
- && ethereum_types::H160::from_str(url.path().trim_start_matches('/')).is_ok();
-}
-
-/// Assert request method match expected
-fn assert_method(parts: &Parts, method: Method) -> Result<(), ServerError> {
- if parts.method == method {
- Ok(())
- } else {
- Err(ServerError::code(
- StatusCode::METHOD_NOT_ALLOWED,
- ErrorCode::GENERIC_METHOD_INVALID,
- ))
- }
-}
-
-/// Parse history params from request
-fn history_params(parts: &Parts) -> Result<HistoryParams, ServerError> {
- let params: HistoryParams = serde_urlencoded::from_str(parts.uri.query().unwrap_or(""))
- .catch_code(
- StatusCode::BAD_REQUEST,
- ErrorCode::GENERIC_PARAMETER_MALFORMED,
- )?;
- if params.delta == 0 {
- return Err(ServerError::code(
- StatusCode::BAD_REQUEST,
- ErrorCode::GENERIC_PARAMETER_MALFORMED,
- ));
- }
- Ok(params)
-}
-
-/// Generate sql query filter from history params
-fn sql_history_filter(params: &HistoryParams) -> String {
- let asc = params.delta > 0;
- let limit = params.delta.abs();
- let order_sql = if asc { "ASC" } else { "DESC" };
- let where_sql = if let Some(start) = params.start {
- format!("WHERE id {} {}", if asc { '>' } else { '<' }, start)
- } else {
- String::new()
- };
- format!("{} ORDER BY id {} LIMIT {}", where_sql, order_sql, limit)
-}
-
-/// Ensure a payto URI has a receiver-name using a dummy name if necessary
-fn full_payto(mut url: Url, currency: Currency) -> Url {
- if url
- .query_pairs()
- .into_iter()
- .all(|(name, _)| name != "receiver-name")
- {
- let prefix = match currency {
- Currency::ETH(_) => "Ethereum",
- Currency::BTC(_) => "Bitcoin",
- };
- let name = format!("{prefix} User");
- url.query_pairs_mut().append_pair("receiver-name", &name);
- }
- url
-}
-
-async fn router(
- parts: &Parts,
- body: Body,
- state: &'static ServerState,
-) -> Result<Response<Body>, ServerError> {
- // Check status error
- if !state.status.load(Ordering::SeqCst) {
- return Ok(Response::builder()
- .status(StatusCode::BAD_GATEWAY)
- .body(Body::empty())
- .unwrap());
- }
-
- // Check auth method
- match &state.auth {
- AuthMethod::Basic(auth) => {
- if !matches!(parts.headers
- .get(hyper::header::AUTHORIZATION)
- .and_then(|h| h.to_str().ok())
- .and_then(|s| s.strip_prefix("Basic ")),
- Some(token) if token == auth )
- {
- return Ok(Response::builder()
- .status(StatusCode::UNAUTHORIZED)
- .body(Body::empty())
- .unwrap());
- }
- }
- AuthMethod::None => {}
- }
-
- let response = match parts.uri.path() {
- "/config" => {
- assert_method(parts, Method::GET)?;
- encode_body(
- parts,
- StatusCode::OK,
- &WireConfig {
- name: "taler-wire-gateway",
- version: "0.0.0",
- currency: state.currency.to_str(),
- },
- )
- .unexpected()?
- }
- "/transfer" => {
- assert_method(parts, Method::POST)?;
- let request: TransferRequest = parse_body(parts, body).await.catch_code(
- StatusCode::BAD_REQUEST,
- ErrorCode::GENERIC_PARAMETER_MALFORMED,
- )?;
- if !check_payto(&request.credit_account, state.currency) {
- return Err(ServerError::code(
- StatusCode::BAD_REQUEST,
- ErrorCode::GENERIC_PAYTO_URI_MALFORMED,
- ));
- }
- if Currency::from_str(&request.amount.currency) != Ok(state.currency) {
- return Err(ServerError::code(
- StatusCode::BAD_REQUEST,
- ErrorCode::GENERIC_PARAMETER_MALFORMED,
- ));
- }
- let mut db = state.pool.get().await.catch_code(
- StatusCode::GATEWAY_TIMEOUT,
- ErrorCode::GENERIC_DB_FETCH_FAILED,
- )?;
- // Handle idempotence, check previous transaction with the same request_uid
- let row = db.query_opt("SELECT amount, exchange_url, wtid, credit_acc, id, _date FROM tx_out WHERE request_uid = $1", &[&request.request_uid.as_slice()])
- .await?;
- if let Some(row) = row {
- let prev = TransferRequest {
- request_uid: request.request_uid.clone(),
- amount: sql_amount(&row, 0),
- exchange_base_url: sql_url(&row, 1),
- wtid: ShortHashCode::from(sql_array(&row, 2)),
- credit_account: sql_url(&row, 3),
- };
- if prev == request {
- // Idempotence
- return encode_body(
- parts,
- StatusCode::OK,
- &TransferResponse {
- timestamp: Timestamp::Time(row.get(5)),
- row_id: sql_safe_u64(&row, 4),
- },
- )
- .unexpected();
- } else {
- return Err(ServerError::status(StatusCode::CONFLICT));
- }
- }
-
- let timestamp = Timestamp::now();
- let tx = db.transaction().await?;
- let row = tx.query_one("INSERT INTO tx_out (amount, wtid, debit_acc, credit_acc, exchange_url, request_uid) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", &[
- &request.amount.to_string(), &request.wtid.as_slice(), &state.payto.as_ref(), &request.credit_account.as_ref(), &request.exchange_base_url.as_ref(), &request.request_uid.as_slice()
- ]).await?;
- tx.execute("NOTIFY new_tx", &[]).await?;
- tx.commit().await?;
- encode_body(
- parts,
- StatusCode::OK,
- &TransferResponse {
- timestamp,
- row_id: sql_safe_u64(&row, 0),
- },
- )
- .unexpected()?
- }
- "/history/incoming" => {
- assert_method(parts, Method::GET)?;
- let params = history_params(parts)?;
- let filter = sql_history_filter(¶ms);
- let db = state.pool.get().await.catch_code(
- StatusCode::GATEWAY_TIMEOUT,
- ErrorCode::GENERIC_DB_FETCH_FAILED,
- )?;
- let transactions: Vec<_> = db
- .query(
- &format!(
- "SELECT id, _date, amount, reserve_pub, debit_acc FROM tx_in {}",
- filter
- ),
- &[],
- )
- .await
- .catch_code(StatusCode::BAD_GATEWAY, ErrorCode::GENERIC_DB_FETCH_FAILED)?
- .into_iter()
- .map(|row| IncomingBankTransaction::IncomingReserveTransaction {
- row_id: sql_safe_u64(&row, 0),
- date: Timestamp::Time(row.get(1)),
- amount: sql_amount(&row, 2),
- reserve_pub: ShortHashCode::from(sql_array(&row, 3)),
- debit_account: full_payto(sql_url(&row, 4), state.currency),
- })
- .collect();
- if transactions.is_empty() {
- Response::builder()
- .status(StatusCode::NO_CONTENT)
- .body(Body::empty())
- .unwrap()
- } else {
- encode_body(
- parts,
- StatusCode::OK,
- &IncomingHistory {
- credit_account: full_payto(state.payto.clone(), state.currency),
- incoming_transactions: transactions,
- },
- )
- .unexpected()?
- }
- }
- "/history/outgoing" => {
- assert_method(parts, Method::GET)?;
- let params = history_params(parts)?;
- let filter = sql_history_filter(¶ms);
-
- let db = state.pool.get().await.catch_code(
- StatusCode::GATEWAY_TIMEOUT,
- ErrorCode::GENERIC_DB_FETCH_FAILED,
- )?;
- let transactions: Vec<_> = db
- .query(
- &format!(
- "SELECT id, _date, amount, wtid, credit_acc, exchange_url FROM tx_out {}",
- filter
- ),
- &[],
- )
- .await
- .catch_code(StatusCode::BAD_GATEWAY, ErrorCode::GENERIC_DB_FETCH_FAILED)?
- .into_iter()
- .map(|row| OutgoingBankTransaction {
- row_id: sql_safe_u64(&row, 0),
- date: Timestamp::Time(row.get(1)),
- amount: sql_amount(&row, 2),
- wtid: ShortHashCode::from(sql_array(&row, 3)),
- credit_account: full_payto(sql_url(&row, 4), state.currency),
- exchange_base_url: sql_url(&row, 5),
- })
- .collect();
- if transactions.is_empty() {
- Response::builder()
- .status(StatusCode::NO_CONTENT)
- .body(Body::empty())
- .unwrap()
- } else {
- encode_body(
- parts,
- StatusCode::OK,
- &OutgoingHistory {
- debit_account: full_payto(state.payto.clone(), state.currency),
- outgoing_transactions: transactions,
- },
- )
- .unexpected()?
- }
- }
- #[cfg(feature = "test")]
- "/admin/add-incoming" => {
- // We do not check input as this is a test admin endpoint
- assert_method(&parts, Method::POST).unwrap();
- let request: common::api_wire::AddIncomingRequest =
- parse_body(&parts, body).await.unwrap();
- let timestamp = Timestamp::now();
- let db = state.pool.get().await.catch_code(
- StatusCode::GATEWAY_TIMEOUT,
- ErrorCode::GENERIC_DB_FETCH_FAILED,
- )?;
- let row = db.query_one("INSERT INTO tx_in (_date, amount, reserve_pub, debit_acc, credit_acc) VALUES (now(), $1, $2, $3, $4) RETURNING id", &[
- &request.amount.to_string(), &request.reserve_pub.as_slice(), &request.debit_account.as_ref(), &"payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj"
- ]).await.catch_code(
- StatusCode::BAD_GATEWAY,
- ErrorCode::GENERIC_DB_FETCH_FAILED,
- )?;
- encode_body(
- parts,
- StatusCode::OK,
- &TransferResponse {
- timestamp,
- row_id: sql_safe_u64(&row, 0),
- },
- )
- .unexpected()?
- }
- _ => {
- return Err(ServerError::code(
- StatusCode::NOT_FOUND,
- ErrorCode::GENERIC_ENDPOINT_UNKNOWN,
- ))
- }
- };
- Ok(response)
+ && ethereum_types::H160::from_str(url.path().trim_start_matches('/')).is_ok()
}
/// Listen to backend status change
-fn status_watcher(state: &'static ServerState) {
- fn inner(state: &'static ServerState) -> Result<(), Box<dyn std::error::Error>> {
- let mut db = state.db_config.connect(NoTls)?;
- // Register as listener
- db.batch_execute("LISTEN status")?;
+async fn status_watcher(state: Arc<ServerState>) {
+ async fn inner(state: &ServerState) -> Result<(), sqlx::error::Error> {
+ let mut listener = PgListener::connect_with(&state.pool).await?;
+ listener.listen("status").await?;
loop {
// Sync state
- let row = db.query_one("SELECT value FROM state WHERE name = 'status'", &[])?;
- let status: &[u8] = row.get(0);
+ let row = sqlx::query("SELECT value FROM state WHERE name = 'status'")
+ .fetch_one(&state.pool)
+ .await?;
+ let status: &[u8] = row.try_get(0)?;
assert!(status.len() == 1 && status[0] < 2);
state.status.store(status[0] == 1, Ordering::SeqCst);
// Wait for next notification
- db.notifications().blocking_iter().next()?;
+ listener.recv().await?;
}
}
loop {
- if let Err(err) = inner(state) {
+ if let Err(err) = inner(&state).await {
error!("status-watcher: {}", err);
- std::thread::sleep(Duration::from_secs(5));
+ sleep(Duration::from_secs(5)).await;
}
}
}