Работа с провайдерами в блокчейн-модуле Saga¶
Общая архитектура работы с провайдерами¶
В системе Saga реализован модуль для работы с различными блокчейн-провайдерами через ProviderManager, который является составной частью BlockchainService. ProviderManager отвечает за управление подключениями к различным блокчейн-сетям через настраиваемые RPC-провайдеры.
Структура ProviderManager¶
ProviderManager определен в файле backend/blockchain/service/blockchain_service.go и имеет следующую структуру:
// ProviderManager управляет блокчейн-провайдерами
type ProviderManager struct {
repo *repository.BlockchainRepository
clients map[string]*ethclient.Client
}
Где:
repo- ссылка на репозиторий блокчейн-данныхclients- карта клиентов Ethereum, где ключ - имя блокчейн-сети, а значение - клиент для взаимодействия с этой сетью
Хранение информации о провайдерах¶
Информация о блокчейн-провайдерах хранится в базе данных PostgreSQL в таблице blockchain_providers. Каждый провайдер представлен структурой BlockchainProvider в models/blockchain_models.go:
// BlockchainProvider представляет информацию о провайдере блокчейн-сети
type BlockchainProvider struct {
ID string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
BlockchainNetwork string `json:"blockchain_network" db:"blockchain_network"`
ProviderType string `json:"provider_type" db:"provider_type"`
URL string `json:"url" db:"url"`
APIKey string `json:"api_key,omitempty" db:"-"`
Priority int `json:"priority" db:"priority"`
Status string `json:"status" db:"status"`
HealthCheckURL string `json:"health_check_url" db:"health_check_url"`
TimeoutMS int `json:"timeout_ms" db:"timeout_ms"`
HealthStatus string `json:"health_status" db:"health_status"`
LastHealthCheck time.Time `json:"last_health_check" db:"last_health_check"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
Инициализация провайдеров¶
Инициализация подключений к провайдерам происходит в методе Initialize сервиса BlockchainService:
// Initialize инициализирует сервис блокчейна, создавая соединения с провайдерами
func (s *BlockchainService) Initialize(ctx context.Context) error {
// Получаем все активные сети
networks, err := s.repo.GetBlockchainNetworks(ctx)
if err != nil {
return fmt.Errorf("ошибка при получении списка блокчейн-сетей: %w", err)
}
for _, network := range networks {
if network.Status != models.NetworkStatusActive {
continue
}
// Получаем провайдеров для этой сети
providers, err := s.repo.GetProviders(ctx, network.Name)
if err != nil {
return fmt.Errorf("ошибка при получении провайдеров для сети %s: %w", network.Name, err)
}
// Пытаемся подключиться к первому доступному провайдеру
for _, provider := range providers {
if provider.Status != "active" {
continue
}
client, err := ethclient.Dial(provider.URL)
if err != nil {
continue // Пробуем следующего провайдера
}
// Сохраняем клиент для всех компонентов
s.walletManager.clients[network.Name] = client
s.txManager.clients[network.Name] = client
s.monitorService.clients[network.Name] = client
s.providerManager.clients[network.Name] = client
break
}
}
return nil
}
Процесс инициализации включает следующие шаги:
- Получение списка всех активных блокчейн-сетей
- Для каждой сети получение списка провайдеров (отсортированных по приоритету)
- Попытка подключиться к первому доступному провайдеру
- Сохранение клиента для использования в различных компонентах системы
Получение провайдеров из репозитория¶
За получение провайдеров из базы данных отвечает метод GetProviders в BlockchainRepository:
// GetProviders возвращает список всех блокчейн-провайдеров
func (r *BlockchainRepository) GetProviders(ctx context.Context, networkName string) ([]models.BlockchainProvider, error) {
var providers []models.BlockchainProvider
query := `
SELECT id, name, blockchain_network, provider_type, url, priority,
status, health_check_url, timeout_ms, health_status, last_health_check,
created_at, updated_at
FROM blockchain_providers
WHERE blockchain_network = $1 AND status != 'deleted'
ORDER BY priority ASC
`
err := r.db.SelectContext(ctx, &providers, query, networkName)
if err != nil {
return nil, fmt.Errorf("ошибка при получении списка провайдеров для сети %s: %w", networkName, err)
}
return providers, nil
}
Провайдеры сортируются по полю priority для обеспечения выбора наиболее предпочтительного провайдера.
Использование провайдеров в системе¶
ProviderManager используется для взаимодействия с блокчейн-сетями в различных компонентах системы:
1. Проверка статуса сети¶
// GetNetworkStatus получает статус блокчейн-сети
func (s *BlockchainService) GetNetworkStatus(ctx context.Context, blockchain string) (*models.BlockchainNetwork, error) {
// Получаем информацию о сети из БД
network, err := s.repo.GetBlockchainNetwork(ctx, blockchain)
if err != nil {
return nil, fmt.Errorf("ошибка при получении информации о сети: %w", err)
}
// Проверяем наличие клиента для сети
client, ok := s.providerManager.clients[blockchain]
if !ok {
network.Status = models.NetworkStatusInactive
return network, nil
}
// Пробуем получить номер последнего блока для проверки соединения
_, err = client.BlockNumber(ctx)
if err != nil {
network.Status = models.NetworkStatusDegraded
return network, nil
}
// Обновляем статус на активный
network.Status = models.NetworkStatusActive
return network, nil
}
2. Получение цены газа¶
// GetGasPrice получает текущую цену газа для указанной блокчейн-сети
func (s *BlockchainService) GetGasPrice(ctx context.Context, blockchain string) (map[string]interface{}, error) {
// Проверяем наличие клиента для сети
client, ok := s.providerManager.clients[blockchain]
if !ok {
return nil, ErrProviderNotAvailable
}
// Получаем цену газа из блокчейна
gasPrice, err := client.SuggestGasPrice(ctx)
if err != nil {
return nil, fmt.Errorf("ошибка при получении цены газа: %w", err)
}
// Формируем ответ
result := map[string]interface{}{
"network": blockchain,
"gas_price": gasPrice.String(),
"timestamp": time.Now().Unix(),
}
return result, nil
}
Обработка ошибок провайдеров¶
В системе определены специальные ошибки для обработки проблем с провайдерами:
Эта ошибка возвращается, когда провайдер для запрошенной сети недоступен или отсутствует.
Поддерживаемые блокчейн-сети¶
Система использует ЕДИНУЮ VPS блокчейн-сеть согласно архитектурному принципу VPS-FIRST:
// Единая Saga VPS production конфигурация
var sagaVPSConfig = map[string]string{
"network_id": "1337", // Saga VPS Production
"rpc_url": "http://188.42.218.164:8545", // VPS нода
"usdc_token": "0x5FbDB2315678afecb367f032d93F642f64180aa3", // USDC контракт (детерминистический)
"name": "Saga VPS Production", // Единое название сети
}
Резервирование и отказоустойчивость¶
Текущая реализация выбирает первый доступный провайдер для каждой сети. При недоступности этого провайдера система помечает сеть как degraded или inactive.
В будущих версиях может быть реализована более сложная стратегия с автоматическим переключением между провайдерами в случае отказа.
API для работы с провайдерами¶
API модуля блокчейн содержит следующие методы для работы с провайдерами:
// BlockchainGetStatus получает текущий статус блокчейн-сети
func (api *BlockchainAPI) BlockchainGetStatus(r *http.Request, args *BlockchainStatusArgs, reply *BlockchainStatusResponse) error {
// ...
}
Заключение¶
Архитектура работы с провайдерами в системе Saga построена на базе модуля ProviderManager, который обеспечивает управление подключением к единой VPS блокчейн-сети. Провайдеры хранятся в базе данных и инициализируются при запуске системы.
Система использует исключительно Saga VPS Production (Network ID: 1337) согласно архитектурному принципу VPS-FIRST.