Podczas budowania systemu MEV na Solanie deweloperzy często napotykają klasyczny kompromis: szybkość Rusta wobec elastyczności Pythona.
Aby móc być zarówno szybkim jak gepard (wydajność wykonania), jak i sprytnym jak lis (elastyczność strategii) w „lesie ciemności”, zastosowaliśmy dwuwarstwowy projekt: płaszczyznę sterowania (Control Plane) zbudowaną w Pythonie, która odpowiada za koordynację strategii i zarządzanie konfiguracją, oraz płaszczyznę wykonania (Data Plane) zbudowaną w Rust, która odpowiada za przetwarzanie danych w trybie wysokiej konkurencji.
W tym artykule przeanalizujemy logikę tego architektury oraz sposób implementacji przemysłowego silnika harmonogramowania strategii w Pythonie.
1. Dlaczego potrzebujemy "Control Plane"?
Jeśli porównamy bota MEV do samochodu wyścigowego, silnik wykonawczy Rust to V12, który może znieść wysokie obroty, podczas gdy płaszczyzna kontrolna w Pythonie to deska rozdzielcza i dźwignia w kokpicie.
1.1 Rozdzielenie konfiguracji i logiki
Strategie MEV (takie jak arbitraż, snajperstwo, likwidacja) obejmują wiele parametrów: adresy węzłów RPC, limity Jito Tip, tokeny z białej listy, kontrola maksymalnego poślizgu itp.
Ból: Jeśli te konfiguracje są zakodowane na stałe w Rust, każda drobna zmiana parametrów wymaga ponownej kompilacji. Na zmieniającym się rynku, kilkadziesiąt sekund czasu kompilacji może wystarczyć, aby stracić okazję.
Rozwiązanie: Python odpowiada za odczyt plików konfiguracyjnych YAML/JSON, przetwarzanie logiki, a następnie wstrzykiwanie ich do procesu Rust w postaci argumentów wiersza poleceń lub zmiennych środowiskowych.
1.2 Jednolity punkt wejścia i zarządzanie wieloma strategiami
Dojrzały system często jednocześnie uruchamia różne strategie.
Arb (arbitraż): Długoterminowe uruchamianie, monitorowanie głównych pul.
Sniper (snajperstwo): Tymczasowe uruchamianie, skierowane na nowe tokeny.
Płaszczyzna kontrolna jako jednolity harmonogram (Commander) może uruchamiać różne instancje strategii za pomocą jednego przycisku, realizując "strategię jako wtyczkę".
2. Przegląd architektury: przekraczanie granic językowych i interfejsów
Podstawowa interakcja systemu opiera się na zasadzie **"jednokierunkowego wnioskowania, izolacji procesów"**:
sequenceDiagram
participant Dev as Programista
participant CP as Płaszczyzna kontrolna Python (Commander)
participant RS as Płaszczyzna wykonawcza Rust (Scavenger)
participant Node as Węzeł Solana/Jito
Dev->>CP: Wykonaj polecenie (np. --strategy arb)
CP->>CP: 1. Automatycznie znajdź odpowiedni plik konfiguracyjny (arb.yaml)
CP->>CP: 2. Sprawdź produkty kompilacji Rust (Binarne pliki release)
CP->>RS: 3. Uruchom proces Rust (przekazywany parametr: --config )
RS->>Node: 4. Ustanów WebSocket i połączenie gRPC
Note over RS,Node: Przetwarzanie danych o wysokiej przepustowości
Obowiązki płaszczyzny kontrolnej: sprawdzanie środowiska, automatyczne wnioskowanie ścieżek, zarządzanie cyklem życia procesu, eleganckie zakończenie (Graceful Shutdown).
Obowiązki płaszczyzny wykonawczej: analiza stanu konta, lokalne obliczenia cenowe, konstrukcja transakcji, przesyłanie pakietów.
3. Szczegóły realizacji technicznej
3.1 Adaptacja ścieżek i powrót do kompilacji
W środowisku produkcyjnym uruchamiamy bezpośrednio wstępnie skompilowane pliki binarne Rust Release, aby uzyskać najszybszy czas uruchamiania. Jednak w fazie rozwoju i debugowania chcemy, aby automatycznie wykrywało.
Pseudo kod logiki harmonogramu:
Sprawdź, czy w katalogu target/release/ istnieje plik binarny.
Jeśli istnieje, uruchom bezpośrednio subprocess.spawn.
Jeśli nie istnieje, wróć do cargo run --release.
3.2 Izolacja środowiska i ograniczenia katalogu roboczego
Bot MEV zwykle potrzebuje odczytać lokalny portfel (Keypair) i pliki pamięci podręcznej. Aby zapewnić bezpieczeństwo i spójność, płaszczyzna kontrolna musi ściśle ograniczać bieżący katalog roboczy procesu Rust (CWD). To skutecznie zapobiega dryfowaniu ścieżek w różnych środowiskach (Docker vs maszyna fizyczna).
4. Przykład kodu harmonogramu na poziomie przemysłowym
Poniżej znajduje się uproszczony przykład implementacji płaszczyzny kontrolnej w Pythonie. Demonstruje, jak zarządzać podprocesami i dynamicznie wstrzykiwać konfigurację.
import argparse
import os
import subprocess
import sys
from pathlib import Path
class BotCommander:
def init(self, strategy: str, config_name: str):
self.strategy = strategy
self.config_path = Path(f"configs/{config_name}.yaml").absolute()
self.root_dir = Path(__file__).parent.parent # Katalog główny projektu
self.engine_dir = self.root_dir / "engine_rust" # Katalog źródłowy Rust
def findbinary(self) -> list:
"""Wybierz polecenie do wykonania: preferuj użycie binarnego pliku release, w przeciwnym razie wróć do cargo run"""
release_bin = self.engine_dir / "target" / "release" / "mev_engine"
if release_bin.exists():
print(f"[*] Używanie wstępnie skompilowanego binarnego pliku: {release_bin}")
return [str(release_bin)]
print("[!] Nie znaleziono binarnego pliku release, próbuję uruchomić za pomocą cargo run...")
return ["cargo", "run", "--release", "--bin", "mev_engine", "--"]
def run(self):
# Zbuduj pełne polecenie do wykonania
base_cmd = self._find_binary()
args = [
"--strategy", self.strategy,
"--config", str(self.config_path)
]
full_cmd = base_cmd + args
print(f"[*] Uruchamianie strategii [{self.strategy}]...")
try:
# Użyj subprocess do uruchomienia wykonania, blokując katalog roboczy
subprocess.run(full_cmd, cwd=self.engine_dir, check=True)
except KeyboardInterrupt:
print("\n[!] Otrzymano sygnał zatrzymania, zamykam bota...")
except subprocess.CalledProcessError as e:
print(f"[X] Silnik wykonania uległ awarii, kod wyjścia: {e.returncode}")
if name == "__main__":
parser = argparse.ArgumentParser(description="Solana MEV kontrola płaszczyzny")
parser.add_argument("--strategy", default="arbitrage", help="Wybierz strategię uruchomienia")
parser.add_argument("--config", default="mainnet_alpha", help="Nazwa pliku konfiguracyjnego")
cmd_args = parser.parse_args()
commander = BotCommander(cmd_args.strategy, cmd_args.config)
commander.run()
5. Optymalizacja wydajności i myślenie o operacjach
W rzeczywistej produkcji projekt płaszczyzny kontrolnej musi również uwzględniać następujące punkty:
Wstępne uruchamianie (Warm-up): przed oficjalnym uruchomieniem monitorowania arbitrażu, płaszczyzna kontrolna może najpierw uruchomić prosty skrypt Pythona, aby sprawdzić opóźnienie węzła RPC i saldo portfela, aby upewnić się, że "wszystko jest w porządku" zanim przekażemy pałeczkę Rust.
Rozdział logów: Rust generuje strukturalne dzienniki JSON, a Python zbiera je i przesyła do zdalnego monitorowania (np. Loki lub Telegram Bot).
Strategia gorącej aktualizacji: dla tokenów z czarnej listy, które nie wymagają zmiany logiki kodu, można wykorzystać mechanizm monitorowania plików (Watcher). Kiedy Python zmienia plik konfiguracyjny, Rust ponownie ładowałby go na bieżąco przez bibliotekę notify, bez potrzeby restartowania procesu.
6. Zapowiedź kolejnego kroku
Mając płaszczyznę kontrolną jako zabezpieczenie, możemy pewnie wejść w świat Rust. W następnym artykule przeanalizujemy "monitorowanie oparte na inwentarzu (Inventory-Driven Monitoring)" - jak zbudować wydajny indeks wszystkich tokenów i pul płynności w Rust, aby natychmiast zablokować możliwość wygranej w ogromnym strumieniu transakcji.
Artykuł napisany przez Levi.eth. W świecie Solana MEV, mały krok optymalizacji architektury często prowadzi do dużego kroku w zyskach.
