1use super::crypto;
9use super::rlp::{self, Rlp};
10use anyhow::{bail, ensure, Context, Result};
11use serde_json::{json, Map, Value};
12
13pub const BASE_VERSION: u64 = 5;
15pub const MAX_HELLO_BYTES: usize = 2048;
17pub const MAX_CAPS: usize = 64;
19pub const MAX_CLIENT_ID_CHARS: usize = 256;
21pub const ETH_VERSIONS: [u64; 2] = [69, 68];
23pub const ETH_OFFSET: u64 = 0x10;
25
26pub mod code {
27 pub const HELLO: u64 = 0x00;
28 pub const DISCONNECT: u64 = 0x01;
29 pub const PING: u64 = 0x02;
30 pub const PONG: u64 = 0x03;
31 pub const STATUS: u64 = 0x00;
33 pub const NEW_BLOCK_HASHES: u64 = 0x01;
34 pub const TRANSACTIONS: u64 = 0x02;
35 pub const GET_BLOCK_HEADERS: u64 = 0x03;
36 pub const BLOCK_HEADERS: u64 = 0x04;
37 pub const GET_BLOCK_BODIES: u64 = 0x05;
38 pub const BLOCK_BODIES: u64 = 0x06;
39 pub const NEW_BLOCK: u64 = 0x07;
40 pub const NEW_POOLED_TRANSACTION_HASHES: u64 = 0x08;
41 pub const GET_POOLED_TRANSACTIONS: u64 = 0x09;
42 pub const POOLED_TRANSACTIONS: u64 = 0x0a;
43 pub const GET_RECEIPTS: u64 = 0x0f;
44 pub const RECEIPTS: u64 = 0x10;
45 pub const BLOCK_RANGE_UPDATE: u64 = 0x11;
46}
47
48pub fn eth_length(version: u64) -> u64 {
50 if version >= 69 {
51 0x12
52 } else {
53 0x11
54 }
55}
56
57pub const DISCONNECT_REASONS: &[(u64, &str)] = &[
59 (0x00, "requested"),
60 (0x01, "tcp_error"),
61 (0x02, "breach_of_protocol"),
62 (0x03, "useless_peer"),
63 (0x04, "too_many_peers"),
64 (0x05, "already_connected"),
65 (0x06, "incompatible_version"),
66 (0x07, "invalid_identity"),
67 (0x08, "client_quitting"),
68 (0x09, "unexpected_identity"),
69 (0x0a, "connected_to_self"),
70 (0x0b, "timeout"),
71 (0x10, "subprotocol_error"),
72];
73
74pub fn reason_name(code: u64) -> String {
75 DISCONNECT_REASONS
76 .iter()
77 .find(|(c, _)| *c == code)
78 .map(|(_, n)| n.to_string())
79 .unwrap_or_else(|| format!("unknown_{code:#04x}"))
80}
81
82pub fn reason_code(name: &str) -> Result<u64> {
83 DISCONNECT_REASONS
84 .iter()
85 .find(|(_, n)| *n == name)
86 .map(|(c, _)| *c)
87 .with_context(|| {
88 format!(
89 "unknown disconnect reason {name:?}; one of {}",
90 DISCONNECT_REASONS
91 .iter()
92 .map(|(_, n)| *n)
93 .collect::<Vec<_>>()
94 .join(", ")
95 )
96 })
97}
98
99pub fn encode_disconnect(reason: u64) -> Vec<u8> {
101 Rlp::List(vec![Rlp::uint(reason)]).encode()
102}
103
104pub fn decode_disconnect(payload: &[u8]) -> u64 {
106 match rlp::decode(payload) {
107 Ok(Rlp::List(l)) => l.first().and_then(|r| r.as_u64().ok()).unwrap_or(0),
108 Ok(item) => item.as_u64().unwrap_or(0),
109 Err(_) => 0,
110 }
111}
112
113pub fn empty_payload() -> Vec<u8> {
115 Rlp::empty_list().encode()
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct Hello {
121 pub version: u64,
122 pub client_id: String,
123 pub caps: Vec<(String, u64)>,
124 pub listen_port: u64,
125 pub id: [u8; 64],
126}
127
128impl Hello {
129 pub fn ours(client_id: &str, listen_port: u16, id: [u8; 64]) -> Hello {
130 Hello {
131 version: BASE_VERSION,
132 client_id: client_id.to_string(),
133 caps: ETH_VERSIONS
134 .iter()
135 .map(|v| ("eth".to_string(), *v))
136 .collect(),
137 listen_port: u64::from(listen_port),
138 id,
139 }
140 }
141
142 pub fn encode(&self) -> Vec<u8> {
143 Rlp::List(vec![
144 Rlp::uint(self.version),
145 Rlp::string(&self.client_id),
146 Rlp::List(
147 self.caps
148 .iter()
149 .map(|(n, v)| Rlp::List(vec![Rlp::string(n), Rlp::uint(*v)]))
150 .collect(),
151 ),
152 Rlp::uint(self.listen_port),
153 Rlp::bytes(&self.id),
154 ])
155 .encode()
156 }
157
158 pub fn decode(payload: &[u8]) -> Result<Hello> {
161 ensure!(
162 payload.len() <= MAX_HELLO_BYTES,
163 "Hello of {} bytes is above {MAX_HELLO_BYTES}",
164 payload.len()
165 );
166 let item = rlp::decode(payload).context("Hello is not RLP")?;
167 let l = item.as_list().context("Hello")?;
168 ensure!(l.len() >= 5, "Hello has {} fields, not 5", l.len());
169 let caps_l = l[2].as_list().context("Hello capabilities")?;
170 ensure!(
171 caps_l.len() <= MAX_CAPS,
172 "Hello lists {} capabilities, above {MAX_CAPS}",
173 caps_l.len()
174 );
175 let mut caps = Vec::new();
176 for c in caps_l {
177 let c = c.as_list().context("capability")?;
178 ensure!(c.len() >= 2, "capability without a version");
179 let name = crate::utils::sanitize::token(
180 &String::from_utf8_lossy(c[0].as_bytes().context("capability name")?),
181 32,
182 );
183 caps.push((name, c[1].as_u64().context("capability version")?));
184 }
185 Ok(Hello {
186 version: l[0].as_u64().context("Hello version")?,
187 client_id: crate::utils::sanitize::token(
188 &String::from_utf8_lossy(l[1].as_bytes().context("client id")?),
189 MAX_CLIENT_ID_CHARS,
190 ),
191 caps,
192 listen_port: l[3].as_u64().context("listen port")?,
193 id: l[4].as_fixed().context("Hello node id")?,
194 })
195 }
196
197 pub fn shared_eth(&self) -> Option<u64> {
199 ETH_VERSIONS
200 .iter()
201 .copied()
202 .find(|v| self.caps.iter().any(|(n, cv)| n == "eth" && cv == v))
203 }
204
205 pub fn caps_strings(&self) -> Vec<String> {
206 self.caps.iter().map(|(n, v)| format!("{n}/{v}")).collect()
207 }
208}
209
210pub fn hex0x(b: &[u8]) -> String {
215 format!("0x{}", hex::encode(b))
216}
217
218pub fn parse_hex(v: &Value, what: &str) -> Result<Vec<u8>> {
220 let s = v
221 .as_str()
222 .with_context(|| format!("{what} is a hex string"))?;
223 let s = s.strip_prefix("0x").unwrap_or(s);
224 let s = if s.len() % 2 == 1 {
225 format!("0{s}")
226 } else {
227 s.to_string()
228 };
229 hex::decode(&s).with_context(|| format!("{what} is not hex"))
230}
231
232pub fn parse_fixed<const N: usize>(v: &Value, what: &str) -> Result<[u8; N]> {
233 let b = parse_hex(v, what)?;
234 b.as_slice()
235 .try_into()
236 .map_err(|_| anyhow::anyhow!("{what} is {} bytes, not {N}", b.len()))
237}
238
239pub fn parse_u64(v: &Value, what: &str) -> Result<u64> {
241 if let Some(n) = v.as_u64() {
242 return Ok(n);
243 }
244 let s = v.as_str().with_context(|| format!("{what} is a number"))?;
245 if let Some(h) = s.strip_prefix("0x") {
246 ensure!(!h.is_empty(), "{what} is an empty hex number");
247 return u64::from_str_radix(h, 16)
248 .with_context(|| format!("{what} is not a 64-bit number"));
249 }
250 s.parse::<u64>()
251 .with_context(|| format!("{what} is not a 64-bit number"))
252}
253
254pub fn parse_big(v: &Value, what: &str) -> Result<Vec<u8>> {
256 if let Some(n) = v.as_u64() {
257 return Ok(Rlp::uint(n).as_bytes()?.to_vec());
258 }
259 let s = v.as_str().with_context(|| format!("{what} is a number"))?;
260 let bytes = if s.starts_with("0x") {
261 parse_hex(v, what)?
262 } else {
263 decimal_to_be(s).with_context(|| format!("{what} is not a number"))?
264 };
265 let start = bytes.iter().position(|b| *b != 0).unwrap_or(bytes.len());
266 ensure!(bytes.len() - start <= 32, "{what} is above 256 bits");
267 Ok(bytes[start..].to_vec())
268}
269
270fn decimal_to_be(s: &str) -> Result<Vec<u8>> {
271 ensure!(
272 !s.is_empty() && s.len() <= 80 && s.bytes().all(|b| b.is_ascii_digit()),
273 "not a decimal number"
274 );
275 let mut out: Vec<u8> = vec![0];
276 for d in s.bytes() {
277 let mut carry = u32::from(d - b'0');
278 for byte in out.iter_mut().rev() {
279 let v = u32::from(*byte) * 10 + carry;
280 *byte = (v & 0xff) as u8;
281 carry = v >> 8;
282 }
283 while carry > 0 {
284 out.insert(0, (carry & 0xff) as u8);
285 carry >>= 8;
286 }
287 }
288 Ok(out)
289}
290
291pub fn big_json(be: &[u8]) -> Value {
293 if be.len() <= 8 {
294 json!(be.iter().fold(0u64, |n, d| (n << 8) | u64::from(*d)))
295 } else {
296 json!(hex0x(be))
297 }
298}
299
300#[derive(Clone, Copy)]
305enum Kind {
306 Hash,
307 Address,
308 Bloom,
309 Big,
310 U64,
311 Bytes,
312 Nonce,
313}
314
315const HEADER_FIELDS: &[(&str, Kind)] = &[
318 ("parentHash", Kind::Hash),
319 ("sha3Uncles", Kind::Hash),
320 ("miner", Kind::Address),
321 ("stateRoot", Kind::Hash),
322 ("transactionsRoot", Kind::Hash),
323 ("receiptsRoot", Kind::Hash),
324 ("logsBloom", Kind::Bloom),
325 ("difficulty", Kind::Big),
326 ("number", Kind::U64),
327 ("gasLimit", Kind::U64),
328 ("gasUsed", Kind::U64),
329 ("timestamp", Kind::U64),
330 ("extraData", Kind::Bytes),
331 ("mixHash", Kind::Hash),
332 ("nonce", Kind::Nonce),
333 ("baseFeePerGas", Kind::Big),
334 ("withdrawalsRoot", Kind::Hash),
335 ("blobGasUsed", Kind::U64),
336 ("excessBlobGas", Kind::U64),
337 ("parentBeaconBlockRoot", Kind::Hash),
338 ("requestsHash", Kind::Hash),
339 ("blockAccessListHash", Kind::Hash),
340 ("slotNumber", Kind::U64),
341];
342const REQUIRED_HEADER_FIELDS: usize = 15;
343const NON_HEADER_BLOCK_KEYS: &[&str] = &[
346 "hash",
347 "size",
348 "transactions",
349 "uncles",
350 "totalDifficulty",
351 "withdrawals",
352 "blockAccessList",
353];
354pub const MAX_EXTRA_DATA: usize = 1024;
356
357fn field_rlp(v: &Value, name: &str, kind: Kind) -> Result<Rlp> {
358 Ok(match kind {
359 Kind::Hash => Rlp::bytes(&parse_fixed::<32>(v, name)?),
360 Kind::Address => Rlp::bytes(&parse_fixed::<20>(v, name)?),
361 Kind::Bloom => Rlp::bytes(&parse_fixed::<256>(v, name)?),
362 Kind::Nonce => Rlp::bytes(&parse_fixed::<8>(v, name)?),
363 Kind::Big => Rlp::big_uint(&parse_big(v, name)?),
364 Kind::U64 => Rlp::uint(parse_u64(v, name)?),
365 Kind::Bytes => {
366 let b = parse_hex(v, name)?;
367 ensure!(
368 b.len() <= MAX_EXTRA_DATA,
369 "{name} is above {MAX_EXTRA_DATA} bytes"
370 );
371 Rlp::bytes(&b)
372 }
373 })
374}
375
376fn field_json(item: &Rlp, name: &str, kind: Kind) -> Result<Value> {
377 Ok(match kind {
378 Kind::Hash => json!(hex0x(&item.as_fixed::<32>().context(name.to_string())?)),
379 Kind::Address => json!(hex0x(&item.as_fixed::<20>().context(name.to_string())?)),
380 Kind::Bloom => json!(hex0x(&item.as_fixed::<256>().context(name.to_string())?)),
381 Kind::Nonce => json!(hex0x(&item.as_fixed::<8>().context(name.to_string())?)),
382 Kind::Big => big_json(item.as_big_uint().context(name.to_string())?),
383 Kind::U64 => json!(item.as_u64().context(name.to_string())?),
384 Kind::Bytes => {
385 let b = item.as_bytes().context(name.to_string())?;
386 ensure!(
387 b.len() <= MAX_EXTRA_DATA,
388 "{name} is above {MAX_EXTRA_DATA} bytes"
389 );
390 json!(hex0x(b))
391 }
392 })
393}
394
395pub fn header_from_json(v: &Value) -> Result<Rlp> {
398 let obj = v.as_object().context("a header is an object of fields")?;
399 for k in obj.keys() {
400 ensure!(
401 HEADER_FIELDS.iter().any(|(n, _)| n == k)
402 || NON_HEADER_BLOCK_KEYS.contains(&k.as_str()),
403 "{k:?} is not a block header field"
404 );
405 }
406 let mut items = Vec::new();
407 let mut ended = None;
408 for (i, (name, kind)) in HEADER_FIELDS.iter().enumerate() {
409 match obj.get(*name).filter(|v| !v.is_null()) {
410 Some(value) => {
411 if let Some(missing) = ended {
412 bail!("{name} is present but {missing} before it is not; header fields after baseFeePerGas are added in order");
413 }
414 items.push(field_rlp(value, name, *kind)?);
415 }
416 None if i < REQUIRED_HEADER_FIELDS => bail!("header field {name} is required"),
417 None => {
418 ended.get_or_insert(*name);
419 }
420 }
421 }
422 let header = Rlp::List(items);
423 if let Some(claimed) = obj.get("hash").filter(|v| !v.is_null()) {
424 let claimed = parse_fixed::<32>(claimed, "hash")?;
425 let actual = header_hash(&header);
426 ensure!(
427 claimed == actual,
428 "the header's hash is {}, not the {} it claims",
429 hex0x(&actual),
430 hex0x(&claimed)
431 );
432 }
433 Ok(header)
434}
435
436pub fn header_hash(header: &Rlp) -> [u8; 32] {
438 crypto::keccak256(&[&header.encode()])
439}
440
441fn header_fields(header: &Rlp) -> Result<&[Rlp]> {
442 let l = header.as_list().context("a header is a list")?;
443 ensure!(
444 (REQUIRED_HEADER_FIELDS..=HEADER_FIELDS.len()).contains(&l.len()),
445 "a header has {} fields, not {REQUIRED_HEADER_FIELDS} to {}",
446 l.len(),
447 HEADER_FIELDS.len()
448 );
449 Ok(l)
450}
451
452pub fn header_number(header: &Rlp) -> Result<u64> {
453 header_fields(header)?[8].as_u64().context("header number")
454}
455
456pub fn header_parent(header: &Rlp) -> Result<[u8; 32]> {
457 header_fields(header)?[0]
458 .as_fixed()
459 .context("header parentHash")
460}
461
462pub fn header_to_json(header: &Rlp) -> Result<Value> {
464 let l = header_fields(header)?;
465 let mut m = Map::new();
466 m.insert("hash".into(), json!(hex0x(&header_hash(header))));
467 for (item, (name, kind)) in l.iter().zip(HEADER_FIELDS) {
468 m.insert((*name).into(), field_json(item, name, *kind)?);
469 }
470 Ok(Value::Object(m))
471}
472
473pub const MAX_TRANSACTION_BYTES: usize = 128 * 1024;
479
480pub fn transaction_from_hex(v: &Value) -> Result<(Rlp, [u8; 32])> {
484 let raw = parse_hex(v, "transaction")?;
485 ensure!(!raw.is_empty(), "a transaction is not empty");
486 ensure!(
487 raw.len() <= MAX_TRANSACTION_BYTES,
488 "a transaction is at most {MAX_TRANSACTION_BYTES} bytes"
489 );
490 let hash = crypto::keccak256(&[&raw]);
491 if raw[0] >= 0xc0 {
492 let item = rlp::decode(&raw).context("legacy transaction")?;
493 item.as_list().context("legacy transaction")?;
494 Ok((item, hash))
495 } else {
496 ensure!(
497 raw[0] <= 0x7f,
498 "transaction type byte {:#04x} is not EIP-2718",
499 raw[0]
500 );
501 let inner = rlp::decode(&raw[1..]).context("typed transaction payload")?;
502 inner.as_list().context("typed transaction payload")?;
503 Ok((Rlp::Bytes(raw), hash))
504 }
505}
506
507pub fn transaction_json(item: &Rlp) -> Result<Value> {
509 let (raw, kind) = match item {
510 Rlp::List(_) => (item.encode(), 0u64),
511 Rlp::Bytes(b) => {
512 ensure!(
513 b.first().is_some_and(|t| *t <= 0x7f),
514 "typed transaction without a type byte"
515 );
516 (b.clone(), u64::from(b[0]))
517 }
518 };
519 Ok(json!({"hash": hex0x(&crypto::keccak256(&[&raw])), "type": kind, "size": raw.len()}))
520}
521
522fn withdrawal_from_json(v: &Value) -> Result<Rlp> {
523 Ok(Rlp::List(vec![
524 Rlp::uint(parse_u64(&v["index"], "withdrawal index")?),
525 Rlp::uint(parse_u64(
526 &v["validatorIndex"],
527 "withdrawal validatorIndex",
528 )?),
529 Rlp::bytes(&parse_fixed::<20>(&v["address"], "withdrawal address")?),
530 Rlp::uint(parse_u64(&v["amount"], "withdrawal amount")?),
531 ]))
532}
533
534fn withdrawal_json(item: &Rlp) -> Result<Value> {
535 let l = item.as_list().context("withdrawal")?;
536 ensure!(l.len() == 4, "a withdrawal has 4 fields");
537 Ok(json!({
538 "index": l[0].as_u64()?,
539 "validatorIndex": l[1].as_u64()?,
540 "address": hex0x(&l[2].as_fixed::<20>()?),
541 "amount": l[3].as_u64()?,
542 }))
543}
544
545pub const MAX_BODY_ITEMS: usize = 4096;
547
548pub fn body_from_json(v: &Value) -> Result<Rlp> {
550 let list = |k: &str| -> Result<Vec<Value>> {
551 match v.get(k) {
552 None | Some(Value::Null) => Ok(Vec::new()),
553 Some(Value::Array(a)) => {
554 ensure!(
555 a.len() <= MAX_BODY_ITEMS,
556 "{k} holds more than {MAX_BODY_ITEMS}"
557 );
558 Ok(a.clone())
559 }
560 Some(_) => bail!("{k} is an array"),
561 }
562 };
563 let mut txs = Vec::new();
564 for t in list("transactions")? {
565 txs.push(transaction_from_hex(&t)?.0);
566 }
567 let mut uncles = Vec::new();
568 for u in list("uncles")? {
569 uncles.push(header_from_json(&u)?);
570 }
571 let mut fields = vec![Rlp::List(txs), Rlp::List(uncles)];
572 if v.get("withdrawals").is_some_and(|w| !w.is_null()) {
573 let mut ws = Vec::new();
574 for w in list("withdrawals")? {
575 ws.push(withdrawal_from_json(&w)?);
576 }
577 fields.push(Rlp::List(ws));
578 }
579 Ok(Rlp::List(fields))
580}
581
582pub fn body_json(item: &Rlp) -> Result<Value> {
583 let l = item.as_list().context("block body")?;
584 ensure!((2..=3).contains(&l.len()), "a block body has 2 or 3 fields");
585 let txs = l[0]
586 .as_list()?
587 .iter()
588 .map(transaction_json)
589 .collect::<Result<Vec<_>>>()?;
590 let uncles = l[1]
591 .as_list()?
592 .iter()
593 .map(header_to_json)
594 .collect::<Result<Vec<_>>>()?;
595 let mut out = json!({"transactions": txs, "uncles": uncles, "uncles_hash": hex0x(&crypto::keccak256(&[&l[1].encode()]))});
596 if let Some(w) = l.get(2) {
597 out["withdrawals"] = json!(w
598 .as_list()?
599 .iter()
600 .map(withdrawal_json)
601 .collect::<Result<Vec<_>>>()?);
602 }
603 Ok(out)
604}
605
606pub fn crc32_update(crc: u32, data: &[u8]) -> u32 {
612 let mut c = !crc;
613 for b in data {
614 c ^= u32::from(*b);
615 for _ in 0..8 {
616 c = if c & 1 != 0 {
617 (c >> 1) ^ 0xedb8_8320
618 } else {
619 c >> 1
620 };
621 }
622 }
623 !c
624}
625
626pub fn fork_hash(genesis: &[u8; 32], passed: &[u64]) -> [u8; 4] {
629 let mut h = crc32_update(0, genesis);
630 let mut last = 0;
631 for f in passed {
632 if *f == 0 || *f == last {
633 continue;
634 }
635 last = *f;
636 h = crc32_update(h, &f.to_be_bytes());
637 }
638 h.to_be_bytes()
639}
640
641#[derive(Debug, Clone, PartialEq, Eq)]
647pub struct Status {
648 pub version: u64,
649 pub network_id: u64,
650 pub genesis: [u8; 32],
651 pub fork_hash: [u8; 4],
652 pub fork_next: u64,
653 pub total_difficulty: Vec<u8>,
655 pub head: [u8; 32],
656 pub earliest_block: u64,
658 pub latest_block: u64,
659 pub latest_block_hash: [u8; 32],
660}
661
662impl Status {
663 pub fn encode(&self) -> Vec<u8> {
664 let fork = Rlp::List(vec![Rlp::bytes(&self.fork_hash), Rlp::uint(self.fork_next)]);
665 let fields = if self.version >= 69 {
666 vec![
667 Rlp::uint(self.version),
668 Rlp::uint(self.network_id),
669 Rlp::bytes(&self.genesis),
670 fork,
671 Rlp::uint(self.earliest_block),
672 Rlp::uint(self.latest_block),
673 Rlp::bytes(&self.latest_block_hash),
674 ]
675 } else {
676 vec![
677 Rlp::uint(self.version),
678 Rlp::uint(self.network_id),
679 Rlp::big_uint(&self.total_difficulty),
680 Rlp::bytes(&self.head),
681 Rlp::bytes(&self.genesis),
682 fork,
683 ]
684 };
685 Rlp::List(fields).encode()
686 }
687
688 pub fn decode(payload: &[u8], version: u64) -> Result<Status> {
690 let item = rlp::decode(payload).context("Status is not RLP")?;
691 let l = item.as_list().context("Status")?;
692 ensure!(!l.is_empty(), "Status is empty");
693 let sent = l[0].as_u64().context("Status version")?;
694 ensure!(
695 sent == version,
696 "Status says eth/{sent} on a session that negotiated eth/{version}"
697 );
698 let fork = |f: &Rlp| -> Result<([u8; 4], u64)> {
699 let f = f.as_list().context("fork id")?;
700 ensure!(f.len() >= 2, "fork id has {} fields", f.len());
701 Ok((
702 f[0].as_fixed().context("fork hash")?,
703 f[1].as_u64().context("fork next")?,
704 ))
705 };
706 let mut s = Status {
707 version,
708 network_id: 0,
709 genesis: [0; 32],
710 fork_hash: [0; 4],
711 fork_next: 0,
712 total_difficulty: Vec::new(),
713 head: [0; 32],
714 earliest_block: 0,
715 latest_block: 0,
716 latest_block_hash: [0; 32],
717 };
718 if version >= 69 {
719 ensure!(l.len() >= 7, "eth/69 Status has {} fields, not 7", l.len());
720 s.network_id = l[1].as_u64().context("network id")?;
721 s.genesis = l[2].as_fixed().context("genesis hash")?;
722 (s.fork_hash, s.fork_next) = fork(&l[3])?;
723 s.earliest_block = l[4].as_u64().context("earliest block")?;
724 s.latest_block = l[5].as_u64().context("latest block")?;
725 s.latest_block_hash = l[6].as_fixed().context("latest block hash")?;
726 } else {
727 ensure!(l.len() >= 6, "eth/68 Status has {} fields, not 6", l.len());
728 s.network_id = l[1].as_u64().context("network id")?;
729 s.total_difficulty = l[2].as_big_uint().context("total difficulty")?.to_vec();
730 s.head = l[3].as_fixed().context("head hash")?;
731 s.genesis = l[4].as_fixed().context("genesis hash")?;
732 (s.fork_hash, s.fork_next) = fork(&l[5])?;
733 }
734 Ok(s)
735 }
736
737 pub fn to_json(&self) -> Value {
738 let mut v = json!({
739 "version": self.version,
740 "network_id": self.network_id,
741 "genesis_hash": hex0x(&self.genesis),
742 "fork_hash": hex0x(&self.fork_hash),
743 "fork_next": self.fork_next,
744 });
745 if self.version >= 69 {
746 v["earliest_block"] = json!(self.earliest_block);
747 v["latest_block"] = json!(self.latest_block);
748 v["latest_block_hash"] = json!(hex0x(&self.latest_block_hash));
749 } else {
750 v["total_difficulty"] = big_json(&self.total_difficulty);
751 v["head_hash"] = json!(hex0x(&self.head));
752 }
753 v
754 }
755}
756
757#[derive(Debug, Clone, Copy, PartialEq, Eq)]
759pub enum Origin {
760 Number(u64),
761 Hash([u8; 32]),
762}
763
764impl Origin {
765 pub fn to_json(self) -> Value {
766 match self {
767 Origin::Number(n) => json!(n),
768 Origin::Hash(h) => json!(hex0x(&h)),
769 }
770 }
771
772 pub fn from_json(v: &Value) -> Result<Origin> {
774 if let Some(s) = v.as_str() {
775 if s.starts_with("0x") && s.len() == 66 {
776 return Ok(Origin::Hash(parse_fixed(v, "origin")?));
777 }
778 }
779 Ok(Origin::Number(parse_u64(v, "origin").context(
780 "origin is a block number or a 0x-prefixed 32-byte block hash",
781 )?))
782 }
783}
784
785#[derive(Debug, Clone, PartialEq, Eq)]
787pub struct HeaderRequest {
788 pub origin: Origin,
789 pub amount: u64,
790 pub skip: u64,
791 pub reverse: bool,
792}
793
794impl HeaderRequest {
795 pub fn encode(&self, request_id: u64) -> Vec<u8> {
796 let origin = match self.origin {
797 Origin::Number(n) => Rlp::uint(n),
798 Origin::Hash(h) => Rlp::bytes(&h),
799 };
800 Rlp::List(vec![
801 Rlp::uint(request_id),
802 Rlp::List(vec![
803 origin,
804 Rlp::uint(self.amount),
805 Rlp::uint(self.skip),
806 Rlp::uint(u64::from(self.reverse)),
807 ]),
808 ])
809 .encode()
810 }
811
812 pub fn decode(payload: &[u8]) -> Result<(u64, HeaderRequest)> {
813 let item = rlp::decode(payload).context("GetBlockHeaders is not RLP")?;
814 let l = item.as_list()?;
815 ensure!(l.len() == 2, "GetBlockHeaders has {} fields", l.len());
816 let q = l[1].as_list()?;
817 ensure!(q.len() == 4, "a header query has {} fields", q.len());
818 let origin_bytes = q[0].as_bytes().context("origin")?;
819 let origin = if origin_bytes.len() == 32 {
820 Origin::Hash(q[0].as_fixed()?)
821 } else {
822 Origin::Number(q[0].as_u64().context("origin number")?)
823 };
824 Ok((
825 l[0].as_u64().context("request id")?,
826 HeaderRequest {
827 origin,
828 amount: q[1].as_u64().context("amount")?,
829 skip: q[2].as_u64().context("skip")?,
830 reverse: q[3].as_bool().context("reverse")?,
831 },
832 ))
833 }
834
835 pub fn to_json(&self) -> Value {
836 json!({"origin": self.origin.to_json(), "amount": self.amount, "skip": self.skip, "reverse": self.reverse})
837 }
838
839 pub fn number_at(&self, first: u64, i: u64) -> Option<u64> {
842 let step = self.skip.checked_add(1)?.checked_mul(i)?;
843 if self.reverse {
844 first.checked_sub(step)
845 } else {
846 first.checked_add(step)
847 }
848 }
849}
850
851pub fn encode_response(request_id: u64, items: Vec<Rlp>) -> Vec<u8> {
853 Rlp::List(vec![Rlp::uint(request_id), Rlp::List(items)]).encode()
854}
855
856pub fn decode_response(payload: &[u8], max: usize, what: &str) -> Result<(u64, Vec<Rlp>)> {
858 let item = rlp::decode(payload).with_context(|| format!("{what} is not RLP"))?;
859 let mut l = match item {
860 Rlp::List(l) => l,
861 Rlp::Bytes(_) => bail!("{what} is not a list"),
862 };
863 ensure!(l.len() == 2, "{what} has {} fields", l.len());
864 let items = match l.pop() {
865 Some(Rlp::List(items)) => items,
866 _ => bail!("{what}'s second field is not a list"),
867 };
868 ensure!(
869 items.len() <= max,
870 "{what} carries {} items, above {max}",
871 items.len()
872 );
873 Ok((l[0].as_u64().context("request id")?, items))
874}
875
876pub fn decode_hash_request(payload: &[u8], max: usize, what: &str) -> Result<(u64, Vec<[u8; 32]>)> {
878 let (id, items) = decode_response(payload, max, what)?;
879 let hashes = items
880 .iter()
881 .map(|h| h.as_fixed::<32>())
882 .collect::<Result<Vec<_>>>()
883 .with_context(|| format!("{what} hash"))?;
884 Ok((id, hashes))
885}
886
887pub fn encode_hash_request(request_id: u64, hashes: &[[u8; 32]]) -> Vec<u8> {
888 encode_response(request_id, hashes.iter().map(|h| Rlp::bytes(h)).collect())
889}
890
891#[derive(Debug, Clone, PartialEq, Eq)]
893pub struct Announcement {
894 pub types: Vec<u8>,
895 pub sizes: Vec<u64>,
896 pub hashes: Vec<[u8; 32]>,
897}
898
899impl Announcement {
900 pub fn decode(payload: &[u8], max: usize) -> Result<Announcement> {
901 let item = rlp::decode(payload).context("NewPooledTransactionHashes is not RLP")?;
902 let l = item.as_list()?;
903 ensure!(
904 l.len() >= 3,
905 "NewPooledTransactionHashes has {} fields",
906 l.len()
907 );
908 let types = l[0].as_bytes().context("types")?.to_vec();
909 let sizes = l[1]
910 .as_list()?
911 .iter()
912 .map(Rlp::as_u64)
913 .collect::<Result<Vec<_>>>()
914 .context("sizes")?;
915 let hashes = l[2]
916 .as_list()?
917 .iter()
918 .map(|h| h.as_fixed::<32>())
919 .collect::<Result<Vec<_>>>()
920 .context("hashes")?;
921 ensure!(
922 types.len() == sizes.len() && sizes.len() == hashes.len(),
923 "NewPooledTransactionHashes lists {} types, {} sizes and {} hashes",
924 types.len(),
925 sizes.len(),
926 hashes.len()
927 );
928 ensure!(
929 hashes.len() <= max,
930 "{} hashes announced, above {max}",
931 hashes.len()
932 );
933 Ok(Announcement {
934 types,
935 sizes,
936 hashes,
937 })
938 }
939
940 pub fn encode(&self) -> Vec<u8> {
941 Rlp::List(vec![
942 Rlp::bytes(&self.types),
943 Rlp::List(self.sizes.iter().map(|s| Rlp::uint(*s)).collect()),
944 Rlp::List(self.hashes.iter().map(|h| Rlp::bytes(h)).collect()),
945 ])
946 .encode()
947 }
948}
949
950pub fn decode_block_range(payload: &[u8]) -> Result<(u64, u64, [u8; 32])> {
952 let item = rlp::decode(payload).context("BlockRangeUpdate is not RLP")?;
953 let l = item.as_list()?;
954 ensure!(l.len() >= 3, "BlockRangeUpdate has {} fields", l.len());
955 let (earliest, latest) = (l[0].as_u64()?, l[1].as_u64()?);
956 ensure!(
957 earliest <= latest,
958 "BlockRangeUpdate's earliest is after its latest"
959 );
960 Ok((earliest, latest, l[2].as_fixed()?))
961}
962
963pub fn parse_enode(s: &str) -> Result<([u8; 64], String)> {
969 let rest = s
970 .trim()
971 .strip_prefix("enode://")
972 .context("a devp2p peer is named by its enode URL: enode://<node id>@<host>:<port>")?;
973 let (id_hex, addr) = rest
974 .split_once('@')
975 .context("the enode URL has no @host:port")?;
976 let id = hex::decode(id_hex).context("the enode node id is not hex")?;
977 let id: [u8; 64] = id
978 .as_slice()
979 .try_into()
980 .map_err(|_| anyhow::anyhow!("the enode node id is {} bytes, not 64", id.len()))?;
981 crypto::public_from_id(&id)?;
982 let addr = addr
983 .split('?')
984 .next()
985 .unwrap_or_default()
986 .trim_end_matches('/');
987 ensure!(
988 addr.rsplit_once(':')
989 .is_some_and(|(h, p)| !h.is_empty() && p.parse::<u16>().is_ok()),
990 "the enode URL names no port"
991 );
992 Ok((id, addr.to_string()))
993}
994
995pub fn format_enode(id: &[u8; 64], addr: std::net::SocketAddr) -> String {
996 format!("enode://{}@{}", hex::encode(id), addr)
997}