Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ jobs:
steps:
- uses: actions/checkout@v4

# Pin to the version in rust-toolchain.toml so rustfmt/clippy components
# are installed for the toolchain cargo actually selects.
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.90.0
with:
components: rustfmt, clippy

Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ jobs:
steps:
- uses: actions/checkout@v4

# Pin to rust-toolchain.toml so the matrix target's std is installed for
# the toolchain cargo selects.
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.90.0
with:
targets: ${{ matrix.target }}

Expand Down
92 changes: 92 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Architecture

`chainbench-grpc` is organized into four layers with dependencies pointing
**inward**. The domain is the stable core; everything else depends on it, and it
depends on nothing in the crate.

```
┌─────────────────────────────────────────────┐
│ presentation │ console · json · csv · html
└───────────────┬───────────────┬─────────────┘
│ │
┌───────────────▼─────┐ │
│ application │ │ use-case pipelines
│ run · slots · │ │ (orchestration)
│ throughput │ │
└───────┬─────────────┘ │
│ │
┌───────────▼───────────┐ │
│ infrastructure │ │ I/O adapters
│ geyser · proto · │ │ (gRPC, NTP, TOML)
│ sntp · config_file │ │
└───────────┬───────────┘ │
│ │
┌───────▼───────────────────────▼─────┐
│ domain │ pure model + logic
│ analysis · scoring · collector · │
│ timing · clock · config · warmup │
└──────────────────────────────────────┘
```

**Dependency rule.** `domain` imports nothing from the other layers.
`infrastructure` imports only `domain` (it adapts protobuf/NTP/TOML *to* domain
types). `application` imports `domain` + `infrastructure`. `presentation`
imports `domain` + `application` result types. `main.rs` is the composition
root that wires them together and selects a renderer.

## Module map

| Layer | Module | Responsibility |
|---|---|---|
| domain | `analysis` | `RunSummary`/`EndpointSummary`, percentiles, win-rate, backfill + clock-skew handling |
| domain | `scoring` | composite 0–100 score (fixed-threshold weights) |
| domain | `collector` | `Comparator` + `TransactionAccumulator` (dedup, earliest-wins, emit-once) |
| domain | `timing` | `TransactionData`/`TimestampSource` value objects; `observe()` |
| domain | `clock` | `ClockOffset` value object + offset formula |
| domain | `config` | `BenchConfig`, `Endpoint`, `EndpointKind`, `ArgsCommitment` |
| domain | `warmup` | `WarmupGuard` |
| application | `run` | race/latency/full pipeline (spawn providers, shutdown, summarize) |
| application | `slots` | slot-lifecycle pipeline + computation |
| application | `throughput` | throughput pipeline |
| infrastructure | `geyser` | `GeyserProvider` trait, factory, `ProviderContext`, Yellowstone provider + gRPC client; protobuf↔domain conversions |
| infrastructure | `proto` | generated protobuf (build-time) |
| infrastructure | `sntp` | NTP probe over UDP → `ClockOffset` |
| infrastructure | `config_file` | TOML config loading |
| presentation | `output` | console tables, JSON, CSV |
| presentation | `html` | standalone HTML report |

## Ubiquitous language

- **Endpoint** — a gRPC provider under test.
- **Observation** — one endpoint receiving one transaction signature, carrying
server (`created_at`) and client timestamps (`domain::timing`).
- **Comparator** — aggregates observations per signature across endpoints
(`domain::collector`).
- **Run / Summary** — a completed benchmark and its computed metrics
(`domain::analysis`).
- **Provider** — an adapter that streams observations from an endpoint
(`infrastructure::geyser`).
- **Clock offset** — host-vs-UTC skew used to correct absolute latency
(`domain::clock`, probed by `infrastructure::sntp`).

## How a `race`/`latency`/`full` run flows

1. **`main`** parses the CLI, builds a `BenchConfig` + `Endpoint`s
(`domain::config`), and resolves the clock offset (probing
`infrastructure::sntp`).
2. **`application::run::run_comparison`** spawns one `infrastructure::geyser`
provider per endpoint. Each provider converts protobuf updates into
`domain::timing` observations and feeds the shared `domain::collector::Comparator`.
3. When the transaction target is hit (or Ctrl+C / max-duration), the pipeline
calls `domain::analysis::compute_run_summary`, which uses `domain::scoring`.
4. **`main`** hands the `RunSummary` to a `presentation` renderer.

`slots` and `throughput` follow the same shape with their own pipelines.

## Testing

- **Unit tests** live beside the domain/infra logic they cover (percentiles,
scoring, comparator semantics, clock formula, NTP parsing, timestamp extraction).
- **`tests/pipeline.rs`** drives the public domain API end to end.
- **`tests/mock_server.rs`** runs the real Yellowstone provider against an
in-process mock gRPC server over plaintext HTTP/2.
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@ All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2026-06-15

Layered (DDD-style) architecture refactor. **No behavioral change** — all 28
tests pass unchanged — but the internal structure and the public module paths
moved, so this is a breaking change for library consumers.

### Changed
- Reorganized `src/` into four layers with dependencies pointing inward:
`domain` (model + pure logic), `application` (use-case pipelines),
`infrastructure` (gRPC/NTP/TOML adapters), `presentation` (renderers). See
the new [ARCHITECTURE.md](ARCHITECTURE.md).
- **Public API paths moved** for library users, e.g. `chainbench_grpc::analysis`
→ `chainbench_grpc::domain::analysis`, `chainbench_grpc::providers` →
`chainbench_grpc::infrastructure::geyser`, `output`/`html` →
`chainbench_grpc::presentation::*`.
- Split mixed modules onto layer boundaries: clock math (`domain::clock`) vs NTP
probe (`infrastructure::sntp`); config value objects (`domain::config`) vs TOML
loading (`infrastructure::config_file`); the protobuf `created_at` extraction
and commitment conversion moved into `infrastructure::geyser` so `domain` no
longer depends on generated protobuf; race/latency/full orchestration extracted
from `main` into `application::run`; slot/throughput console renderers moved to
`presentation::output`.

### Removed (dead code)
- `BenchConfig.duration_secs` (written, never read).
- Unused dependencies `chrono` and a duplicate `tokio-stream` (kept as a
dev-dependency for tests).
- Dead `GeyserGrpcClient::subscribe_with_request` branch + the orphaned
`SubscribeSendError` error variant.

## [0.3.0] - 2026-06-15

Clock-offset correction for absolute latency (TS1 clock-skew design, Tier 1).
Expand Down
102 changes: 1 addition & 101 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "chainbench-grpc"
version = "0.3.0"
version = "0.4.0"
edition = "2024"
description = "Comprehensive Solana gRPC benchmarking tool"
license = "Apache-2.0"
Expand All @@ -9,7 +9,6 @@ authors = ["CSFeo"]

[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
tonic = { version = "0.14", features = ["tls-native-roots"] }
rustls = { version = "0.23", features = ["ring"] }
tonic-prost = "0.14"
Expand All @@ -30,7 +29,6 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
bs58 = "0.5"
bytes = "1"
solana-pubkey = "2"
chrono = "0.4"

[build-dependencies]
tonic-prost-build = "0.14"
Expand Down
Loading
Loading