-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
185 lines (160 loc) · 6.89 KB
/
Copy pathserver.js
File metadata and controls
185 lines (160 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// ================================================================
// Bitcopper Node — Nodo P2P Soberano
// © 2026 Bitcopper Tech SpA · Pedro Ramos Morales
// ================================================================
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const crypto = require('crypto');
const pool = require('./db/pool');
const { startP2P, getActiveNodes } = require('./p2p');
const TAL_API = process.env.TAL_API_URL || 'http://bitcopper-tal:8765';
async function getTALScore(temp, cpu) {
try {
const r = await fetch(`${TAL_API}/bloque/completo`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
temp_chip_celsius: temp,
temp_ambiente_celsius: 25,
cpu_percent: cpu,
potencia_cpu_w: cpu * 0.65,
potencia_total_w: cpu * 1.2,
duracion_segundos: 60,
}),
signal: AbortSignal.timeout(8000),
});
if (!r.ok) return null;
return await r.json();
} catch(e) {
return null;
}
}
const app = express();
app.use(cors());
app.use(express.json());
const NODE_URL = process.env.NODE_URL || 'http://localhost:3001';
const PORT = parseInt(process.env.PORT || '3001');
// ── POST /api/mine — recibe bloques de mineros ─────────────────
app.post('/api/mine', async (req, res) => {
const data = req.body;
const { wallet, cpu, temp, rho, ram, reward, tal_score, thermal_proof, device_id } = data;
if (!wallet || !temp) return res.status(400).json({ error: 'Datos incompletos' });
// Consultar TAL local si no viene score del miner
let talScore = data.tal_score || null;
let thermalProof = data.thermal_proof || null;
if (!talScore) {
const tal = await getTALScore(parseFloat(temp), parseFloat(cpu || 15));
if (tal) {
talScore = tal.tal_score;
thermalProof = thermalProof || tal.thermal_proof;
}
}
const client = await pool.connect();
try {
await client.query('BEGIN');
// Registrar minero
await client.query(`
INSERT INTO miners (wallet, last_seen, device_id)
VALUES ($1, NOW(), $2)
ON CONFLICT (wallet) DO UPDATE
SET last_seen = NOW(),
device_id = CASE WHEN miners.device_id IS NULL THEN EXCLUDED.device_id ELSE miners.device_id END
`, [wallet, device_id || null]);
// Obtener último bloque
const chainRes = await client.query(
'SELECT block_number, hash FROM chain ORDER BY block_number DESC LIMIT 1'
);
const prevBlock = chainRes.rows[0];
const prevHash = prevBlock?.hash || '0'.repeat(64);
const blockNumber = (prevBlock?.block_number || 0) + 1;
// Calcular reward si no viene del miner
const blockReward = reward || (parseFloat(temp) * 0.000001 * (parseFloat(cpu) / 100));
// Generar hash del bloque
const hashInput = `${blockNumber}${wallet}${temp}${cpu}${rho}${prevHash}${Date.now()}`;
const hash = crypto.createHash('sha256').update(hashInput).digest('hex');
// Insertar bloque
await client.query(`
INSERT INTO blocks (block_number, wallet, cpu, temp, rho, ram, reward, hash, thermal_proof)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
`, [blockNumber, wallet, cpu, temp, rho, ram, blockReward, hash, thermal_proof || null]);
// Insertar en chain
await client.query(`
INSERT INTO chain (block_number, hash, prev_hash, wallet, reward, temp, thermal_proof)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`, [blockNumber, hash, prevHash, wallet, blockReward, temp, thermal_proof || null]);
// Actualizar saldo minero (97% al minero, 3% al tesoro)
await client.query(`
UPDATE miners SET total_bitcu = total_bitcu + $1, total_blocks = total_blocks + 1
WHERE wallet = $2
`, [blockReward * 0.97, wallet]);
await client.query('COMMIT');
console.log(`[BLOCK] #${blockNumber} wallet=${wallet.slice(0,20)}... temp=${temp}°C proof=${thermal_proof?.slice(0,16) || 'null'}...`);
res.json({ ok: true, block: blockNumber, hash, reward: blockReward, thermal_proof });
} catch(e) {
await client.query('ROLLBACK');
res.status(500).json({ error: e.message });
} finally {
client.release();
}
});
// ── GET /api/blocks — últimos bloques ─────────────────────────
app.get('/api/blocks', async (req, res) => {
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
const r = await pool.query(`
SELECT block_number, wallet, temp, reward, hash, thermal_proof, created_at
FROM chain ORDER BY block_number DESC LIMIT $1
`, [limit]);
res.json(r.rows);
});
// ── GET /api/stats ─────────────────────────────────────────────
app.get('/api/stats', async (req, res) => {
const [miners, blocks, temp] = await Promise.all([
pool.query('SELECT COUNT(*) FROM miners'),
pool.query('SELECT COUNT(*) as total, SUM(reward) as bitcu FROM chain'),
pool.query(`SELECT AVG(temp) as avg FROM chain WHERE created_at > NOW() - INTERVAL '1 hour'`),
]);
res.json({
total_miners: parseInt(miners.rows[0].count),
total_blocks: parseInt(blocks.rows[0].total),
total_bitcu: blocks.rows[0].bitcu,
avg_temp_1h: parseFloat(temp.rows[0].avg || 0).toFixed(2),
node_url: NODE_URL,
});
});
// ── P2P endpoints ──────────────────────────────────────────────
app.post('/api/p2p/announce', async (req, res) => {
const { url } = req.body;
if (!url) return res.status(400).json({ error: 'url requerida' });
await pool.query(`
INSERT INTO nodes (url, is_active, last_seen)
VALUES ($1, TRUE, NOW())
ON CONFLICT (url) DO UPDATE SET is_active = TRUE, last_seen = NOW()
`, [url]);
console.log(`[P2P] 📡 Nodo anunciado: ${url}`);
res.json({ ok: true, node_url: NODE_URL });
});
app.get('/api/p2p/blocks', async (req, res) => {
const since = parseInt(req.query.since || 0);
const limit = Math.min(parseInt(req.query.limit || 100), 500);
const r = await pool.query(`
SELECT block_number, hash, prev_hash, wallet, reward, temp, thermal_proof
FROM chain WHERE block_number > $1 ORDER BY block_number ASC LIMIT $2
`, [since, limit]);
res.json({ blocks: r.rows, node_url: NODE_URL });
});
app.get('/api/p2p/nodes', async (req, res) => {
const nodes = await getActiveNodes();
res.json({ nodes, node_url: NODE_URL });
});
app.get('/', (req, res) => res.json({
status: 'ok',
version: 'Bitcopper Node v1.0',
node_url: NODE_URL,
}));
// ── Arrancar ───────────────────────────────────────────────────
app.listen(PORT, async () => {
console.log(`🔥 Bitcopper Node v1.0 — puerto ${PORT}`);
console.log(` NODE_URL: ${NODE_URL}`);
await startP2P();
});