1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Randomised test data generation for MetaAddr.

use std::net::IpAddr;

use proptest::{collection::vec, prelude::*};

use zebra_chain::{parameters::Network::*, serialization::DateTime32};

use crate::protocol::external::arbitrary::canonical_peer_addr_strategy;

use super::{MetaAddr, MetaAddrChange, PeerServices, PeerSocketAddr};

/// The largest number of random changes we want to apply to a [`MetaAddr`].
///
/// This should be at least twice the number of [`PeerAddrState`][1]s, so the
/// tests can cover multiple transitions through every state.
///
/// [1]: super::PeerAddrState
#[allow(dead_code)]
pub const MAX_ADDR_CHANGE: usize = 15;

/// The largest number of random addresses we want to add to an [`AddressBook`][2].
///
/// This should be at least the number of [`PeerAddrState`][1]s, so the tests
/// can cover interactions between addresses in different states.
///
/// [1]: super::PeerAddrState
/// [2]: crate::AddressBook
#[allow(dead_code)]
pub const MAX_META_ADDR: usize = 8;

impl MetaAddr {
    /// Create a strategy that generates [`MetaAddr`]s in the
    /// [`NeverAttemptedGossiped`][1] state.
    ///
    /// [1]: super::PeerAddrState::NeverAttemptedGossiped
    pub fn gossiped_strategy() -> BoxedStrategy<Self> {
        (
            canonical_peer_addr_strategy(),
            any::<PeerServices>(),
            any::<DateTime32>(),
        )
            .prop_map(|(addr, untrusted_services, untrusted_last_seen)| {
                MetaAddr::new_gossiped_meta_addr(addr, untrusted_services, untrusted_last_seen)
            })
            .boxed()
    }
}

impl MetaAddrChange {
    /// Returns a strategy which generates changes for `addr`.
    ///
    /// `addr` is typically generated by the `canonical_peer_addr` strategy.
    pub fn addr_strategy(addr: PeerSocketAddr) -> BoxedStrategy<Self> {
        any::<MetaAddrChange>()
            .prop_map(move |mut change| {
                change.set_addr(addr);
                change
            })
            .boxed()
    }

    /// Returns a strategy which generates a `MetaAddr`, and a vector of up to
    /// `max_addr_change` changes.
    ///
    /// The address and the changes all have matching `PeerSocketAddr`s.
    pub fn addr_changes_strategy(
        max_addr_change: usize,
    ) -> BoxedStrategy<(MetaAddr, Vec<MetaAddrChange>)> {
        any::<MetaAddr>()
            .prop_flat_map(move |addr| {
                (
                    Just(addr),
                    vec(MetaAddrChange::addr_strategy(addr.addr), 1..max_addr_change),
                )
            })
            .boxed()
    }

    /// Create a strategy that generates [`IpAddr`]s for [`MetaAddrChange`]s which are ready for
    /// outbound connections.
    pub fn ready_outbound_strategy_ip() -> BoxedStrategy<IpAddr> {
        any::<IpAddr>()
            .prop_filter("failed MetaAddr::is_valid_for_outbound", |ip| {
                !ip.is_unspecified()
            })
            .boxed()
    }

    /// Create a strategy that generates port numbers for [`MetaAddr`]s which are ready for
    /// outbound connections.
    ///
    /// Currently, all generated [`MetaAddr`]s are the [`NeverAttemptedGossiped`][1] variant.
    ///
    /// TODO: Generate all [`MetaAddr`] variants, and give them ready fields.
    ///
    /// [1]: super::NeverAttemptedGossiped
    pub fn ready_outbound_strategy_port() -> BoxedStrategy<u16> {
        (
            canonical_peer_addr_strategy(),
            any::<PeerServices>(),
            any::<DateTime32>(),
        )
            .prop_filter_map(
                "failed MetaAddr::is_valid_for_outbound",
                |(addr, services, local_now)| {
                    let addr = MetaAddr::new_gossiped_meta_addr(addr, services, local_now);

                    if addr.last_known_info_is_valid_for_outbound(&Mainnet) {
                        Some(addr.addr.port())
                    } else {
                        None
                    }
                },
            )
            .boxed()
    }
}