Skip to content

Commit 86040e1

Browse files
committed
Merge branch 'develop' into quake/oneway-channel
2 parents 4bfe0d8 + 8f46e3d commit 86040e1

File tree

3 files changed

+324
-45
lines changed

3 files changed

+324
-45
lines changed

AGENTS.md

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# Agent Guidelines for Fiber Network Node (FNN)
2+
3+
This document provides essential guidelines for AI coding agents working on the Fiber Network Node codebase. The Fiber Network is a reference implementation of a peer-to-peer payment/swap network built on CKB blockchain, similar to Lightning Network.
4+
5+
## Build System & Commands
6+
7+
### Primary Language & Toolchain
8+
- **Language**: Rust 1.85.0 (specified in `rust-toolchain.toml`)
9+
- **Build System**: Cargo workspace with 5 member crates
10+
- **Test Runner**: cargo-nextest (preferred over `cargo test`)
11+
12+
### Essential Commands
13+
14+
#### Building
15+
```bash
16+
cargo build # Debug build
17+
cargo build --release # Release build
18+
cargo check --locked # Quick check with Cargo.lock
19+
make check # Full check (debug, release, no-default-features)
20+
```
21+
22+
#### Testing
23+
```bash
24+
# Run all tests with nextest (PREFERRED)
25+
cargo nextest run --no-fail-fast
26+
27+
# Run specific package tests
28+
cargo nextest run -p fnn -p fiber-bin
29+
30+
# Run single test by name
31+
cargo nextest run test_name
32+
33+
# Run tests matching pattern
34+
cargo nextest run 'test_pattern'
35+
36+
# Standard cargo test (if nextest unavailable)
37+
RUST_LOG=off cargo test -p fnn -p fiber-bin
38+
39+
# Run benchmarks
40+
cargo criterion --features bench
41+
make benchmark-test
42+
```
43+
44+
**Note**: The `.config/nextest.toml` configures thread requirements for heavy/channel/payment tests. Tests require `RUST_TEST_THREADS: 2` in CI.
45+
46+
#### Linting & Formatting
47+
```bash
48+
# Format code (REQUIRED before commits)
49+
cargo fmt --all
50+
51+
# Check formatting without modifying
52+
cargo fmt --all -- --check
53+
make fmt
54+
55+
# Run clippy (REQUIRED, must pass with no warnings)
56+
cargo clippy --all-targets --all-features -p fnn -p fiber-bin -- -D warnings
57+
make clippy
58+
59+
# Auto-fix clippy warnings and format
60+
make bless
61+
62+
# Check for typos
63+
typos
64+
typos -w # Auto-fix typos
65+
```
66+
67+
#### WASM-specific
68+
```bash
69+
# Check WASM crates
70+
cargo clippy -p fiber-wasm -p fiber-wasm-db-worker -p fiber-wasm-db-common --target wasm32-unknown-unknown -- -D warnings
71+
```
72+
73+
#### Other Checks
74+
```bash
75+
# Check for unused dependencies
76+
cargo shear
77+
78+
# Generate RPC documentation
79+
make gen-rpc-doc
80+
81+
# Verify data migration schemas
82+
make check-migrate
83+
84+
# Update migration schemas
85+
make update-migrate-check
86+
```
87+
88+
## Code Style Guidelines
89+
90+
### Module & Import Organization
91+
1. **Import ordering** (top to bottom):
92+
- Standard library (`use std::...`)
93+
- External crates (alphabetical)
94+
- Internal crate modules (`use crate::...`)
95+
- Local modules (`use super::...`)
96+
2. Group imports by category with blank lines between groups
97+
3. Use explicit imports, avoid glob imports except for preludes
98+
99+
### Naming Conventions
100+
- **Files**: Snake_case (e.g., `channel_actor.rs`, `payment_handler.rs`)
101+
- **Types**: PascalCase (e.g., `ChannelActor`, `PaymentRequest`)
102+
- **Functions/Variables**: Snake_case (e.g., `process_message`, `peer_id`)
103+
- **Constants**: SCREAMING_SNAKE_CASE (e.g., `MAX_TLC_VALUE`, `DEFAULT_TIMEOUT`)
104+
- **Modules**: Snake_case (e.g., `fiber::channel`, `ckb::actor`)
105+
106+
### Type Annotations
107+
- Use explicit types for public APIs and struct fields
108+
- Type inference is acceptable for local variables when clear
109+
- Always annotate function return types
110+
- Use `#[serde_as]` and `serde_with` for complex serialization (e.g., `U128Hex`, `U64Hex`)
111+
112+
### Error Handling
113+
- Use `thiserror::Error` for custom error types (see `src/errors.rs`)
114+
- Prefer `Result<T, Error>` over `Result<T, SomeError>`
115+
- Use `?` operator for error propagation
116+
- Provide descriptive error messages with context
117+
- Don't panic in production code; use `expect()` only when truly unreachable
118+
119+
**Example**:
120+
```rust
121+
#[derive(Error, Debug)]
122+
pub enum Error {
123+
#[error("Peer not found error: {0:?}")]
124+
PeerNotFound(PeerId),
125+
#[error("Channel not found error: {0:?}")]
126+
ChannelNotFound(Hash256),
127+
#[error("Invalid parameter: {0}")]
128+
InvalidParameter(String),
129+
}
130+
131+
pub type Result<T> = std::result::Result<T, Error>;
132+
```
133+
134+
### Async & Actor Patterns
135+
- This codebase uses the **Ractor** actor framework extensively
136+
- Use `async_trait::async_trait` for async traits
137+
- For WASM compatibility, use `#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]`
138+
- Actor messages use enums (e.g., `ChannelActorMessage`, `NetworkActorMessage`)
139+
- Use `ractor::call` for RPC-style actor communication
140+
141+
**Example**:
142+
```rust
143+
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
144+
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
145+
impl Actor for MyActor {
146+
type Msg = MyActorMessage;
147+
type State = MyState;
148+
type Arguments = MyArgs;
149+
// Implementation...
150+
}
151+
```
152+
153+
### Logging & Tracing
154+
- Use `tracing` crate for structured logging
155+
- Levels: `trace`, `debug`, `info`, `warn`, `error`
156+
- Include context in log messages (peer_id, channel_id, etc.)
157+
- Log at appropriate levels:
158+
- `error!`: Critical failures requiring attention
159+
- `warn!`: Unexpected but recoverable situations
160+
- `info!`: Important state changes
161+
- `debug!`: Detailed operational info
162+
- `trace!`: Very verbose, protocol-level details
163+
164+
### Documentation
165+
- Public APIs MUST have doc comments (`///`)
166+
- RPC methods require detailed documentation (checked by `make gen-rpc-doc`)
167+
- Use `/// # Example` sections for complex APIs
168+
- Document invariants and assumptions
169+
- Explain "why" in comments, not just "what"
170+
171+
### Testing Conventions
172+
- Unit tests in same file: `#[cfg(test)] mod tests { ... }`
173+
- Integration tests in `tests/` directory
174+
- Use descriptive test names: `test_channel_open_accept_flow`
175+
- Mock actors and external dependencies
176+
- Test both success and error paths
177+
178+
### Platform-Specific Code
179+
- Use `#[cfg(not(target_arch = "wasm32"))]` for native-only code
180+
- Use `#[cfg(target_arch = "wasm32")]` for WASM-only code
181+
- Keep platform-specific code minimal and isolated
182+
- Test both native and WASM builds in CI
183+
184+
### Serialization
185+
- Use `serde` with `#[derive(Serialize, Deserialize)]`
186+
- Use `serde_with` for custom serialization (hex, base64, etc.)
187+
- Use `molecule` for CKB-specific binary formats
188+
- Maintain backward compatibility for persisted data
189+
190+
### Security & Best Practices
191+
- No unsafe code without thorough review
192+
- Validate all external inputs (RPC, peer messages)
193+
- Use constant-time operations for cryptographic comparisons
194+
- Clear sensitive data (private keys) when done
195+
- Follow principle of least privilege
196+
197+
## Allowed Clippy Lints
198+
199+
The following clippy lints are explicitly allowed (see `Cargo.toml`):
200+
- `expect-fun-call`: Acceptable to use `expect()` with computed messages
201+
- `fallible-impl-from`: Allow `From` impls that can panic
202+
- `large-enum-variant`: Acceptable for actor message enums
203+
- `mutable-key-type`: Needed for certain data structures
204+
- `needless-return`: Explicit returns are sometimes clearer
205+
- `upper-case-acronyms`: Allow acronyms like `TLC`, `RPC`, `UDT`
206+
207+
## Project Structure
208+
209+
```
210+
fiber/
211+
├── crates/
212+
│ ├── fiber-lib/ # Core library (fnn crate)
213+
│ │ └── src/
214+
│ │ ├── fiber/ # Protocol implementation
215+
│ │ ├── ckb/ # CKB blockchain integration
216+
│ │ ├── rpc/ # JSON-RPC API
217+
│ │ ├── store/ # Data persistence
218+
│ │ └── watchtower/ # Watchtower service
219+
│ ├── fiber-bin/ # Binary executable
220+
│ ├── fiber-wasm/ # WebAssembly bindings
221+
│ └── fiber-wasm-db-*/ # WASM database workers
222+
├── tests/ # Integration tests
223+
├── migrate/ # Database migration tool
224+
└── Makefile # Convenience targets
225+
```
226+
227+
## Common Pitfalls
228+
229+
1. **Don't forget to run tests with nextest**, not standard `cargo test`
230+
2. **Always run `make clippy` before committing** - CI enforces `-D warnings`
231+
3. **Check WASM compatibility** for code in `fiber-lib` - it must compile for both native and WASM
232+
4. **Update migration schemas** if you change data structures (`make check-migrate`)
233+
5. **Regenerate RPC docs** if you modify RPC methods (`make gen-rpc-doc`)
234+
6. **Use `cargo fmt` before committing** - formatting is strictly enforced
235+
236+
## Continuous Integration
237+
238+
CI runs the following checks (all must pass):
239+
- `make check` (multiple build configurations)
240+
- `make check-migrate` (migration schema validation)
241+
- `make check-dirty-rpc-doc` (RPC documentation up-to-date)
242+
- `cargo nextest run --no-fail-fast` (all tests)
243+
- `cargo fmt --all -- --check` (formatting)
244+
- `make clippy` (linting with warnings as errors)
245+
- `typos` (spell checking)
246+
- `cargo shear` (unused dependencies)
247+
248+
## Additional Resources
249+
250+
- RPC API documentation: `crates/fiber-lib/src/rpc/README.md` (auto-generated)
251+
- Protocol specifications: `docs/specs/`
252+
- Development notes: `docs/notes/`

