Skip to main content

netget/server/devp2p/
session.rs

1//! One devp2p session after the TCP connection, shared by the server and the client.
2//!
3//! [`establish`] runs the RLPx handshake and the Hello exchange (bounded by
4//! [`HANDSHAKE_TIMEOUT`]); [`read_loop`] is the reader task, which answers Ping itself so a
5//! model call in progress never makes the peer time the session out; [`Session`] holds what the
6//! two sides have said — both Statuses, the requests each side still owes an answer — decodes
7//! every `eth` message into a [`PeerEvent`] for the model, and carries out the model's
8//! [`Command`]s, refusing any that would put something on the wire the session cannot vouch for:
9//! headers that do not answer the request they claim to, transactions nobody asked for, a reply
10//! to a request that is already answered.
11use super::crypto::{self, NodeKey};
12use super::eth::{self, code, Announcement, HeaderRequest, Hello, Origin, Status};
13use super::rlp::Rlp;
14use super::rlpx::{self, FrameError, FrameReader, FrameWriter};
15use crate::utils::clock::Instant;
16use anyhow::{bail, ensure, Context, Result};
17use secp256k1::PublicKey;
18use serde_json::{json, Map, Value};
19use std::collections::HashMap;
20use std::sync::Arc;
21use std::time::Duration;
22use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
23use tokio::sync::{mpsc, Mutex};
24
25/// The largest message accepted after decompression, and the server's `max_inbound_bytes`.
26/// go-ethereum's own cap is 10 MiB; nothing a model answers or asks for comes near either.
27pub const MAX_MESSAGE_BYTES: usize = 4 * 1024 * 1024;
28/// The RLPx handshake and the Hello exchange together.
29pub const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
30/// How long a session may go without a frame. Peers ping every 15 seconds.
31pub const IDLE_TIMEOUT: Duration = Duration::from_secs(60);
32/// How often this side pings.
33pub const PING_INTERVAL: Duration = Duration::from_secs(15);
34/// How long a request NetGet sent may stay unanswered.
35pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(20);
36/// How long one frame may take to write.
37pub const WRITE_TIMEOUT: Duration = Duration::from_secs(20);
38/// Requests NetGet may have outstanding at once.
39pub const MAX_OUTSTANDING: usize = 16;
40/// Peer requests NetGet may owe an answer to at once; past this, the session is closed.
41pub const MAX_OWED: usize = 64;
42/// The most headers NetGet asks for in one request.
43pub const MAX_REQUEST_HEADERS: u64 = 64;
44/// The most hashes NetGet asks about in one request.
45pub const MAX_REQUEST_HASHES: usize = 256;
46/// The most headers, bodies or transactions one answer may carry (go-ethereum serves at most
47/// 1024 headers).
48pub const MAX_ANSWER_ITEMS: usize = 1024;
49/// The most hashes a peer may ask about or announce in one message.
50pub const MAX_PEER_HASHES: usize = 4096;
51
52pub type BoxRead = Box<dyn AsyncRead + Send + Unpin>;
53pub type BoxWrite = Box<dyn AsyncWrite + Send + Unpin>;
54
55/// The write side: the frame codec and the socket half, behind one lock so frames never
56/// interleave.
57pub struct Writer {
58    codec: FrameWriter,
59    half: BoxWrite,
60}
61
62pub type SharedWriter = Arc<Mutex<Writer>>;
63
64/// Send one message; returns the bytes written.
65pub async fn send(w: &SharedWriter, msg_code: u64, payload: &[u8]) -> Result<usize> {
66    ensure!(
67        payload.len() <= MAX_MESSAGE_BYTES,
68        "a {}-byte message is above the {MAX_MESSAGE_BYTES}-byte bound",
69        payload.len()
70    );
71    let mut g = w.lock().await;
72    let frame = g.codec.encode(msg_code, payload)?;
73    let half = &mut g.half;
74    tokio::time::timeout(WRITE_TIMEOUT, async {
75        half.write_all(&frame).await?;
76        half.flush().await
77    })
78    .await
79    .context("writing a frame timed out")??;
80    Ok(frame.len())
81}
82
83/// Send Disconnect and close our side. Best effort: the peer may already be gone.
84pub async fn disconnect(w: &SharedWriter, reason: u64) {
85    let _ = send(w, code::DISCONNECT, &eth::encode_disconnect(reason)).await;
86    close(w).await;
87}
88
89pub async fn close(w: &SharedWriter) {
90    let mut g = w.lock().await;
91    let _ = tokio::time::timeout(Duration::from_secs(2), g.half.shutdown()).await;
92}
93
94/// Who dials.
95pub enum Role {
96    Dial(PublicKey),
97    Listen,
98}
99
100/// A session past Hello.
101pub struct Established {
102    pub reader: FrameReader,
103    pub read_half: BoxRead,
104    pub writer: SharedWriter,
105    pub hello: Hello,
106    pub eth_version: u64,
107    pub remote_id: [u8; 64],
108}
109
110/// Why a session never got past Hello.
111#[derive(Debug)]
112pub enum EstablishError {
113    /// The handshake failed, timed out, or the peer is not speaking RLPx.
114    Transport(String),
115    /// The peer sent Disconnect instead of Hello.
116    PeerDisconnected(u64),
117    /// NetGet refused the peer at the protocol level and told it why.
118    Refused { reason: u64, detail: String },
119}
120
121impl std::fmt::Display for EstablishError {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        match self {
124            EstablishError::Transport(m) => write!(f, "{m}"),
125            EstablishError::PeerDisconnected(r) => {
126                write!(f, "the peer disconnected: {}", eth::reason_name(*r))
127            }
128            EstablishError::Refused { reason, detail } => {
129                write!(f, "refused ({}): {detail}", eth::reason_name(*reason))
130            }
131        }
132    }
133}
134
135/// The RLPx handshake, then Hello both ways. Our Hello goes out first and unconditionally — it
136/// is NetGet's identity, not a decision — and the peer's must name the key it authenticated
137/// with and share an `eth` version.
138pub async fn establish(
139    mut read_half: BoxRead,
140    mut write_half: BoxWrite,
141    key: &NodeKey,
142    role: Role,
143    client_id: &str,
144    listen_port: u16,
145) -> std::result::Result<Established, EstablishError> {
146    let transport = |e: anyhow::Error| EstablishError::Transport(format!("{e:#}"));
147    tokio::time::timeout(HANDSHAKE_TIMEOUT, async move {
148        let secrets = match role {
149            Role::Dial(remote) => {
150                rlpx::initiate(&mut read_half, &mut write_half, key, &remote).await
151            }
152            Role::Listen => rlpx::respond(&mut read_half, &mut write_half, key).await,
153        }
154        .map_err(transport)?;
155        let remote_id = crypto::node_id(&secrets.remote);
156        let (mut reader, codec) = rlpx::codec(secrets, MAX_MESSAGE_BYTES);
157        let writer: SharedWriter = Arc::new(Mutex::new(Writer {
158            codec,
159            half: write_half,
160        }));
161        let ours = Hello::ours(client_id, listen_port, key.id());
162        send(&writer, code::HELLO, &ours.encode())
163            .await
164            .map_err(transport)?;
165        let (msg, payload) = reader.read(&mut read_half).await.map_err(|e| match e {
166            FrameError::Io(e) => EstablishError::Transport(format!("reading Hello: {e}")),
167            other => EstablishError::Transport(format!("reading Hello: {other}")),
168        })?;
169        if msg == code::DISCONNECT {
170            return Err(EstablishError::PeerDisconnected(eth::decode_disconnect(
171                &payload,
172            )));
173        }
174        if msg != code::HELLO {
175            disconnect(&writer, 0x02).await;
176            return Err(EstablishError::Refused {
177                reason: 0x02,
178                detail: format!("the first message is {msg:#04x}, not Hello"),
179            });
180        }
181        let hello = match Hello::decode(&payload) {
182            Ok(h) => h,
183            Err(e) => {
184                disconnect(&writer, 0x02).await;
185                return Err(EstablishError::Refused {
186                    reason: 0x02,
187                    detail: format!("Hello: {e:#}"),
188                });
189            }
190        };
191        // From here on, both directions are Snappy when the peer speaks version 5.
192        let snappy = hello.version >= eth::BASE_VERSION;
193        reader.set_snappy(snappy);
194        writer.lock().await.codec.set_snappy(snappy);
195        if hello.id != remote_id {
196            disconnect(&writer, 0x09).await;
197            return Err(EstablishError::Refused {
198                reason: 0x09,
199                detail: "the Hello names a different node than the handshake authenticated".into(),
200            });
201        }
202        if hello.id == key.id() {
203            disconnect(&writer, 0x0a).await;
204            return Err(EstablishError::Refused {
205                reason: 0x0a,
206                detail: "the peer is this node".into(),
207            });
208        }
209        let Some(eth_version) = hello.shared_eth() else {
210            disconnect(&writer, 0x03).await;
211            return Err(EstablishError::Refused {
212                reason: 0x03,
213                detail: format!(
214                    "no shared eth version: the peer offers {:?}, NetGet speaks eth/68 and eth/69",
215                    hello.caps_strings()
216                ),
217            });
218        };
219        Ok(Established {
220            reader,
221            read_half,
222            writer,
223            hello,
224            eth_version,
225            remote_id,
226        })
227    })
228    .await
229    .unwrap_or_else(|_| {
230        Err(EstablishError::Transport(format!(
231            "the handshake did not finish within {}s",
232            HANDSHAKE_TIMEOUT.as_secs()
233        )))
234    })
235}
236
237/// What the reader task hands over.
238#[derive(Debug)]
239pub enum Inbound {
240    Message { code: u64, payload: Vec<u8> },
241    Ended(End),
242}
243
244/// Why a session ended, and how to tag it.
245#[derive(Debug, Clone)]
246pub struct End {
247    pub reason: String,
248    /// The `decision=` tag for the log, or None for an ordinary end.
249    pub decision: Option<&'static str>,
250}
251
252impl End {
253    pub fn plain(reason: impl Into<String>) -> End {
254        End {
255            reason: reason.into(),
256            decision: None,
257        }
258    }
259}
260
261/// The reader task: read frames until the session ends, answering Ping with Pong itself.
262pub async fn read_loop(
263    mut reader: FrameReader,
264    mut half: BoxRead,
265    writer: SharedWriter,
266    tx: mpsc::Sender<Inbound>,
267) {
268    let end = loop {
269        match reader.read(&mut half).await {
270            Ok((code::PING, _)) => {
271                if send(&writer, code::PONG, &eth::empty_payload())
272                    .await
273                    .is_err()
274                {
275                    break End::plain("the connection could not be written");
276                }
277            }
278            Ok((code::PONG, _)) => {}
279            Ok((code, payload)) => {
280                if tx.send(Inbound::Message { code, payload }).await.is_err() {
281                    return;
282                }
283            }
284            Err(FrameError::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
285                break End::plain("the peer closed the connection");
286            }
287            Err(FrameError::Io(e)) if e.kind() == std::io::ErrorKind::TimedOut => {
288                break End {
289                    reason: format!("no frame for {}s", IDLE_TIMEOUT.as_secs()),
290                    decision: Some("fail_closed_idle_timeout"),
291                };
292            }
293            Err(FrameError::Io(e)) => break End::plain(format!("read failed: {e}")),
294            Err(e @ FrameError::TooLarge { .. }) => {
295                disconnect(&writer, 0x02).await;
296                break End {
297                    reason: format!("message too large: {e}"),
298                    decision: Some("fail_closed_message_too_large"),
299                };
300            }
301            Err(FrameError::Protocol(m)) => {
302                disconnect(&writer, 0x02).await;
303                break End {
304                    reason: m,
305                    decision: Some("protocol_error"),
306                };
307            }
308        }
309    };
310    let _ = tx.send(Inbound::Ended(end)).await;
311}
312
313// ---------------------------------------------------------------------------------------
314// What the model may tell a session to do
315// ---------------------------------------------------------------------------------------
316
317/// A Status as the model specifies it: explicit fields, optionally mirroring the peer's.
318#[derive(Debug, Clone, Default)]
319pub struct StatusSpec {
320    pub fields: Map<String, Value>,
321    pub mirror_peer: bool,
322}
323
324/// One validated action.
325#[derive(Debug, Clone)]
326pub enum Command {
327    SendStatus(StatusSpec),
328    SendHeaders {
329        request_id: Option<u64>,
330        headers: Vec<Rlp>,
331    },
332    SendBodies {
333        request_id: Option<u64>,
334        bodies: Vec<Rlp>,
335    },
336    SendPooled {
337        request_id: Option<u64>,
338        transactions: Vec<(Rlp, [u8; 32])>,
339    },
340    RequestHeaders(HeaderRequest),
341    RequestBodies(Vec<[u8; 32]>),
342    RequestPooled(Vec<[u8; 32]>),
343    Disconnect(u64),
344}
345
346pub const SEND_STATUS: &str = "eth_send_status";
347pub const SEND_HEADERS: &str = "eth_send_block_headers";
348pub const SEND_BODIES: &str = "eth_send_block_bodies";
349pub const SEND_POOLED: &str = "eth_send_pooled_transactions";
350pub const REQUEST_HEADERS: &str = "eth_request_block_headers";
351pub const REQUEST_BODIES: &str = "eth_request_block_bodies";
352pub const REQUEST_POOLED: &str = "eth_request_pooled_transactions";
353pub const DISCONNECT: &str = "devp2p_disconnect";
354
355const STATUS_FIELDS: &[&str] = &[
356    "network_id",
357    "genesis_hash",
358    "fork_hash",
359    "fork_next",
360    "forks",
361    "earliest_block",
362    "latest_block",
363    "latest_block_hash",
364    "total_difficulty",
365    "head_hash",
366];
367
368fn request_id(v: &Value) -> Result<Option<u64>> {
369    match v.get("request_id") {
370        None | Some(Value::Null) => Ok(None),
371        Some(r) => Ok(Some(eth::parse_u64(r, "request_id")?)),
372    }
373}
374
375fn array<'a>(v: &'a Value, key: &str, max: usize) -> Result<&'a Vec<Value>> {
376    let a = v[key]
377        .as_array()
378        .with_context(|| format!("{key} is an array"))?;
379    ensure!(
380        a.len() <= max,
381        "{key} holds {} entries, above {max}",
382        a.len()
383    );
384    Ok(a)
385}
386
387fn hashes(v: &Value, key: &str, max: usize) -> Result<Vec<[u8; 32]>> {
388    let a = array(v, key, max)?;
389    ensure!(!a.is_empty(), "{key} names at least one hash");
390    a.iter().map(|h| eth::parse_fixed::<32>(h, key)).collect()
391}
392
393/// Validate one action. Everything a peer would see is checked here, so an action the
394/// executor accepts is one the session can carry out.
395pub fn parse_command(v: &Value) -> Result<Command> {
396    let kind = v["type"].as_str().context("an action has a type")?;
397    Ok(match kind {
398        SEND_STATUS => {
399            let obj = v.as_object().context("action")?;
400            let mut fields = Map::new();
401            for (k, val) in obj {
402                if k == "type" || k == "mirror_peer" || val.is_null() {
403                    continue;
404                }
405                ensure!(
406                    STATUS_FIELDS.contains(&k.as_str()),
407                    "{k:?} is not a Status field; the fields are {}",
408                    STATUS_FIELDS.join(", ")
409                );
410                fields.insert(k.clone(), val.clone());
411            }
412            let spec = StatusSpec {
413                fields,
414                mirror_peer: v["mirror_peer"].as_bool().unwrap_or(false),
415            };
416            // Check the formats now; which fields are missing depends on the peer.
417            check_status_fields(&spec)?;
418            Command::SendStatus(spec)
419        }
420        SEND_HEADERS => {
421            let headers = array(v, "headers", MAX_ANSWER_ITEMS)?
422                .iter()
423                .enumerate()
424                .map(|(i, h)| eth::header_from_json(h).with_context(|| format!("headers[{i}]")))
425                .collect::<Result<Vec<_>>>()?;
426            Command::SendHeaders {
427                request_id: request_id(v)?,
428                headers,
429            }
430        }
431        SEND_BODIES => {
432            let bodies = array(v, "bodies", MAX_ANSWER_ITEMS)?
433                .iter()
434                .enumerate()
435                .map(|(i, b)| eth::body_from_json(b).with_context(|| format!("bodies[{i}]")))
436                .collect::<Result<Vec<_>>>()?;
437            Command::SendBodies {
438                request_id: request_id(v)?,
439                bodies,
440            }
441        }
442        SEND_POOLED => {
443            let transactions = array(v, "transactions", MAX_ANSWER_ITEMS)?
444                .iter()
445                .enumerate()
446                .map(|(i, t)| {
447                    eth::transaction_from_hex(t).with_context(|| format!("transactions[{i}]"))
448                })
449                .collect::<Result<Vec<_>>>()?;
450            Command::SendPooled {
451                request_id: request_id(v)?,
452                transactions,
453            }
454        }
455        REQUEST_HEADERS => {
456            let origin = Origin::from_json(&v["origin"])?;
457            let num = |k: &str, d: u64| -> Result<u64> {
458                match v.get(k) {
459                    None | Some(Value::Null) => Ok(d),
460                    Some(x) => eth::parse_u64(x, k),
461                }
462            };
463            let amount = num("amount", 1)?;
464            ensure!(
465                (1..=MAX_REQUEST_HEADERS).contains(&amount),
466                "amount is 1 to {MAX_REQUEST_HEADERS}"
467            );
468            let skip = num("skip", 0)?;
469            ensure!(skip <= 1_000_000, "skip is at most 1000000");
470            Command::RequestHeaders(HeaderRequest {
471                origin,
472                amount,
473                skip,
474                reverse: v["reverse"].as_bool().unwrap_or(false),
475            })
476        }
477        REQUEST_BODIES => Command::RequestBodies(hashes(v, "hashes", MAX_REQUEST_HASHES)?),
478        REQUEST_POOLED => Command::RequestPooled(hashes(v, "hashes", MAX_REQUEST_HASHES)?),
479        DISCONNECT | "close_connection" => Command::Disconnect(eth::reason_code(
480            v["reason"].as_str().unwrap_or("requested"),
481        )?),
482        other => bail!("unknown devp2p action {other:?}"),
483    })
484}
485
486fn check_status_fields(spec: &StatusSpec) -> Result<()> {
487    for (k, v) in &spec.fields {
488        match k.as_str() {
489            "network_id" | "fork_next" | "earliest_block" | "latest_block" => {
490                eth::parse_u64(v, k)?;
491            }
492            "genesis_hash" | "latest_block_hash" | "head_hash" => {
493                eth::parse_fixed::<32>(v, k)?;
494            }
495            "fork_hash" => {
496                eth::parse_fixed::<4>(v, k)?;
497            }
498            "total_difficulty" => {
499                eth::parse_big(v, k)?;
500            }
501            "forks" => {
502                let a = v
503                    .as_array()
504                    .context("forks is an array of block numbers or timestamps")?;
505                ensure!(a.len() <= 64, "at most 64 forks");
506                for f in a {
507                    eth::parse_u64(f, "forks entry")?;
508                }
509            }
510            _ => {}
511        }
512    }
513    Ok(())
514}
515
516/// Status values configured at startup, used for whatever the model leaves out.
517#[derive(Debug, Clone, Default)]
518pub struct StatusDefaults {
519    pub network_id: Option<u64>,
520    pub genesis: Option<[u8; 32]>,
521    pub forks: Vec<u64>,
522    pub fork_next: Option<u64>,
523    pub latest_block: Option<u64>,
524    pub latest_block_hash: Option<[u8; 32]>,
525}
526
527impl StatusDefaults {
528    pub fn to_json(&self) -> Value {
529        json!({
530            "network_id": self.network_id,
531            "genesis_hash": self.genesis.map(|g| eth::hex0x(&g)),
532            "forks": self.forks,
533            "fork_next": self.fork_next,
534            "latest_block": self.latest_block,
535            "latest_block_hash": self.latest_block_hash.map(|h| eth::hex0x(&h)),
536        })
537    }
538}
539
540/// Resolve a Status: each field from the action, else the peer's (with `mirror_peer`), else
541/// the startup configuration, else a refusal naming the field. The fork hash, when nobody gave
542/// one, is computed from the genesis hash and the passed forks.
543pub fn build_status(
544    spec: &StatusSpec,
545    version: u64,
546    peer: Option<&Status>,
547    defaults: &StatusDefaults,
548) -> Result<Status> {
549    let mirror = if spec.mirror_peer {
550        Some(peer.context(
551            "mirror_peer: the peer has not sent its Status yet, so there is nothing to mirror",
552        )?)
553    } else {
554        None
555    };
556    let f = |k: &str| spec.fields.get(k);
557    let missing = |k: &str| {
558        anyhow::anyhow!(
559            "Status needs {k}: give it in eth_send_status, set mirror_peer, or configure it at startup"
560        )
561    };
562    let network_id = match f("network_id") {
563        Some(v) => eth::parse_u64(v, "network_id")?,
564        None => mirror
565            .map(|p| p.network_id)
566            .or(defaults.network_id)
567            .ok_or_else(|| missing("network_id"))?,
568    };
569    let genesis = match f("genesis_hash") {
570        Some(v) => eth::parse_fixed(v, "genesis_hash")?,
571        None => mirror
572            .map(|p| p.genesis)
573            .or(defaults.genesis)
574            .ok_or_else(|| missing("genesis_hash"))?,
575    };
576    let forks = match f("forks") {
577        Some(Value::Array(a)) => a
578            .iter()
579            .map(|x| eth::parse_u64(x, "forks entry"))
580            .collect::<Result<Vec<_>>>()?,
581        _ => defaults.forks.clone(),
582    };
583    let fork_next_given = match f("fork_next") {
584        Some(v) => Some(eth::parse_u64(v, "fork_next")?),
585        None => None,
586    };
587    let (fork_hash, fork_next) = match f("fork_hash") {
588        Some(v) => (
589            eth::parse_fixed(v, "fork_hash")?,
590            fork_next_given.unwrap_or(0),
591        ),
592        None => match mirror {
593            Some(p) if f("forks").is_none() => {
594                (p.fork_hash, fork_next_given.unwrap_or(p.fork_next))
595            }
596            _ => (
597                eth::fork_hash(&genesis, &forks),
598                fork_next_given.or(defaults.fork_next).unwrap_or(0),
599            ),
600        },
601    };
602    let u = |k: &str, from_peer: Option<u64>, default: Option<u64>| -> Result<u64> {
603        Ok(match f(k) {
604            Some(v) => eth::parse_u64(v, k)?,
605            None => from_peer.or(default).unwrap_or(0),
606        })
607    };
608    let h = |k: &str, from_peer: Option<[u8; 32]>, default: Option<[u8; 32]>| -> Result<[u8; 32]> {
609        Ok(match f(k) {
610            Some(v) => eth::parse_fixed(v, k)?,
611            None => from_peer.or(default).unwrap_or(genesis),
612        })
613    };
614    let mut s = Status {
615        version,
616        network_id,
617        genesis,
618        fork_hash,
619        fork_next,
620        total_difficulty: Vec::new(),
621        head: [0; 32],
622        earliest_block: 0,
623        latest_block: 0,
624        latest_block_hash: [0; 32],
625    };
626    if version >= 69 {
627        s.earliest_block = u("earliest_block", mirror.map(|p| p.earliest_block), Some(0))?;
628        s.latest_block = u(
629            "latest_block",
630            mirror.map(|p| p.latest_block),
631            defaults.latest_block,
632        )?;
633        s.latest_block_hash = h(
634            "latest_block_hash",
635            mirror.map(|p| p.latest_block_hash),
636            defaults.latest_block_hash,
637        )?;
638        ensure!(
639            s.earliest_block <= s.latest_block,
640            "earliest_block {} is after latest_block {}",
641            s.earliest_block,
642            s.latest_block
643        );
644        ensure!(s.latest_block_hash != [0; 32], "latest_block_hash is zero");
645    } else {
646        s.total_difficulty = match f("total_difficulty") {
647            Some(v) => eth::parse_big(v, "total_difficulty")?,
648            None => mirror
649                .map(|p| p.total_difficulty.clone())
650                .unwrap_or_default(),
651        };
652        s.head = h(
653            "head_hash",
654            mirror.map(|p| p.head),
655            defaults.latest_block_hash,
656        )?;
657    }
658    Ok(s)
659}
660
661/// Whether two Statuses describe the same chain, and if not, why not.
662pub fn compatible(ours: &Status, theirs: &Status) -> std::result::Result<(), String> {
663    if ours.network_id != theirs.network_id {
664        return Err(format!(
665            "network id {} is not the peer's {}",
666            ours.network_id, theirs.network_id
667        ));
668    }
669    if ours.genesis != theirs.genesis {
670        return Err(format!(
671            "genesis {} is not the peer's {}",
672            eth::hex0x(&ours.genesis),
673            eth::hex0x(&theirs.genesis)
674        ));
675    }
676    Ok(())
677}
678
679// ---------------------------------------------------------------------------------------
680// The running session
681// ---------------------------------------------------------------------------------------
682
683/// What NetGet asked for.
684#[derive(Debug, Clone)]
685enum Asked {
686    Headers(HeaderRequest),
687    Bodies(Vec<[u8; 32]>),
688    Pooled(Vec<[u8; 32]>),
689}
690
691struct Outstanding {
692    asked: Asked,
693    deadline: Instant,
694    depth: u32,
695}
696
697/// What the peer asked for and is owed.
698#[derive(Debug, Clone)]
699enum Owed {
700    Headers(HeaderRequest),
701    Bodies(usize),
702    Pooled(Vec<[u8; 32]>),
703}
704
705/// Something the model should hear about.
706#[derive(Debug, Clone)]
707pub struct PeerEvent {
708    pub kind: PeerEventKind,
709    pub data: Value,
710    /// For a request from the peer: its id, which an answer defaults to.
711    pub request_id: Option<u64>,
712    /// How many model turns led here.
713    pub depth: u32,
714}
715
716#[derive(Debug, Clone, Copy, PartialEq, Eq)]
717pub enum PeerEventKind {
718    HeadersRequested,
719    BodiesRequested,
720    PooledRequested,
721    Announced,
722    HeadersReceived,
723    BodiesReceived,
724    PooledReceived,
725    RequestTimedOut,
726}
727
728/// The session state both roles share.
729pub struct Session {
730    pub writer: SharedWriter,
731    pub eth_version: u64,
732    pub peer_status: Option<Status>,
733    pub our_status: Option<Status>,
734    next_request_id: u64,
735    outstanding: HashMap<u64, Outstanding>,
736    owed: HashMap<u64, Owed>,
737    /// Bytes written, for the connection's statistics.
738    pub bytes_sent: u64,
739}
740
741/// The outcome of one command.
742#[derive(Debug)]
743pub enum Applied {
744    Sent(String),
745    StatusSent(Status),
746    Disconnected(u64),
747}
748
749fn eth_code(c: u64) -> u64 {
750    eth::ETH_OFFSET + c
751}
752
753impl Session {
754    pub fn new(writer: SharedWriter, eth_version: u64) -> Session {
755        Session {
756            writer,
757            eth_version,
758            peer_status: None,
759            our_status: None,
760            next_request_id: rand::random::<u32>() as u64,
761            outstanding: HashMap::new(),
762            owed: HashMap::new(),
763            bytes_sent: 0,
764        }
765    }
766
767    /// Both Statuses exchanged.
768    pub fn ready(&self) -> bool {
769        self.peer_status.is_some() && self.our_status.is_some()
770    }
771
772    /// Whether a request from the peer is still unanswered.
773    pub fn owes(&self, request_id: u64) -> bool {
774        self.owed.contains_key(&request_id)
775    }
776
777    async fn send_eth(&mut self, c: u64, payload: &[u8]) -> Result<()> {
778        let n = send(&self.writer, eth_code(c), payload).await?;
779        self.bytes_sent += n as u64;
780        Ok(())
781    }
782
783    /// Read the peer's Status from a message: Ok(None) for anything that is not one.
784    pub fn take_status(&mut self, msg_code: u64, payload: &[u8]) -> Result<Option<Status>> {
785        if msg_code != eth_code(code::STATUS) {
786            return Ok(None);
787        }
788        ensure!(self.peer_status.is_none(), "a second Status");
789        let s = Status::decode(payload, self.eth_version)?;
790        self.peer_status = Some(s.clone());
791        Ok(Some(s))
792    }
793
794    /// Requests NetGet sent that have gone unanswered too long.
795    pub fn expire(&mut self) -> Vec<PeerEvent> {
796        let now = Instant::now();
797        let expired: Vec<u64> = self
798            .outstanding
799            .iter()
800            .filter(|(_, o)| o.deadline <= now)
801            .map(|(id, _)| *id)
802            .collect();
803        expired
804            .into_iter()
805            .filter_map(|id| self.outstanding.remove(&id).map(|o| (id, o)))
806            .map(|(id, o)| PeerEvent {
807                kind: PeerEventKind::RequestTimedOut,
808                data: json!({"request_id": id, "request": asked_json(&o.asked), "timeout_secs": REQUEST_TIMEOUT.as_secs()}),
809                request_id: None,
810                depth: o.depth,
811            })
812            .collect()
813    }
814
815    /// Decode one message from the running session. `Ok(None)` is a message handled here
816    /// (logged, or answered without the model); `Err` ends the session with that reason
817    /// after a Disconnect `breach_of_protocol`.
818    pub async fn on_message(&mut self, msg_code: u64, payload: &[u8]) -> Result<Option<PeerEvent>> {
819        if msg_code < eth::ETH_OFFSET {
820            bail!("base protocol message {msg_code:#04x} after Hello");
821        }
822        let c = msg_code - eth::ETH_OFFSET;
823        ensure!(
824            c < eth::eth_length(self.eth_version),
825            "message {msg_code:#04x} is outside eth/{}",
826            self.eth_version
827        );
828        ensure!(
829            self.peer_status.is_some(),
830            "message {msg_code:#04x} before the peer's Status"
831        );
832        let event = |kind, data, request_id, depth| {
833            Ok(Some(PeerEvent {
834                kind,
835                data,
836                request_id,
837                depth,
838            }))
839        };
840        match c {
841            code::STATUS => bail!("a second Status"),
842            code::GET_BLOCK_HEADERS => {
843                let (id, req) = HeaderRequest::decode(payload)?;
844                self.owe(id, Owed::Headers(req.clone()))?;
845                event(
846                    PeerEventKind::HeadersRequested,
847                    json!({"request_id": id, "origin": req.origin.to_json(), "amount": req.amount, "skip": req.skip, "reverse": req.reverse}),
848                    Some(id),
849                    0,
850                )
851            }
852            code::GET_BLOCK_BODIES => {
853                let (id, hashes) =
854                    eth::decode_hash_request(payload, MAX_PEER_HASHES, "GetBlockBodies")?;
855                self.owe(id, Owed::Bodies(hashes.len()))?;
856                event(
857                    PeerEventKind::BodiesRequested,
858                    json!({"request_id": id, "hashes": hashes.iter().map(|h| eth::hex0x(h)).collect::<Vec<_>>()}),
859                    Some(id),
860                    0,
861                )
862            }
863            code::GET_POOLED_TRANSACTIONS => {
864                let (id, hashes) =
865                    eth::decode_hash_request(payload, MAX_PEER_HASHES, "GetPooledTransactions")?;
866                self.owe(id, Owed::Pooled(hashes.clone()))?;
867                event(
868                    PeerEventKind::PooledRequested,
869                    json!({"request_id": id, "hashes": hashes.iter().map(|h| eth::hex0x(h)).collect::<Vec<_>>()}),
870                    Some(id),
871                    0,
872                )
873            }
874            code::GET_RECEIPTS => {
875                // NetGet keeps no receipts and the model cannot produce them: say so, the way
876                // the protocol says it — an empty list.
877                let (id, _) = eth::decode_hash_request(payload, MAX_PEER_HASHES, "GetReceipts")?;
878                self.send_eth(code::RECEIPTS, &eth::encode_response(id, Vec::new()))
879                    .await?;
880                Ok(None)
881            }
882            code::NEW_POOLED_TRANSACTION_HASHES => {
883                let a = Announcement::decode(payload, MAX_PEER_HASHES)?;
884                event(
885                    PeerEventKind::Announced,
886                    json!({
887                        "transactions": a.hashes.iter().zip(&a.types).zip(&a.sizes)
888                            .map(|((h, t), s)| json!({"hash": eth::hex0x(h), "type": t, "size": s}))
889                            .collect::<Vec<_>>()
890                    }),
891                    None,
892                    0,
893                )
894            }
895            code::BLOCK_HEADERS => self.answer_for(payload, "BlockHeaders"),
896            code::BLOCK_BODIES => self.answer_for(payload, "BlockBodies"),
897            code::POOLED_TRANSACTIONS => self.answer_for(payload, "PooledTransactions"),
898            code::BLOCK_RANGE_UPDATE => {
899                let (earliest, latest, hash) = eth::decode_block_range(payload)?;
900                if let Some(p) = self.peer_status.as_mut() {
901                    p.earliest_block = earliest;
902                    p.latest_block = latest;
903                    p.latest_block_hash = hash;
904                }
905                Ok(None)
906            }
907            code::TRANSACTIONS | code::NEW_BLOCK_HASHES | code::NEW_BLOCK | code::RECEIPTS => {
908                // Broadcasts and an unasked-for answer: noted, not modelled.
909                Ok(None)
910            }
911            other => bail!("eth message {other:#04x} is not handled"),
912        }
913    }
914
915    fn owe(&mut self, id: u64, owed: Owed) -> Result<()> {
916        ensure!(
917            self.owed.len() < MAX_OWED,
918            "the peer has {MAX_OWED} requests unanswered"
919        );
920        ensure!(
921            !self.owed.contains_key(&id),
922            "the peer reused request id {id} while it is still unanswered"
923        );
924        self.owed.insert(id, owed);
925        Ok(())
926    }
927
928    /// An answer to something NetGet asked: decoded, checked against the question, reported.
929    fn answer_for(&mut self, payload: &[u8], what: &str) -> Result<Option<PeerEvent>> {
930        let (id, items) = eth::decode_response(payload, MAX_ANSWER_ITEMS, what)?;
931        let Some(o) = self.outstanding.remove(&id) else {
932            tracing::debug!(
933                "devp2p {what} for request {id}, which NetGet did not send or gave up on"
934            );
935            return Ok(None);
936        };
937        let depth = o.depth + 1;
938        let request = asked_json(&o.asked);
939        match o.asked {
940            Asked::Headers(req) => {
941                ensure!(
942                    items.len() as u64 <= req.amount,
943                    "{} headers for a request of {}",
944                    items.len(),
945                    req.amount
946                );
947                let headers = items
948                    .iter()
949                    .map(eth::header_to_json)
950                    .collect::<Result<Vec<_>>>()?;
951                let (matches, linked) = check_headers(&req, &items);
952                Ok(Some(PeerEvent {
953                    kind: PeerEventKind::HeadersReceived,
954                    data: json!({"request_id": id, "request": request, "count": headers.len(),
955                                 "headers": headers, "matches_request": matches, "linked": linked}),
956                    request_id: None,
957                    depth,
958                }))
959            }
960            Asked::Bodies(hashes) => {
961                ensure!(
962                    items.len() <= hashes.len(),
963                    "{} bodies for {} hashes",
964                    items.len(),
965                    hashes.len()
966                );
967                let bodies = items
968                    .iter()
969                    .zip(&hashes)
970                    .map(|(b, h)| {
971                        let mut j = eth::body_json(b)?;
972                        j["block_hash"] = json!(eth::hex0x(h));
973                        Ok(j)
974                    })
975                    .collect::<Result<Vec<_>>>()?;
976                Ok(Some(PeerEvent {
977                    kind: PeerEventKind::BodiesReceived,
978                    data: json!({"request_id": id, "request": request, "count": bodies.len(), "bodies": bodies}),
979                    request_id: None,
980                    depth,
981                }))
982            }
983            Asked::Pooled(hashes) => {
984                let txs = items
985                    .iter()
986                    .map(eth::transaction_json)
987                    .collect::<Result<Vec<_>>>()?;
988                let asked: Vec<String> = hashes.iter().map(|h| eth::hex0x(h)).collect();
989                let unasked = txs
990                    .iter()
991                    .filter(|t| !asked.iter().any(|a| Some(a.as_str()) == t["hash"].as_str()))
992                    .count();
993                Ok(Some(PeerEvent {
994                    kind: PeerEventKind::PooledReceived,
995                    data: json!({"request_id": id, "request": request, "count": txs.len(),
996                                 "transactions": txs, "unrequested": unasked}),
997                    request_id: None,
998                    depth,
999                }))
1000            }
1001        }
1002    }
1003
1004    /// Carry out one command. `answering` is the peer request the current event is about,
1005    /// which a reply without its own `request_id` answers; `depth` is the turn this command
1006    /// came from, carried onto the answer to any request it sends.
1007    pub async fn apply(
1008        &mut self,
1009        command: Command,
1010        answering: Option<u64>,
1011        depth: u32,
1012        defaults: &StatusDefaults,
1013    ) -> Result<Applied> {
1014        let reply_id = |given: Option<u64>| {
1015            given
1016                .or(answering)
1017                .context("request_id is required: this is not an answer to a request event")
1018        };
1019        match command {
1020            Command::Disconnect(reason) => {
1021                disconnect(&self.writer, reason).await;
1022                Ok(Applied::Disconnected(reason))
1023            }
1024            Command::SendStatus(spec) => {
1025                ensure!(self.our_status.is_none(), "Status was already sent");
1026                let status =
1027                    build_status(&spec, self.eth_version, self.peer_status.as_ref(), defaults)?;
1028                self.send_eth(code::STATUS, &status.encode()).await?;
1029                self.our_status = Some(status.clone());
1030                Ok(Applied::StatusSent(status))
1031            }
1032            _ if !self.ready() => {
1033                bail!("the eth handshake has not finished: both Statuses must be exchanged first")
1034            }
1035            Command::SendHeaders {
1036                request_id,
1037                headers,
1038            } => {
1039                let id = reply_id(request_id)?;
1040                let Some(Owed::Headers(req)) = self.owed.get(&id).cloned() else {
1041                    bail!("there is no unanswered GetBlockHeaders {id}");
1042                };
1043                validate_answer(&req, &headers)?;
1044                let hashes: Vec<String> = headers
1045                    .iter()
1046                    .map(|h| eth::hex0x(&eth::header_hash(h)))
1047                    .collect();
1048                self.send_eth(code::BLOCK_HEADERS, &eth::encode_response(id, headers))
1049                    .await?;
1050                self.owed.remove(&id);
1051                Ok(Applied::Sent(format!(
1052                    "sent {} headers for request {id}: {}",
1053                    hashes.len(),
1054                    hashes.join(", ")
1055                )))
1056            }
1057            Command::SendBodies { request_id, bodies } => {
1058                let id = reply_id(request_id)?;
1059                let Some(Owed::Bodies(n)) = self.owed.get(&id).cloned() else {
1060                    bail!("there is no unanswered GetBlockBodies {id}");
1061                };
1062                ensure!(
1063                    bodies.len() <= n,
1064                    "{} bodies for a request naming {n} blocks",
1065                    bodies.len()
1066                );
1067                let count = bodies.len();
1068                self.send_eth(code::BLOCK_BODIES, &eth::encode_response(id, bodies))
1069                    .await?;
1070                self.owed.remove(&id);
1071                Ok(Applied::Sent(format!(
1072                    "sent {count} bodies for request {id}"
1073                )))
1074            }
1075            Command::SendPooled {
1076                request_id,
1077                transactions,
1078            } => {
1079                let id = reply_id(request_id)?;
1080                let Some(Owed::Pooled(asked)) = self.owed.get(&id).cloned() else {
1081                    bail!("there is no unanswered GetPooledTransactions {id}");
1082                };
1083                for (_, h) in &transactions {
1084                    ensure!(
1085                        asked.contains(h),
1086                        "transaction {} was not asked for",
1087                        eth::hex0x(h)
1088                    );
1089                }
1090                let count = transactions.len();
1091                let items = transactions.into_iter().map(|(t, _)| t).collect();
1092                self.send_eth(code::POOLED_TRANSACTIONS, &eth::encode_response(id, items))
1093                    .await?;
1094                self.owed.remove(&id);
1095                Ok(Applied::Sent(format!(
1096                    "sent {count} transactions for request {id}"
1097                )))
1098            }
1099            Command::RequestHeaders(req) => {
1100                let id = self.next_id()?;
1101                self.send_eth(code::GET_BLOCK_HEADERS, &req.encode(id))
1102                    .await?;
1103                let what = format!(
1104                    "asked for {} headers from {:?} (request {id})",
1105                    req.amount, req.origin
1106                );
1107                self.track(id, Asked::Headers(req), depth);
1108                Ok(Applied::Sent(what))
1109            }
1110            Command::RequestBodies(hashes) => {
1111                let id = self.next_id()?;
1112                self.send_eth(
1113                    code::GET_BLOCK_BODIES,
1114                    &eth::encode_hash_request(id, &hashes),
1115                )
1116                .await?;
1117                let what = format!("asked for {} bodies (request {id})", hashes.len());
1118                self.track(id, Asked::Bodies(hashes), depth);
1119                Ok(Applied::Sent(what))
1120            }
1121            Command::RequestPooled(hashes) => {
1122                let id = self.next_id()?;
1123                self.send_eth(
1124                    code::GET_POOLED_TRANSACTIONS,
1125                    &eth::encode_hash_request(id, &hashes),
1126                )
1127                .await?;
1128                let what = format!("asked for {} transactions (request {id})", hashes.len());
1129                self.track(id, Asked::Pooled(hashes), depth);
1130                Ok(Applied::Sent(what))
1131            }
1132        }
1133    }
1134
1135    fn next_id(&mut self) -> Result<u64> {
1136        ensure!(
1137            self.outstanding.len() < MAX_OUTSTANDING,
1138            "{MAX_OUTSTANDING} requests are already waiting for answers"
1139        );
1140        self.next_request_id = self.next_request_id.wrapping_add(1);
1141        Ok(self.next_request_id)
1142    }
1143
1144    fn track(&mut self, id: u64, asked: Asked, depth: u32) {
1145        self.outstanding.insert(
1146            id,
1147            Outstanding {
1148                asked,
1149                deadline: Instant::now() + REQUEST_TIMEOUT,
1150                depth,
1151            },
1152        );
1153    }
1154}
1155
1156fn asked_json(a: &Asked) -> Value {
1157    match a {
1158        Asked::Headers(r) => json!({"kind": "headers", "query": r.to_json()}),
1159        Asked::Bodies(h) => {
1160            json!({"kind": "bodies", "hashes": h.iter().map(|x| eth::hex0x(x)).collect::<Vec<_>>()})
1161        }
1162        Asked::Pooled(h) => {
1163            json!({"kind": "transactions", "hashes": h.iter().map(|x| eth::hex0x(x)).collect::<Vec<_>>()})
1164        }
1165    }
1166}
1167
1168/// Whether received headers are the ones asked for (numbers, or the first hash) and, for a
1169/// contiguous request, whether each names the previous one as its parent.
1170fn check_headers(req: &HeaderRequest, headers: &[Rlp]) -> (bool, bool) {
1171    let numbers: Vec<Option<u64>> = headers.iter().map(|h| eth::header_number(h).ok()).collect();
1172    let first = match req.origin {
1173        Origin::Number(n) => Some(n),
1174        Origin::Hash(h) => headers
1175            .first()
1176            .filter(|f| eth::header_hash(f) == h)
1177            .and_then(|f| eth::header_number(f).ok()),
1178    };
1179    let matches = headers.is_empty()
1180        || first.is_some_and(|first| {
1181            numbers
1182                .iter()
1183                .enumerate()
1184                .all(|(i, n)| *n == req.number_at(first, i as u64))
1185        });
1186    (matches, linked(req, headers))
1187}
1188
1189fn linked(req: &HeaderRequest, headers: &[Rlp]) -> bool {
1190    if req.skip != 0 {
1191        return true;
1192    }
1193    headers.windows(2).all(|w| {
1194        let (older, newer) = if req.reverse {
1195            (&w[1], &w[0])
1196        } else {
1197            (&w[0], &w[1])
1198        };
1199        eth::header_parent(newer).ok() == Some(eth::header_hash(older))
1200    })
1201}
1202
1203/// Refuse headers that do not answer `req`: more than asked for, the wrong numbers, a first
1204/// header whose hash is not the one asked for, or (for a contiguous request) a header that
1205/// does not name the previous one as its parent.
1206pub fn validate_answer(req: &HeaderRequest, headers: &[Rlp]) -> Result<()> {
1207    ensure!(
1208        headers.len() as u64 <= req.amount,
1209        "{} headers for a request of {}",
1210        headers.len(),
1211        req.amount
1212    );
1213    let Some(first) = headers.first() else {
1214        return Ok(());
1215    };
1216    let first_number = eth::header_number(first)?;
1217    match req.origin {
1218        Origin::Number(n) => ensure!(
1219            first_number == n,
1220            "the first header is block {first_number}, the request starts at {n}"
1221        ),
1222        Origin::Hash(h) => ensure!(
1223            eth::header_hash(first) == h,
1224            "the first header's hash is {}, the request starts at {}",
1225            eth::hex0x(&eth::header_hash(first)),
1226            eth::hex0x(&h)
1227        ),
1228    }
1229    for (i, h) in headers.iter().enumerate() {
1230        let n = eth::header_number(h)?;
1231        let want = req.number_at(first_number, i as u64);
1232        ensure!(
1233            Some(n) == want,
1234            "headers[{i}] is block {n}, the request asks for {want:?} there"
1235        );
1236    }
1237    ensure!(
1238        linked(req, headers),
1239        "the headers do not form a chain: a header's parentHash is not the previous header's hash"
1240    );
1241    Ok(())
1242}