Skip to content

Codebase Architecture

miniproto is a modular asynchronous protocol core organized by domain boundary rather than a controller/service/repository web stack. One public Client coordinates explicit components for auth, storage, peer resolution, invocation, sender/transport, updates, and media. Telegram schema generation and native acceleration are build-time/runtime adapters around that Python policy layer.

Three constraints shape the design:

  1. Protocol correctness and replay safety fail closed: ambiguous writes, invalid encrypted envelopes, stale correlation, unsafe DH/SRP input, and malformed/oversized framing do not become silent retries.
  2. Resources are bounded and cancellation-owned: pending RPCs, update queues, transport payloads, media windows, shared per-DC budgets, retries, caches, and auxiliary clients have explicit limits/lifecycle rules.
  3. Performance paths are capability-selected rather than correctness-required: Rust accelerates measured operations, but wrappers preserve documented behavior through supported fallbacks where a native symbol is unavailable.

Evidence: src/miniproto/client.py, src/miniproto/invoke.py, src/miniproto/connection/sender.py, src/miniproto/config.py, and src/miniproto/crypto/native.py.

caller -> Client.invoke -> request wrapper/retry policy -> MTProtoSender -> encrypted envelope/frame -> asyncio transport -> Telegram DC
caller <- result validation/error mapping <- pending correlation <- decoded/authenticated envelope <- frame pump <- asyncio transport <- Telegram DC
  1. A caller supplies a generated request to Client.invoke() or a high-level method that constructs one. src/miniproto/client.py resolves the sender and delegates through the invocation policy in src/miniproto/invoke.py.
  2. Initialization wrappers, retry safety, method flood cache, timeouts, migration, expected result type, and optional quick-ACK behavior are applied before MTProtoSender.request() owns one logical pending slot.
  3. MTProtoSender allocates MTProto message IDs/sequence numbers, serializes and encrypts the envelope, registers pending aliases/quick-ACK tokens, and sends bytes through a selected Transport implementation.
  4. connection/transport.py owns asyncio stream I/O and proxy setup; connection/framing.py converts arbitrary TCP chunks into payload, quick-ACK, or transport-error events through the native codec when available or the Python codec otherwise.
  5. Incoming MTProto envelopes are authenticated and structurally decoded before sender state changes. Container members and correlations are prevalidated, then results/errors/acks/salts/updates are committed and delivered.
  6. The invocation layer maps raw RPC errors, enforces expected result types, sleeps only eligible flood waits, performs safe retries/migration, and returns the decoded result or a typed failure to the caller.

Media requests use the same invocation/sender machinery through dedicated per-operation/per-DC pools. media/scheduler.py wraps live attempts in shared byte permits, while media/download.py and media/upload.py own ordered windows, retries, integrity, destinations, and progress.

ModuleOwnsMust not ownEvidence
ClientPublic lifecycle and orchestrationLow-level cipher/TL implementationsrc/miniproto/client.py
InvocationRequest wrappers, result type, safe retry/flood/migration policyTCP reads/writessrc/miniproto/invoke.py
Sender/stateMessage sequencing, pending correlation, encrypted MTProto protocol lifecycleSession database transactionssrc/miniproto/connection/sender.py, src/miniproto/mtproto/state.py
Transport/framingProxy connection, streams, wire-frame encoding and chunk pumpingRPC meaningsrc/miniproto/connection/transport.py, src/miniproto/connection/framing.py
Session/storageTyped state, atomic per-domain encrypted persistence, portable formatsNetwork authorization operationssrc/miniproto/session/
Updates/peersGap recovery, cursor/deduplication, queues/handlers, indexed access-hash resolutionApplication work queues or exactly-once side effectssrc/miniproto/updates/, src/miniproto/peers.py
MediaUpload/download/CDN/hash/scheduler/pool primitivesFramework-level albums, thumbnailing, or bound message methodssrc/miniproto/media/, docs/media.md
Schema tooling/rawPinned structural truth and deterministic generated bindingsHandwritten patches to generated classestools/schema/, src/miniproto/raw/
Native adapterCapability probes, parity-preserving routing, PyO3 operationsPublic lifecycle policysrc/miniproto/crypto/native.py, rust/miniproto/src/
ObservabilityOpt-in local logs, metrics sinks, resource snapshots, redactionNetwork telemetry exportsrc/miniproto/observability.py, src/miniproto/security/redaction.py
PatternWherePurpose
Capability/strategy selectioncrypto/native.py, connection/framing.py, event_loop.pyChoose available measured implementations without changing the public contract
Adapter/protocol boundarySessionStorage, MetricsSink, sender/transport protocols, docs extractorsPermit substitution while keeping required behavior explicit and type-checked
Atomic repository-like storagesession/storage.pySerialize mutations and advance only changed domain revisions
Revision-aware local indexpeers.pyAvoid canonical peer scans while reconciling external storage changes safely
State machine with correlated futuresconnection/sender.py, mtproto/state.pyTrack request aliases, retries, acks, results, and fatal protocol state
Bounded queue/window/schedulerupdates/manager.py, media/download.py, media/scheduler.pyMake backpressure, memory, fairness, and cancellation behavior measurable
Deterministic code generationtools/schema/, tools/docs/Keep external schema/reference output reviewable, reproducible, and stale-checkable

Client.connect() loads storage, establishes or reuses the primary sender, starts receive/update dispatch, and keeps repeat calls serialized. The update manager owns a raw-drainer task and public queue when enabled. Media sender pools and auxiliary bot sessions are created lazily, prewarmed for a transfer, and closed after idle/lifecycle boundaries. Disconnect stops update dispatch, media pools/schedulers, auxiliary clients, primary sender, and storage; application-owned update iterator tasks still require cancellation because the public stream has no end sentinel.

  • src/miniproto/client.py, src/miniproto/connection/sender.py, and src/miniproto/media/download.py are large, high-churn orchestration files. Cross-cutting lifecycle changes can violate ownership in more than one subsystem; use focused fake-server/cancellation tests before the complete suite.
  • Generated Layer 228 code is large and upstream-driven. Hand edits or a source-precedence mistake can create broad API drift; only the pinned multi-source generator and offline stale check are authoritative.
  • Session/peer/update correctness spans async and thread locks plus encrypted SQLite transactions. New read-modify-write paths must use atomic mutations and preserve the established lock order.
  • Native and fallback implementations can diverge by platform or interpreter even when one local benchmark is green. Parity tests and cross-platform benchmark evidence remain mandatory before changing routing.
  • src/miniproto/client.py
  • src/miniproto/invoke.py
  • src/miniproto/connection/sender.py
  • src/miniproto/connection/transport.py
  • src/miniproto/session/storage.py
  • src/miniproto/updates/manager.py
  • src/miniproto/media/scheduler.py
  • tools/schema/generate.py
  • tools/docs/__main__.py