From ef5f1ebf88158f87a9adc489b53db1c2fe8d221f Mon Sep 17 00:00:00 2001 From: Mshehu5 Date: Sat, 19 Jul 2025 22:22:50 +0100 Subject: [PATCH 1/2] Migrate: replace sled with rusqlite for session persistence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit replaces the sled-based embedded database with SQLite, addressing the need for more robust, SQL-compatible persistence across payjoin-cli. This aligns with common practices in other projects (e.g., Liana, BullBitcoin) and makes payjoin-cli a better reference for downstream wallet developers. Key changes: Replace sled with rusqlite (v0.29.0) and r2d2_sqlite (v0.22.0) for pooled connection support Introduce a relational schema with three tables: sessions: tracks session_id and completed_at session_events: logs events, sorted by created_at, FK to sessions inputs_seen: stores seen inputs with timestamps Redesign SessionId as a fixed-length 8-byte identifier using a deterministic hash from system time and thread ID Improve error handling by simplifying enum variants and supporting JSON deserialization errors via From Update config defaults and documentation: payjoin.sled → payjoin.sqlite Remove obsolete sled-based files and dependencies This migration preserves interface compatibility where possible and makes only the necessary changes to ensure smooth integration. --- .gitignore | 1 + Cargo-minimal.lock | 299 ++++++++++++++++++++------------ Cargo-recent.lock | 299 ++++++++++++++++++++------------ payjoin-cli/Cargo.toml | 4 +- payjoin-cli/README.md | 2 +- payjoin-cli/example.config.toml | 2 +- payjoin-cli/src/db/error.rs | 46 +++-- payjoin-cli/src/db/mod.rs | 88 +++++++++- payjoin-cli/src/db/v2.rs | 247 ++++++++++++++------------ 9 files changed, 643 insertions(+), 345 deletions(-) diff --git a/.gitignore b/.gitignore index 6b8879a38..5512a7462 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Cargo.lock .vscode mutants.out* *.ikm +*payjoin.sqlite diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index e81bddd47..526afd897 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -30,7 +30,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -91,6 +91,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.8.26", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -100,6 +112,12 @@ dependencies = [ "memchr", ] +[[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" @@ -251,7 +269,7 @@ dependencies = [ "generic-array", "hkdf 0.12.4", "hmac 0.12.1", - "rand_core", + "rand_core 0.6.4", "secp256k1", "sha2 0.10.8", "subtle", @@ -288,7 +306,7 @@ dependencies = [ "hkdf 0.11.0", "lazy_static", "log", - "rand", + "rand 0.8.5", "serde", "serde_derive", "sha2 0.9.9", @@ -727,15 +745,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -749,7 +758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -933,6 +942,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.1.1" @@ -976,16 +997,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "futures" version = "0.3.30" @@ -1075,15 +1086,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1139,7 +1141,17 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", ] [[package]] @@ -1148,6 +1160,15 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heck" version = "0.4.1" @@ -1480,22 +1501,13 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" version = "2.10.0" @@ -1583,6 +1595,17 @@ dependencies = [ "redox_syscall 0.5.3", ] +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1833,17 +1856,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -1851,28 +1863,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[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", @@ -1913,7 +1911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1966,12 +1964,14 @@ dependencies = [ "nix", "payjoin", "payjoin-test-utils", + "r2d2", + "r2d2_sqlite", "rcgen", "reqwest", + "rusqlite", "rustls 0.22.4", "serde", "serde_json", - "sled", "tempfile", "tokio", "tokio-rustls 0.25.0", @@ -2189,7 +2189,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2261,6 +2261,28 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + +[[package]] +name = "r2d2_sqlite" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f31323d6161385f385046738df520e0e8694fa74852d35891fc0be08348ddc" +dependencies = [ + "r2d2", + "rusqlite", + "uuid", +] + [[package]] name = "rand" version = "0.8.5" @@ -2268,8 +2290,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -2279,7 +2311,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2291,6 +2333,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rcgen" version = "0.11.3" @@ -2324,15 +2375,6 @@ dependencies = [ "url", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -2509,6 +2551,20 @@ dependencies = [ "serde", ] +[[package]] +name = "rusqlite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +dependencies = [ + "bitflags 2.6.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rust-ini" version = "0.18.0" @@ -2682,6 +2738,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "schemars" version = "0.9.0" @@ -2729,7 +2794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -2934,22 +2999,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -3235,7 +3284,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.3", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.5.7", @@ -3441,7 +3490,7 @@ dependencies = [ "http", "httparse", "log", - "rand", + "rand 0.8.5", "sha1", "thiserror 1.0.63", "url", @@ -3531,12 +3580,28 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.3", + "rand 0.9.1", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -3963,7 +4028,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive 0.8.26", ] [[package]] @@ -3977,6 +4051,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index e81bddd47..526afd897 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -30,7 +30,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -91,6 +91,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.8.26", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -100,6 +112,12 @@ dependencies = [ "memchr", ] +[[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" @@ -251,7 +269,7 @@ dependencies = [ "generic-array", "hkdf 0.12.4", "hmac 0.12.1", - "rand_core", + "rand_core 0.6.4", "secp256k1", "sha2 0.10.8", "subtle", @@ -288,7 +306,7 @@ dependencies = [ "hkdf 0.11.0", "lazy_static", "log", - "rand", + "rand 0.8.5", "serde", "serde_derive", "sha2 0.9.9", @@ -727,15 +745,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -749,7 +758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -933,6 +942,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.1.1" @@ -976,16 +997,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "futures" version = "0.3.30" @@ -1075,15 +1086,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1139,7 +1141,17 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", ] [[package]] @@ -1148,6 +1160,15 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heck" version = "0.4.1" @@ -1480,22 +1501,13 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" version = "2.10.0" @@ -1583,6 +1595,17 @@ dependencies = [ "redox_syscall 0.5.3", ] +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1833,17 +1856,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -1851,28 +1863,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[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", @@ -1913,7 +1911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1966,12 +1964,14 @@ dependencies = [ "nix", "payjoin", "payjoin-test-utils", + "r2d2", + "r2d2_sqlite", "rcgen", "reqwest", + "rusqlite", "rustls 0.22.4", "serde", "serde_json", - "sled", "tempfile", "tokio", "tokio-rustls 0.25.0", @@ -2189,7 +2189,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2261,6 +2261,28 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + +[[package]] +name = "r2d2_sqlite" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f31323d6161385f385046738df520e0e8694fa74852d35891fc0be08348ddc" +dependencies = [ + "r2d2", + "rusqlite", + "uuid", +] + [[package]] name = "rand" version = "0.8.5" @@ -2268,8 +2290,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -2279,7 +2311,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2291,6 +2333,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rcgen" version = "0.11.3" @@ -2324,15 +2375,6 @@ dependencies = [ "url", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -2509,6 +2551,20 @@ dependencies = [ "serde", ] +[[package]] +name = "rusqlite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +dependencies = [ + "bitflags 2.6.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rust-ini" version = "0.18.0" @@ -2682,6 +2738,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "schemars" version = "0.9.0" @@ -2729,7 +2794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -2934,22 +2999,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -3235,7 +3284,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.3", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.5.7", @@ -3441,7 +3490,7 @@ dependencies = [ "http", "httparse", "log", - "rand", + "rand 0.8.5", "sha1", "thiserror 1.0.63", "url", @@ -3531,12 +3580,28 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.3", + "rand 0.9.1", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -3963,7 +4028,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive 0.8.26", ] [[package]] @@ -3977,6 +4051,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/payjoin-cli/Cargo.toml b/payjoin-cli/Cargo.toml index 650f68501..f56dca545 100644 --- a/payjoin-cli/Cargo.toml +++ b/payjoin-cli/Cargo.toml @@ -39,12 +39,14 @@ hyper-rustls = { version = "0.26", optional = true } hyper-util = { version = "0.1", optional = true } log = "0.4.7" payjoin = { version = "0.24.0", default-features = false } +r2d2 = "0.8.10" +r2d2_sqlite = "0.22.0" rcgen = { version = "0.11.1", optional = true } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } serde_json = "1.0" +rusqlite = { version = "0.29.0", features = ["bundled"] } rustls = { version = "0.22.4", optional = true } serde = { version = "1.0.160", features = ["derive"] } -sled = "0.34" tokio = { version = "1.38.1", features = ["full"] } tokio-rustls = { version = "0.25", features = ["ring"], default-features = false, optional = true } url = { version = "2.3.1", features = ["serde"] } diff --git a/payjoin-cli/README.md b/payjoin-cli/README.md index ab194f591..566a12043 100644 --- a/payjoin-cli/README.md +++ b/payjoin-cli/README.md @@ -138,7 +138,7 @@ for inspiration. ### Asynchronous Operation -Sender and receiver state is saved to a database in the directory from which `payjoin-cli` is run, called `payjoin.sled`. Once a send or receive session is started, it may resume using the `resume` argument if prior payjoin sessions have not yet complete. +Sender and receiver state is saved to a database in the directory from which `payjoin-cli` is run, called `payjoin.sqlite`. Once a send or receive session is started, it may resume using the `resume` argument if prior payjoin sessions have not yet complete. ## Usage diff --git a/payjoin-cli/example.config.toml b/payjoin-cli/example.config.toml index ffd363172..ad1864ced 100644 --- a/payjoin-cli/example.config.toml +++ b/payjoin-cli/example.config.toml @@ -6,7 +6,7 @@ # -------------- # The path to the database file -db_path = "payjoin.sled" +db_path = "payjoin.sqlite" # The maximum fee rate that the receiver is willing to pay (in sat/vB) max_fee_rate = 2.0 diff --git a/payjoin-cli/src/db/error.rs b/payjoin-cli/src/db/error.rs index 00807fc8e..4c442a1d0 100644 --- a/payjoin-cli/src/db/error.rs +++ b/payjoin-cli/src/db/error.rs @@ -1,43 +1,65 @@ use std::fmt; use payjoin::ImplementationError; -use sled::Error as SledError; +use r2d2::Error as R2d2Error; +use rusqlite::Error as RusqliteError; pub(crate) type Result = std::result::Result; #[derive(Debug)] pub(crate) enum Error { - Sled(SledError), + Rusqlite(RusqliteError), + R2d2(R2d2Error), #[cfg(feature = "v2")] Serialize(serde_json::Error), #[cfg(feature = "v2")] Deserialize(serde_json::Error), - #[cfg(feature = "v2")] - NotFound(String), - #[cfg(feature = "v2")] - TryFromSlice(std::array::TryFromSliceError), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::Sled(e) => write!(f, "Database operation failed: {e}"), + Error::Rusqlite(e) => write!(f, "Database operation failed: {e}"), + Error::R2d2(e) => write!(f, "Connection pool error: {e}"), #[cfg(feature = "v2")] Error::Serialize(e) => write!(f, "Serialization failed: {e}"), #[cfg(feature = "v2")] Error::Deserialize(e) => write!(f, "Deserialization failed: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Rusqlite(e) => Some(e), + Error::R2d2(e) => Some(e), #[cfg(feature = "v2")] - Error::NotFound(key) => write!(f, "Key not found: {key}"), + Error::Serialize(e) => Some(e), #[cfg(feature = "v2")] - Error::TryFromSlice(e) => write!(f, "TryFromSlice failed: {e}"), + Error::Deserialize(e) => Some(e), } } } -impl std::error::Error for Error {} +impl From for Error { + fn from(error: RusqliteError) -> Self { Error::Rusqlite(error) } +} + +impl From for Error { + fn from(error: R2d2Error) -> Self { Error::R2d2(error) } +} -impl From for Error { - fn from(error: SledError) -> Self { Error::Sled(error) } +#[cfg(feature = "v2")] +impl From for Error { + fn from(error: serde_json::Error) -> Self { + match error.classify() { + serde_json::error::Category::Io => Error::Serialize(error), // I/O errors during writing/serialization + serde_json::error::Category::Syntax + | serde_json::error::Category::Data + | serde_json::error::Category::Eof => Error::Deserialize(error), // All parsing/reading errors + } + } } impl From for ImplementationError { diff --git a/payjoin-cli/src/db/mod.rs b/payjoin-cli/src/db/mod.rs index c65f67e44..2931fab5a 100644 --- a/payjoin-cli/src/db/mod.rs +++ b/payjoin-cli/src/db/mod.rs @@ -2,26 +2,100 @@ use std::path::Path; use payjoin::bitcoin::consensus::encode::serialize; use payjoin::bitcoin::OutPoint; -use sled::IVec; +use r2d2::Pool; +use r2d2_sqlite::SqliteConnectionManager; +use rusqlite::{params, Connection}; pub(crate) mod error; use error::*; -pub(crate) const DB_PATH: &str = "payjoin.sled"; +#[inline] +pub(crate) fn now() -> i64 { + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64 +} + +pub(crate) const DB_PATH: &str = "payjoin.sqlite"; -pub(crate) struct Database(sled::Db); +pub(crate) struct Database(Pool); impl Database { pub(crate) fn create(path: impl AsRef) -> Result { - let db = sled::open(path)?; - Ok(Self(db)) + let manager = SqliteConnectionManager::file(path.as_ref()); + let pool = Pool::new(manager)?; + + // Initialize database schema + let conn = pool.get()?; + Self::init_schema(&conn)?; + + Ok(Self(pool)) } + fn init_schema(conn: &Connection) -> Result<()> { + // Enable foreign keys + conn.execute("PRAGMA foreign_keys = ON", [])?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS send_sessions ( + session_id INTEGER PRIMARY KEY AUTOINCREMENT, + completed_at INTEGER + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS receive_sessions ( + session_id INTEGER PRIMARY KEY AUTOINCREMENT, + completed_at INTEGER + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS send_session_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id INTEGER NOT NULL, + event_data TEXT NOT NULL, + created_at INTEGER NOT NULL, + FOREIGN KEY(session_id) REFERENCES send_sessions(session_id) + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS receive_session_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id INTEGER NOT NULL, + event_data TEXT NOT NULL, + created_at INTEGER NOT NULL, + FOREIGN KEY(session_id) REFERENCES receive_sessions(session_id) + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS inputs_seen ( + outpoint BLOB PRIMARY KEY, + created_at INTEGER NOT NULL + )", + [], + )?; + + Ok(()) + } + + pub(crate) fn get_connection(&self) -> Result> { + Ok(self.0.get()?) + } /// Inserts the input and returns true if the input was seen before, false otherwise. pub(crate) fn insert_input_seen_before(&self, input: OutPoint) -> Result { + let conn = self.get_connection()?; let key = serialize(&input); - let was_seen_before = self.0.insert(key.as_slice(), IVec::from(vec![]))?.is_some(); - self.0.flush()?; + + let was_seen_before = conn.execute( + "INSERT OR IGNORE INTO inputs_seen (outpoint, created_at) VALUES (?1, ?2)", + params![key, now()], + )? == 0; + Ok(was_seen_before) } } diff --git a/payjoin-cli/src/db/v2.rs b/payjoin-cli/src/db/v2.rs index c0f3a2e80..c07c2ded4 100644 --- a/payjoin-cli/src/db/v2.rs +++ b/payjoin-cli/src/db/v2.rs @@ -1,29 +1,25 @@ use std::sync::Arc; -use std::time::SystemTime; -use payjoin::bitcoin::hex::DisplayHex; use payjoin::persist::SessionPersister; use payjoin::receive::v2::SessionEvent as ReceiverSessionEvent; use payjoin::send::v2::SessionEvent as SenderSessionEvent; -use serde::{Deserialize, Serialize}; +use rusqlite::params; use super::*; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct SessionWrapper { - pub(crate) completed_at: Option, - pub(crate) events: Vec, -} - #[derive(Debug, Clone)] -pub struct SessionId([u8; 8]); - -impl SessionId { - pub fn new(id: u64) -> Self { Self(id.to_be_bytes()) } +pub enum SessionId { + Send(i64), + Receive(i64), } -impl AsRef<[u8]> for SessionId { - fn as_ref(&self) -> &[u8] { self.0.as_ref() } +impl SessionId { + pub fn as_integer(&self) -> i64 { + match self { + SessionId::Send(id) => *id, + SessionId::Receive(id) => *id, + } + } } #[derive(Clone)] @@ -31,41 +27,46 @@ pub(crate) struct SenderPersister { db: Arc, session_id: SessionId, } + impl SenderPersister { pub fn new(db: Arc) -> crate::db::Result { - let id = SessionId::new(db.0.generate_id().unwrap()); - let send_tree = db.0.open_tree("send_sessions")?; - let empty_session: SessionWrapper = - SessionWrapper { completed_at: None, events: vec![] }; - let value = serde_json::to_vec(&empty_session).map_err(Error::Serialize)?; - send_tree.insert(id.as_ref(), value.as_slice())?; - send_tree.flush()?; - - Ok(Self { db, session_id: id }) + let conn = db.get_connection()?; + + // Create a new session in send_sessions and get its ID + let session_id: i64 = conn.query_row( + "INSERT INTO send_sessions (session_id) VALUES (NULL) RETURNING session_id", + [], + |row| row.get(0), + )?; + + Ok(Self { db, session_id: SessionId::Send(session_id) }) } pub fn from_id(db: Arc, id: SessionId) -> crate::db::Result { - Ok(Self { db, session_id: id }) + match id { + SessionId::Send(_) => Ok(Self { db, session_id: id }), + SessionId::Receive(_) => + panic!("Attempted to create SenderPersister with Receive session ID"), + } } } impl SessionPersister for SenderPersister { type SessionEvent = SenderSessionEvent; type InternalStorageError = crate::db::error::Error; + fn save_event( &self, event: SenderSessionEvent, ) -> std::result::Result<(), Self::InternalStorageError> { - let send_tree = self.db.0.open_tree("send_sessions")?; - let key = self.session_id.as_ref(); - let session = send_tree.get(key)?.expect("key should exist"); - let mut session_wrapper: SessionWrapper = - serde_json::from_slice(&session).map_err(Error::Deserialize)?; - session_wrapper.events.push(event); - let value = serde_json::to_vec(&session_wrapper).map_err(Error::Serialize)?; - send_tree.insert(key, value.as_slice())?; - - send_tree.flush()?; + let conn = self.db.get_connection()?; + let event_data = serde_json::to_string(&event).map_err(Error::Serialize)?; + + conn.execute( + "INSERT INTO send_session_events (session_id, event_data, created_at) VALUES (?1, ?2, ?3)", + params![self.session_id.as_integer(), event_data, now()], + )?; + Ok(()) } @@ -73,25 +74,35 @@ impl SessionPersister for SenderPersister { &self, ) -> std::result::Result>, Self::InternalStorageError> { - let send_tree = self.db.0.open_tree("send_sessions")?; - let session_wrapper = send_tree.get(self.session_id.as_ref())?; - let value = session_wrapper.expect("key should exist"); - let wrapper: SessionWrapper = - serde_json::from_slice(&value).map_err(Error::Deserialize)?; - Ok(Box::new(wrapper.events.into_iter())) + let conn = self.db.get_connection()?; + let mut stmt = conn.prepare( + "SELECT event_data FROM send_session_events WHERE session_id = ?1 ORDER BY created_at ASC", + )?; + + let event_rows = stmt.query_map(params![self.session_id.as_integer()], |row| { + let event_data: String = row.get(0)?; + Ok(event_data) + })?; + + let events: Vec = event_rows + .map(|row| { + let event_data = row.expect("Failed to read event data from database"); + serde_json::from_str::(&event_data) + .expect("Database corruption: failed to deserialize session event") + }) + .collect(); + + Ok(Box::new(events.into_iter())) } fn close(&self) -> std::result::Result<(), Self::InternalStorageError> { - let send_tree = self.db.0.open_tree("send_sessions")?; - let key = self.session_id.as_ref(); - if let Some(existing) = send_tree.get(key)? { - let mut wrapper: SessionWrapper = - serde_json::from_slice(&existing).map_err(Error::Deserialize)?; - wrapper.completed_at = Some(SystemTime::now()); - let value = serde_json::to_vec(&wrapper).map_err(Error::Serialize)?; - send_tree.insert(key, value.as_slice())?; - } - send_tree.flush()?; + let conn = self.db.get_connection()?; + + conn.execute( + "UPDATE send_sessions SET completed_at = ?1 WHERE session_id = ?2", + params![now(), self.session_id.as_integer()], + )?; + Ok(()) } } @@ -101,21 +112,27 @@ pub(crate) struct ReceiverPersister { db: Arc, session_id: SessionId, } + impl ReceiverPersister { pub fn new(db: Arc) -> crate::db::Result { - let id = SessionId::new(db.0.generate_id()?); - let recv_tree = db.0.open_tree("recv_sessions")?; - let empty_session: SessionWrapper = - SessionWrapper { completed_at: None, events: vec![] }; - let value = serde_json::to_vec(&empty_session).map_err(Error::Serialize)?; - recv_tree.insert(id.as_ref(), value.as_slice())?; - recv_tree.flush()?; - - Ok(Self { db, session_id: id }) + let conn = db.get_connection()?; + + // Create a new session in receive_sessions and get its ID + let session_id: i64 = conn.query_row( + "INSERT INTO receive_sessions (session_id) VALUES (NULL) RETURNING session_id", + [], + |row| row.get(0), + )?; + + Ok(Self { db, session_id: SessionId::Receive(session_id) }) } pub fn from_id(db: Arc, id: SessionId) -> crate::db::Result { - Ok(Self { db, session_id: id }) + match id { + SessionId::Receive(_) => Ok(Self { db, session_id: id }), + SessionId::Send(_) => + panic!("Attempted to create ReceiverPersister with Send session ID"), + } } } @@ -127,16 +144,14 @@ impl SessionPersister for ReceiverPersister { &self, event: ReceiverSessionEvent, ) -> std::result::Result<(), Self::InternalStorageError> { - let recv_tree = self.db.0.open_tree("recv_sessions")?; - let key = self.session_id.as_ref(); - let session = - recv_tree.get(key)?.ok_or(Error::NotFound(key.to_vec().to_lower_hex_string()))?; - let mut session_wrapper: SessionWrapper = - serde_json::from_slice(&session).map_err(Error::Deserialize)?; - session_wrapper.events.push(event); - let value = serde_json::to_vec(&session_wrapper).map_err(Error::Serialize)?; - recv_tree.insert(key, value.as_slice())?; - recv_tree.flush()?; + let conn = self.db.get_connection()?; + let event_data = serde_json::to_string(&event).map_err(Error::Serialize)?; + + conn.execute( + "INSERT INTO receive_session_events (session_id, event_data, created_at) VALUES (?1, ?2, ?3)", + params![self.session_id.as_integer(), event_data, now()], + )?; + Ok(()) } @@ -146,61 +161,75 @@ impl SessionPersister for ReceiverPersister { Box>, Self::InternalStorageError, > { - let recv_tree = self.db.0.open_tree("recv_sessions")?; - let session_wrapper = recv_tree.get(self.session_id.as_ref())?; - let value = session_wrapper.expect("key should exist"); - let wrapper: SessionWrapper = - serde_json::from_slice(&value).map_err(Error::Deserialize)?; - Ok(Box::new(wrapper.events.into_iter())) + let conn = self.db.get_connection()?; + let mut stmt = conn.prepare( + "SELECT event_data FROM receive_session_events WHERE session_id = ?1 ORDER BY created_at ASC", + )?; + + let event_rows = stmt.query_map(params![self.session_id.as_integer()], |row| { + let event_data: String = row.get(0)?; + Ok(event_data) + })?; + + let events: Vec = event_rows + .map(|row| { + let event_data = row.expect("Failed to read event data from database"); + serde_json::from_str::(&event_data) + .expect("Database corruption: failed to deserialize session event") + }) + .collect(); + + Ok(Box::new(events.into_iter())) } fn close(&self) -> std::result::Result<(), Self::InternalStorageError> { - let recv_tree = self.db.0.open_tree("recv_sessions")?; - let key = self.session_id.as_ref(); - if let Some(existing) = recv_tree.get(key)? { - let mut wrapper: SessionWrapper = - serde_json::from_slice(&existing).map_err(Error::Deserialize)?; - wrapper.completed_at = Some(SystemTime::now()); - let value = serde_json::to_vec(&wrapper).map_err(Error::Serialize)?; - recv_tree.insert(key, value.as_slice())?; - } - recv_tree.flush()?; + let conn = self.db.get_connection()?; + + conn.execute( + "UPDATE receive_sessions SET completed_at = ?1 WHERE session_id = ?2", + params![now(), self.session_id.as_integer()], + )?; + Ok(()) } } impl Database { pub(crate) fn get_recv_session_ids(&self) -> Result> { - let recv_tree = self.0.open_tree("recv_sessions")?; + let conn = self.get_connection()?; + let mut stmt = + conn.prepare("SELECT session_id FROM receive_sessions WHERE completed_at IS NULL")?; + + let session_rows = stmt.query_map([], |row| { + let session_id: i64 = row.get(0)?; + Ok(SessionId::Receive(session_id)) + })?; + let mut session_ids = Vec::new(); - for item in recv_tree.iter() { - let (key, value) = item?; - let wrapper: SessionWrapper = - serde_json::from_slice(&value).map_err(Error::Deserialize)?; - if wrapper.completed_at.is_some() { - continue; - } - session_ids.push(SessionId::new(u64::from_be_bytes( - key.as_ref().try_into().map_err(Error::TryFromSlice)?, - ))); + for session_row in session_rows { + let session_id = session_row?; + session_ids.push(session_id); } + Ok(session_ids) } pub(crate) fn get_send_session_ids(&self) -> Result> { - let send_tree = self.0.open_tree("send_sessions")?; + let conn = self.get_connection()?; + let mut stmt = + conn.prepare("SELECT session_id FROM send_sessions WHERE completed_at IS NULL")?; + + let session_rows = stmt.query_map([], |row| { + let session_id: i64 = row.get(0)?; + Ok(SessionId::Send(session_id)) + })?; + let mut session_ids = Vec::new(); - for item in send_tree.iter() { - let (key, value) = item?; - let wrapper: SessionWrapper = - serde_json::from_slice(&value).map_err(Error::Deserialize)?; - if wrapper.completed_at.is_some() { - continue; - } - session_ids.push(SessionId::new(u64::from_be_bytes( - key.as_ref().try_into().map_err(Error::TryFromSlice)?, - ))); + for session_row in session_rows { + let session_id = session_row?; + session_ids.push(session_id); } + Ok(session_ids) } } From 114eb89f3820781c6219c3a8b2d7a9f197841207 Mon Sep 17 00:00:00 2001 From: Armin Sabouri Date: Fri, 22 Aug 2025 10:41:29 -0400 Subject: [PATCH 2/2] Ignore sqlite db files And remove sled dirs as they have now been replaced. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5512a7462..6aee2dd3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ target *config.toml !example.config.toml -*payjoin.sled +*.sqlite Cargo.lock .vscode mutants.out*