391 lines
9.1 KiB
Markdown
391 lines
9.1 KiB
Markdown
|
|
# FELDMAN - Agente de Consolidación
|
||
|
|
|
||
|
|
**Versión:** 5.0
|
||
|
|
**Fecha:** 2024-12-24
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Resumen
|
||
|
|
|
||
|
|
| Aspecto | Valor |
|
||
|
|
|---------|-------|
|
||
|
|
| Servidor | CORP (92.112.181.188) |
|
||
|
|
| Puerto | 5054 |
|
||
|
|
| Base de datos | corp |
|
||
|
|
| Tablas | feldman_cola, feldman_bloques, feldman_validaciones |
|
||
|
|
| Estado | Operativo |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Rol en el Sistema
|
||
|
|
|
||
|
|
FELDMAN es el "notario" del sistema: valida y consolida datos en bloques inmutables.
|
||
|
|
|
||
|
|
```
|
||
|
|
MASON (enriquecido)
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ FELDMAN │
|
||
|
|
│ │
|
||
|
|
│ 1. Recibir en feldman_cola │
|
||
|
|
│ 2. Validar reglas (M-001, M-002, M-003)│
|
||
|
|
│ 3. Calcular hash_contenido │
|
||
|
|
│ 4. Encadenar (hash_previo) │
|
||
|
|
│ 5. Crear milestone o bloque │
|
||
|
|
│ 6. Marcar como consolidado │
|
||
|
|
└─────────────────────────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ milestones / bloques (inmutables) │
|
||
|
|
└─────────────────────────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
SENTINEL (auditoría futura)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Reglas de Validación
|
||
|
|
|
||
|
|
| Regla | Nombre | Descripción | Acción si falla |
|
||
|
|
|-------|--------|-------------|-----------------|
|
||
|
|
| M-001 | Hash único | h_bloque/h_milestone no existe previamente | Rechazar |
|
||
|
|
| M-002 | Encadenamiento | hash_previo apunta a bloque existente | Rechazar |
|
||
|
|
| M-003 | Integridad | hash_contenido = SHA-256(contenido) | Rechazar |
|
||
|
|
|
||
|
|
### Implementación
|
||
|
|
|
||
|
|
```python
|
||
|
|
def validar_m001(h_bloque):
|
||
|
|
"""M-001: Hash debe ser único."""
|
||
|
|
existe = db.execute(
|
||
|
|
"SELECT 1 FROM bloques WHERE h_bloque = %s",
|
||
|
|
[h_bloque]
|
||
|
|
).fetchone()
|
||
|
|
return existe is None
|
||
|
|
|
||
|
|
def validar_m002(hash_previo, h_instancia):
|
||
|
|
"""M-002: hash_previo debe existir (excepto génesis)."""
|
||
|
|
if hash_previo is None:
|
||
|
|
# Primer bloque de la instancia (génesis)
|
||
|
|
existe = db.execute(
|
||
|
|
"SELECT 1 FROM bloques WHERE h_instancia = %s",
|
||
|
|
[h_instancia]
|
||
|
|
).fetchone()
|
||
|
|
return existe is None # OK si no hay bloques previos
|
||
|
|
|
||
|
|
existe = db.execute(
|
||
|
|
"SELECT 1 FROM bloques WHERE h_bloque = %s",
|
||
|
|
[hash_previo]
|
||
|
|
).fetchone()
|
||
|
|
return existe is not None
|
||
|
|
|
||
|
|
def validar_m003(hash_contenido, contenido):
|
||
|
|
"""M-003: Hash debe coincidir con contenido."""
|
||
|
|
import hashlib
|
||
|
|
import json
|
||
|
|
|
||
|
|
contenido_serializado = json.dumps(contenido, sort_keys=True)
|
||
|
|
hash_calculado = hashlib.sha256(contenido_serializado.encode()).hexdigest()
|
||
|
|
|
||
|
|
return hash_contenido == hash_calculado
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Schema de Tablas
|
||
|
|
|
||
|
|
### feldman_cola
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE feldman_cola (
|
||
|
|
id BIGSERIAL PRIMARY KEY,
|
||
|
|
tipo VARCHAR(50) NOT NULL, -- 'milestone' o 'bloque'
|
||
|
|
h_entrada VARCHAR(64) NOT NULL, -- Referencia a origen
|
||
|
|
h_instancia VARCHAR(64) NOT NULL,
|
||
|
|
contenido JSONB NOT NULL,
|
||
|
|
prioridad INTEGER DEFAULT 0,
|
||
|
|
estado VARCHAR(20) DEFAULT 'pendiente',
|
||
|
|
intentos INTEGER DEFAULT 0,
|
||
|
|
ultimo_error TEXT,
|
||
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
|
|
processed_at TIMESTAMPTZ
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX idx_feldman_cola_estado ON feldman_cola(estado);
|
||
|
|
CREATE INDEX idx_feldman_cola_prioridad ON feldman_cola(prioridad DESC);
|
||
|
|
```
|
||
|
|
|
||
|
|
### feldman_bloques (alias de bloques)
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Ver 02_MODELO_DATOS/planos.md para schema completo
|
||
|
|
CREATE TABLE bloques (
|
||
|
|
id BIGSERIAL PRIMARY KEY,
|
||
|
|
h_bloque VARCHAR(64) UNIQUE NOT NULL,
|
||
|
|
h_instancia VARCHAR(64) NOT NULL,
|
||
|
|
secuencia BIGINT NOT NULL,
|
||
|
|
hash_previo VARCHAR(64),
|
||
|
|
hash_contenido VARCHAR(64) NOT NULL,
|
||
|
|
tipo_bloque VARCHAR(50) NOT NULL,
|
||
|
|
contenido JSONB NOT NULL,
|
||
|
|
-- ... campos adicionales
|
||
|
|
UNIQUE (h_instancia, secuencia)
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
### feldman_validaciones
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE feldman_validaciones (
|
||
|
|
id BIGSERIAL PRIMARY KEY,
|
||
|
|
h_entrada VARCHAR(64) NOT NULL,
|
||
|
|
tipo_destino VARCHAR(50) NOT NULL, -- 'milestone' o 'bloque'
|
||
|
|
h_destino VARCHAR(64), -- h_milestone o h_bloque
|
||
|
|
regla VARCHAR(50) NOT NULL, -- M-001, M-002, M-003
|
||
|
|
resultado BOOLEAN NOT NULL,
|
||
|
|
mensaje TEXT,
|
||
|
|
validated_at TIMESTAMPTZ DEFAULT NOW()
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX idx_feldman_val_entrada ON feldman_validaciones(h_entrada);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Flujo de Consolidación
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Entrada en feldman_cola
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
2. FELDMAN procesa (poll cada N segundos)
|
||
|
|
│
|
||
|
|
├─► Validar M-001 (hash único)
|
||
|
|
│ ├─ PASS → continuar
|
||
|
|
│ └─ FAIL → registrar error, marcar 'rechazado'
|
||
|
|
│
|
||
|
|
├─► Validar M-002 (encadenamiento)
|
||
|
|
│ ├─ PASS → continuar
|
||
|
|
│ └─ FAIL → registrar error, marcar 'rechazado'
|
||
|
|
│
|
||
|
|
├─► Validar M-003 (integridad)
|
||
|
|
│ ├─ PASS → continuar
|
||
|
|
│ └─ FAIL → registrar error, marcar 'rechazado'
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
3. Todas las validaciones PASS
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
4. Obtener última secuencia de h_instancia
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
5. Calcular h_bloque/h_milestone
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
6. INSERT en bloques/milestones
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
7. Actualizar feldman_cola → 'consolidado'
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
8. Registrar en feldman_validaciones
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Cálculo de Hashes
|
||
|
|
|
||
|
|
### h_bloque
|
||
|
|
|
||
|
|
```python
|
||
|
|
def calcular_h_bloque(h_instancia, secuencia, hash_previo, hash_contenido):
|
||
|
|
"""
|
||
|
|
Calcula el hash del bloque.
|
||
|
|
|
||
|
|
h_bloque = SHA-256(h_instancia:secuencia:hash_previo:hash_contenido)
|
||
|
|
"""
|
||
|
|
import hashlib
|
||
|
|
|
||
|
|
# hash_previo puede ser None para bloque génesis
|
||
|
|
previo = hash_previo or "genesis"
|
||
|
|
|
||
|
|
data = f"{h_instancia}:{secuencia}:{previo}:{hash_contenido}"
|
||
|
|
|
||
|
|
return hashlib.sha256(data.encode()).hexdigest()
|
||
|
|
```
|
||
|
|
|
||
|
|
### hash_contenido
|
||
|
|
|
||
|
|
```python
|
||
|
|
def calcular_hash_contenido(contenido):
|
||
|
|
"""
|
||
|
|
Calcula el hash del contenido.
|
||
|
|
|
||
|
|
hash_contenido = SHA-256(contenido_serializado)
|
||
|
|
"""
|
||
|
|
import hashlib
|
||
|
|
import json
|
||
|
|
|
||
|
|
# Serialización determinista
|
||
|
|
contenido_str = json.dumps(contenido, sort_keys=True, ensure_ascii=False)
|
||
|
|
|
||
|
|
return hashlib.sha256(contenido_str.encode()).hexdigest()
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## API Endpoints
|
||
|
|
|
||
|
|
### POST /consolidar
|
||
|
|
|
||
|
|
```http
|
||
|
|
POST /consolidar HTTP/1.1
|
||
|
|
Host: corp.example.com:5054
|
||
|
|
Content-Type: application/json
|
||
|
|
|
||
|
|
{
|
||
|
|
"tipo": "bloque",
|
||
|
|
"h_entrada": "abc123...",
|
||
|
|
"h_instancia": "def456...",
|
||
|
|
"contenido": {
|
||
|
|
"tipo_bloque": "transaccion",
|
||
|
|
"monto": 1000,
|
||
|
|
"descripcion": "Pago factura #123"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Response (éxito)
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"h_bloque": "ghi789...",
|
||
|
|
"secuencia": 42,
|
||
|
|
"hash_contenido": "jkl012...",
|
||
|
|
"validaciones": {
|
||
|
|
"M-001": true,
|
||
|
|
"M-002": true,
|
||
|
|
"M-003": true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Response (error)
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"error": "Validación fallida",
|
||
|
|
"validaciones": {
|
||
|
|
"M-001": true,
|
||
|
|
"M-002": false,
|
||
|
|
"M-003": true
|
||
|
|
},
|
||
|
|
"mensaje": "M-002: hash_previo no existe en cadena"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Configuración
|
||
|
|
|
||
|
|
### Variables de Entorno
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# /opt/feldman/.env
|
||
|
|
DB_HOST=localhost
|
||
|
|
DB_PORT=5432
|
||
|
|
DB_NAME=corp
|
||
|
|
DB_USER=feldman
|
||
|
|
DB_PASSWORD=****
|
||
|
|
|
||
|
|
POLL_INTERVAL=5 # Segundos entre polls
|
||
|
|
MAX_BATCH=10 # Elementos por lote
|
||
|
|
MAX_RETRIES=3 # Reintentos antes de rechazar
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Estados de la Cola
|
||
|
|
|
||
|
|
| Estado | Descripción |
|
||
|
|
|--------|-------------|
|
||
|
|
| pendiente | En espera de procesamiento |
|
||
|
|
| procesando | FELDMAN está validando |
|
||
|
|
| consolidado | Validado y creado bloque/milestone |
|
||
|
|
| rechazado | Falló validación, no se reintentará |
|
||
|
|
| error | Error técnico, puede reintentarse |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Auditoría
|
||
|
|
|
||
|
|
Cada validación se registra en `feldman_validaciones`:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Consultar historial de validaciones
|
||
|
|
SELECT
|
||
|
|
h_entrada,
|
||
|
|
regla,
|
||
|
|
resultado,
|
||
|
|
mensaje,
|
||
|
|
validated_at
|
||
|
|
FROM feldman_validaciones
|
||
|
|
WHERE h_entrada = 'abc123...'
|
||
|
|
ORDER BY validated_at;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Futuro: Blockchain
|
||
|
|
|
||
|
|
FELDMAN está diseñado para evolucionar hacia blockchain:
|
||
|
|
|
||
|
|
1. **Actual:** Encadenamiento por hash_previo en PostgreSQL
|
||
|
|
2. **Fase 2:** Merkle tree para verificación eficiente
|
||
|
|
3. **Fase 3:** Smart contract en blockchain (Ethereum/Polygon)
|
||
|
|
|
||
|
|
```
|
||
|
|
Merkle Root
|
||
|
|
│
|
||
|
|
┌───────────┴───────────┐
|
||
|
|
│ │
|
||
|
|
Hash(AB) Hash(CD)
|
||
|
|
│ │
|
||
|
|
┌─────┴─────┐ ┌─────┴─────┐
|
||
|
|
│ │ │ │
|
||
|
|
Hash(A) Hash(B) Hash(C) Hash(D)
|
||
|
|
│ │ │ │
|
||
|
|
Bloque A Bloque B Bloque C Bloque D
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Monitoreo
|
||
|
|
|
||
|
|
### Logs
|
||
|
|
```bash
|
||
|
|
docker logs feldman-feldman -f
|
||
|
|
```
|
||
|
|
|
||
|
|
### Consultas de Estado
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Elementos pendientes
|
||
|
|
SELECT COUNT(*) FROM feldman_cola WHERE estado = 'pendiente';
|
||
|
|
|
||
|
|
-- Últimas validaciones fallidas
|
||
|
|
SELECT * FROM feldman_validaciones
|
||
|
|
WHERE resultado = false
|
||
|
|
ORDER BY validated_at DESC
|
||
|
|
LIMIT 10;
|
||
|
|
|
||
|
|
-- Bloques creados hoy
|
||
|
|
SELECT COUNT(*) FROM bloques
|
||
|
|
WHERE created_at >= CURRENT_DATE;
|
||
|
|
```
|