diff --git a/.gitignore b/.gitignore index df2a974..4ef2785 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ Cargo.lock.bak* # OS .DS_Store Thumbs.db +compose.env +docker-compose.override.yml diff --git a/Cargo.lock b/Cargo.lock index 35fb14f..adee17a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2855,17 +2855,13 @@ dependencies = [ "alloy-transport-http", "futures", "indexer-domain", - "prost", - "prost-build", "reqwest 0.12.28", + "sentrix-proto", "serde", "serde_json", "thiserror 2.0.18", "tokio", "tonic", - "tonic-build", - "tonic-prost", - "tonic-prost-build", "tracing", ] @@ -4754,6 +4750,20 @@ dependencies = [ "pest", ] +[[package]] +name = "sentrix-proto" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a96198c479e3dad9d7a2718c3d12d86a228cd07a2bade9091ef64206c273c9" +dependencies = [ + "prost", + "prost-build", + "tonic", + "tonic-build", + "tonic-prost", + "tonic-prost-build", +] + [[package]] name = "serde" version = "1.0.228" diff --git a/crates/chain/Cargo.toml b/crates/chain/Cargo.toml index b16ecd9..a933fdc 100644 --- a/crates/chain/Cargo.toml +++ b/crates/chain/Cargo.toml @@ -6,7 +6,6 @@ rust-version.workspace = true license.workspace = true repository.workspace = true authors.workspace = true -build = "build.rs" [dependencies] indexer-domain = { path = "../domain", version = "0.2.4" } @@ -17,10 +16,13 @@ alloy-provider.workspace = true alloy-rpc-types.workspace = true alloy-transport-http.workspace = true -# tonic: gRPC client (generated from proto). +# tonic: gRPC transport (generated types live in sentrix-proto). tonic.workspace = true -tonic-prost.workspace = true -prost.workspace = true + +# Single source of truth for the sentrix.v1 schema. Replaces the local +# proto/ + build.rs codegen we used to ship inside this crate; bumps to +# the schema now flow through `cargo update -p sentrix-proto`. +sentrix-proto = "0.1.1" # REST client for native `/tx/` fetch. reqwest = { workspace = true } @@ -37,10 +39,5 @@ futures.workspace = true tracing.workspace = true thiserror.workspace = true -[build-dependencies] -tonic-build.workspace = true -tonic-prost-build.workspace = true -prost-build.workspace = true - [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt", "test-util"] } diff --git a/crates/chain/build.rs b/crates/chain/build.rs deleted file mode 100644 index b9543ff..0000000 --- a/crates/chain/build.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Build-time codegen for the `sentrix.v1` gRPC schema. -//! -//! Inputs: `proto/sentrix.proto` (canonical copy of the chain's gRPC schema — -//! pulled from `sentrix-labs/sentrix/crates/sentrix-grpc/proto/sentrix.proto` -//! and kept in sync manually; CI guards against drift in a later phase). -//! -//! Output: client-only Rust bindings under `OUT_DIR/sentrix.v1.rs`, -//! re-exported from `crate::pb`. - -fn main() -> Result<(), Box> { - println!("cargo:rerun-if-changed=proto/sentrix.proto"); - - // Older apt-installed protoc (Ubuntu 22.04 ships 3.12) treats proto3 - // `optional` as experimental and rejects without the explicit flag. - // The proto declares `optional BlockHeight at_height = 2` so this is - // load-bearing on bullseye/jammy CI runners. - let mut config = prost_build::Config::new(); - config.protoc_arg("--experimental_allow_proto3_optional"); - - tonic_prost_build::configure() - .build_client(true) - .build_server(false) - .compile_with_config(config, &["proto/sentrix.proto"], &["proto"])?; - Ok(()) -} diff --git a/crates/chain/proto/sentrix.proto b/crates/chain/proto/sentrix.proto deleted file mode 100644 index 2eb7567..0000000 --- a/crates/chain/proto/sentrix.proto +++ /dev/null @@ -1,257 +0,0 @@ -syntax = "proto3"; - -// Sentrix gRPC service v0.1 — package sentrix.v1. -// -// Parallel transport to JSON-RPC at port 8545. Same backend, same state, -// different wire format. Methods here mirror the most common JSON-RPC paths -// plus add a server-streaming variant for events that JSON-RPC + WebSocket -// can express but at a higher per-subscriber cost. -// -// Versioning: "sentrix.v1" is a hard contract. Breaking changes go to -// "sentrix.v2" with v1 retained for ≥ 2 minor releases overlap. -// -// Status: v0.1 skeleton. Handlers in crates/sentrix-grpc/src/lib.rs return -// tonic::Status::unimplemented until main.rs integration lands. Reference -// design: internal Sentrix Labs gRPC service-proto draft (2026-05-05). - -package sentrix.v1; - -// 20-byte EVM-compatible address. Mirror of sentrix-primitives::Address. -// Wire = bytes (NOT hex string) for binary efficiency. -message Address { - bytes value = 1; // exactly 20 bytes -} - -// 32-byte hash — block hash, tx hash, state root. -message Hash { - bytes value = 1; // exactly 32 bytes -} - -// Native amount — sentri (10^-8 SRX). u64 wire to match Sentrix internal. -message Amount { - uint64 sentri = 1; -} - -// Block height — u64 starting at 0 (genesis). -message BlockHeight { - uint64 value = 1; -} - -// Mirror of sentrix-primitives::Transaction. Identical wire format to chain -// MDBX serialisation (modulo prost-encoding). Field numbers match the -// canonical struct order; do NOT renumber on changes. -message Transaction { - Hash txid = 1; - Address from_address = 2; - Address to_address = 3; - Amount amount = 4; - Amount fee = 5; - uint64 nonce = 6; - uint64 timestamp = 7; - bytes signature = 8; // 64-byte ed25519 OR 65-byte secp256k1 - bytes payload = 9; // contract calldata (EVM) or staking-op blob - uint32 chain_id = 10; - uint32 tx_type = 11; // 0 = transfer, 1 = contract, 2 = staking-op -} - -message Block { - uint64 index = 1; - Hash hash = 2; - Hash parent_hash = 3; - Hash state_root = 4; - uint64 timestamp = 5; - Address proposer = 6; - uint32 round = 7; - repeated Transaction transactions = 8; - // BFT justification carried from validator-side; included for audit. - // Empty for blocks at h<100_000 (pre state-root-fork). - bytes justification = 9; -} - -message Account { - Address address = 1; - Amount balance = 2; - uint64 nonce = 3; - // For contract accounts: 32-byte storage root + non-empty code_hash. - Hash storage_root = 4; - Hash code_hash = 5; -} - -// Server-streaming event types. -message ChainEvent { - oneof event { - BlockFinalized block_finalized = 1; - PendingTx pending_tx = 2; - ValidatorSetChange validator_set_change = 3; - LogEmitted log = 4; - // Synthetic Lagged sentinel — emitted when the per-subscriber broadcast - // channel drops oldest events (slow consumer). Mirrors tokio broadcast - // RecvError::Lagged semantics. Consumer should resync state from RPC. - StreamLagged lagged = 5; - } - uint64 sequence = 100; // monotonic across all events on this stream - uint64 timestamp = 101; // server-wall-clock unix seconds -} - -message BlockFinalized { Block block = 1; } -message PendingTx { Transaction tx = 1; } - -message ValidatorSetChange { - uint32 epoch = 1; - repeated Address active = 2; - repeated Address jailed = 3; -} - -message LogEmitted { - Address contract = 1; - repeated Hash topics = 2; // 0..4 topics per EVM convention - bytes data = 3; - Hash tx_hash = 4; - uint64 block_height = 5; - uint32 log_index = 6; -} - -message StreamLagged { - uint64 skipped_count = 1; // events dropped since last delivery -} - -// ──────────────────────────────────────────────────────────── -// Service definition -// ──────────────────────────────────────────────────────────── -service Sentrix { - // Submit a signed transaction to the local mempool. Same semantics as - // JSON-RPC eth_sendRawTransaction for EVM txs, with native fields exposed - // for staking-ops and other native variants. - rpc BroadcastTx(BroadcastTxRequest) returns (BroadcastTxResponse); - - // Fetch a block by height OR by hash. Returns NOT_FOUND if outside the - // local chain window (currently 1000 blocks; older blocks need indexer). - rpc GetBlock(GetBlockRequest) returns (Block); - - // Fetch account balance + nonce. Mirrors eth_getBalance + - // eth_getTransactionCount in a single round-trip. Includes mempool-pending - // nonce so wallets build correct next-tx without an extra call (matches - // the pending-aware nonce behaviour from chain v2.1.57). - rpc GetBalance(GetBalanceRequest) returns (Account); - - // v0.4 read-only state queries — drop the REST `/sentrix_status_extended` - // bridge from the explorer's stats hot path. All three are pure reads - // off `state.read()` snapshots; same lock contention profile as - // GetBlock/GetBalance. - // - // Active validator set + per-validator stake/active/jailed flags. - rpc GetValidatorSet(GetValidatorSetRequest) returns (ValidatorSet); - // Native-token supply snapshot (minted, burned, circulating). - rpc GetSupply(GetSupplyRequest) returns (Supply); - // Pending-tx count + a capped header window for UI display. - rpc GetMempool(GetMempoolRequest) returns (Mempool); - - // Server-streaming chain events. Subscribe once, receive every event type - // until cancel or server restart. Replaces N separate eth_subscribe calls. - // Backpressure: server bounded channel per stream (capacity 4096 — same as - // event_tx in chain). On slow client, server drops OLDEST events and emits - // a StreamLagged sentinel. Client should resync state from RPC. - rpc StreamEvents(StreamEventsRequest) returns (stream ChainEvent); -} - -message BroadcastTxRequest { Transaction tx = 1; } - -message BroadcastTxResponse { - Hash txid = 1; - uint64 mempool_position = 2; // 0-indexed; FIFO ordering UX -} - -message GetBlockRequest { - oneof selector { - BlockHeight height = 1; - Hash hash = 2; - bool latest = 3; // selector == "latest" shorthand - bool finalized = 4; // selector == "finalized" (BFT-finalized head) - } -} - -message GetBalanceRequest { - Address address = 1; - // Snapshot height — read state as-of this block. Default = latest. - // Returns FAILED_PRECONDITION if outside chain window. - optional BlockHeight at_height = 2; -} - -message StreamEventsRequest { - // Filter set — empty = subscribe to all event types. Backend filters - // server-side so the wire only carries matched events. - repeated EventFilter filters = 1; - // Resume from a sequence number (0 = current head). Backend keeps last - // 4096 events in a ring buffer — beyond that, sequence is not resumable - // and the server returns FAILED_PRECONDITION. - uint64 from_sequence = 2; -} - -enum EventFilter { - EVENT_FILTER_UNSPECIFIED = 0; - EVENT_FILTER_BLOCK_FINALIZED = 1; - EVENT_FILTER_PENDING_TX = 2; - EVENT_FILTER_VALIDATOR_SET = 3; - EVENT_FILTER_LOG = 4; -} - -// ──────────────────────────────────────────────────────────── -// v0.4 read-only state-query messages -// ──────────────────────────────────────────────────────────── - -message GetValidatorSetRequest { - // Reserved for at_height historical reads — gated on MDBX snapshot - // isolation (Refactor 5). v0.4 returns the latest finalized set. - optional BlockHeight at_height = 1; -} - -message ValidatorSet { - uint32 epoch = 1; - uint32 active_count = 2; - uint32 total_count = 3; - // Total stake across the active set, in sentri (10^-8 SRX). - uint64 total_active_stake_sentri = 4; - repeated ValidatorEntry validators = 5; -} - -message ValidatorEntry { - Address address = 1; - uint64 stake_sentri = 2; - bool active = 3; - bool jailed = 4; -} - -message GetSupplyRequest { - optional BlockHeight at_height = 1; -} - -message Supply { - // sentri = 10^-8 SRX. Cap is fork-gated (210M pre-fork, 315M post- - // tokenomics-v2 at h=640800); leave conversion to display layer. - uint64 minted_sentri = 1; - uint64 burned_sentri = 2; - uint64 circulating_sentri = 3; // = minted − burned -} - -message GetMempoolRequest { - // Cap returned tx headers; default 100, max 500. Larger windows need - // pagination (deferred). 0 = use server default (100). - uint32 limit = 1; -} - -message Mempool { - // Authoritative pending-tx count. `entries` is capped by `limit`; - // size always reflects the full mempool depth. - uint32 size = 1; - repeated MempoolEntry entries = 2; -} - -message MempoolEntry { - Hash txid = 1; - Address from_address = 2; - Address to_address = 3; - Amount amount = 4; - Amount fee = 5; - uint64 nonce = 6; - uint32 tx_type = 7; -} diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index f32cf60..5cd59e6 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -18,12 +18,10 @@ pub mod provider; pub mod rest; pub mod retry; -/// Generated tonic / prost types for `sentrix.v1.*`. The build script writes -/// `sentrix.v1.rs` into `OUT_DIR`; this module simply includes it. -#[allow(missing_docs, clippy::all)] -pub mod pb { - tonic::include_proto!("sentrix.v1"); -} +/// Re-export of the canonical `sentrix.v1` protobuf types from the +/// `sentrix-proto` crate (single source of truth, published on crates.io). +/// Existing `crate::pb::Foo` call sites keep working unchanged. +pub use sentrix_proto as pb; pub use error::{ChainError, ChainResult}; pub use grpc::GrpcClient;