feat(visualizer): add runtime architecture diagram

This commit is contained in:
u1
2026-03-13 22:33:28 +01:00
parent 5a9d61d674
commit a674d40453
12 changed files with 4953 additions and 76 deletions

View File

@@ -0,0 +1,424 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>mevnode topology</title>
<style>
:root {
--bg: #f4efe7;
--panel: #fffaf2;
--ink: #1f1c17;
--muted: #6b6358;
--line: #d9ccb7;
--accent: #b6412f;
--accent-2: #0e6f74;
--accent-3: #7c5e10;
--shadow: 0 18px 40px rgba(31, 28, 23, 0.08);
--radius: 18px;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
background:
radial-gradient(circle at top left, rgba(182, 65, 47, 0.12), transparent 28%),
radial-gradient(circle at top right, rgba(14, 111, 116, 0.12), transparent 24%),
linear-gradient(180deg, #fbf7f0 0%, var(--bg) 100%);
color: var(--ink);
}
.page {
max-width: 1480px;
margin: 0 auto;
padding: 32px 20px 48px;
}
.hero {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 24px;
margin-bottom: 24px;
}
.hero-card,
.legend,
.node,
.strip,
.note {
background: rgba(255, 250, 242, 0.88);
border: 1px solid rgba(217, 204, 183, 0.9);
border-radius: var(--radius);
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
}
.hero-card {
padding: 28px;
}
.eyebrow {
display: inline-block;
padding: 6px 10px;
border-radius: 999px;
border: 1px solid rgba(182, 65, 47, 0.22);
color: var(--accent);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1 {
margin: 16px 0 10px;
font-family: "IBM Plex Serif", Georgia, serif;
font-size: clamp(2rem, 4vw, 3.8rem);
line-height: 0.98;
}
.hero-card p,
.legend p,
.note p,
.box p,
.strip p {
margin: 0;
color: var(--muted);
line-height: 1.55;
}
.legend {
padding: 24px;
display: grid;
gap: 14px;
align-content: start;
}
.legend-item {
display: flex;
align-items: center;
gap: 12px;
color: var(--ink);
font-weight: 600;
}
.swatch {
width: 14px;
height: 14px;
border-radius: 999px;
flex: 0 0 14px;
}
.swatch.read { background: var(--accent-2); }
.swatch.write { background: var(--accent); }
.swatch.ops { background: var(--accent-3); }
.swatch.store { background: #4a5565; }
.diagram {
display: grid;
grid-template-columns: minmax(280px, 0.9fr) 120px minmax(320px, 1.25fr);
gap: 18px;
align-items: stretch;
}
.node {
padding: 20px;
}
.node h2 {
margin: 0 0 12px;
font-size: 1.35rem;
font-family: "IBM Plex Serif", Georgia, serif;
}
.node-head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 12px;
margin-bottom: 14px;
}
.node-head span {
color: var(--muted);
font-size: 13px;
font-weight: 600;
}
.stack {
display: grid;
gap: 10px;
}
.box {
border: 1px solid var(--line);
border-radius: 14px;
padding: 14px;
background: #fffdf8;
}
.box h3 {
margin: 0 0 6px;
font-size: 1rem;
}
.box ul {
margin: 10px 0 0;
padding-left: 18px;
color: var(--ink);
}
.box li {
margin: 6px 0;
line-height: 1.45;
}
.box.read { border-left: 6px solid var(--accent-2); }
.box.write { border-left: 6px solid var(--accent); }
.box.ops { border-left: 6px solid var(--accent-3); }
.box.store { border-left: 6px solid #4a5565; }
.bridge {
display: grid;
grid-template-rows: 1fr auto 1fr auto 1fr;
align-items: center;
justify-items: center;
color: var(--muted);
font-weight: 700;
}
.bridge-line {
width: 2px;
height: 100%;
background:
linear-gradient(180deg,
rgba(14, 111, 116, 0.95) 0%,
rgba(14, 111, 116, 0.95) 38%,
rgba(182, 65, 47, 0.95) 38%,
rgba(182, 65, 47, 0.95) 72%,
rgba(124, 94, 16, 0.95) 72%,
rgba(124, 94, 16, 0.95) 100%);
border-radius: 999px;
min-height: 88px;
}
.bridge-tag {
writing-mode: vertical-rl;
transform: rotate(180deg);
padding: 10px 0;
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.strips {
margin-top: 24px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
}
.strip {
padding: 18px;
}
.strip h3 {
margin: 0 0 10px;
font-size: 1rem;
}
.strip code,
.note code {
font-family: "IBM Plex Mono", "Fira Code", monospace;
font-size: 0.95em;
background: rgba(31, 28, 23, 0.06);
padding: 2px 6px;
border-radius: 6px;
}
.note {
margin-top: 24px;
padding: 18px 20px;
}
@media (max-width: 1120px) {
.hero,
.diagram,
.strips {
grid-template-columns: 1fr;
}
.bridge {
grid-template-columns: 1fr auto 1fr auto 1fr;
grid-template-rows: unset;
}
.bridge-line {
width: 100%;
height: 2px;
min-height: 2px;
}
.bridge-tag {
writing-mode: horizontal-tb;
transform: none;
}
}
</style>
</head>
<body>
<div class="page">
<section class="hero">
<div class="hero-card">
<div class="eyebrow">Topology / mev stack</div>
<h1>mevnode_sol daje chain access. mevnode_bot robi produkt, ingest i execution.</h1>
<p>
Ten układ rozdziela warstwę validatora od warstwy aplikacyjnej. Na <strong>sol</strong>
trzymasz tylko Agave, Yellowstone i lokalne RPC. Na <strong>bot</strong> działa
<strong>single-node k3s</strong> z frontendem, API, DLOB, strategiami i bazami danych.
</p>
</div>
<aside class="legend">
<div class="legend-item"><span class="swatch read"></span> Read path: Yellowstone / live stream</div>
<div class="legend-item"><span class="swatch write"></span> Write path: RPC / TPU / Jito</div>
<div class="legend-item"><span class="swatch ops"></span> Ops path: monitoring, metryki, GitOps</div>
<div class="legend-item"><span class="swatch store"></span> Data path: Postgres, Redis, analityka</div>
<p>
Zasada jest prosta: <strong>na bot nie trafia nigdy Agave ani ledger</strong>.
`mevnode_bot` ma tylko klienty, logikę tradingową i interfejs użytkownika.
</p>
</aside>
</section>
<section class="diagram">
<article class="node">
<div class="node-head">
<h2>mevnode_sol</h2>
<span>bare metal / validator host</span>
</div>
<div class="stack">
<div class="box read">
<h3>Agave validator</h3>
<p>Źródło stanu chaina, snapshotów i lokalnego read/write RPC.</p>
<ul>
<li><code>127.0.0.1:8899</code> RPC</li>
<li><code>10.91.0.1:10000</code> Yellowstone gRPC</li>
<li><code>10.91.0.1:8999</code> Prometheus plugin metrics</li>
</ul>
</div>
<div class="box write">
<h3>Write capability</h3>
<p>Bot może później używać lokalnego RPC przez proxy lub iść bezpośrednio przez TPU/Jito.</p>
</div>
<div class="box ops">
<h3>Node-only responsibilities</h3>
<ul>
<li>snapshot recovery</li>
<li>accounts index</li>
<li>geyser / yellowstone plugin</li>
<li>node metrics</li>
</ul>
</div>
</div>
</article>
<div class="bridge" aria-hidden="true">
<div class="bridge-line"></div>
<div class="bridge-tag">wg0 / yellowstone / rpc / metrics</div>
<div class="bridge-line"></div>
<div class="bridge-tag">low latency read + controlled write</div>
<div class="bridge-line"></div>
</div>
<article class="node">
<div class="node-head">
<h2>mevnode_bot</h2>
<span>single-node k3s / app + execution host</span>
</div>
<div class="stack">
<div class="box read">
<h3>Read plane</h3>
<ul>
<li><strong>yellowstone-consumer</strong>: bierze live stream z <code>10.91.0.1:10000</code></li>
<li><strong>trade-ingestor</strong>: transformuje i dystrybuuje dane aplikacyjne</li>
<li><strong>dlob-publisher</strong>: dociąga stan kont przez RPC i buduje DLOB</li>
<li><strong>dlob-server</strong>: wystawia gotowy widok dla API i UI</li>
</ul>
</div>
<div class="box write">
<h3>Execution plane</h3>
<ul>
<li><strong>strategy-engine</strong>: decyzje tradingowe</li>
<li><strong>risk-manager</strong>: limity i walidacja</li>
<li><strong>order-manager</strong>: lifecycle zleceń</li>
<li><strong>tx-router</strong>: wybiera <code>RPC</code>, <code>TPU</code> albo <code>Jito</code></li>
</ul>
</div>
<div class="box store">
<h3>Data plane</h3>
<ul>
<li><strong>Postgres</strong>: trwały storage, pozycje, historia, config</li>
<li><strong>Hasura</strong>: warstwa GraphQL nad Postgres</li>
<li><strong>Redis</strong>: cache, pub/sub, szybki stan DLOB</li>
<li><strong>ClickHouse</strong>: opcjonalnie później pod cięższą analitykę</li>
</ul>
</div>
<div class="box ops">
<h3>Product and ops</h3>
<ul>
<li><strong>frontend</strong> + <strong>api</strong> + <strong>ingress</strong></li>
<li><strong>Prometheus</strong> + <strong>Grafana</strong></li>
<li><strong>Portainer agent</strong> dla widoczności klastra w Portainerze</li>
<li><strong>GitOps</strong>: Argo lub Flux z Gitea</li>
</ul>
</div>
</div>
</article>
</section>
<section class="strips">
<div class="strip">
<h3>Read path</h3>
<p>
<code>Yellowstone gRPC</code> daje live account updates, sloty i transakcje.
<code>Agave RPC</code> zostaje do odczytów punktowych typu
<code>getAccountInfo</code>, <code>getMultipleAccounts</code>,
<code>getHealth</code> i <code>getLatestBlockhash</code>.
</p>
</div>
<div class="strip">
<h3>Write path</h3>
<p>
Wysyłka transakcji nie idzie przez Yellowstone. To robi
<code>RPC</code>, <code>TPU</code> albo <code>Jito</code>.
Dzięki temu read plane i write plane są od siebie operacyjnie odseparowane.
</p>
</div>
<div class="strip">
<h3>Deployment rule</h3>
<p>
Wszystko aplikacyjne i webowe ląduje na <code>mevnode_bot</code>.
Wszystko validatorowe zostaje na <code>mevnode_sol</code>.
To upraszcza debug, upgrade i kontrolę ryzyka.
</p>
</div>
</section>
<section class="note">
<p>
Docelowy przepływ: <code>mevnode_sol -> wg0 -> yellowstone-consumer / dlob-publisher -> redis / postgres -> api -> frontend</code>.
Osobno działa execution path: <code>strategy-engine -> tx-router -> RPC / TPU / Jito</code>.
</p>
</section>
</div>
</body>
</html>