api-anchorage: fix syntax errors, api-relay: refactor routes setup
**Motivations:** - Corriger erreurs de syntaxe dans api-anchorage (bloc else non fermé, variable dupliquée) - Refactorer api-relay pour extraire setupRoutes et améliorer la structure **Root causes:** - api-anchorage: bloc else non fermé après verrouillage UTXO, variable totalInputAmount déclarée deux fois - api-relay: code dupliqué dans main(), besoin de meilleure séparation des responsabilités **Correctifs:** - api-anchorage: fermeture bloc else ligne 392, renommage totalInputAmount en totalInputAmountForFee dans calcul frais, utilisation totalSelectedAmount pour plusieurs UTXOs **Evolutions:** - api-relay: extraction setupRoutes() depuis main(), refactoring routes (keys, messages, signatures), middleware auth, index.ts restructuré **Pages affectées:** - api-anchorage: bitcoin-rpc.js - api-relay: index.ts, middleware/auth.ts, routes (keys, messages, signatures), services/storage.ts - data: sync-utxos.log
This commit is contained in:
parent
3c212e56e9
commit
4833fdbb53
@ -389,6 +389,7 @@ class BitcoinRPC {
|
||||
|
||||
// Verrouiller l'UTXO sélectionné
|
||||
this.lockUtxo(selectedUtxo.txid, selectedUtxo.vout);
|
||||
}
|
||||
|
||||
// Créer les outputs
|
||||
// Note: Bitcoin Core ne permet qu'un seul OP_RETURN par transaction via 'data'
|
||||
@ -627,7 +628,7 @@ class BitcoinRPC {
|
||||
|
||||
// Calculer les frais réels de la transaction
|
||||
// Frais = somme des inputs - somme des outputs
|
||||
let totalInputAmount = 0;
|
||||
let totalInputAmountForFee = 0;
|
||||
let totalOutputAmountInTx = 0;
|
||||
|
||||
// Calculer la somme des inputs
|
||||
@ -637,16 +638,18 @@ class BitcoinRPC {
|
||||
try {
|
||||
const prevTx = await this.client.getRawTransaction(input.txid, true);
|
||||
if (prevTx.vout && prevTx.vout[input.vout]) {
|
||||
totalInputAmount += prevTx.vout[input.vout].value || 0;
|
||||
totalInputAmountForFee += prevTx.vout[input.vout].value || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
// Si on ne peut pas obtenir la transaction précédente, utiliser le montant de l'UTXO sélectionné
|
||||
// Si on ne peut pas obtenir la transaction précédente, utiliser le montant total des UTXOs sélectionnés
|
||||
logger.debug('Could not get previous transaction for fee calculation', {
|
||||
txid: input.txid,
|
||||
error: error.message,
|
||||
});
|
||||
totalInputAmount += selectedUtxo.amount;
|
||||
break; // Utiliser le montant connu de l'UTXO sélectionné
|
||||
// Utiliser le montant total des UTXOs sélectionnés
|
||||
const totalSelectedAmountForFee = selectedUtxos.length > 1 ? totalSelectedAmount : selectedUtxo.amount;
|
||||
totalInputAmountForFee += totalSelectedAmountForFee;
|
||||
break; // Utiliser le montant connu des UTXOs sélectionnés
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -658,7 +661,7 @@ class BitcoinRPC {
|
||||
}
|
||||
}
|
||||
|
||||
const actualFee = roundTo8Decimals(totalInputAmount - totalOutputAmountInTx);
|
||||
const actualFee = roundTo8Decimals(totalInputAmountForFee - totalOutputAmountInTx);
|
||||
|
||||
// Construire la liste des outputs avec leur type explicite
|
||||
// En analysant les outputs réels de la transaction brute
|
||||
|
||||
@ -26,31 +26,12 @@ const PEER_RELAYS = process.env.PEER_RELAYS
|
||||
: [];
|
||||
const REQUIRE_API_KEY = process.env.REQUIRE_API_KEY !== 'false'; // Default: true
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const app = express();
|
||||
|
||||
registerMiddleware(app);
|
||||
app.use(express.json({ limit: getBodyLimit() }));
|
||||
|
||||
// Initialize database
|
||||
const dbStorage = new DatabaseStorageService(STORAGE_PATH);
|
||||
await dbStorage.initialize();
|
||||
|
||||
// Create adapter for compatibility
|
||||
const storage = new StorageAdapter(dbStorage);
|
||||
|
||||
// Initialize API key service
|
||||
const apiKeyService = new ApiKeyService(dbStorage);
|
||||
|
||||
// Register authentication middleware if required
|
||||
if (REQUIRE_API_KEY) {
|
||||
app.use(
|
||||
createAuthMiddleware((key) => apiKeyService.validateApiKey(key)),
|
||||
);
|
||||
}
|
||||
|
||||
const relay = new RelayService(storage, PEER_RELAYS);
|
||||
|
||||
function setupRoutes(
|
||||
app: express.Application,
|
||||
storage: StorageAdapter,
|
||||
relay: RelayService,
|
||||
apiKeyService: ApiKeyService,
|
||||
): void {
|
||||
app.use('/health', createHealthRouter());
|
||||
app.use('/messages', createMessagesRouter(storage, relay));
|
||||
app.use('/signatures', createSignaturesRouter(storage, relay));
|
||||
@ -58,7 +39,6 @@ async function main(): Promise<void> {
|
||||
app.use('/metrics', createMetricsRouter(storage));
|
||||
app.use('/bloom', createBloomRouter(storage));
|
||||
|
||||
// API key management endpoint (admin only, should be protected in production)
|
||||
app.post('/admin/api-keys', (req, res) => {
|
||||
const { description } = req.body as { description?: string };
|
||||
const { key, prefix } = apiKeyService.generateApiKey(description);
|
||||
@ -79,23 +59,9 @@ async function main(): Promise<void> {
|
||||
res.status(404).json({ error: 'API key not found' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const server = http.createServer(app);
|
||||
server.timeout = getRequestTimeoutMs();
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
logger.info(
|
||||
{
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
storagePath: STORAGE_PATH,
|
||||
peerRelays: PEER_RELAYS.length > 0 ? PEER_RELAYS : 'none',
|
||||
requireApiKey: REQUIRE_API_KEY,
|
||||
},
|
||||
'Relay server listening',
|
||||
);
|
||||
});
|
||||
|
||||
function setupShutdown(dbStorage: DatabaseStorageService): void {
|
||||
const shutdown = async (): Promise<void> => {
|
||||
logger.info('Shutting down...');
|
||||
try {
|
||||
@ -116,6 +82,46 @@ async function main(): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const app = express();
|
||||
|
||||
registerMiddleware(app);
|
||||
app.use(express.json({ limit: getBodyLimit() }));
|
||||
|
||||
const dbStorage = new DatabaseStorageService(STORAGE_PATH);
|
||||
await dbStorage.initialize();
|
||||
|
||||
const storage = new StorageAdapter(dbStorage);
|
||||
const apiKeyService = new ApiKeyService(dbStorage);
|
||||
|
||||
if (REQUIRE_API_KEY) {
|
||||
app.use(
|
||||
createAuthMiddleware((key) => apiKeyService.validateApiKey(key)),
|
||||
);
|
||||
}
|
||||
|
||||
const relay = new RelayService(storage, PEER_RELAYS);
|
||||
setupRoutes(app, storage, relay, apiKeyService);
|
||||
|
||||
const server = http.createServer(app);
|
||||
server.timeout = getRequestTimeoutMs();
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
logger.info(
|
||||
{
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
storagePath: STORAGE_PATH,
|
||||
peerRelays: PEER_RELAYS.length > 0 ? PEER_RELAYS : 'none',
|
||||
requireApiKey: REQUIRE_API_KEY,
|
||||
},
|
||||
'Relay server listening',
|
||||
);
|
||||
});
|
||||
|
||||
setupShutdown(dbStorage);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
logger.fatal({ err: error }, 'Fatal error');
|
||||
process.exit(1);
|
||||
|
||||
@ -1,6 +1,43 @@
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import { logger } from '../lib/logger.js';
|
||||
|
||||
function extractApiKey(req: Request): string | undefined {
|
||||
const authHeader = req.headers.authorization;
|
||||
const apiKeyHeader = req.headers['x-api-key'] as string | undefined;
|
||||
|
||||
if (authHeader?.startsWith('Bearer ') === true) {
|
||||
return authHeader.slice(7);
|
||||
}
|
||||
if (apiKeyHeader !== undefined) {
|
||||
return apiKeyHeader;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function handleMissingApiKey(req: Request, res: Response): void {
|
||||
logger.warn(
|
||||
{
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
ip: req.ip,
|
||||
},
|
||||
'POST request without API key',
|
||||
);
|
||||
res.status(401).json({ error: 'API key required for POST requests' });
|
||||
}
|
||||
|
||||
function handleInvalidApiKey(req: Request, res: Response): void {
|
||||
logger.warn(
|
||||
{
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
ip: req.ip,
|
||||
},
|
||||
'POST request with invalid API key',
|
||||
);
|
||||
res.status(403).json({ error: 'Invalid API key' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to authenticate POST requests using API key.
|
||||
* API key should be provided in the Authorization header as: "Bearer <key>"
|
||||
@ -10,46 +47,19 @@ export function createAuthMiddleware(
|
||||
validateApiKey: (key: string) => boolean,
|
||||
): (req: Request, res: Response, next: NextFunction) => void {
|
||||
return (req: Request, res: Response, next: NextFunction): void => {
|
||||
// Only require auth for POST requests
|
||||
if (req.method !== 'POST') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
const apiKeyHeader = req.headers['x-api-key'] as string | undefined;
|
||||
|
||||
let apiKey: string | undefined;
|
||||
|
||||
if (authHeader?.startsWith('Bearer ') === true) {
|
||||
apiKey = authHeader.slice(7);
|
||||
} else if (apiKeyHeader !== undefined) {
|
||||
apiKey = apiKeyHeader;
|
||||
}
|
||||
|
||||
const apiKey = extractApiKey(req);
|
||||
if (apiKey === undefined || apiKey.length === 0) {
|
||||
logger.warn(
|
||||
{
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
ip: req.ip,
|
||||
},
|
||||
'POST request without API key',
|
||||
);
|
||||
res.status(401).json({ error: 'API key required for POST requests' });
|
||||
handleMissingApiKey(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateApiKey(apiKey)) {
|
||||
logger.warn(
|
||||
{
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
ip: req.ip,
|
||||
},
|
||||
'POST request with invalid API key',
|
||||
);
|
||||
res.status(403).json({ error: 'Invalid API key' });
|
||||
handleInvalidApiKey(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -5,82 +5,89 @@ import type { MsgCle, StoredKey } from '../types/message.js';
|
||||
import { validateMsgCle } from '../lib/validate.js';
|
||||
import { logger } from '../lib/logger.js';
|
||||
|
||||
function handleGetKeysInWindow(
|
||||
storage: StorageServiceInterface,
|
||||
req: Request,
|
||||
res: Response,
|
||||
): void {
|
||||
const startRaw = req.query.start as string | undefined;
|
||||
const endRaw = req.query.end as string | undefined;
|
||||
if (startRaw === undefined || endRaw === undefined) {
|
||||
res.status(400).json({ error: 'keys window requires start and end query params' });
|
||||
return;
|
||||
}
|
||||
const start = parseInt(startRaw, 10);
|
||||
const end = parseInt(endRaw, 10);
|
||||
if (Number.isNaN(start) || Number.isNaN(end)) {
|
||||
res.status(400).json({ error: 'invalid start or end' });
|
||||
return;
|
||||
}
|
||||
const stored = storage.getKeysInWindow(start, end);
|
||||
const keys: MsgCle[] = stored.map((k) => k.msg);
|
||||
res.json(keys);
|
||||
}
|
||||
|
||||
function handleGetKeysByHash(
|
||||
storage: StorageServiceInterface,
|
||||
req: Request,
|
||||
res: Response,
|
||||
): void {
|
||||
try {
|
||||
const hash = req.params.hash as string;
|
||||
if (hash.length === 0) {
|
||||
res.status(400).json({ error: 'Hash parameter required' });
|
||||
return;
|
||||
}
|
||||
const stored = storage.getKeys(hash);
|
||||
const keys: MsgCle[] = stored.map((k) => k.msg);
|
||||
res.json(keys);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error getting keys');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
function handlePostKey(
|
||||
storage: StorageServiceInterface,
|
||||
relay: RelayService,
|
||||
req: Request,
|
||||
res: Response,
|
||||
): void {
|
||||
void (async (): Promise<void> => {
|
||||
try {
|
||||
if (!validateMsgCle(req.body)) {
|
||||
res.status(400).json({ error: 'Invalid key format' });
|
||||
return;
|
||||
}
|
||||
const key = req.body as MsgCle;
|
||||
|
||||
const stored: StoredKey = {
|
||||
msg: key,
|
||||
received_at: Date.now(),
|
||||
relayed: false,
|
||||
};
|
||||
|
||||
storage.storeKey(stored);
|
||||
await relay.relayKey(key);
|
||||
stored.relayed = true;
|
||||
|
||||
res.status(201).json({ stored: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error storing key');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export function createKeysRouter(
|
||||
storage: StorageServiceInterface,
|
||||
relay: RelayService,
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* GET /keys?start=&end= - Get decryption keys in time window (received_at).
|
||||
* Used for scan-first flow: fetch keys, then fetch messages by hash.
|
||||
*/
|
||||
router.get('/', (req: Request, res: Response): void => {
|
||||
const startRaw = req.query.start as string | undefined;
|
||||
const endRaw = req.query.end as string | undefined;
|
||||
if (startRaw === undefined || endRaw === undefined) {
|
||||
res.status(400).json({ error: 'keys window requires start and end query params' });
|
||||
return;
|
||||
}
|
||||
const start = parseInt(startRaw, 10);
|
||||
const end = parseInt(endRaw, 10);
|
||||
if (Number.isNaN(start) || Number.isNaN(end)) {
|
||||
res.status(400).json({ error: 'invalid start or end' });
|
||||
return;
|
||||
}
|
||||
const stored = storage.getKeysInWindow(start, end);
|
||||
const keys: MsgCle[] = stored.map((k) => k.msg);
|
||||
res.json(keys);
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /keys/:hash - Get decryption keys for a message hash.
|
||||
*/
|
||||
router.get('/:hash', (req: Request, res: Response): void => {
|
||||
try {
|
||||
const hash = req.params.hash as string;
|
||||
if (hash.length === 0) {
|
||||
res.status(400).json({ error: 'Hash parameter required' });
|
||||
return;
|
||||
}
|
||||
const stored = storage.getKeys(hash);
|
||||
const keys: MsgCle[] = stored.map((k) => k.msg);
|
||||
res.json(keys);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error getting keys');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /keys - Store and relay a decryption key.
|
||||
*/
|
||||
router.post('/', (req: Request, res: Response): void => {
|
||||
void (async (): Promise<void> => {
|
||||
try {
|
||||
if (!validateMsgCle(req.body)) {
|
||||
res.status(400).json({ error: 'Invalid key format' });
|
||||
return;
|
||||
}
|
||||
const key = req.body as MsgCle;
|
||||
|
||||
const stored: StoredKey = {
|
||||
msg: key,
|
||||
received_at: Date.now(),
|
||||
relayed: false,
|
||||
};
|
||||
|
||||
storage.storeKey(stored);
|
||||
await relay.relayKey(key);
|
||||
stored.relayed = true;
|
||||
|
||||
res.status(201).json({ stored: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error storing key');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
})();
|
||||
});
|
||||
router.get('/', (req, res) => handleGetKeysInWindow(storage, req, res));
|
||||
router.get('/:hash', (req, res) => handleGetKeysByHash(storage, req, res));
|
||||
router.post('/', (req, res) => handlePostKey(storage, relay, req, res));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@ -5,91 +5,98 @@ import type { MsgChiffre, StoredMessage } from '../types/message.js';
|
||||
import { validateMsgChiffre } from '../lib/validate.js';
|
||||
import { logger } from '../lib/logger.js';
|
||||
|
||||
function handleGetMessages(
|
||||
storage: StorageServiceInterface,
|
||||
req: Request,
|
||||
res: Response,
|
||||
): void {
|
||||
try {
|
||||
const start = parseInt(req.query.start as string, 10);
|
||||
const end = parseInt(req.query.end as string, 10);
|
||||
const service = req.query.service as string | undefined;
|
||||
|
||||
if (isNaN(start) || isNaN(end)) {
|
||||
res.status(400).json({ error: 'start and end timestamps required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = storage.getMessages(start, end, service);
|
||||
const msgChiffres: MsgChiffre[] = messages.map((m) => m.msg);
|
||||
res.json(msgChiffres);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error getting messages');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
function handlePostMessage(
|
||||
storage: StorageServiceInterface,
|
||||
relay: RelayService,
|
||||
req: Request,
|
||||
res: Response,
|
||||
): void {
|
||||
void (async (): Promise<void> => {
|
||||
try {
|
||||
if (!validateMsgChiffre(req.body)) {
|
||||
res.status(400).json({ error: 'Invalid message format' });
|
||||
return;
|
||||
}
|
||||
const msg = req.body as MsgChiffre;
|
||||
|
||||
const alreadySeen = storage.hasSeenHash(msg.hash);
|
||||
const stored: StoredMessage = {
|
||||
msg,
|
||||
received_at: Date.now(),
|
||||
relayed: false,
|
||||
};
|
||||
|
||||
storage.storeMessage(stored);
|
||||
|
||||
if (!alreadySeen) {
|
||||
await relay.relayMessage(msg);
|
||||
stored.relayed = true;
|
||||
}
|
||||
|
||||
res.status(201).json({ hash: msg.hash, stored: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error storing message');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function handleGetMessageByHash(
|
||||
storage: StorageServiceInterface,
|
||||
req: Request,
|
||||
res: Response,
|
||||
): void {
|
||||
try {
|
||||
const hash = req.params.hash as string;
|
||||
if (hash.length === 0) {
|
||||
res.status(400).json({ error: 'Hash parameter required' });
|
||||
return;
|
||||
}
|
||||
const stored = storage.getMessage(hash);
|
||||
if (stored === undefined) {
|
||||
res.status(404).json({ error: 'Message not found' });
|
||||
return;
|
||||
}
|
||||
res.json(stored.msg);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error getting message by hash');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
export function createMessagesRouter(
|
||||
storage: StorageServiceInterface,
|
||||
relay: RelayService,
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* GET /messages - Retrieve encrypted messages in a time window.
|
||||
* Query params: start (timestamp), end (timestamp), service (optional service UUID)
|
||||
*/
|
||||
router.get('/', (req: Request, res: Response): void => {
|
||||
try {
|
||||
const start = parseInt(req.query.start as string, 10);
|
||||
const end = parseInt(req.query.end as string, 10);
|
||||
const service = req.query.service as string | undefined;
|
||||
|
||||
if (isNaN(start) || isNaN(end)) {
|
||||
res.status(400).json({ error: 'start and end timestamps required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = storage.getMessages(start, end, service);
|
||||
const msgChiffres: MsgChiffre[] = messages.map((m) => m.msg);
|
||||
res.json(msgChiffres);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error getting messages');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /messages - Store and relay an encrypted message.
|
||||
*/
|
||||
router.post('/', (req: Request, res: Response): void => {
|
||||
void (async (): Promise<void> => {
|
||||
try {
|
||||
if (!validateMsgChiffre(req.body)) {
|
||||
res.status(400).json({ error: 'Invalid message format' });
|
||||
return;
|
||||
}
|
||||
const msg = req.body as MsgChiffre;
|
||||
|
||||
const alreadySeen = storage.hasSeenHash(msg.hash);
|
||||
const stored: StoredMessage = {
|
||||
msg,
|
||||
received_at: Date.now(),
|
||||
relayed: false,
|
||||
};
|
||||
|
||||
storage.storeMessage(stored);
|
||||
|
||||
if (!alreadySeen) {
|
||||
await relay.relayMessage(msg);
|
||||
stored.relayed = true;
|
||||
}
|
||||
|
||||
res.status(201).json({ hash: msg.hash, stored: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error storing message');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /messages/:hash - Get a specific message by hash.
|
||||
*/
|
||||
router.get('/:hash', (req: Request, res: Response): void => {
|
||||
try {
|
||||
const hash = req.params.hash as string;
|
||||
if (hash.length === 0) {
|
||||
res.status(400).json({ error: 'Hash parameter required' });
|
||||
return;
|
||||
}
|
||||
const stored = storage.getMessage(hash);
|
||||
if (stored === undefined) {
|
||||
res.status(404).json({ error: 'Message not found' });
|
||||
return;
|
||||
}
|
||||
res.json(stored.msg);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error getting message by hash');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
router.get('/', (req, res) => handleGetMessages(storage, req, res));
|
||||
router.post('/', (req, res) => handlePostMessage(storage, relay, req, res));
|
||||
router.get('/:hash', (req, res) => handleGetMessageByHash(storage, req, res));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@ -5,60 +5,66 @@ import type { MsgSignature, StoredSignature } from '../types/message.js';
|
||||
import { validateMsgSignature } from '../lib/validate.js';
|
||||
import { logger } from '../lib/logger.js';
|
||||
|
||||
function handleGetSignatures(
|
||||
storage: StorageServiceInterface,
|
||||
req: Request,
|
||||
res: Response,
|
||||
): void {
|
||||
try {
|
||||
const hash = req.params.hash as string;
|
||||
if (hash.length === 0) {
|
||||
res.status(400).json({ error: 'Hash parameter required' });
|
||||
return;
|
||||
}
|
||||
const stored = storage.getSignatures(hash);
|
||||
const signatures: MsgSignature[] = stored.map((s) => s.msg);
|
||||
res.json(signatures);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error getting signatures');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
function handlePostSignature(
|
||||
storage: StorageServiceInterface,
|
||||
relay: RelayService,
|
||||
req: Request,
|
||||
res: Response,
|
||||
): void {
|
||||
void (async (): Promise<void> => {
|
||||
try {
|
||||
if (!validateMsgSignature(req.body)) {
|
||||
res.status(400).json({ error: 'Invalid signature format' });
|
||||
return;
|
||||
}
|
||||
const sig = req.body as MsgSignature;
|
||||
|
||||
const stored: StoredSignature = {
|
||||
msg: sig,
|
||||
received_at: Date.now(),
|
||||
relayed: false,
|
||||
};
|
||||
|
||||
storage.storeSignature(stored);
|
||||
await relay.relaySignature(sig);
|
||||
stored.relayed = true;
|
||||
|
||||
res.status(201).json({ stored: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error storing signature');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export function createSignaturesRouter(
|
||||
storage: StorageServiceInterface,
|
||||
relay: RelayService,
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* GET /signatures/:hash - Get signatures for a message hash.
|
||||
*/
|
||||
router.get('/:hash', (req: Request, res: Response): void => {
|
||||
try {
|
||||
const hash = req.params.hash as string;
|
||||
if (hash.length === 0) {
|
||||
res.status(400).json({ error: 'Hash parameter required' });
|
||||
return;
|
||||
}
|
||||
const stored = storage.getSignatures(hash);
|
||||
const signatures: MsgSignature[] = stored.map((s) => s.msg);
|
||||
res.json(signatures);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error getting signatures');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /signatures - Store and relay a signature.
|
||||
*/
|
||||
router.post('/', (req: Request, res: Response): void => {
|
||||
void (async (): Promise<void> => {
|
||||
try {
|
||||
if (!validateMsgSignature(req.body)) {
|
||||
res.status(400).json({ error: 'Invalid signature format' });
|
||||
return;
|
||||
}
|
||||
const sig = req.body as MsgSignature;
|
||||
|
||||
const stored: StoredSignature = {
|
||||
msg: sig,
|
||||
received_at: Date.now(),
|
||||
relayed: false,
|
||||
};
|
||||
|
||||
storage.storeSignature(stored);
|
||||
await relay.relaySignature(sig);
|
||||
stored.relayed = true;
|
||||
|
||||
res.status(201).json({ stored: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error storing signature');
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
})();
|
||||
});
|
||||
router.get('/:hash', (req, res) => handleGetSignatures(storage, req, res));
|
||||
router.post('/', (req, res) => handlePostSignature(storage, relay, req, res));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ export class StorageService {
|
||||
const results: StoredMessage[] = [];
|
||||
const iter =
|
||||
candidates !== null
|
||||
? (function* (self: StorageService) {
|
||||
? (function* (self: StorageService): Generator<StoredMessage, void, unknown> {
|
||||
for (const h of candidates) {
|
||||
const m = self.messages.get(h);
|
||||
if (m !== undefined) {
|
||||
|
||||
@ -1,45 +1,3 @@
|
||||
⏳ Traitement: 200000/225802 UTXOs insérés...
|
||||
⏳ Traitement: 210000/225802 UTXOs insérés...
|
||||
⏳ Traitement: 220000/225802 UTXOs insérés...
|
||||
💾 Mise à jour des UTXOs dépensés...
|
||||
|
||||
📊 Résumé:
|
||||
- UTXOs vérifiés: 61609
|
||||
- UTXOs toujours disponibles: 61609
|
||||
- UTXOs dépensés détectés: 0
|
||||
|
||||
📈 Statistiques finales:
|
||||
- Total UTXOs: 68398
|
||||
- Dépensés: 6789
|
||||
- Non dépensés: 61609
|
||||
|
||||
✅ Synchronisation terminée
|
||||
🔍 Démarrage de la synchronisation des UTXOs dépensés...
|
||||
|
||||
📊 UTXOs à vérifier: 61609
|
||||
📡 Récupération des UTXOs depuis Bitcoin...
|
||||
📊 UTXOs disponibles dans Bitcoin: 225826
|
||||
💾 Création de la table temporaire...
|
||||
💾 Insertion des UTXOs disponibles par batch...
|
||||
⏳ Traitement: 10000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 20000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 30000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 40000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 50000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 60000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 70000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 80000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 90000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 100000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 110000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 120000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 130000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 140000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 150000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 160000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 170000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 180000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 190000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 200000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 210000/225826 UTXOs insérés...
|
||||
⏳ Traitement: 220000/225826 UTXOs insérés...
|
||||
@ -98,3 +56,45 @@
|
||||
- Non dépensés: 61609
|
||||
|
||||
✅ Synchronisation terminée
|
||||
🔍 Démarrage de la synchronisation des UTXOs dépensés...
|
||||
|
||||
📊 UTXOs à vérifier: 61609
|
||||
📡 Récupération des UTXOs depuis Bitcoin...
|
||||
📊 UTXOs disponibles dans Bitcoin: 225855
|
||||
💾 Création de la table temporaire...
|
||||
💾 Insertion des UTXOs disponibles par batch...
|
||||
⏳ Traitement: 10000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 20000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 30000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 40000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 50000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 60000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 70000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 80000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 90000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 100000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 110000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 120000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 130000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 140000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 150000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 160000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 170000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 180000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 190000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 200000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 210000/225855 UTXOs insérés...
|
||||
⏳ Traitement: 220000/225855 UTXOs insérés...
|
||||
💾 Mise à jour des UTXOs dépensés...
|
||||
|
||||
📊 Résumé:
|
||||
- UTXOs vérifiés: 61609
|
||||
- UTXOs toujours disponibles: 61609
|
||||
- UTXOs dépensés détectés: 0
|
||||
|
||||
📈 Statistiques finales:
|
||||
- Total UTXOs: 68398
|
||||
- Dépensés: 6789
|
||||
- Non dépensés: 61609
|
||||
|
||||
✅ Synchronisation terminée
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user