**Motivations:** - Ajouter dates manquantes dans hash_list.txt et compléter historique - Compléter blockTime manquants dans utxo_list.txt et compléter historique - Récupérer frais depuis transactions d'ancrage (OP_RETURN) et les stocker - Bouton UI pour déclencher récupération frais - Diagnostic Bloc Rewards (pourquoi ~4700 BTC au lieu de 50 BTC) **Root causes:** - hash_list.txt sans date (format ancien) - utxo_list.txt blockTime souvent vide - Frais absents du fichier (métadonnées OP_RETURN non stockées) - Pas de moyen de récupérer/compléter frais depuis UI **Correctifs:** - hash_list.txt : format étendu avec date (rétrocompatible) - utxo_list.txt : blockTime complété automatiquement lors écritures - fees_list.txt : nouveau fichier pour stocker frais - updateFeesFromAnchors() : récupère frais depuis OP_RETURN ancrages - Endpoint /api/utxo/fees/update pour déclencher récupération - Bouton "Récupérer les frais depuis les ancrages" dans section Frais (spinner) - Scripts batch : complete-hash-list-dates.js, complete-utxo-list-blocktime.js - Script diagnostic : diagnose-bloc-rewards.js (subsidy, coinbase, listunspent) **Evolutions:** - Frais chargés depuis fees_list.txt dans getUtxoList - Complétion automatique dates/blockTime lors écritures futures **Pages affectées:** - signet-dashboard/src/bitcoin-rpc.js - signet-dashboard/src/server.js - signet-dashboard/public/utxo-list.html - scripts/complete-hash-list-dates.js - scripts/complete-utxo-list-blocktime.js - scripts/diagnose-bloc-rewards.js - features/utxo-list-fees-update-and-historical-completion.md
172 lines
4.5 KiB
Markdown
172 lines
4.5 KiB
Markdown
# Stockage des données - UserWallet
|
||
|
||
**Author:** Équipe 4NK
|
||
**Date:** 2026-01-26
|
||
|
||
## Synthèse
|
||
|
||
- **Relais (api-relay)** : Stockage hybride (mémoire + `./data/messages.json`). Messages, `seenHashes`, signatures et clés persistés. Sauvegarde à l’arrêt (SIGINT/SIGTERM) et périodique (configurable via `SAVE_INTERVAL_SECONDS`).
|
||
- **Front (userwallet)** : LocalStorage (`userwallet_identity`, `userwallet_relays`, `userwallet_pairs`, `userwallet_hash_cache` ; legacy : `userwallet_keypair`, `userwallet_services`). Graphe contractuel en mémoire uniquement (`GraphResolver`).
|
||
|
||
## Stockage sur le relais (api-relay)
|
||
|
||
### Architecture
|
||
|
||
Le relais utilise un stockage **hybride** : mémoire + persistance sur disque.
|
||
|
||
### Structure de stockage
|
||
|
||
**En mémoire :**
|
||
- `messages: Map<string, StoredMessage>` - Messages chiffrés indexés par hash
|
||
- `signatures: Map<string, StoredSignature[]>` - Signatures indexées par hash de message
|
||
- `keys: Map<string, StoredKey[]>` - Clés de déchiffrement indexées par hash de message
|
||
- `seenHashes: Set<string>` - Hash vus pour déduplication
|
||
|
||
**Sur disque :**
|
||
- Fichier `{STORAGE_PATH}/messages.json` contenant :
|
||
- `messages`: Array de `[hash, StoredMessage]`
|
||
- `seenHashes`: Array de hash (string[])
|
||
- `signatures`: Array de `[hash, StoredSignature[]]`
|
||
- `keys`: Array de `[hash, StoredKey[]]`
|
||
|
||
### Persistance
|
||
|
||
- **Chargement** : Au démarrage, charge `messages.json` si présent (ENOENT = premier run, démarrage à vide).
|
||
- **Sauvegarde** : À l’arrêt (SIGINT/SIGTERM) et périodiquement si `SAVE_INTERVAL_SECONDS` > 0 (défaut 300 s).
|
||
|
||
### Format de données
|
||
|
||
**StoredMessage :**
|
||
```typescript
|
||
{
|
||
msg: {
|
||
hash: string,
|
||
message_chiffre: string,
|
||
datajson_public: {
|
||
services_uuid: string[],
|
||
types_uuid: string[],
|
||
timestamp?: number,
|
||
...
|
||
}
|
||
},
|
||
received_at: number,
|
||
relayed: boolean
|
||
}
|
||
```
|
||
|
||
**StoredSignature :**
|
||
```typescript
|
||
{
|
||
msg: {
|
||
signature: {
|
||
hash: string,
|
||
cle_publique: string,
|
||
signature: string,
|
||
nonce: string,
|
||
materiel?: object
|
||
},
|
||
hash_cible?: string
|
||
},
|
||
received_at: number,
|
||
relayed: boolean
|
||
}
|
||
```
|
||
|
||
**StoredKey :**
|
||
```typescript
|
||
{
|
||
msg: {
|
||
hash_message: string,
|
||
cle_de_chiffrement_message: {
|
||
algo: string,
|
||
params: object,
|
||
cle_chiffree?: string
|
||
},
|
||
df_ecdh_scannable: string
|
||
},
|
||
received_at: number,
|
||
relayed: boolean
|
||
}
|
||
```
|
||
|
||
### Configuration
|
||
|
||
- `STORAGE_PATH` : Chemin du répertoire de stockage (défaut: `./data`)
|
||
- `SAVE_INTERVAL_SECONDS` : Intervalle de sauvegarde périodique en secondes (défaut: 300). Mettre à 0 pour désactiver.
|
||
|
||
### Limitations actuelles
|
||
|
||
- Pas de base de données (SQLite/PostgreSQL recommandé en production)
|
||
- Pas de compression des données
|
||
|
||
## Stockage sur le front (userwallet)
|
||
|
||
### Architecture
|
||
|
||
Le front utilise **LocalStorage** du navigateur pour toutes les données locales.
|
||
|
||
### Structure de stockage
|
||
|
||
**Clés utilisées :**
|
||
|
||
1. **`userwallet_identity`** : Identité locale
|
||
```typescript
|
||
{
|
||
uuid: string,
|
||
privateKey: string,
|
||
publicKey: string,
|
||
name?: string,
|
||
t0_anniversaire: number,
|
||
version: string
|
||
}
|
||
```
|
||
|
||
2. **`userwallet_relays`** : Configuration des relais
|
||
```typescript
|
||
Array<{
|
||
endpoint: string,
|
||
priority: number,
|
||
enabled: boolean,
|
||
last_sync?: number
|
||
}>
|
||
```
|
||
|
||
3. **`userwallet_pairs`** : Configuration des pairs
|
||
```typescript
|
||
Array<{
|
||
uuid: string,
|
||
membres_parents_uuid: string[],
|
||
is_local: boolean,
|
||
can_sign: boolean
|
||
}>
|
||
```
|
||
|
||
4. **`userwallet_hash_cache`** : Cache des hash vus
|
||
```typescript
|
||
string[] // Array de hash
|
||
```
|
||
|
||
5. **`userwallet_keypair`** : (Legacy) Paire de clés
|
||
6. **`userwallet_services`** : (Legacy) Services configurés
|
||
|
||
### Données en mémoire (non persistées)
|
||
|
||
- **Graphe contractuel** : Résolu dynamiquement depuis les messages synchronisés
|
||
- Services, Contrats, Champs, Actions, Membres, Pairs
|
||
- Stocké dans `GraphResolver.cache` (Map)
|
||
- Perdu au rechargement de page
|
||
|
||
### Limitations
|
||
|
||
- **Taille limitée** : LocalStorage a une limite (~5-10MB selon navigateur)
|
||
- **Pas de synchronisation** : Données locales uniquement
|
||
- **Sécurité** : Clés privées stockées en clair (à chiffrer avec mot de passe)
|
||
- **Pas de backup** : Pas de mécanisme d'export/import automatique
|
||
|
||
### Recommandations
|
||
|
||
- Chiffrer les clés privées avec un mot de passe utilisateur
|
||
- Implémenter un mécanisme d'export/import
|
||
- Utiliser IndexedDB pour des données plus volumineuses
|
||
- Implémenter une synchronisation cloud optionnelle (chiffrée)
|