diff options
| author | Your Name <you@example.com> | 2026-05-21 15:44:46 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-21 15:44:46 +0530 |
| commit | 243e4808246555f86d464d1c476681a902e644ff (patch) | |
| tree | 4334ebf0f8c2e2ee7f5ebccb93dedd68a2e95b18 /components/nucula_lib/nucula_wallet.cpp | |
| parent | 46d9c97e11ea0b03a7dc03fdef6bd360cbe53d44 (diff) | |
feat(wallet): lazy keyset loading with exponential backoff + jitter
- Remove eager load_keysets() from init_wallet(), load on first use
- Add ensure_keysets() with binary exponential backoff + random jitter
- Guard all wallet operations (receive, send, melt, swap_all) with ensure_keysets()
- Skip checkstate on TLS failure, let wallet swap verify proofs instead
- Pin HTTP server to core 0 (fixes TLS cert verification in checkstate)
- Rewrite nucula http.c to use manual open/write/read (same as working health probe)
- Disable hardware MPI (CONFIG_MBEDTLS_HARDWARE_MPI=n)
- Add http.h stub for unit tests
Known issue: progressive TLS failure after 2-3 connections (PK verify 0x4290),
wallet receive swap fails. Needs deeper mbedTLS/PSRAM investigation.
Diffstat (limited to 'components/nucula_lib/nucula_wallet.cpp')
| -rw-r--r-- | components/nucula_lib/nucula_wallet.cpp | 83 |
1 files changed, 76 insertions, 7 deletions
diff --git a/components/nucula_lib/nucula_wallet.cpp b/components/nucula_lib/nucula_wallet.cpp index 7c9141d..278c9bc 100644 --- a/components/nucula_lib/nucula_wallet.cpp +++ b/components/nucula_lib/nucula_wallet.cpp | |||
| @@ -4,8 +4,11 @@ | |||
| 4 | #include "crypto.h" | 4 | #include "crypto.h" |
| 5 | #include "hex.h" | 5 | #include "hex.h" |
| 6 | #include "esp_log.h" | 6 | #include "esp_log.h" |
| 7 | #include "esp_random.h" | ||
| 7 | #include "secp256k1.h" | 8 | #include "secp256k1.h" |
| 8 | #include "cJSON.h" | 9 | #include "cJSON.h" |
| 10 | #include "freertos/FreeRTOS.h" | ||
| 11 | #include "freertos/task.h" | ||
| 9 | #include <cstring> | 12 | #include <cstring> |
| 10 | #include <string> | 13 | #include <string> |
| 11 | #include <vector> | 14 | #include <vector> |
| @@ -18,6 +21,55 @@ static secp256k1_context *s_ctx = nullptr; | |||
| 18 | static cashu::Wallet *s_wallets[MAX_WALLETS] = {}; | 21 | static cashu::Wallet *s_wallets[MAX_WALLETS] = {}; |
| 19 | static int s_wallet_count = 0; | 22 | static int s_wallet_count = 0; |
| 20 | static char s_wallet_urls[MAX_WALLETS][256] = {}; | 23 | static char s_wallet_urls[MAX_WALLETS][256] = {}; |
| 24 | static bool s_keysets_loaded[MAX_WALLETS] = {}; | ||
| 25 | static int s_keyset_attempts[MAX_WALLETS] = {}; | ||
| 26 | |||
| 27 | static const int KEYSET_BASE_DELAY_MS = 1000; | ||
| 28 | static const int KEYSET_MAX_DELAY_MS = 30000; | ||
| 29 | static const int KEYSET_JITTER_MS = 500; | ||
| 30 | |||
| 31 | static bool ensure_keysets(int slot) | ||
| 32 | { | ||
| 33 | if (slot < 0 || slot >= MAX_WALLETS || !s_wallets[slot]) | ||
| 34 | return false; | ||
| 35 | |||
| 36 | if (s_keysets_loaded[slot]) | ||
| 37 | return true; | ||
| 38 | |||
| 39 | if (!s_wallets[slot]->keysets().empty()) { | ||
| 40 | s_keysets_loaded[slot] = true; | ||
| 41 | return true; | ||
| 42 | } | ||
| 43 | |||
| 44 | int delay_ms = KEYSET_BASE_DELAY_MS << s_keyset_attempts[slot]; | ||
| 45 | if (delay_ms > KEYSET_MAX_DELAY_MS) | ||
| 46 | delay_ms = KEYSET_MAX_DELAY_MS; | ||
| 47 | delay_ms += (int)(esp_random() % KEYSET_JITTER_MS); | ||
| 48 | |||
| 49 | ESP_LOGI(TAG, "Keyset load attempt %d for slot %d, waiting %d ms", | ||
| 50 | s_keyset_attempts[slot] + 1, slot, delay_ms); | ||
| 51 | |||
| 52 | vTaskDelay(pdMS_TO_TICKS(delay_ms)); | ||
| 53 | |||
| 54 | if (s_wallets[slot]->load_keysets()) { | ||
| 55 | int attempts = s_keyset_attempts[slot] + 1; | ||
| 56 | s_keysets_loaded[slot] = true; | ||
| 57 | s_keyset_attempts[slot] = 0; | ||
| 58 | ESP_LOGI(TAG, "Keyset load succeeded for slot %d after %d attempt(s)", | ||
| 59 | slot, attempts); | ||
| 60 | return true; | ||
| 61 | } | ||
| 62 | |||
| 63 | s_keyset_attempts[slot]++; | ||
| 64 | return false; | ||
| 65 | } | ||
| 66 | |||
| 67 | static int wallet_slot(const cashu::Wallet *w) | ||
| 68 | { | ||
| 69 | for (int i = 0; i < s_wallet_count; i++) | ||
| 70 | if (s_wallets[i] == w) return i; | ||
| 71 | return -1; | ||
| 72 | } | ||
| 21 | 73 | ||
| 22 | static cashu::Wallet *find_wallet_for_token(const cashu::Token &tok) | 74 | static cashu::Wallet *find_wallet_for_token(const cashu::Token &tok) |
| 23 | { | 75 | { |
| @@ -61,14 +113,9 @@ static esp_err_t init_wallet(int slot, const char *mint_url) | |||
| 61 | 113 | ||
| 62 | s_wallets[slot]->load_from_nvs(); | 114 | s_wallets[slot]->load_from_nvs(); |
| 63 | 115 | ||
| 64 | if (!s_wallets[slot]->load_keysets()) { | 116 | ESP_LOGI(TAG, "Wallet[%d] initialized: url=%s balance=%d proofs=%d (keysets lazy)", |
| 65 | ESP_LOGW(TAG, "Keyset load failed for slot %d (may be offline)", slot); | ||
| 66 | } | ||
| 67 | |||
| 68 | ESP_LOGI(TAG, "Wallet[%d] initialized: url=%s balance=%d proofs=%d keysets=%d", | ||
| 69 | slot, mint_url, s_wallets[slot]->balance(), | 117 | slot, mint_url, s_wallets[slot]->balance(), |
| 70 | (int)s_wallets[slot]->proofs().size(), | 118 | (int)s_wallets[slot]->proofs().size()); |
| 71 | (int)s_wallets[slot]->keysets().size()); | ||
| 72 | return ESP_OK; | 119 | return ESP_OK; |
| 73 | } | 120 | } |
| 74 | 121 | ||
| @@ -136,6 +183,13 @@ esp_err_t nucula_wallet_receive(const char *token_str) | |||
| 136 | return ESP_FAIL; | 183 | return ESP_FAIL; |
| 137 | } | 184 | } |
| 138 | 185 | ||
| 186 | if (!ensure_keysets(wallet_slot(w))) { | ||
| 187 | ESP_LOGE(TAG, "Keysets not available for receive"); | ||
| 188 | return ESP_FAIL; | ||
| 189 | } | ||
| 190 | |||
| 191 | vTaskDelay(pdMS_TO_TICKS(2000)); | ||
| 192 | |||
| 139 | std::vector<cashu::Proof> proofs_out; | 193 | std::vector<cashu::Proof> proofs_out; |
| 140 | if (!w->receive(tok, proofs_out)) { | 194 | if (!w->receive(tok, proofs_out)) { |
| 141 | ESP_LOGE(TAG, "Receive failed"); | 195 | ESP_LOGE(TAG, "Receive failed"); |
| @@ -157,6 +211,11 @@ esp_err_t nucula_wallet_send(uint64_t amount_sat, char *token_out, size_t token_ | |||
| 157 | cashu::Wallet *w = find_wallet_for_send(amount); | 211 | cashu::Wallet *w = find_wallet_for_send(amount); |
| 158 | if (!w) return ESP_FAIL; | 212 | if (!w) return ESP_FAIL; |
| 159 | 213 | ||
| 214 | if (!ensure_keysets(wallet_slot(w))) { | ||
| 215 | ESP_LOGE(TAG, "Keysets not available for send"); | ||
| 216 | return ESP_FAIL; | ||
| 217 | } | ||
| 218 | |||
| 160 | std::vector<cashu::Proof> selected, remaining; | 219 | std::vector<cashu::Proof> selected, remaining; |
| 161 | if (!w->select_proofs(amount, selected, remaining)) { | 220 | if (!w->select_proofs(amount, selected, remaining)) { |
| 162 | ESP_LOGE(TAG, "Insufficient balance for %d sat", amount); | 221 | ESP_LOGE(TAG, "Insufficient balance for %d sat", amount); |
| @@ -246,6 +305,11 @@ esp_err_t nucula_wallet_swap_all(void) | |||
| 246 | auto &proofs = mutable_proofs(s_wallets[i]); | 305 | auto &proofs = mutable_proofs(s_wallets[i]); |
| 247 | if (proofs.empty()) continue; | 306 | if (proofs.empty()) continue; |
| 248 | 307 | ||
| 308 | if (!ensure_keysets(i)) { | ||
| 309 | ESP_LOGE(TAG, "Keysets not available for swap_all wallet[%d]", i); | ||
| 310 | continue; | ||
| 311 | } | ||
| 312 | |||
| 249 | int old_balance = s_wallets[i]->balance(); | 313 | int old_balance = s_wallets[i]->balance(); |
| 250 | 314 | ||
| 251 | std::vector<cashu::Proof> inputs = proofs; | 315 | std::vector<cashu::Proof> inputs = proofs; |
| @@ -301,6 +365,11 @@ esp_err_t nucula_wallet_melt(const char *bolt11_invoice, uint64_t max_fee_sats) | |||
| 301 | } | 365 | } |
| 302 | if (!w) return ESP_FAIL; | 366 | if (!w) return ESP_FAIL; |
| 303 | 367 | ||
| 368 | if (!ensure_keysets(wallet_slot(w))) { | ||
| 369 | ESP_LOGE(TAG, "Keysets not available for melt"); | ||
| 370 | return ESP_FAIL; | ||
| 371 | } | ||
| 372 | |||
| 304 | cashu::MeltQuote quote; | 373 | cashu::MeltQuote quote; |
| 305 | if (!w->request_melt_quote(std::string(bolt11_invoice), quote)) { | 374 | if (!w->request_melt_quote(std::string(bolt11_invoice), quote)) { |
| 306 | ESP_LOGE(TAG, "Melt quote request failed"); | 375 | ESP_LOGE(TAG, "Melt quote request failed"); |