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:
@@ -103,6 +103,20 @@ y el versionado sigue [Versionado Semántico](https://semver.org/lang/es/):
|
|||||||
`mixer` del conjunto de direcciones del actor y del union-find, y la
|
`mixer` del conjunto de direcciones del actor y del union-find, y la
|
||||||
atribución por huella salta esos nodos.
|
atribución por huella salta esos nodos.
|
||||||
- Verificado con el mismo caso: de 10 direcciones atribuidas a 0.
|
- Verificado con el mismo caso: de 10 direcciones atribuidas a 0.
|
||||||
|
- **El informe de wallet ya no da un aprobado optimista cuando faltan datos.**
|
||||||
|
Mismo patrón que el fallo del peritaje, en la pieza central del proyecto:
|
||||||
|
`scanWallet` usaba `.catch(()=>[])` en las tres consultas del escaneo, así que
|
||||||
|
una dirección que el nodo no pudo servir quedaba indistinguible de una
|
||||||
|
dirección sin actividad. Sus transacciones no se traían, no contaban para la
|
||||||
|
reutilización, no entraban en el union-find y no aparecían en el historial —
|
||||||
|
y el informe daba su valoración de salud sin mencionar que le faltaban datos.
|
||||||
|
El sesgo iba siempre hacia el optimismo: menos actividad vista es mejor nota.
|
||||||
|
- Las tres consultas usan ahora `getStrict` y registran los fallos en
|
||||||
|
`report.scanErrors`, con la dirección, la rama y en qué fase ocurrió.
|
||||||
|
- Aviso en rojo **antes** de la banda de salud (una valoración leída sin saber
|
||||||
|
que faltan datos es peor que ninguna valoración), y bloque equivalente al
|
||||||
|
principio del export Markdown.
|
||||||
|
|
||||||
- **Tampoco atribuye al actor las direcciones de un custodio.** Misma raíz que
|
- **Tampoco atribuye al actor las direcciones de un custodio.** Misma raíz que
|
||||||
el fallo anterior, descubierta probando la poda por rama con un depósito real
|
el fallo anterior, descubierta probando la poda por rama con un depósito real
|
||||||
en Bitfinex: el cluster CIOH ya excluía las direcciones de custodio, pero la
|
en Bitfinex: el cluster CIOH ya excluía las direcciones de custodio, pero la
|
||||||
|
|||||||
+57
-7
@@ -4748,7 +4748,7 @@
|
|||||||
|
|
||||||
// ── AUDITORÍA ──────────────────────────────────────────────────────────
|
// ── AUDITORÍA ──────────────────────────────────────────────────────────
|
||||||
function Auditoria({base, initialQuery}) {
|
function Auditoria({base, initialQuery}) {
|
||||||
const {get}=useApi(base);
|
const {get, getStrict}=useApi(base);
|
||||||
const [query,setQuery]=useState(initialQuery||"");
|
const [query,setQuery]=useState(initialQuery||"");
|
||||||
const [tx,setTx]=useState(null);
|
const [tx,setTx]=useState(null);
|
||||||
const [analysis,setAnalysis]=useState(null);
|
const [analysis,setAnalysis]=useState(null);
|
||||||
@@ -4806,14 +4806,22 @@
|
|||||||
const BATCH = 5; // direcciones por lote
|
const BATCH = 5; // direcciones por lote
|
||||||
const PAUSE = 120; // ms entre lotes
|
const PAUSE = 120; // ms entre lotes
|
||||||
const active = []; // direcciones con actividad
|
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
|
// Fase 1: estado ligero de cada dirección
|
||||||
for (let i = 0; i < all.length; i += BATCH) {
|
for (let i = 0; i < all.length; i += BATCH) {
|
||||||
const slice = all.slice(i, i + BATCH);
|
const slice = all.slice(i, i + BATCH);
|
||||||
setWalletProgress(`Explorando direcciones ${Math.min(i+BATCH,all.length)}/${all.length}`);
|
setWalletProgress(`Explorando direcciones ${Math.min(i+BATCH,all.length)}/${all.length}`);
|
||||||
const infos = await Promise.all(slice.map(s =>
|
const infos = await Promise.all(slice.map(async s => {
|
||||||
get(`/api/address/${s.addr}`, null).catch(()=>null)
|
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) => {
|
infos.forEach((info, j) => {
|
||||||
const txc = info?.chain_stats?.tx_count || 0;
|
const txc = info?.chain_stats?.tx_count || 0;
|
||||||
if (txc > 0) active.push({ ...slice[j], info });
|
if (txc > 0) active.push({ ...slice[j], info });
|
||||||
@@ -4829,21 +4837,30 @@
|
|||||||
const slice = active.slice(i, i + BATCH);
|
const slice = active.slice(i, i + BATCH);
|
||||||
setWalletProgress(`Trayendo transacciones ${Math.min(i+BATCH,active.length)}/${active.length}`);
|
setWalletProgress(`Trayendo transacciones ${Math.min(i+BATCH,active.length)}/${active.length}`);
|
||||||
await Promise.all(slice.map(async s => {
|
await Promise.all(slice.map(async s => {
|
||||||
let page = await get(`/api/address/${s.addr}/txs`, []).catch(()=>[]);
|
try {
|
||||||
|
let page = await getStrict(`/api/address/${s.addr}/txs`);
|
||||||
let guard = 0;
|
let guard = 0;
|
||||||
while (Array.isArray(page) && page.length > 0 && guard < 8) {
|
while (Array.isArray(page) && page.length > 0 && guard < 8) {
|
||||||
page.forEach(t => { if (t && t.txid) txMap.set(t.txid, t); });
|
page.forEach(t => { if (t && t.txid) txMap.set(t.txid, t); });
|
||||||
if (page.length < 25) break; // última página
|
if (page.length < 25) break; // última página
|
||||||
const last = page[page.length-1].txid;
|
const last = page[page.length-1].txid;
|
||||||
page = await get(`/api/address/${s.addr}/txs/chain/${last}`, []).catch(()=>[]);
|
page = await getStrict(`/api/address/${s.addr}/txs/chain/${last}`);
|
||||||
guard++;
|
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));
|
if (i + BATCH < active.length) await new Promise(r => setTimeout(r, PAUSE));
|
||||||
}
|
}
|
||||||
|
|
||||||
setWalletProgress("Analizando vinculación…");
|
setWalletProgress("Analizando vinculación…");
|
||||||
const report = buildWalletReport(active, [...txMap.values()], myAddrSet);
|
const report = buildWalletReport(active, [...txMap.values()], myAddrSet);
|
||||||
|
report.scanErrors = scanErrors;
|
||||||
|
report.scanComplete = scanErrors.length === 0;
|
||||||
setWalletReport(report);
|
setWalletReport(report);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
setWalletReport({ error: e.message });
|
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();
|
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>
|
}} 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={()=>{
|
<button onClick={()=>{
|
||||||
const {health,totalTxs,activeCount,reusedAddrs,clusters,history}=walletReport;
|
const {health,totalTxs,activeCount,reusedAddrs,clusters,history,scanErrors}=walletReport;
|
||||||
const lines=[
|
const lines=[
|
||||||
`# Informe de Wallet — Txoko`,``,
|
`# 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,``,
|
`## Salud general: ${health.band}`,``,health.msg,``,
|
||||||
`- Transacciones analizadas: ${totalTxs}`,
|
`- Transacciones analizadas: ${totalTxs}`,
|
||||||
`- Direcciones con actividad: ${activeCount}`,
|
`- Direcciones con actividad: ${activeCount}`,
|
||||||
@@ -5000,6 +5024,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* 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={{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}}>
|
<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