package rest import ( "context" "database/sql" "encoding/json" "net/http" "time" ) // handleGetBlockByNumber handles GET /api/v1/blocks/{chain_id}/{number} func (s *Server) handleGetBlockByNumber(w http.ResponseWriter, r *http.Request, blockNumber int64) { if !s.requireDB(w) { return } // Validate input (already validated in routes.go, but double-check) if blockNumber < 0 { writeValidationError(w, ErrInvalidBlockNumber) return } // Add query timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() query := ` SELECT chain_id, number, hash, parent_hash, timestamp, timestamp_iso, miner, transaction_count, gas_used, gas_limit, size, logs_bloom FROM blocks WHERE chain_id = $1 AND number = $2 ` var chainID, number, transactionCount int var hash, parentHash, miner string var timestamp time.Time var timestampISO sql.NullString var gasUsed, gasLimit, size int64 var logsBloom sql.NullString err := s.db.QueryRow(ctx, query, s.chainID, blockNumber).Scan( &chainID, &number, &hash, &parentHash, ×tamp, ×tampISO, &miner, &transactionCount, &gasUsed, &gasLimit, &size, &logsBloom, ) if err != nil { writeNotFound(w, "Block") return } block := map[string]interface{}{ "chain_id": chainID, "number": number, "hash": hash, "parent_hash": parentHash, "timestamp": timestamp, "miner": miner, "transaction_count": transactionCount, "gas_used": gasUsed, "gas_limit": gasLimit, "size": size, } if timestampISO.Valid { block["timestamp_iso"] = timestampISO.String } if logsBloom.Valid { block["logs_bloom"] = logsBloom.String } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "data": block, }) } // handleGetBlockByHash handles GET /api/v1/blocks/{chain_id}/hash/{hash} func (s *Server) handleGetBlockByHash(w http.ResponseWriter, r *http.Request, hash string) { if !s.requireDB(w) { return } hash = normalizeHash(hash) // Validate hash format (already validated in routes.go, but double-check) if !isValidHash(hash) { writeValidationError(w, ErrInvalidHash) return } // Add query timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() query := ` SELECT chain_id, number, hash, parent_hash, timestamp, timestamp_iso, miner, transaction_count, gas_used, gas_limit, size, logs_bloom FROM blocks WHERE chain_id = $1 AND hash = $2 ` var chainID, number, transactionCount int var blockHash, parentHash, miner string var timestamp time.Time var timestampISO sql.NullString var gasUsed, gasLimit, size int64 var logsBloom sql.NullString err := s.db.QueryRow(ctx, query, s.chainID, hash).Scan( &chainID, &number, &blockHash, &parentHash, ×tamp, ×tampISO, &miner, &transactionCount, &gasUsed, &gasLimit, &size, &logsBloom, ) if err != nil { writeNotFound(w, "Block") return } block := map[string]interface{}{ "chain_id": chainID, "number": number, "hash": blockHash, "parent_hash": parentHash, "timestamp": timestamp, "miner": miner, "transaction_count": transactionCount, "gas_used": gasUsed, "gas_limit": gasLimit, "size": size, } if timestampISO.Valid { block["timestamp_iso"] = timestampISO.String } if logsBloom.Valid { block["logs_bloom"] = logsBloom.String } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "data": block, }) }