← Back to list

Three Classic Blockchain P2P Attacks, Rebuilt in a Rust Local Lab

Sybil, Eclipse, and Network Partition. From real incidents to local simulations

Wen-Ting (Joseph) Wang in Stackademic · 2026-06-09 13:02 · 3 claps · 5.8 min read
#rust #blockchain #cybersecurity #peer-to-peer
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3 🔒 · Cybersecurity

Three Classic Blockchain P2P 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 *NodeId*s in P2P; I track *sybil_peers / total_peers* to 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_remaining* to see whether Group A still advertises Group B.

Lab setup: one Rust workspace, three scenarios

Four crates, one runner:

Fig. 1. `p2p-core` → `p2p-node` / `p2p-lab` → `p2p-env`.

Fig. 1. p2p-corep2p-node / p2p-labp2p-env.

  • `p2p-core:NodeId,Message`
  • ***p2p-node***: honest node behavior, peer table (*MAX_PEERS = 8*)
  • ***p2p-lab***: crawler and attack tools
  • ***p2p-env:** scenario runner (`cargo run -p p2p-env — …*`)

Fig. 2. Workspace build.

Fig. 2. Workspace build.

Protocol is small on purpose:

Fig. 3. `Hello`, `Ping`/`Pong`, `GetPeers`/`Peers`, `Tip`.

Fig. 3. Hello, Ping/Pong, GetPeers/Peers, Tip.

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 peers
  • ***Ping*** / ***Pong***: keepalive
  • ***GetPeers*** / ***Peers***: what the gym queries after each run
  • ***Tip***: 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 *NodeId*s. The victim has *MAX_PEERS = 8*. I launched 10 Sybil identities and measured how many peer slots they occupied.

Fig. 4. Eight slots; many identities compete to hold them open.

Fig. 4. Eight slots; many identities compete to hold them open.

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

Fig. 5. 5/8 slots Sybil; `occupancy: 62.5`, `Success: false`.

Fig. 5. 5/8 slots Sybil; occupancy: 62.5, Success: false.

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?

Fig. 6. Eclipse path: connect, send fake `Tip`, check peer mix.

Fig. 6. Eclipse path: connect, send fake Tip, check peer mix.

send_tip(&mut writer, Tip { height: 9999, hash: "attacker_tip_FAKE".into() }).await;

let honest_peers = peers.len().saturating_sub(sybil_peers);
let state_diverged = victim_tip != honest_tip && honest_peers == 0;

AttackResult { success: state_diverged, /* ... */ }
cargo run -p p2p-env - - honest 4 - attack eclipse - sybil 20

Fig. 7. `attacker_tip_FAKE` logged; 3/8 Sybil, 5 honest; `state_diverged=false`.

Fig. 7. attacker_tip_FAKE logged; 3/8 Sybil, 5 honest; state_diverged=false.

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?

Fig. 8. Symmetric blocklists; metric is `cross_peers_remaining`.

Fig. 8. Symmetric blocklists; metric is cross_peers_remaining.

block_cross_group_links(env, &addrs_a, &addrs_b);

let cross_peers = a_peers
    .iter()
    .filter(|p| addrs_b.iter().any(|b| b.port() == p.port()))
    .count();

AttackResult { success: cross_peers == 0, /* ... */ }```bash
cargo run -p p2p-env - - honest 6 - attack partition

Fig. 9. Group A `[0,1,2]` / Group B `[3,4,5]`; `cross_peers_remaining: 0`, `Success: true`.

Fig. 9. Group A [0,1,2] / Group B [3,4,5]; cross_peers_remaining: 0, Success: true.

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 *NodeId* or 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*, and *cross_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:

This post was originally published on my personal blog: https://egpivo.github.io/2026/06/09/building-blockchain-p2p-security-gym-rust.html


메타데이터
post_id
fc13761904cc
slug
three-classic-blockchain-p2p-attacks-rebuilt-in-a-rust-local-lab-fc13761904cc
url
https://blog.stackademic.com/three-classic-blockchain-p2p-attacks-rebuilt-in-a-rust-local-lab-fc13761904cc
canonical_url
https://blog.stackademic.com/three-classic-blockchain-p2p-attacks-rebuilt-in-a-rust-local-lab-fc13761904cc
author_url
https://medium.com/@egpivo
status
ok
fetched_at
2026-06-11 05:11:55