Skip to content

Commit 944d44c

Browse files
committed
added enhancements
1 parent 88c679f commit 944d44c

10 files changed

Lines changed: 171 additions & 145 deletions

File tree

Cargo.lock

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ members = [
55
"bin/flashstat-server",
66
"bin/flashstat-tui",
77
"bin/flashstat-simulate",
8+
"bin/flashstat-watchtower-test",
89
"crates/flashstat-core",
910
"crates/flashstat-api",
1011
"crates/flashstat-db",

README.md

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
# 🏮 FlashStat
22
**The Transparency Layer for Unichain Soft-Finality**
33

4-
FlashStat provides real-time cryptographic confidence scores for Unichain's 200ms Flashblocks. It monitors the sequencer for equivocations (soft-reorgs) and provides an Ethereum-compatible JSON-RPC interface for wallets and DApps.
4+
FlashStat provides real-time cryptographic confidence scores for Unichain's 200ms Flashblocks. It monitors the sequencer for equivocations (soft-reorgs) and provides an Ethereum-compatible JSON-RPC interface with active fraud proof protection (Watchtower).
55

66
## 🏗 Architecture
77
FlashStat is built as a high-performance Rust monorepo:
88

9-
- **`bin/flashstat`**: The primary indexing engine. Subscribes to 200ms Flashblocks via WebSockets.
10-
- **`bin/flashstat-server`**: JSON-RPC server providing confidence metrics.
11-
- **`crates/flashstat-core`**: Core monitoring and reorg detection logic.
12-
- **`crates/flashstat-db`**: Ultra-low latency persistence layer using redb (pure-Rust).
9+
- **`bin/flashstat-server`**: Main entry point. Runs the JSON-RPC server and the indexing engine.
10+
- **`bin/flashstat-tui`**: Terminal UI Dashboard for real-time monitoring and forensics.
11+
- **`bin/flashstat-simulate`**: Forensic simulation tool for testing detection and slashing.
12+
- **`crates/flashstat-core`**: Core monitoring, TEE verification, and reorg detection logic.
13+
- **`crates/flashstat-db`**: Ultra-low latency persistence layer using `redb`.
1314
- **`crates/flashstat-api`**: Type-safe JSON-RPC interface definitions.
1415

1516
## 🚀 Getting Started
@@ -21,37 +22,39 @@ FlashStat is built as a high-performance Rust monorepo:
2122
Edit `flashstat.toml` or set environment variables:
2223
```toml
2324
[rpc]
24-
ws_url = "wss://sepolia.unichain.org"
25-
http_url = "https://sepolia.unichain.org"
25+
ws_url = "wss://unichain-sepolia..."
26+
http_url = "https://unichain-sepolia..."
2627

27-
[storage]
28-
db_path = "./data/flashstat_db"
28+
[guardian]
29+
private_key = "0x..." # Or set FLASHSTAT__GUARDIAN__PRIVATE_KEY
30+
slashing_contract = "0x..."
2931
```
3032

31-
### Running the Monitor
32-
```bash
33-
cargo run -p flashstat
34-
```
33+
### Running the System
34+
1. **Start the Monitor & Server**:
35+
```bash
36+
cargo run -p flashstat-server
37+
```
3538

36-
### Running the API Server
37-
```bash
38-
cargo run -p flashstat-server
39-
```
39+
2. **Launch the Dashboard**:
40+
```bash
41+
cargo run -p flashstat-tui
42+
```
4043

4144
## 📡 JSON-RPC API
4245
The API server runs by default on `127.0.0.1:9944`.
4346

44-
### `flash_getConfidence`
45-
Returns the cryptographic confidence score for a given block hash.
46-
```bash
47-
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"flash_getConfidence","params":["0x..."],"id":1}' http://localhost:9944
48-
```
49-
50-
## 🛡 Security & Trust
51-
FlashStat calculates confidence based on:
52-
1. **Persistence**: Number of consecutive sub-blocks seen for a hash.
53-
2. **TEE Validity**: Verification of the Intel TDX sequencer signature (In Progress).
54-
3. **Equivocation Checks**: Detection of conflicting TEE signatures for the same slot.
47+
### Key Methods
48+
- `flash_getConfidence`: Returns confidence score for a hash.
49+
- `flash_getLatestBlock`: Returns the most recent processed block.
50+
- `flash_getSequencerRankings`: Returns reputation stats for all sequencers.
51+
- `flash_ingestBlock`: Manually submit a block for analysis (useful for external indexers).
52+
53+
## 🛡 Security & Active Protection
54+
FlashStat doesn't just watch; it protects.
55+
- **TEE Attestation**: Verifies Intel TDX quotes for every sequencer signature.
56+
- **Reputation Scoring**: Tracks sequencer performance and reset streaks on reorgs.
57+
- **Active Watchtower**: Automatically submits fraud proofs to the `SlashingManager` contract upon detecting equivocation.
5558

5659
---
5760
Built with 🦀 by One Block Org.

bin/flashstat-server/src/main.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub struct FlashServer {
2424
total_blocks: Arc<AtomicU64>,
2525
total_reorgs: Arc<AtomicU64>,
2626
db_path: String,
27+
monitor: Arc<flashstat_core::FlashMonitor>,
2728
}
2829

2930
#[async_trait]
@@ -96,6 +97,13 @@ impl FlashApiServer for FlashServer {
9697
Ok(stats)
9798
}
9899

100+
async fn ingest_block(&self, block: ethers::types::Block<ethers::types::H256>) -> RpcResult<()> {
101+
self.monitor
102+
.handle_new_block(block)
103+
.await
104+
.map_err(|e| ErrorObjectOwned::owned(-32603, e.to_string(), None::<()>))
105+
}
106+
99107
async fn subscribe_blocks(
100108
&self,
101109
pending: jsonrpsee::PendingSubscriptionSink,
@@ -154,15 +162,17 @@ async fn main() -> eyre::Result<()> {
154162
let storage = std::sync::Arc::new(flashstat_db::RedbStorage::new(&config.storage.db_path)?);
155163

156164
// 3. Initialize Monitor
157-
let mut monitor =
165+
let monitor = Arc::new(
158166
flashstat_core::FlashMonitor::new(config.clone(), storage.clone(), shutdown_tx.subscribe())
159-
.await?;
167+
.await?,
168+
);
160169
let block_tx = monitor.block_notifier();
161170
let event_tx = monitor.event_notifier();
162171

163172
// 3. Start Monitor in background
173+
let monitor_clone = monitor.clone();
164174
tokio::spawn(async move {
165-
if let Err(e) = monitor.run().await {
175+
if let Err(e) = monitor_clone.run().await {
166176
tracing::error!("Monitor error: {:?}", e);
167177
}
168178
});
@@ -183,6 +193,7 @@ async fn main() -> eyre::Result<()> {
183193
total_blocks: Arc::new(AtomicU64::new(0)),
184194
total_reorgs: Arc::new(AtomicU64::new(initial_reorgs)),
185195
db_path: config.storage.db_path.clone(),
196+
monitor: monitor.clone(),
186197
};
187198

188199
// Stats listeners

bin/flashstat-simulate/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ chrono = { workspace = true }
1313
flashstat-common = { path = "../../crates/flashstat-common" }
1414
flashstat-db = { path = "../../crates/flashstat-db" }
1515
clap = { version = "4", features = ["derive"] }
16+
jsonrpsee = { workspace = true }

bin/flashstat-simulate/src/main.rs

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
use clap::Parser;
2-
use ethers::types::{Address, Bytes, H256, U256};
2+
use ethers::types::{Address, Block, H256, Transaction, U256};
33
use eyre::Result;
4-
use flashstat_common::{
5-
ConflictAnalysis, DoubleSpendProof, EquivocationEvent, ReorgEvent, ReorgSeverity,
6-
};
7-
use flashstat_db::{FlashStorage, RedbStorage};
8-
use std::sync::Arc;
4+
use jsonrpsee::http_client::HttpClientBuilder;
5+
use jsonrpsee::core::client::ClientT;
6+
use std::time::Duration;
97

108
#[derive(Parser, Debug)]
119
#[command(author, version, about = "🏮 FlashStat Forensic Simulation Tool", long_about = None)]
1210
struct Args {
13-
#[arg(short, long, default_value = "flashstat.db")]
14-
db_path: String,
11+
#[arg(short, long, default_value = "http://127.0.0.1:9944")]
12+
url: String,
1513

1614
#[arg(short, long, default_value_t = 1)]
1715
count: usize,
@@ -23,65 +21,65 @@ struct Args {
2321
#[tokio::main]
2422
async fn main() -> Result<()> {
2523
let args = Args::parse();
26-
let storage: Arc<dyn FlashStorage> = Arc::new(RedbStorage::new(&args.db_path)?);
24+
let client = HttpClientBuilder::default().build(&args.url)?;
2725

2826
println!(
29-
"🏮 Injecting {} synthetic {} events into {}...",
30-
args.count, args.severity, args.db_path
27+
"🏮 Simulating {} {} event(s) via RPC at {}...",
28+
args.count, args.severity, args.url
3129
);
3230

3331
for i in 0..args.count {
34-
let block_number = 50_000_000 + i as u64;
35-
let severity = match args.severity.as_str() {
36-
"soft" => ReorgSeverity::Soft,
37-
"deep" => ReorgSeverity::Deep,
38-
_ => ReorgSeverity::Equivocation,
39-
};
32+
let block_number = 60_000_000 + i as u64;
33+
let hash_1 = H256::random();
34+
let hash_2 = H256::random();
4035

41-
let event = if severity == ReorgSeverity::Equivocation {
42-
// Create a detailed equivocation with double-spend data
43-
let ds_tx = DoubleSpendProof {
44-
tx_hash_1: H256::random(),
45-
tx_hash_2: H256::random(),
46-
sender: Address::random(),
47-
nonce: U256::from(1),
48-
};
36+
if args.severity == "equivocation" {
37+
println!("⚔️ Simulating Equivocation at block #{}", block_number);
4938

50-
let conflict = ConflictAnalysis {
51-
dropped_txs: vec![H256::random(), H256::random()],
52-
double_spend_txs: vec![ds_tx],
53-
};
39+
// Block 1
40+
let mut block_1 = create_mock_block(block_number, hash_1);
41+
// Mock signature in extra_data (last 65 bytes)
42+
let mut extra_1 = vec![0u8; 32];
43+
extra_1.extend_from_slice(&[1u8; 65]);
44+
block_1.extra_data = extra_1.into();
5445

55-
let equivocation = EquivocationEvent {
56-
signer: Address::random(),
57-
signature_1: Bytes::from(vec![1, 2, 3]),
58-
signature_2: Bytes::from(vec![4, 5, 6]),
59-
conflict_analysis: Some(conflict),
60-
};
46+
// Block 2 (conflicting)
47+
let mut block_2 = create_mock_block(block_number, hash_2);
48+
let mut extra_2 = vec![0u8; 32];
49+
extra_2.extend_from_slice(&[2u8; 65]);
50+
block_2.extra_data = extra_2.into();
51+
52+
// Ingest both
53+
let _: () = client.request("flash_ingestBlock", (block_1,)).await?;
54+
tokio::time::sleep(Duration::from_millis(500)).await;
55+
let _: () = client.request("flash_ingestBlock", (block_2,)).await?;
6156

62-
ReorgEvent {
63-
block_number: U256::from(block_number),
64-
old_hash: H256::random(),
65-
new_hash: H256::random(),
66-
detected_at: chrono::Utc::now(),
67-
severity,
68-
equivocation: Some(equivocation),
69-
}
7057
} else {
71-
ReorgEvent {
72-
block_number: U256::from(block_number),
73-
old_hash: H256::random(),
74-
new_hash: H256::random(),
75-
detected_at: chrono::Utc::now(),
76-
severity,
77-
equivocation: None,
78-
}
79-
};
58+
println!("📦 Simulating Standard Block #{}", block_number);
59+
let block = create_mock_block(block_number, hash_1);
60+
let _: () = client.request("flash_ingestBlock", (block,)).await?;
61+
}
8062

81-
storage.save_reorg(event).await?;
82-
println!(" ✅ Injected alert at block #{}", block_number);
63+
tokio::time::sleep(Duration::from_millis(1000)).await;
8364
}
8465

85-
println!("🎉 Done!");
66+
println!("🎉 Simulation complete!");
8667
Ok(())
8768
}
69+
70+
fn create_mock_block(number: u64, hash: H256) -> Block<H256> {
71+
let mut block = Block::default();
72+
block.number = Some(number.into());
73+
block.hash = Some(hash);
74+
block.parent_hash = H256::random();
75+
block.timestamp = U256::from(chrono::Utc::now().timestamp());
76+
77+
// Add some mock transactions if needed
78+
let mut tx = Transaction::default();
79+
tx.hash = H256::random();
80+
tx.from = Address::random();
81+
tx.nonce = U256::from(0);
82+
// block.transactions.push(tx); // Block<H256> only has hashes by default
83+
84+
block
85+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "flashstat-watchtower-test"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
tokio = { workspace = true }
8+
ethers = { workspace = true }
9+
eyre = { workspace = true }
10+
jsonrpsee = { workspace = true }
11+
chrono = { workspace = true }

0 commit comments

Comments
 (0)