Week 1 Report
I. Overview
This was the first week of the project. The core objectives were to complete an in-depth architectural study of the CKB codebase (full call-chain tracing across the P2P, storage, and synchronization layers), set up the local development environment, and deploy and verify a CKB testnet node. The following is a module-by-module summary.
II. Task Completion Details
1. CKB Source Code Architecture Study (P2P / Storage / Sync Layer Call-Chain Analysis)
Objective: Understand the CKB node’s overall assembly, P2P communication, persistent storage, and block synchronization mechanisms at the source-code level, laying the groundwork for identifying eBPF probe attachment points in subsequent phases.
Completion:
1.1 Overall Architecture and Startup Flow
CKB adopts a layered, modular architecture in which core components are interconnected through the Shared struct (shared/src/shared.rs:58). The complete node startup call chain is: main() → ckb_bin::run_app() (ckb-bin/src/lib.rs:61) → run subcommand entry (ckb-bin/src/subcommand/run.rs:17). Within run, four key steps are executed sequentially: first, Launcher::build_shared() (util/launcher/src/lib.rs:191) constructs Shared and SharedPackage—Shared holds the RocksDB storage instance, the chain-tip snapshot manager (SnapshotMgr), the transaction pool, and other global state, serving as the central hub that spans all subsystems; then start_chain_service() launches the chain service and returns a ChainController; next, start_network_and_rpc() (:403) assembles and starts the network layer—internally it creates SyncShared::new(shared), Synchronizer::new() (registered as Protocol ID 100), Relayer::new() (Protocol ID 101), and BlockFilter::new() (Protocol ID 121), then calls NetworkService::new().start() to bring up the P2P service; finally, tx_pool_builder.start(network_controller) starts the transaction pool service.
1.2 P2P Network Layer (network/ crate)
Underlying Library — Tentacle: CKB’s P2P layer is built on the in-house Tentacle library. Key types include ServiceControl / ServiceAsyncControl (P2P control interfaces), ProtocolId(u32) (protocol identifiers), SessionId(u32) (session identifiers), and PeerId (public-key-based identity).
Core Abstraction Layer: On top of Tentacle, CKB defines its own protocol-handling trait CKBProtocolHandler (network/src/protocols/mod.rs:161), which exposes five methods: init, connected, disconnected, received, and notify. An adapter layer called CKBHandler bridges this trait to Tentacle’s native ServiceProtocol. The protocol context is encapsulated as CKBProtocolContext (:47), providing operations such as sending messages, disconnecting peers, adjusting scores, and banning. Global network state is held by NetworkState (network/src/network.rs:73), which contains key fields including peer_registry, peer_store, and local_peer_id. NetworkService (:827) is responsible for assembling and starting the P2P service, while NetworkController (:1334) serves as the external API entry point, exposing methods such as add_node, remove_node, and p2p_control.
Protocol Registry — SupportProtocols: Defined at network/src/protocols/support_protocols.rs:20, CKB registers 12 P2P sub-protocols: ID 0 Ping (heartbeat/RTT, max frame 1 KB), ID 1 Discovery (peer discovery, 512 KB), ID 2 Identify (identity/capability exchange, 2 KB), ID 3 Feeler (probing node reachability, 1 KB), ID 4 DisconnectMsg (disconnect notification, 1 KB), ID 100 Sync (block synchronization, 2 MB), ID 101 Relay (CompactBlock / transaction relay, 4 MB), ID 102 Time (time synchronization, 1 KB), ID 110 Alert (alerts, 128 KB), ID 120 LightClient (light client, 2 MB), ID 121 BlockFilter (block filters, 2 MB), and ID 130 HolePunching (NAT traversal, 512 KB).
Peer Management: PeerRegistry (peer_registry.rs:22) manages in-memory active sessions and enforces upper limits on inbound/outbound connections. It supports Block-Relay-Only connections (at most 2, relaying blocks only—not transactions) and employs an eviction strategy based on RTT, message timing, connection age, and network group. PeerStore (peer_store/peer_store_impl.rs:20) handles persistent address storage and internally comprises three sub-structures: AddrManager (address pool), BanList (banning by IP subnet with expiration support), and Anchors (Block-Relay-Only anchor nodes). Network grouping rules (network_group.rs) partition addresses by IPv4 /16 and IPv6 /64 to ensure topological diversity during eviction.
Message Flow: On the send path: Protocol → context.send_message() → ServiceControl → Snappy compression (triggered only when the message exceeds 1 KB) → TCP. On the receive path: TCP → decompression → CKBHandler.received() → CKBProtocolHandler.received(). Broadcasting uses filter_broadcast() to support conditional multi-peer delivery.
1.3 Storage Layer (db/ + db-schema/ + store/ + freezer/)
Database Engine: CKB’s underlying storage engine uses the OptimisticTransactionDB variant of RocksDB (db/src/db.rs:27), providing optimistic transaction semantics—snapshot isolation for reads and writes with conflict detection at commit time. The engine is configured with a HyperClockCache (256 MB) and Ribbon Filters to accelerate lookups. Data compression employs a Snappy + LZ4 strategy.
Column Family Definitions (19 total): Defined at db-schema/src/lib.rs:1-59. CKB organizes on-chain data across 19 Column Families: Col 0 INDEX (bidirectional block-number ⇔ hash index), Col 1 BLOCK_HEADER (block_hash → HeaderView), Col 2 BLOCK_BODY ((block_hash, tx_index) → TransactionView), Col 3 BLOCK_UNCLE (uncle blocks), Col 4 META (metadata including TIP_HEADER, CURRENT_EPOCH, and db-version), Col 5 TRANSACTION_INFO (tx_hash → (block_hash, block_number, index)), Col 6 BLOCK_EXT (block extension info including total_difficulty), Col 7 BLOCK_PROPOSAL_IDS (proposal short IDs), Col 8 BLOCK_EPOCH (block-to-epoch mapping), Col 9 EPOCH (epoch data), Col 10 CELL (live Cells / UTXOs, OutPoint → CellEntry), Col 11 UNCLES (uncle index), Col 12 CELL_DATA (Cell data content), Col 13 NUMBER_HASH (block-number–hash pairs), Col 14 CELL_DATA_HASH (Cell data hashes), Col 15 BLOCK_EXTENSION (block extension fields), Col 16 CHAIN_ROOT_MMR (MMR tree, position → HeaderDigest), Col 17 BLOCK_FILTER (block filter data), and Col 18 BLOCK_FILTER_HASH (filter hashes).
High-Level Store API: ChainDB (store/src/db.rs:25) is the primary entry point for the storage layer. It internally holds three components: db: RocksDB (the database instance), freezer: Option<Freezer> (the cold-storage engine), and cache: Arc<StoreCache> (an application-level LRU cache covering multiple hot data types such as headers, cell_data, and uncles). The core read interface is defined by the ChainStore trait (store/src/store.rs:27), which contains 50+ methods including get_block(), get_block_header(), get_block_body(), get_transaction(), get_transaction_info(), get_cell(), get_cell_data(), have_cell(), get_tip_header(), get_current_epoch_ext(), and more.
Write Path: All block writes are performed atomically via StoreTransaction (store/src/transaction.rs:31). The complete flow is: StoreTransaction::begin() → insert_block() writes block data [Cols 1, 2, 3, 7, 13, 15] → insert_block_ext() writes extension info [Col 6] → attach_block() establishes main-chain indexes [Cols 0, 5, 11] → attach_block_cell() creates new Cells and deletes spent Cells [Cols 10, 12, 14] → insert_tip_header() updates the chain tip [Col 4] → insert_epoch_ext() updates the epoch [Cols 8, 9] → commit() performs the atomic RocksDB commit. Notably, insert_block and attach_block are two distinct phases: the former writes raw block data, while the latter builds main-chain indexes and updates UTXO state.
Read Path (Three-Tier Fallback): get_block(hash) searches in order: ① StoreCache (LRU in-memory cache) → ② Freezer (cold storage, hit when block_number < freezer.number()) → ③ RocksDB (hot data).
Freezer Cold Storage: freezer/src/freezer.rs:30 implements a standalone archival storage system using memory-mapped, append-only, Snappy-compressed archive files, with a maximum data file size of 2 GB and 12-byte index entries. A background thread periodically migrates old blocks from RocksDB to the Freezer, reducing RocksDB’s data volume and compaction pressure. At read time, get_block() first checks freezer.number() to determine whether the target block has already been archived.
1.4 Synchronization Layer (sync/ + chain/)
Synchronizer — Block Sync Protocol (Protocol ID 100): Defined at sync/src/synchronizer/mod.rs:357. Key fields include chain: ChainController (chain service controller), shared: Arc<SyncShared> (sync-layer shared state), and fetch_channel: Option<Sender<FetchCMD>>. SyncShared (sync/src/types/mod.rs:991) wraps the global Shared and adds sync-specific state such as header_map and InFlightBlocks.
Timer-Driven Model: The Synchronizer’s core runtime logic is driven by four timers: SEND_GET_HEADERS (1 s interval, triggers start_sync_headers() to request headers from peers), IBD_BLOCK_FETCH (40 ms interval, find_blocks_to_fetch(IBD::In) for rapid block fetching during IBD), NOT_IBD_BLOCK_FETCH (200 ms interval, find_blocks_to_fetch(IBD::Out) for regular block fetching outside IBD), and TIMEOUT_EVICTION (1 s interval, eviction() for checking and evicting timed-out peers). During IBD, the block-fetch frequency is 5× that of non-IBD (40 ms vs. 200 ms)—a key design choice for synchronization performance.
Message Dispatch: try_process (line 381) dispatches messages to different processors based on message type: GetHeaders → GetHeadersProcess, SendHeaders → HeadersProcess (validates continuity, updates shared_best_header), GetBlocks → GetBlocksProcess, SendBlock → BlockProcess (marks BLOCK_RECEIVED, forwards to the chain service), and InIBD → InIBDProcess (peers inform each other of their IBD status).
Headers-First Sync Strategy: ① Send a GetHeaders request to a peer → ② HeadersProcess receives Headers, validates continuity + PoW, stores them in header_map and marks them HEADER_VALID → ③ BlockFetcher (sync/src/synchronizer/block_fetcher.rs:18) decides which blocks to request from which peer based on header_map → ④ Send GetBlocks; the peer responds with SendBlock.
IBD State Determination: Located at shared/src/shared.rs:382. The condition is: when (current_time − tip_header.timestamp) > 24 hours, the node is considered to be in IBD. Once the node exits IBD, it never re-enters (a unidirectional design).
Relayer — Relay Protocol (Protocol ID 101): Defined at sync/src/relayer/mod.rs:78. Responsible for rapid CompactBlock propagation, this is the primary mechanism for receiving new blocks during non-IBD steady-state operation. The processing flow is: receive CompactBlock → non-contextual verification (format, PoW) → contextual verification (parent block exists, timestamp, difficulty) → reconstruct the full block using transactions already present in the tx_pool → if any transactions are missing, send a GetBlockTransactions request to fill the gaps → once reconstruction succeeds, forward to the chain service. The concrete implementation is at sync/src/relayer/compact_block_process.rs:56.
Chain Service — Block Verification and Insertion (Multi-Threaded Architecture): ChainController (chain/src/chain_controller.rs:16) sends blocks via channel to the backend ChainService thread (chain/src/chain_service.rs:17). The ChainService thread performs non-contextual verification (PoW + transaction syntax + signatures) and calls insert_block() to write to the DB. Then OrphanBroker::process_lonely_block() routes the block: if the parent block is already stored, it forwards the block to the ConsumeUnverifiedBlocks thread; if the parent is absent, it enters the OrphanBlockPool (chain/src/utils/orphan_block_pool.rs:129) to wait. The OrphanBlockPool internally uses HashMap<ParentHash, HashMap<BlockHash, LonelyBlock>> to store orphan blocks; once a parent is verified, search_orphan_leader() recursively processes its descendant blocks.
ConsumeUnverifiedBlocks: Located at chain/src/verify.rs:31, running as an independent thread. It performs full contextual verification (UTXO checks, script execution, proposal window, uncle validation), computes total_difficulty, and determines whether a new best chain has emerged. If a fork switch is needed, it executes detach of old blocks + attach of new blocks, then atomically commits via StoreTransaction::commit() to RocksDB, updates SnapshotMgr to produce a new chain-tip snapshot, and fires a verify_callback to notify the sync layer of the verification result, completing the feedback loop.
1.5 Complete Call Chain: Block from Peer to Disk
[Peer]
│ TCP / Tentacle
▼
NetworkService (receive and decompress message)
│
├─ Protocol ID 100 ──→ Synchronizer.received()
│ └─ BlockProcess::execute()
│
└─ Protocol ID 101 ──→ Relayer.received()
└─ CompactBlockProcess::execute()
└─ Reconstruct full block via tx_pool
│
▼
ChainController::asynchronous_process_remote_block()
│ Send via channel
▼
ChainService thread
│ ① Non-contextual verification (PoW, tx syntax, signatures)
│ ② insert_block() → RocksDB [Cols 1,2,3,7,13,15]
▼
OrphanBroker::process_lonely_block()
│ Parent stored? ──No──→ OrphanBlockPool (wait for parent)
│ │
│ Yes
▼
ConsumeUnverifiedBlocks thread
│ ① Full contextual verification (UTXO, Script, Epoch, Uncle)
│ ② Compute total_difficulty
│ ③ New best chain? → Fork switch (detach_block + attach_block)
│ ④ StoreTransaction::commit() → atomic RocksDB write
│ [Cols 0,4,5,6,8,9,10,11,12,14]
│ ⑤ Update SnapshotMgr → new chain-tip snapshot
│ ⑥ verify_callback → notify sync layer of result
▼
[Block persisted]
│
▼ (background)
Freezer::freeze() — periodically migrate old blocks to cold storage
1.6 Key File Index
Startup entry ckb-bin/src/subcommand/run.rs:17
Service assembly util/launcher/src/lib.rs:191 (build_shared), :403 (start_network)
Shared hub shared/src/shared.rs:58 (struct), :382 (IBD determination)
Network service network/src/network.rs:73 (State), :827 (Service), :1334 (Controller)
Protocol trait network/src/protocols/mod.rs:47 (Context), :161 (Handler)
Protocol IDs network/src/protocols/support_protocols.rs:20-57
Peer management network/src/peer_registry.rs:22
DB engine db/src/db.rs:27 (OptimisticTransactionDB)
Column families db-schema/src/lib.rs:1-59
Store API store/src/store.rs:27 (ChainStore trait)
Store writes store/src/transaction.rs:131-415
ChainDB store/src/db.rs:25
Freezer freezer/src/freezer.rs:30-177
Synchronizer sync/src/synchronizer/mod.rs:357 (struct), :869 (Handler impl)
BlockFetcher sync/src/synchronizer/block_fetcher.rs:18
Relayer sync/src/relayer/mod.rs:78
CompactBlock sync/src/relayer/compact_block_process.rs:56
SyncShared sync/src/types/mod.rs:991
ChainController chain/src/chain_controller.rs:16
ChainService chain/src/chain_service.rs:17
Contextual verify chain/src/verify.rs:31
Orphan pool chain/src/utils/orphan_block_pool.rs:129
DB migration db-migration/src/lib.rs:34-394
2. Development Environment Setup
The local development machine runs Ubuntu 24.04 LTS (kernel version 5.15+, meeting eBPF feature requirements). The Rust toolchain was installed via rustup: the stable channel is used for userspace development, and the nightly channel is used for eBPF kernel-side compilation (as specified in rust-toolchain.toml). The rust-src component and bpf-linker were also installed to support cross-compilation for the BPF target. All system libraries required for compiling the CKB source (clang, llvm, pkg-config, libssl-dev, librocksdb-dev, etc.) have been installed and verified. The IDE is VS Code with the rust-analyzer extension, configured with automatic clippy checks and cargo fmt formatting. Additionally, auxiliary debugging tools such as bpftool and bpftrace were installed for eBPF program inspection and tracing.
3. CKB Testnet Node Deployment
A CKB testnet node was deployed using the official release binary downloaded from GitHub. The specific steps were: ckb init --chain testnet was used to generate testnet configuration files (ckb.toml and ckb-miner.toml); after confirming the bootnode configuration in ckb.toml, ckb run was executed to start the node. Once the node was running, connectivity was verified via the RPC interface (default http://127.0.0.1:8114): curl was used to call get_tip_block_number to confirm that block height was continuously increasing, and get_peers was called to confirm successful connections to multiple testnet peers. During the initial sync phase, the IBD speed was approximately one block per second, and the node has since synced to the latest height.
III. Next Week’s Plan (Week 2)
The core objective for next week is to conduct a comprehensive symbol reconnaissance and analysis of the CKB binary, and to engineer the results into the first subcommand of the ckb-probe tool. This breaks down into the following four areas:
Comprehensive CKB Binary Symbol Reconnaissance: Obtain the precompiled binary published on CKB’s official GitHub Release page (Linux x86_64) as well as locally compiled debug and release builds from source. Use nm, readelf --syms, objdump -T, and similar tools to extract symbol tables from all three binaries. Perform a comparative analysis of whether the official release has been stripped, differences in symbol visibility (exported / local / undefined), and coverage differences between the two build methods. The output will be a symbol-difference matrix comparing the official release against self-compiled builds.
In-Depth Analysis of RocksDB Linkage: Focus on analyzing CKB’s RocksDB linking strategy. Use ldd to check dynamic link dependencies and nm -D to check whether rocksdb_* C API symbols are exposed in the dynamic symbol table. Cross-reference with the ckb-db crate’s Cargo.toml and build.rs to determine whether RocksDB is statically linked (bundled) or dynamically linked to the system library. Further analyze the symbol names and mangling of key RocksDB functions (e.g., rocksdb_open, rocksdb_put, rocksdb_get, rocksdb_write) in the final binary, providing a basis for selecting uprobe attachment points.
Tiered Report Generation: Organize the reconnaissance results into a structured, tiered report, classifying symbols into three levels: “directly uprobe-attachable symbols,” “symbols requiring DWARF-assisted location,” and “symbols that have been inlined or stripped and are unavailable.” The report will cover key functions across the P2P layer, storage layer (including RocksDB), synchronization layer, and transaction pool, serving as a selection reference manual for subsequent eBPF probe development.
Implement the ckb-probe symbols Subcommand: Based on the above reconnaissance methodology, implement a symbols subcommand in the ckb-probe project. This command will accept the CKB binary path as input and automate symbol extraction, classification, and report generation. On the technical implementation side, the plan is to use the goblin or object crate for ELF file parsing, clap for the CLI framework, and support two output formats: terminal tables (with color-coded tier annotations) and JSON for downstream pipeline integration.
Self-Assessment: All tasks were completed on schedule. A precise, source-line-level understanding has been established of the complete code path from CKB node startup and assembly through P2P communication, storage persistence, and block synchronization and insertion. The development environment and testnet node are fully operational, providing a solid foundation for next week’s binary symbol analysis and ckb-probe tool development.