crates/fiber-lib/src/fiber/network.rs

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,10 +1985,13 @@ where
19851985
}
19861986
}
19871987
NetworkActorCommand::BroadcastMessages(message) => {
1988-
state
1989-
.gossip_actor
1990-
.send_message(GossipActorMessage::TryBroadcastMessages(message))
1991-
.expect(ASSUME_GOSSIP_ACTOR_ALIVE);
1988+
if let Some(ref gossip_actor) = state.gossip_actor {
1989+
gossip_actor
1990+
.send_message(GossipActorMessage::TryBroadcastMessages(message))
1991+
.expect(ASSUME_GOSSIP_ACTOR_ALIVE);
1992+
} else {
1993+
debug!("Gossip actor is not available, skipping broadcast message");
1994+
}
19921995
}
19931996
NetworkActorCommand::SendPayment(payment_request, reply) => {
19941997
let payment_request = match payment_request.build_send_payment_data() {
@@ -2824,7 +2827,8 @@ pub struct NetworkActorState<S, C> {
28242827
// The default tlc fee proportional millionths to be used when auto accepting a channel.
28252828
tlc_fee_proportional_millionths: u128,
28262829
// The gossip messages actor to process and send gossip messages.
2827-
gossip_actor: ActorRef<GossipActorMessage>,
2830+
// None if gossip is disabled via sync_network_graph config.
2831+
gossip_actor: Option<ActorRef<GossipActorMessage>>,
28282832
max_inbound_peers: usize,
28292833
min_outbound_peers: usize,
28302834
// The features of the node, used to indicate the capabilities of the node.
@@ -4181,45 +4185,65 @@ where
41814185
let my_peer_id: PeerId = PeerId::from(secio_pk);
41824186
let handle = NetworkServiceHandle::new(myself.clone());
41834187
let fiber_handle = FiberProtocolHandle::from(&handle);
4184-
let mut gossip_config = GossipConfig::from(&config);
4185-
gossip_config.peer_id = Some(my_peer_id.clone());
4186-
let (gossip_service, gossip_handle) = GossipService::start(
4187-
gossip_config,
4188-
self.store.clone(),
4189-
self.chain_actor.clone(),
4190-
self.chain_client.clone(),
4191-
myself.get_cell(),
4192-
)
4193-
.await;
4194-
let mut graph = self.network_graph.write().await;
4195-
let graph_subscribing_cursor = graph
4196-
.get_latest_cursor()
4197-
.go_back_for_some_time(MAX_GRAPH_MISSING_BROADCAST_MESSAGE_TIMESTAMP_DRIFT);
4198-
4199-
gossip_service
4200-
.get_subscriber()
4201-
.subscribe(graph_subscribing_cursor, myself.clone(), |m| {
4202-
Some(NetworkActorMessage::new_event(
4203-
NetworkActorEvent::GossipMessageUpdates(m),
4204-
))
4205-
})
4206-
.await
4207-
.expect("subscribe to gossip store updates");
4208-
let gossip_actor = gossip_handle.actor().clone();
4188+
4189+
// Conditionally start GossipService based on sync_network_graph config
4190+
let (gossip_actor, gossip_handle_opt) = if config.sync_network_graph() {
4191+
let mut gossip_config = GossipConfig::from(&config);
4192+
gossip_config.peer_id = Some(my_peer_id.clone());
4193+
let (gossip_service, gossip_handle) = GossipService::start(
4194+
gossip_config,
4195+
self.store.clone(),
4196+
self.chain_actor.clone(),
4197+
self.chain_client.clone(),
4198+
myself.get_cell(),
4199+
)
4200+
.await;
4201+
4202+
let graph_subscribing_cursor = {
4203+
let graph = self.network_graph.write().await;
4204+
graph
4205+
.get_latest_cursor()
4206+
.go_back_for_some_time(MAX_GRAPH_MISSING_BROADCAST_MESSAGE_TIMESTAMP_DRIFT)
4207+
};
4208+
4209+
gossip_service
4210+
.get_subscriber()
4211+
.subscribe(graph_subscribing_cursor, myself.clone(), |m| {
4212+
Some(NetworkActorMessage::new_event(
4213+
NetworkActorEvent::GossipMessageUpdates(m),
4214+
))
4215+
})
4216+
.await
4217+
.expect("subscribe to gossip store updates");
4218+
(Some(gossip_handle.actor().clone()), Some(gossip_handle))
4219+
} else {
4220+
info!("Gossip network synchronization is disabled (sync_network_graph = false)");
4221+
(None, None)
4222+
};
4223+
4224+
// Build service with or without gossip protocol based on configuration
42094225
#[cfg(not(target_arch = "wasm32"))]
4210-
let mut service = ServiceBuilder::default()
4211-
.insert_protocol(fiber_handle.create_meta())
4212-
.insert_protocol(gossip_handle.create_meta())
4213-
.handshake_type(secio_kp.into())
4214-
.build(handle);
4226+
let mut service = {
4227+
let mut builder = ServiceBuilder::default()
4228+
.insert_protocol(fiber_handle.create_meta())
4229+
.handshake_type(secio_kp.into());
4230+
if let Some(gossip_handle) = gossip_handle_opt {
4231+
builder = builder.insert_protocol(gossip_handle.create_meta());
4232+
}
4233+
builder.build(handle)
4234+
};
42154235
#[cfg(target_arch = "wasm32")]
4216-
let mut service = ServiceBuilder::default()
4217-
.insert_protocol(fiber_handle.create_meta())
4218-
.insert_protocol(gossip_handle.create_meta())
4219-
.handshake_type(secio_kp.into())
4220-
// Sets forever to true so the network service won't be shutdown due to no incoming connections
4221-
.forever(true)
4222-
.build(handle);
4236+
let mut service = {
4237+
let mut builder = ServiceBuilder::default()
4238+
.insert_protocol(fiber_handle.create_meta())
4239+
.handshake_type(secio_kp.into())
4240+
// Sets forever to true so the network service won't be shutdown due to no incoming connections
4241+
.forever(true);
4242+
if let Some(gossip_handle) = gossip_handle_opt {
4243+
builder = builder.insert_protocol(gossip_handle.create_meta());
4244+
}
4245+
builder.build(handle)
4246+
};
42234247

42244248
let mut announced_addrs = Vec::with_capacity(config.announced_addrs.len() + 1);
42254249

@@ -4374,7 +4398,10 @@ where
43744398
};
43754399

43764400
let node_announcement = state.get_or_create_new_node_announcement_message();
4377-
graph.process_node_announcement(node_announcement);
4401+
{
4402+
let mut graph = self.network_graph.write().await;
4403+
graph.process_node_announcement(node_announcement);
4404+
}
43784405
let announce_node_interval_seconds = config.announce_node_interval_seconds();
43794406
if announce_node_interval_seconds > 0 {
43804407
myself.send_interval(Duration::from_secs(announce_node_interval_seconds), || {

0 commit comments

Comments
 (0)