Skip to main content

bitcoin_address_book/
lib.rs

1//! This crate assists in managing network addresses gossiped over the Bitcoin peer-to-peer
2//! network. The goals of an address book are to prevent any single peer from filling all entries
3//! in the address book, resist eclipse attacks, and to help find useful peers quickly.
4
5use std::{
6    hash::{DefaultHasher, Hash, Hasher},
7    io::Read,
8    net::IpAddr,
9    time::{Duration, SystemTime, UNIX_EPOCH},
10};
11
12use bitcoin::{
13    consensus,
14    p2p::{address::AddrV2, ServiceFlags},
15};
16/// Perform basic I/O operations on the address book.
17pub mod io;
18
19const ONE_MINUTE: Duration = Duration::from_secs(60);
20const ONE_WEEK: Duration = Duration::from_secs(604800);
21
22/// A record of a potential Bitcoin peer.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Record {
25    addr: AddrV2,
26    port: u16,
27    source: SourceId,
28    services: ServiceFlags,
29    failed_attempts: u8,
30    last_connection: Option<Duration>,
31    last_attempt: Option<Duration>,
32}
33
34impl Record {
35    fn compute_size(&self) -> u8 {
36        let mut size = 0;
37        match &self.addr {
38            AddrV2::I2p(_) => size += 34,
39            AddrV2::Ipv4(_) => size += 6,
40            AddrV2::Ipv6(_) => size += 18,
41            AddrV2::TorV2(_) => size += 12,
42            AddrV2::TorV3(_) => size += 34,
43            AddrV2::Cjdns(_) => size += 18,
44            AddrV2::Unknown(len, _) => size += *len,
45        }
46        // port
47        size += 2;
48        // source id
49        size += self.source.0.len() as u8;
50        // service flags
51        size += 8;
52        // failed attempts
53        size += 1;
54        // time encoding
55        size += 9;
56        //time encoding
57        size += 9;
58        size
59    }
60
61    /// Construct a new record from a gossip message.
62    pub fn new(addr: AddrV2, port: u16, services: ServiceFlags, source: &IpAddr) -> Self {
63        let source = source.source_id();
64        Self {
65            addr,
66            port,
67            source,
68            services,
69            failed_attempts: 0,
70            last_connection: None,
71            last_attempt: None,
72        }
73    }
74
75    pub fn new_from_addrv2_source(
76        addr: AddrV2,
77        port: u16,
78        services: ServiceFlags,
79        source: &AddrV2,
80    ) -> Self {
81        let source = match source {
82            AddrV2::Ipv4(ip) => IpAddr::V4(*ip).source_id(),
83            AddrV2::Ipv6(ip) => IpAddr::V6(*ip).source_id(),
84            // All the mixnets go in the same source,
85            _ => SourceId([1u8; 4]),
86        };
87        Self {
88            addr,
89            port,
90            source,
91            services,
92            failed_attempts: 0,
93            last_connection: None,
94            last_attempt: None,
95        }
96    }
97
98    /// Build a new record from deserialization
99    pub fn deserialize<R: Read>(reader: &mut R) -> Result<Self, std::io::Error> {
100        let mut size_buf = [0u8; 1];
101        reader.read_exact(&mut size_buf)?;
102        let size = u8::from_le_bytes(size_buf);
103        let mut content_buf = vec![0u8; size as usize];
104        reader.read_exact(&mut content_buf)?;
105        let (addr, len) =
106            consensus::deserialize_partial::<AddrV2>(&content_buf).expect("must have 33 bytes");
107        let mut content_slice = &content_buf[len..];
108        let mut port_buf = [0u8; 2];
109        content_slice.read_exact(&mut port_buf)?;
110        let port = u16::from_le_bytes(port_buf);
111        let mut source_buf = [0u8; 4];
112        content_slice.read_exact(&mut source_buf)?;
113        let source = SourceId(source_buf);
114        let mut service_buf = [0u8; 8];
115        content_slice.read_exact(&mut service_buf)?;
116        let services = ServiceFlags::from(u64::from_le_bytes(service_buf));
117        let mut failed_buf = [0u8; 1];
118        content_slice.read_exact(&mut failed_buf)?;
119        let failed_attempts = u8::from_le_bytes(failed_buf);
120        let mut record = Record {
121            addr,
122            port,
123            source,
124            services,
125            failed_attempts,
126            last_connection: None,
127            last_attempt: None,
128        };
129        let mut last_attempt_buf = [0u8; 1];
130        content_slice.read_exact(&mut last_attempt_buf)?;
131        let should_read = u8::from_le_bytes(last_attempt_buf);
132        match should_read {
133            0u8 => {
134                content_slice.read_exact(&mut [0u8; 8])?;
135            }
136            1u8 => {
137                let mut time_buf = [0u8; 8];
138                content_slice.read_exact(&mut time_buf)?;
139                let secs = u64::from_le_bytes(time_buf);
140                record.last_attempt = Some(Duration::from_secs(secs));
141            }
142            _ => panic!("invalid time encoding"),
143        }
144        let mut last_conn_buf = [0u8; 1];
145        content_slice.read_exact(&mut last_conn_buf)?;
146        let should_read = u8::from_le_bytes(last_conn_buf);
147        match should_read {
148            0u8 => {
149                content_slice.read_exact(&mut [0u8; 8])?;
150            }
151            1u8 => {
152                let mut time_buf = [0u8; 8];
153                content_slice.read_exact(&mut time_buf)?;
154                let secs = u64::from_le_bytes(time_buf);
155                record.last_connection = Some(Duration::from_secs(secs));
156            }
157            _ => panic!("invalid time encoding"),
158        }
159        Ok(record)
160    }
161
162    fn destination_id(&self) -> DestinationId {
163        self.addr.destination_id()
164    }
165
166    /// The network address and port to reach this peer.
167    pub fn network_addr(&self) -> (AddrV2, u16) {
168        (self.addr.clone(), self.port)
169    }
170
171    /// The services advertised by the peer.
172    pub fn service_flags(&self) -> ServiceFlags {
173        self.services
174    }
175
176    /// Update the most recent service flag information.
177    pub fn update_service_flags(&mut self, flags: ServiceFlags) {
178        self.services = flags;
179    }
180
181    /// Serialize a record into bytes.
182    pub fn serialize(self) -> Vec<u8> {
183        let len = self.compute_size();
184        let mut buf = Vec::with_capacity(len.into());
185        buf.push(len);
186        let addr = consensus::serialize(&self.addr);
187        buf.extend(addr);
188        buf.extend(self.port.to_le_bytes());
189        buf.extend(self.source.0);
190        buf.extend(self.services.to_u64().to_le_bytes());
191        buf.push(self.failed_attempts);
192        if let Some(last_attempt) = self.last_attempt {
193            buf.push(0x01);
194            let secs = last_attempt.as_secs().to_le_bytes();
195            buf.extend(secs);
196        } else {
197            buf.extend([0u8; 9]);
198        }
199        if let Some(last_conn) = self.last_connection {
200            buf.push(0x01);
201            let secs = last_conn.as_secs().to_le_bytes();
202            buf.extend(secs);
203        } else {
204            buf.extend([0u8; 9]);
205        }
206        buf
207    }
208
209    /// Similar to the `AddrMan::IsTerrible` function in Bitcoin Core. If the peer has been tried
210    /// many times with no successes, then it is best to evict this peer from the table.
211    pub fn is_terrible(&self, maximum_tries: u8, maximum_weekly_tries: u8) -> bool {
212        if let Some(attempt) = self.last_attempt {
213            if attempt < ONE_MINUTE {
214                return false;
215            }
216            if self.failed_attempts > maximum_weekly_tries && attempt < ONE_WEEK {
217                return true;
218            }
219        }
220        if self.failed_attempts > maximum_tries {
221            return true;
222        }
223        false
224    }
225}
226
227/// A table of records to store potential peers. Some properties of this table are: a single source
228/// of gossip cannot fill this entire table with addresses, the table is a fixed size and held
229/// entirely in memory, the table is represented as a 2D matrix.
230///
231/// `B` represents the bumber of "buckets" that hold addresses. A source may only add addresses to
232/// a subset of the buckets.
233///
234/// `S` represents the number of "slots" per bucket. A slot is either occupied with an entry or free.
235///
236/// `W` is the maximum amount of buckets a source is allowed to add to, where `W < B`
237///
238/// A table is simply a `B x S` matrix to store peers. Limiting the buckets `B` a source may add
239/// peers to creates an eclipse-resistance in the contect of Bitcoin. Otherwise, this is an
240/// un-ordered list.
241#[derive(Debug)]
242pub struct Table<const B: usize, const S: usize, const W: usize> {
243    buckets: [Bucket<S>; B],
244}
245
246impl<const B: usize, const S: usize, const W: usize> Table<B, S, W> {
247    // Used to compute a random bucket range for a `source_id`
248    const RUN: usize = B / W;
249
250    // Derive the bucket to store a record. Crucially, a single source ID cannot fill the entire
251    // range of buckets.
252    //
253    // For example, let B = 1024, S = 64, W = 64, then RUN = 16.
254    // Say the `source_id` modulo W is 63, and the `destination_id` modulo W is 7.
255    //
256    // We derive a bucket (63 * 16) + 7 % 1024 = 1015
257    fn derive_bucket(record: &Record) -> usize {
258        let salt = u32::from_le_bytes(record.source.0) as usize % W;
259        let range = (salt * Self::RUN) % B;
260        let index = u32::from_le_bytes(record.destination_id().0) as usize % W;
261        (range + index) % B
262    }
263
264    // Select a random bucket psuedo-randomly.
265    fn random_bucket() -> usize {
266        let mut hasher = DefaultHasher::new();
267        SystemTime::now().hash(&mut hasher);
268        u32::from_le_bytes(
269            hasher.finish().to_le_bytes()[..4]
270                .try_into()
271                .expect("hash is u64"),
272        ) as usize
273            % B
274    }
275
276    fn random_slot() -> usize {
277        let mut hasher = DefaultHasher::new();
278        SystemTime::now().hash(&mut hasher);
279        u32::from_le_bytes(
280            hasher.finish().to_le_bytes()[..4]
281                .try_into()
282                .expect("hash is u64"),
283        ) as usize
284            % S
285    }
286
287    fn random_from_bucket(bucket: &Bucket<S>) -> Option<Record> {
288        if bucket.is_empty() {
289            return None;
290        }
291        let slot_index = Self::random_slot();
292        let mut tmp = (slot_index + 1) % S;
293        while tmp.ne(&slot_index) {
294            let record = bucket.get(tmp);
295            if let Some(record) = record {
296                let Some(last_attempt) = record.last_attempt else {
297                    return Some(record);
298                };
299                if last_attempt > ONE_MINUTE {
300                    return Some(record);
301                }
302            }
303            tmp = (tmp + 1) % S;
304        }
305        bucket.get(slot_index)
306    }
307
308    /// Build a new table to store records of peers.
309    pub fn new() -> Self {
310        let buckets: [Bucket<S>; B] = [const { Bucket::new() }; B];
311        Self { buckets }
312    }
313
314    /// Add a peer to this table. If there is a conflict at the designated slot, then the
315    /// conflicting record is returned.
316    ///
317    /// Note that bucket and slot indices are computed deterministically, so conflicts must be
318    /// resolved.
319    pub fn add(&mut self, record: &Record) -> Option<Record> {
320        let bucket_index = Self::derive_bucket(record);
321        self.buckets[bucket_index].add(record.clone())
322    }
323
324    /// Remove a record from it's slot.
325    pub fn remove(&mut self, record: &Record) {
326        let bucket_index = Self::derive_bucket(record);
327        self.buckets[bucket_index].remove(record);
328    }
329
330    /// Count the occurrences of a network address.
331    pub fn count(&self, record: &Record) -> usize {
332        self.buckets
333            .iter()
334            .filter(|bucket| bucket.has_record(record))
335            .count()
336    }
337
338    /// Is the entire address book empty.
339    pub fn is_empty(&self) -> bool {
340        self.buckets.iter().all(|bucket| bucket.is_empty())
341    }
342
343    /// Select an address randomly from the address book.
344    ///
345    /// First, a random bucket will be selected to poll a peer from. If the bucket is non-empty,
346    /// a random peer will be returned from the bucket. Otherwise, the buckets will be iterated
347    /// over until a peer is found. If no peers are found after the exhaustive search, `None` is
348    /// returned.
349    pub fn select(&self) -> Option<Record> {
350        if self.is_empty() {
351            return None;
352        };
353        let bucket_index = Self::random_bucket();
354        let bucket = &self.buckets[bucket_index];
355        if bucket.is_empty() {
356            let mut tmp = (bucket_index + 1) % B;
357            while tmp.ne(&bucket_index) {
358                let bucket = &self.buckets[tmp];
359                let random_record = Self::random_from_bucket(bucket);
360                if random_record.is_some() {
361                    return random_record;
362                }
363                tmp = (tmp + 1) % B;
364            }
365            None
366        } else {
367            Self::random_from_bucket(bucket)
368        }
369    }
370
371    /// Report a successful connection to `Record`
372    pub fn successful_connection(&mut self, record: &Record) {
373        let bucket_index = Self::derive_bucket(record);
374        let bucket = &mut self.buckets[bucket_index];
375        bucket.successful_connection(record);
376    }
377
378    /// Report a failed connection to `Record`
379    pub fn failed_connection(&mut self, record: &Record) {
380        let bucket_index = Self::derive_bucket(record);
381        let bucket = &mut self.buckets[bucket_index];
382        bucket.failed_connection(record);
383    }
384}
385
386impl<const B: usize, const S: usize, const W: usize> Default for Table<B, S, W> {
387    fn default() -> Self {
388        Table::<B, S, W>::new()
389    }
390}
391
392#[derive(Debug)]
393struct Bucket<const S: usize> {
394    records: [Option<Record>; S],
395}
396
397impl<const S: usize> Bucket<S> {
398    fn derive_slot(record: &Record) -> usize {
399        let index = u32::from_le_bytes(record.destination_id().0) as usize;
400        index % S
401    }
402
403    const fn new() -> Self {
404        let records: [Option<Record>; S] = [const { None }; S];
405        Self { records }
406    }
407
408    fn add(&mut self, record: Record) -> Option<Record> {
409        let slot = Self::derive_slot(&record);
410        match &self.records[slot] {
411            Some(occupied) => Some(occupied.clone()),
412            None => {
413                self.records[slot] = Some(record);
414                None
415            }
416        }
417    }
418
419    fn remove(&mut self, record: &Record) {
420        let slot = Self::derive_slot(record);
421        self.records[slot] = None;
422    }
423
424    fn has_record(&self, record: &Record) -> bool {
425        let slot = Self::derive_slot(record);
426        match &self.records[slot] {
427            Some(cmp) => cmp.eq(record),
428            None => false,
429        }
430    }
431
432    fn is_empty(&self) -> bool {
433        self.records.iter().all(|record| record.is_none())
434    }
435
436    fn successful_connection(&mut self, record: &Record) {
437        let slot = Self::derive_slot(record);
438        let new_flags = record.services;
439        if let Some(record) = &mut self.records[slot] {
440            record.last_attempt = Some(
441                SystemTime::now()
442                    .duration_since(UNIX_EPOCH)
443                    .expect("time went backwards"),
444            );
445            record.last_connection = Some(
446                SystemTime::now()
447                    .duration_since(UNIX_EPOCH)
448                    .expect("time went backwards"),
449            );
450            record.failed_attempts = 0;
451            record.services = new_flags;
452        }
453    }
454
455    fn failed_connection(&mut self, record: &Record) {
456        let slot = Self::derive_slot(record);
457        if let Some(record) = &mut self.records[slot] {
458            record.last_attempt = Some(
459                SystemTime::now()
460                    .duration_since(UNIX_EPOCH)
461                    .expect("time went backwards"),
462            );
463            record.failed_attempts += 1;
464        }
465    }
466
467    fn get(&self, index: usize) -> Option<Record> {
468        let index = index % S;
469        self.records[index].clone()
470    }
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, std::hash::Hash)]
474struct SourceId([u8; 4]);
475
476trait SourceIdExt {
477    fn source_id(&self) -> SourceId;
478}
479
480impl SourceIdExt for IpAddr {
481    fn source_id(&self) -> SourceId {
482        let mut hasher = DefaultHasher::new();
483        match self {
484            Self::V4(ipv4) => {
485                let octets = ipv4.octets();
486                let first_two_octets = [octets[0], octets[1]];
487                first_two_octets.hash(&mut hasher);
488                let hash = hasher.finish();
489                let bytes: [u8; 4] = hash.to_le_bytes()[..4].try_into().expect("hash is u64");
490                SourceId(bytes)
491            }
492            Self::V6(ipv6) => {
493                let octets = ipv6.octets();
494                let first_four_octets = [octets[0], octets[1], octets[2], octets[3]];
495                first_four_octets.hash(&mut hasher);
496                let hash = hasher.finish();
497                let bytes = hash.to_le_bytes()[..4].try_into().expect("hash is u64");
498                SourceId(bytes)
499            }
500        }
501    }
502}
503
504#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, std::hash::Hash)]
505struct DestinationId([u8; 4]);
506
507trait DestinationIdExt {
508    fn destination_id(&self) -> DestinationId;
509}
510
511impl DestinationIdExt for AddrV2 {
512    fn destination_id(&self) -> DestinationId {
513        let mut hasher = DefaultHasher::new();
514        match self {
515            Self::Ipv4(ipv4) => {
516                ipv4.octets().hash(&mut hasher);
517                DestinationId(
518                    hasher.finish().to_le_bytes()[..4]
519                        .try_into()
520                        .expect("hash is u64"),
521                )
522            }
523            Self::Ipv6(ipv6) => {
524                ipv6.octets().hash(&mut hasher);
525                DestinationId(
526                    hasher.finish().to_le_bytes()[..4]
527                        .try_into()
528                        .expect("hash is u64"),
529                )
530            }
531            Self::I2p(i2p) => {
532                i2p.hash(&mut hasher);
533                DestinationId(
534                    hasher.finish().to_le_bytes()[..4]
535                        .try_into()
536                        .expect("hash is u64"),
537                )
538            }
539            Self::TorV3(tv3) => {
540                tv3.hash(&mut hasher);
541                DestinationId(
542                    hasher.finish().to_le_bytes()[..4]
543                        .try_into()
544                        .expect("hash is u64"),
545                )
546            }
547            Self::Cjdns(ipv6) => {
548                ipv6.octets().hash(&mut hasher);
549                DestinationId(
550                    hasher.finish().to_le_bytes()[..4]
551                        .try_into()
552                        .expect("hash is u64"),
553                )
554            }
555            _ => {
556                "unknown network address".hash(&mut hasher);
557                DestinationId(
558                    hasher.finish().to_le_bytes()[..4]
559                        .try_into()
560                        .expect("hash is u64"),
561                )
562            }
563        }
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use std::{
570        hash::{DefaultHasher, Hash, Hasher},
571        net::{IpAddr, Ipv4Addr},
572        time::SystemTime,
573    };
574
575    use bitcoin::p2p::{address::AddrV2, ServiceFlags};
576
577    use crate::{Record, Table};
578
579    const LOCAL_HOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
580    const DUMB: AddrV2 = AddrV2::Ipv4(LOCAL_HOST);
581
582    const BUCKETS: usize = 256;
583    const SLOTS: usize = 16;
584    const RANGE: usize = 16;
585
586    pub fn random_record() -> Record {
587        let mut hasher = DefaultHasher::new();
588        let now = SystemTime::now();
589        now.hash(&mut hasher);
590        let bytes = hasher.finish();
591        let ip = bytes.to_le_bytes();
592        let dest = Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]);
593        let source = Ipv4Addr::new(ip[4], ip[5], ip[6], ip[7]);
594        let now = SystemTime::now();
595        now.hash(&mut hasher);
596        let bytes = hasher.finish();
597        let addr_v2 = AddrV2::Ipv4(dest);
598        let mut record = Record::new(addr_v2, 8333, ServiceFlags::NETWORK, &IpAddr::V4(source));
599        record.failed_attempts += bytes.to_le_bytes()[0];
600        record
601    }
602
603    #[test]
604    fn test_simple_table_situations() {
605        let mut table = Table::<BUCKETS, SLOTS, RANGE>::new();
606        assert!(table.is_empty());
607        assert!(table.select().is_none());
608        let record = Record::new(DUMB, 8333, ServiceFlags::NONE, &IpAddr::V4(LOCAL_HOST));
609        table.add(&record);
610        assert!(!table.is_empty());
611        // We should always be able to find this peer in exhaustive search.
612        for _ in 0..BUCKETS * SLOTS {
613            assert!(table.select().is_some());
614        }
615        // Adding the same record should always conflict.
616        for _ in 0..BUCKETS * SLOTS {
617            assert!(table.add(&record).is_some());
618        }
619        assert_eq!(table.count(&record), 1);
620    }
621
622    #[test]
623    fn test_encoding_roundtrip() {
624        for _ in 0..BUCKETS * SLOTS {
625            let want = random_record();
626            let bytes = want.clone().serialize();
627            let got = Record::deserialize(&mut bytes.as_slice()).unwrap();
628            assert_eq!(want, got);
629        }
630    }
631}