upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/components/nucula_lib/nucula_wallet.cpp
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-21 15:44:46 +0530
committerYour Name <you@example.com>2026-05-21 15:44:46 +0530
commit243e4808246555f86d464d1c476681a902e644ff (patch)
tree4334ebf0f8c2e2ee7f5ebccb93dedd68a2e95b18 /components/nucula_lib/nucula_wallet.cpp
parent46d9c97e11ea0b03a7dc03fdef6bd360cbe53d44 (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.cpp83
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;
18static cashu::Wallet *s_wallets[MAX_WALLETS] = {}; 21static cashu::Wallet *s_wallets[MAX_WALLETS] = {};
19static int s_wallet_count = 0; 22static int s_wallet_count = 0;
20static char s_wallet_urls[MAX_WALLETS][256] = {}; 23static char s_wallet_urls[MAX_WALLETS][256] = {};
24static bool s_keysets_loaded[MAX_WALLETS] = {};
25static int s_keyset_attempts[MAX_WALLETS] = {};
26
27static const int KEYSET_BASE_DELAY_MS = 1000;
28static const int KEYSET_MAX_DELAY_MS = 30000;
29static const int KEYSET_JITTER_MS = 500;
30
31static 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
67static 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
22static cashu::Wallet *find_wallet_for_token(const cashu::Token &tok) 74static 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");