Three Classic Blockchain Attacks, Rebuilt in a Rust Local Lab
Sybil, Eclipse, and Network Partition — from real incidents to local simulations
I wanted one local Rust lab where I could run three classic blockchain P2P failure modes with one CLI: Sybil, Eclipse, and network partition. The goal was not to attack a live network. The goal was to make each failure mode observable: which peers entered the table, which state message arrived, and whether the victim still had an honest peer to compare against.
Everything runs on 127.0.0.1 in rust-p2p-protocol-lab.
Overview: why these three attacks?
The three modes, in the order I ran them:
- Sybil: one actor creates many identities. Cheap wallets in airdrops, cheap
NodeIds in P2P; I tracksybil_peers / total_peersto see how many peer slots one operator can fill. - Eclipse: those identities try to control what a victim sees. Real concern is attacker-controlled state; in the lab I check
state_diverged—whether bad state sticks when honest peers remain. - Partition: two groups stop seeing each other. Real chain splits show why split views matter; locally I count
cross_peers_remainingto see whether Group A still advertises Group B.
Lab setup: one Rust workspace, three scenarios
Four crates, one runner:
p2p-core:NodeId,Messagep2p-node: honest node behavior, peer table (MAX_PEERS = 8)p2p-lab: crawler and attack toolsp2p-env: scenario runner (cargo run -p p2p-env -- ...)
Protocol is small on purpose:
pub enum Message {
Hello {
node_id: NodeId,
listen_addr: SocketAddr,
peers: Vec<SocketAddr>,
},
Ping,
Pong,
GetPeers,
Peers(Vec<SocketAddr>),
Tip {
height: u64,
hash: String,
},
}
Hello: identity + listen address + known peersPing/Pong: keepaliveGetPeers/Peers: what the gym queries after each runTip: a loggable state string so I can see which hash the victim picked up
Tip is not a validated block header. It exists so the Eclipse scenario has something to push besides peer-table entries.
Attack 1 — Sybil: many identities, limited peer slots
Real case
In May 2022, Optimism removed more than 17,000 Sybil addresses from OP Airdrop #1—wallet farming that had slipped through initial filters. The Block reported the same enforcement. That is application-layer abuse, not a P2P attack. The contested resource was token eligibility, not peer slots.
The lab tests the same pressure with a different object: one operator, many cheap NodeIds, eight peer slots. Wallet farming and peer-table flooding are not the same mechanism. They share one question: how much can one actor gain by multiplying identities?
Local model
In my lab, Sybil identities are local nodes with different NodeIds. The victim has MAX_PEERS = 8. I launched 10 Sybil identities and measured how many peer slots they occupied.
let occupancy = sybil_peers as f64 / peers.len().max(1) as f64 * 100.0;
cargo run -p p2p-env -- --honest 4 --attack sybil --sybil 10
I expected full capture when I outnumbered the table. I got 5 of 8 slots instead. Honest nodes connected during reset() before every Sybil session landed.
This was not Sybil success. It was 62.5% malicious occupancy, i.e., real pressure on the peer table, but three honest slots still held.
Attack 2 — Eclipse: fake state is not enough
Real case
Marcus et al.’s “False Friends” work showed an Ethereum node could be isolated by flooding its discovery table with attacker-controlled peers, then feeding that victim a filtered chain view. The attack targets peer selection and connection management—not consensus math. The paper reports that countermeasures were incorporated into Geth v1.9.0.
I am not reproducing that attack. It is the reference for why Eclipse sits next to Sybil: Sybil fills slots; Eclipse cuts honest cross-checks.
Local model
The lab strips that down to one check: attacker peers send a fake Tip—does the victim lose every honest peer?
let state_diverged = victim_tip != honest_tip && honest_peers == 0;
cargo run -p p2p-env -- --honest 4 --attack eclipse --sybil 20
This was the most useful failed run. The fake state arrived, but the victim still had honest peers. I stopped treating Sybil and Eclipse as the same thing after this run. Sybil gave me peer pressure. Eclipse required removing the honest path.
In this lab, Eclipse requires honest_peers == 0 before state_diverged can flip true.
Attack 3 — Network partition: when groups stop seeing each other
Real case
In August 2021, a Geth bug led some unpatched nodes onto a minority chain—a brief split until operators patched. Bitcoin had an earlier version of the same failure mode: BIP 50 documents a March 2013 fork where Bitcoin 0.8 accepted a block that pre-0.8 nodes rejected.
Neither incident used my blocklist model. Both show what happens when two groups stop sharing the same view: recovery becomes a client rollout and coordination problem, not just a consensus rule.
Partition is also the setup people often cite for double-spending scenarios: broadcast conflicting transactions to two isolated groups. That requires a transaction layer, which this lab does not have. Here I only check whether cross-group peers disappear.
Local model
Six local nodes, Group A [0,1,2] and Group B [3,4,5], symmetric blocklists inside the process. Metric: does Group A’s peer table still list any Group B port?
let cross_peers = a_peers
.iter()
.filter(|p| addrs_b.iter().any(|b| b.port() == p.port()))
.count();
cargo run -p p2p-env -- --honest 6 --attack partition
This is an application-layer local simulation, not BGP routing and not a real Internet partition.
Closing
The most useful result was not the clean partition success. It was the failed Eclipse run. I could deliver fake state, but I could not make that fake state become the victim’s only view while honest peers remained. That gap is the real design space: peer replacement, anchor peers, diversity rules, identity cost, and monitoring.
Next knobs I want to turn in the same lab:
- signed
NodeIdor another identity-cost mechanism - peer diversity policy (subnet / operator bucketing)
- anchor peers that eviction cannot drop
- different peer eviction rules under slot pressure
- IDS scoring on peer-table concentration
- rerun all three modes and compare
occupancy,honest_peers, andcross_peers_remaining
Appendix
Reproduce
git clone https://github.com/egpivo/rust-p2p-protocol-lab.git
cd rust-p2p-protocol-lab
cargo run -p p2p-env -- --honest 4 --attack sybil --sybil 10
cargo run -p p2p-env -- --honest 4 --attack eclipse --sybil 20
cargo run -p p2p-env -- --honest 6 --attack partition
References
Sources cited for the real-world case introductions:
- Optimism, Let the Claims Begin (May 2022): OP Airdrop #1 launch; removal of 17,000+ Sybil addresses.
- The Block, Optimism Cracks Down on Airdrop Farmers (May 2022): press coverage of the same Sybil filtering.
- Marcus et al., Eclipsing Ethereum Peers with False Friends (arXiv, 2019): Ethereum eclipse attack via peer-table flooding; paper reports Geth v1.9.0 countermeasures.
- Go-Ethereum, Minority Split Postmortem (August 2021): August 2021 chain split affecting unpatched Geth nodes.
- BIP 50: March 2013 Chain Fork Post-Mortem: Bitcoin 0.8 vs pre-0.8 block acceptance mismatch.