fix: el informe de wallet avisa cuando el escaneo quedó incompleto
Mismo patrón que el fallo del peritaje, en la pieza central del proyecto. scanWallet usaba .catch(()=>[]) en sus tres consultas, así que una dirección que el nodo no pudo servir era indistinguible de una sin actividad: sus txs no se traían, no contaban para reutilización ni clusters, y el informe daba su valoración sin mencionar que faltaban datos. El sesgo iba siempre al optimismo — menos actividad vista, mejor nota. Ahora se registran los fallos y se avisa antes de la banda de salud, en la UI y en el export MD.
This commit is contained in:
+63
-13
@@ -4748,7 +4748,7 @@
|
||||
|
||||
// ── AUDITORÍA ──────────────────────────────────────────────────────────
|
||||
function Auditoria({base, initialQuery}) {
|
||||
const {get}=useApi(base);
|
||||
const {get, getStrict}=useApi(base);
|
||||
const [query,setQuery]=useState(initialQuery||"");
|
||||
const [tx,setTx]=useState(null);
|
||||
const [analysis,setAnalysis]=useState(null);
|
||||
@@ -4806,14 +4806,22 @@
|
||||
const BATCH = 5; // direcciones por lote
|
||||
const PAUSE = 120; // ms entre lotes
|
||||
const active = []; // direcciones con actividad
|
||||
// Consultas que el nodo no pudo servir. Se registran porque su
|
||||
// ausencia SESGA el informe hacia el optimismo: una dirección que no
|
||||
// se pudo leer parece una dirección sin actividad, y entonces su
|
||||
// reutilización, sus vinculaciones y sus transacciones desaparecen
|
||||
// del análisis. Decir "tu wallet está limpia" cuando en realidad
|
||||
// faltan datos es peor que no decir nada.
|
||||
const scanErrors = [];
|
||||
|
||||
// Fase 1: estado ligero de cada dirección
|
||||
for (let i = 0; i < all.length; i += BATCH) {
|
||||
const slice = all.slice(i, i + BATCH);
|
||||
setWalletProgress(`Explorando direcciones ${Math.min(i+BATCH,all.length)}/${all.length}`);
|
||||
const infos = await Promise.all(slice.map(s =>
|
||||
get(`/api/address/${s.addr}`, null).catch(()=>null)
|
||||
));
|
||||
const infos = await Promise.all(slice.map(async s => {
|
||||
try { return await getStrict(`/api/address/${s.addr}`); }
|
||||
catch (e) { scanErrors.push({ addr:s.addr, branch:s.branch, message:e.message, phase:"estado de la dirección" }); return null; }
|
||||
}));
|
||||
infos.forEach((info, j) => {
|
||||
const txc = info?.chain_stats?.tx_count || 0;
|
||||
if (txc > 0) active.push({ ...slice[j], info });
|
||||
@@ -4829,14 +4837,21 @@
|
||||
const slice = active.slice(i, i + BATCH);
|
||||
setWalletProgress(`Trayendo transacciones ${Math.min(i+BATCH,active.length)}/${active.length}`);
|
||||
await Promise.all(slice.map(async s => {
|
||||
let page = await get(`/api/address/${s.addr}/txs`, []).catch(()=>[]);
|
||||
let guard = 0;
|
||||
while (Array.isArray(page) && page.length > 0 && guard < 8) {
|
||||
page.forEach(t => { if (t && t.txid) txMap.set(t.txid, t); });
|
||||
if (page.length < 25) break; // última página
|
||||
const last = page[page.length-1].txid;
|
||||
page = await get(`/api/address/${s.addr}/txs/chain/${last}`, []).catch(()=>[]);
|
||||
guard++;
|
||||
try {
|
||||
let page = await getStrict(`/api/address/${s.addr}/txs`);
|
||||
let guard = 0;
|
||||
while (Array.isArray(page) && page.length > 0 && guard < 8) {
|
||||
page.forEach(t => { if (t && t.txid) txMap.set(t.txid, t); });
|
||||
if (page.length < 25) break; // última página
|
||||
const last = page[page.length-1].txid;
|
||||
page = await getStrict(`/api/address/${s.addr}/txs/chain/${last}`);
|
||||
guard++;
|
||||
}
|
||||
} catch (e) {
|
||||
// Perder el historial de una dirección con actividad conocida
|
||||
// es lo más grave: sabemos que tiene transacciones y no hemos
|
||||
// podido verlas. El informe queda incompleto por ahí.
|
||||
scanErrors.push({ addr:s.addr, branch:s.branch, message:e.message, phase:"historial de transacciones", txCount:s.info?.chain_stats?.tx_count ?? null });
|
||||
}
|
||||
}));
|
||||
if (i + BATCH < active.length) await new Promise(r => setTimeout(r, PAUSE));
|
||||
@@ -4844,6 +4859,8 @@
|
||||
|
||||
setWalletProgress("Analizando vinculación…");
|
||||
const report = buildWalletReport(active, [...txMap.values()], myAddrSet);
|
||||
report.scanErrors = scanErrors;
|
||||
report.scanComplete = scanErrors.length === 0;
|
||||
setWalletReport(report);
|
||||
} catch(e) {
|
||||
setWalletReport({ error: e.message });
|
||||
@@ -4978,9 +4995,16 @@
|
||||
const a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download="wallet-report.json";a.click();
|
||||
}} style={{padding:"3px 10px",background:"none",border:`1px solid ${C.border}`,borderRadius:4,color:C.t2,fontFamily:"monospace",fontSize:"0.6rem",cursor:"pointer"}}>↓ JSON</button>
|
||||
<button onClick={()=>{
|
||||
const {health,totalTxs,activeCount,reusedAddrs,clusters,history}=walletReport;
|
||||
const {health,totalTxs,activeCount,reusedAddrs,clusters,history,scanErrors}=walletReport;
|
||||
const lines=[
|
||||
`# Informe de Wallet — Txoko`,``,
|
||||
...((scanErrors&&scanErrors.length>0)?[
|
||||
`> **AVISO — informe incompleto.** ${scanErrors.length} consulta(s) al nodo fallaron durante el escaneo.`,
|
||||
`> Las direcciones no leídas se comportan como si no tuvieran actividad, así que la valoración`,
|
||||
`> de abajo es **optimista**: puede faltar reutilización, vinculaciones o transacciones enteras.`,
|
||||
`> Repite el escaneo antes de darla por buena.`,``,
|
||||
...scanErrors.map(e=>`> - ${e.addr} (${e.branch}) — ${e.message} [${e.phase}]`),``,
|
||||
]:[]),
|
||||
`## Salud general: ${health.band}`,``,health.msg,``,
|
||||
`- Transacciones analizadas: ${totalTxs}`,
|
||||
`- Direcciones con actividad: ${activeCount}`,
|
||||
@@ -5000,6 +5024,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Aviso de escaneo incompleto — va ANTES de la banda a
|
||||
propósito: una valoración de salud leída sin saber que
|
||||
faltan datos es peor que no tener valoración. El sesgo
|
||||
siempre va hacia el optimismo, porque una dirección no
|
||||
leída parece una dirección sin actividad. */}
|
||||
{walletReport.scanErrors&&walletReport.scanErrors.length>0&&(
|
||||
<div style={{padding:"10px 14px",background:C.redMuted,border:`1px solid ${C.red}40`,borderRadius:8,marginBottom:14}}>
|
||||
<div style={{fontSize:"0.66rem",color:C.red,fontFamily:"monospace",fontWeight:700,marginBottom:6}}>
|
||||
⚠ Informe incompleto — {walletReport.scanErrors.length} consulta(s) al nodo fallaron
|
||||
</div>
|
||||
<div style={{fontSize:"0.64rem",color:C.t1,lineHeight:1.6,marginBottom:8}}>
|
||||
Las direcciones que no se pudieron leer se comportan aquí como si no tuvieran actividad. Eso hace que la valoración de abajo sea <strong>optimista</strong>: puede faltar reutilización, vinculaciones o transacciones enteras que sí existen. No la des por buena sin repetir el escaneo.
|
||||
</div>
|
||||
<div style={{display:"flex",flexDirection:"column",gap:2}}>
|
||||
{walletReport.scanErrors.slice(0,6).map((e,i)=>(
|
||||
<div key={i} style={{fontSize:"0.56rem",color:C.t2,fontFamily:"monospace",wordBreak:"break-all"}}>
|
||||
{e.addr} ({e.branch}) — {e.message} · {e.phase}
|
||||
</div>
|
||||
))}
|
||||
{walletReport.scanErrors.length>6&&(
|
||||
<div style={{fontSize:"0.56rem",color:C.t2,fontFamily:"monospace"}}>…y {walletReport.scanErrors.length-6} más</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Bloque 1: Salud general */}
|
||||
<div style={{display:"flex",alignItems:"center",gap:14,padding:"12px 14px",background:C.bgCard,borderRadius:8,marginBottom:14,border:`1px solid ${walletReport.health.color}30`}}>
|
||||
<div style={{width:48,height:48,borderRadius:"50%",border:`3px solid ${walletReport.health.color}`,display:"flex",alignItems:"center",justifyContent:"center",flexShrink:0}}>
|
||||
|
||||
Reference in New Issue
Block a user