use {
    crate::{
        contact_info::ContactInfo,
        deprecated,
        duplicate_shred::{DuplicateShred, DuplicateShredIndex, MAX_DUPLICATE_SHREDS},
        epoch_slots::EpochSlots,
        legacy_contact_info::LegacyContactInfo,
        restart_crds_values::{RestartHeaviestFork, RestartLastVotedForkSlots},
    },
    rand::Rng,
    serde::{de::Deserializer, Deserialize, Serialize},
    solana_clock::Slot,
    solana_hash::Hash,
    solana_pubkey::{self, Pubkey},
    solana_sanitize::{Sanitize, SanitizeError},
    solana_time_utils::timestamp,
    solana_transaction::Transaction,
    solana_vote::vote_parser,
    std::collections::BTreeSet,
};

pub(crate) const MAX_WALLCLOCK: u64 = 1_000_000_000_000_000;
pub(crate) const MAX_SLOT: u64 = 1_000_000_000_000_000;
/// Maximum number of hashes in AccountsHashes a node publishes
/// such that the serialized size of the push/pull message stays below
/// PACKET_DATA_SIZE.
const MAX_ACCOUNTS_HASHES: usize = 16;

pub(crate) type VoteIndex = u8;
// Until the cluster upgrades we allow votes from higher indices
const OLD_MAX_VOTES: VoteIndex = 32;
/// Number of votes per validator to store.
pub const MAX_VOTES: VoteIndex = 12;

pub(crate) type EpochSlotsIndex = u8;
pub(crate) const MAX_EPOCH_SLOTS: EpochSlotsIndex = 255;

/// CrdsData that defines the different types of items CrdsValues can hold
/// * Merge Strategy - Latest wallclock is picked
/// * LowestSlot index is deprecated
#[allow(clippy::large_enum_variant)]
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum CrdsData {
    #[allow(private_interfaces)]
    LegacyContactInfo(LegacyContactInfo),
    Vote(VoteIndex, Vote),
    LowestSlot(
        #[serde(deserialize_with = "reject_nonzero_u8")] u8, // u8 is deprecated
        LowestSlot,
    ),
    #[allow(private_interfaces)]
    LegacySnapshotHashes(LegacySnapshotHashes), // Deprecated
    #[allow(private_interfaces)]
    AccountsHashes(AccountsHashes), // Deprecated
    EpochSlots(EpochSlotsIndex, EpochSlots),
    #[allow(private_interfaces)]
    LegacyVersion(LegacyVersion), // Deprecated
    #[allow(private_interfaces)]
    Version(Version), // Deprecated
    #[allow(private_interfaces)]
    NodeInstance(NodeInstance), // Deprecated
    DuplicateShred(DuplicateShredIndex, DuplicateShred),
    SnapshotHashes(SnapshotHashes),
    ContactInfo(ContactInfo),
    RestartLastVotedForkSlots(RestartLastVotedForkSlots),
    RestartHeaviestFork(RestartHeaviestFork),
}

impl Sanitize for CrdsData {
    fn sanitize(&self) -> Result<(), SanitizeError> {
        match self {
            CrdsData::LegacyContactInfo(val) => val.sanitize(),
            CrdsData::Vote(ix, val) => {
                if *ix >= OLD_MAX_VOTES {
                    return Err(SanitizeError::ValueOutOfBounds);
                }
                val.sanitize()
            }
            CrdsData::LowestSlot(ix, val) => {
                if *ix as usize >= 1 {
                    return Err(SanitizeError::ValueOutOfBounds);
                }
                val.sanitize()
            }
            CrdsData::LegacySnapshotHashes(val) => val.sanitize(),
            CrdsData::AccountsHashes(val) => val.sanitize(),
            CrdsData::EpochSlots(ix, val) => {
                if *ix as usize >= MAX_EPOCH_SLOTS as usize {
                    return Err(SanitizeError::ValueOutOfBounds);
                }
                val.sanitize()
            }
            CrdsData::LegacyVersion(version) => version.sanitize(),
            CrdsData::Version(version) => version.sanitize(),
            CrdsData::NodeInstance(node) => node.sanitize(),
            CrdsData::DuplicateShred(ix, shred) => {
                if *ix >= MAX_DUPLICATE_SHREDS {
                    Err(SanitizeError::ValueOutOfBounds)
                } else {
                    shred.sanitize()
                }
            }
            CrdsData::SnapshotHashes(val) => val.sanitize(),
            CrdsData::ContactInfo(node) => node.sanitize(),
            CrdsData::RestartLastVotedForkSlots(slots) => slots.sanitize(),
            CrdsData::RestartHeaviestFork(fork) => fork.sanitize(),
        }
    }
}

/// Random timestamp for tests and benchmarks.
pub(crate) fn new_rand_timestamp<R: Rng>(rng: &mut R) -> u64 {
    const DELAY: u64 = 10 * 60 * 1000; // 10 minutes
    timestamp() - DELAY + rng.gen_range(0..2 * DELAY)
}

impl CrdsData {
    /// New random CrdsData for tests and benchmarks.
    pub(crate) fn new_rand<R: Rng>(rng: &mut R, pubkey: Option<Pubkey>) -> CrdsData {
        let kind = rng.gen_range(0..8);
        // TODO: Implement other kinds of CrdsData here.
        // TODO: Assign ranges to each arm proportional to their frequency in
        // the mainnet crds table.
        match kind {
            0 => CrdsData::from(ContactInfo::new_rand(rng, pubkey)),
            // Index for LowestSlot is deprecated and should be zero.
            1 => CrdsData::LowestSlot(0, LowestSlot::new_rand(rng, pubkey)),
            2 => CrdsData::LegacySnapshotHashes(LegacySnapshotHashes::new_rand(rng, pubkey)),
            3 => CrdsData::AccountsHashes(AccountsHashes::new_rand(rng, pubkey)),
            4 => CrdsData::Vote(rng.gen_range(0..MAX_VOTES), Vote::new_rand(rng, pubkey)),
            5 => CrdsData::RestartLastVotedForkSlots(RestartLastVotedForkSlots::new_rand(
                rng, pubkey,
            )),
            6 => CrdsData::RestartHeaviestFork(RestartHeaviestFork::new_rand(rng, pubkey)),
            _ => CrdsData::EpochSlots(
                rng.gen_range(0..MAX_EPOCH_SLOTS),
                EpochSlots::new_rand(rng, pubkey),
            ),
        }
    }

    pub(crate) fn wallclock(&self) -> u64 {
        match self {
            CrdsData::LegacyContactInfo(contact_info) => contact_info.wallclock(),
            CrdsData::Vote(_, vote) => vote.wallclock,
            CrdsData::LowestSlot(_, obj) => obj.wallclock,
            CrdsData::LegacySnapshotHashes(hash) => hash.wallclock,
            CrdsData::AccountsHashes(hash) => hash.wallclock,
            CrdsData::EpochSlots(_, p) => p.wallclock,
            CrdsData::LegacyVersion(version) => version.wallclock,
            CrdsData::Version(version) => version.wallclock,
            CrdsData::NodeInstance(node) => node.wallclock,
            CrdsData::DuplicateShred(_, shred) => shred.wallclock,
            CrdsData::SnapshotHashes(hash) => hash.wallclock,
            CrdsData::ContactInfo(node) => node.wallclock(),
            CrdsData::RestartLastVotedForkSlots(slots) => slots.wallclock,
            CrdsData::RestartHeaviestFork(fork) => fork.wallclock,
        }
    }

    pub(crate) fn pubkey(&self) -> Pubkey {
        match &self {
            CrdsData::LegacyContactInfo(contact_info) => *contact_info.pubkey(),
            CrdsData::Vote(_, vote) => vote.from,
            CrdsData::LowestSlot(_, slots) => slots.from,
            CrdsData::LegacySnapshotHashes(hash) => hash.from,
            CrdsData::AccountsHashes(hash) => hash.from,
            CrdsData::EpochSlots(_, p) => p.from,
            CrdsData::LegacyVersion(version) => version.from,
            CrdsData::Version(version) => version.from,
            CrdsData::NodeInstance(node) => node.from,
            CrdsData::DuplicateShred(_, shred) => shred.from,
            CrdsData::SnapshotHashes(hash) => hash.from,
            CrdsData::ContactInfo(node) => *node.pubkey(),
            CrdsData::RestartLastVotedForkSlots(slots) => slots.from,
            CrdsData::RestartHeaviestFork(fork) => fork.from,
        }
    }

    #[inline]
    #[must_use]
    pub(crate) fn is_deprecated(&self) -> bool {
        match self {
            Self::LegacyContactInfo(_) => true,
            Self::Vote(..) => false,
            Self::LowestSlot(0, _) => false,
            Self::LowestSlot(1.., _) => true,
            Self::LegacySnapshotHashes(_) => true,
            Self::AccountsHashes(_) => true,
            Self::EpochSlots(..) => false,
            Self::LegacyVersion(_) => true,
            Self::Version(_) => true,
            Self::NodeInstance(_) => true,
            Self::DuplicateShred(..) => false,
            Self::SnapshotHashes(_) => false,
            Self::ContactInfo(_) => false,
            Self::RestartLastVotedForkSlots(_) => false,
            Self::RestartHeaviestFork(_) => false,
        }
    }
}

impl From<ContactInfo> for CrdsData {
    #[inline]
    fn from(node: ContactInfo) -> Self {
        Self::ContactInfo(node)
    }
}

impl From<&ContactInfo> for CrdsData {
    #[inline]
    fn from(node: &ContactInfo) -> Self {
        Self::ContactInfo(node.clone())
    }
}

#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
pub(crate) struct AccountsHashes {
    pub(crate) from: Pubkey,
    pub(crate) hashes: Vec<(Slot, Hash)>,
    pub(crate) wallclock: u64,
}
reject_deserialize!(AccountsHashes, "AccountsHashes is deprecated");

impl Sanitize for AccountsHashes {
    fn sanitize(&self) -> Result<(), SanitizeError> {
        sanitize_wallclock(self.wallclock)?;
        for (slot, _) in &self.hashes {
            if *slot >= MAX_SLOT {
                return Err(SanitizeError::ValueOutOfBounds);
            }
        }
        self.from.sanitize()
    }
}

impl AccountsHashes {
    /// New random AccountsHashes for tests and benchmarks.
    pub(crate) fn new_rand<R: Rng>(rng: &mut R, pubkey: Option<Pubkey>) -> Self {
        let num_hashes = rng.gen_range(0..MAX_ACCOUNTS_HASHES) + 1;
        let hashes = std::iter::repeat_with(|| {
            let slot = 47825632 + rng.gen_range(0..512);
            let hash = Hash::new_unique();
            (slot, hash)
        })
        .take(num_hashes)
        .collect();
        Self {
            from: pubkey.unwrap_or_else(solana_pubkey::new_rand),
            hashes,
            wallclock: new_rand_timestamp(rng),
        }
    }
}

type LegacySnapshotHashes = AccountsHashes;

#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct SnapshotHashes {
    pub from: Pubkey,
    pub full: (Slot, Hash),
    pub incremental: Vec<(Slot, Hash)>,
    pub wallclock: u64,
}

impl Sanitize for SnapshotHashes {
    fn sanitize(&self) -> Result<(), SanitizeError> {
        sanitize_wallclock(self.wallclock)?;
        if self.full.0 >= MAX_SLOT {
            return Err(SanitizeError::ValueOutOfBounds);
        }
        for (slot, _) in &self.incremental {
            if *slot >= MAX_SLOT {
                return Err(SanitizeError::ValueOutOfBounds);
            }
            if self.full.0 >= *slot {
                return Err(SanitizeError::InvalidValue);
            }
        }
        self.from.sanitize()
    }
}

#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct LowestSlot {
    pub(crate) from: Pubkey,
    root: Slot, //deprecated
    pub lowest: Slot,
    slots: BTreeSet<Slot>,                        //deprecated
    stash: Vec<deprecated::EpochIncompleteSlots>, //deprecated
    wallclock: u64,
}

impl LowestSlot {
    pub(crate) fn new(from: Pubkey, lowest: Slot, wallclock: u64) -> Self {
        Self {
            from,
            root: 0,
            lowest,
            slots: BTreeSet::new(),
            stash: vec![],
            wallclock,
        }
    }

    /// New random LowestSlot for tests and benchmarks.
    fn new_rand<R: Rng>(rng: &mut R, pubkey: Option<Pubkey>) -> Self {
        Self {
            from: pubkey.unwrap_or_else(solana_pubkey::new_rand),
            root: rng.gen(),
            lowest: rng.gen(),
            slots: BTreeSet::default(),
            stash: Vec::default(),
            wallclock: new_rand_timestamp(rng),
        }
    }
}

impl Sanitize for LowestSlot {
    fn sanitize(&self) -> Result<(), SanitizeError> {
        sanitize_wallclock(self.wallclock)?;
        if self.lowest >= MAX_SLOT {
            return Err(SanitizeError::ValueOutOfBounds);
        }
        if self.root != 0 {
            return Err(SanitizeError::InvalidValue);
        }
        if !self.slots.is_empty() {
            return Err(SanitizeError::InvalidValue);
        }
        if !self.stash.is_empty() {
            return Err(SanitizeError::InvalidValue);
        }
        self.from.sanitize()
    }
}

fn reject_nonzero_u8<'de, D>(de: D) -> Result<u8, D::Error>
where
    D: Deserializer<'de>,
    D::Error: serde::de::Error,
{
    let v = u8::deserialize(de)?;
    if v == 0 {
        Ok(v)
    } else {
        Err(serde::de::Error::custom(
            "LowestSlot tag != 0 is deprecated",
        ))
    }
}

#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Vote {
    pub(crate) from: Pubkey,
    transaction: Transaction,
    pub(crate) wallclock: u64,
    #[serde(skip_serializing)]
    slot: Option<Slot>,
}

impl Sanitize for Vote {
    fn sanitize(&self) -> Result<(), SanitizeError> {
        sanitize_wallclock(self.wallclock)?;
        self.from.sanitize()?;
        self.transaction.sanitize()
    }
}

impl Vote {
    // Returns None if cannot parse transaction into a vote.
    pub fn new(from: Pubkey, transaction: Transaction, wallclock: u64) -> Option<Self> {
        vote_parser::parse_vote_transaction(&transaction).map(|(_, vote, ..)| Self {
            from,
            transaction,
            wallclock,
            slot: vote.last_voted_slot(),
        })
    }

    /// New random Vote for tests and benchmarks.
    fn new_rand<R: Rng>(rng: &mut R, pubkey: Option<Pubkey>) -> Self {
        Self {
            from: pubkey.unwrap_or_else(solana_pubkey::new_rand),
            transaction: Transaction::default(),
            wallclock: new_rand_timestamp(rng),
            slot: None,
        }
    }

    pub(crate) fn transaction(&self) -> &Transaction {
        &self.transaction
    }

    pub(crate) fn slot(&self) -> Option<Slot> {
        self.slot
    }
}

impl<'de> Deserialize<'de> for Vote {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Vote {
            from: Pubkey,
            transaction: Transaction,
            wallclock: u64,
        }
        let vote = Vote::deserialize(deserializer)?;
        vote.transaction
            .sanitize()
            .map_err(serde::de::Error::custom)?;
        Self::new(vote.from, vote.transaction, vote.wallclock)
            .ok_or_else(|| serde::de::Error::custom("invalid vote tx"))
    }
}

#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
pub(crate) struct LegacyVersion {
    from: Pubkey,
    wallclock: u64,
    version: solana_version::LegacyVersion1,
}
reject_deserialize!(LegacyVersion, "LegacyVersion is deprecated");

impl Sanitize for LegacyVersion {
    fn sanitize(&self) -> Result<(), SanitizeError> {
        sanitize_wallclock(self.wallclock)?;
        self.from.sanitize()?;
        self.version.sanitize()
    }
}

#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
pub(crate) struct Version {
    from: Pubkey,
    wallclock: u64,
    version: solana_version::LegacyVersion2,
}
reject_deserialize!(Version, "Version is deprecated");

impl Sanitize for Version {
    fn sanitize(&self) -> Result<(), SanitizeError> {
        sanitize_wallclock(self.wallclock)?;
        self.from.sanitize()?;
        self.version.sanitize()
    }
}

#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub(crate) struct NodeInstance {
    from: Pubkey,
    wallclock: u64,
    timestamp: u64, // Timestamp when the instance was created.
    token: u64,     // Randomly generated value at node instantiation.
}
reject_deserialize!(NodeInstance, "NodeInstance is deprecated");

impl Sanitize for NodeInstance {
    fn sanitize(&self) -> Result<(), SanitizeError> {
        sanitize_wallclock(self.wallclock)?;
        self.from.sanitize()
    }
}

pub(crate) fn sanitize_wallclock(wallclock: u64) -> Result<(), SanitizeError> {
    if wallclock >= MAX_WALLCLOCK {
        Err(SanitizeError::ValueOutOfBounds)
    } else {
        Ok(())
    }
}

macro_rules! reject_deserialize {
    ($ty:ty, $msg:expr) => {
        impl<'de> serde::Deserialize<'de> for $ty {
            fn deserialize<D>(_de: D) -> Result<Self, D::Error>
            where
                D: serde::Deserializer<'de>,
                D::Error: serde::de::Error,
            {
                Err(serde::de::Error::custom($msg))
            }
        }
    };
}
pub(crate) use reject_deserialize;

#[cfg(test)]
mod test {
    use {
        super::*,
        crate::crds_value::CrdsValue,
        bincode::Options,
        solana_keypair::Keypair,
        solana_perf::test_tx::new_test_vote_tx,
        solana_signer::Signer,
        solana_time_utils::timestamp,
        solana_vote_program::{vote_instruction, vote_state},
    };

    #[test]
    fn test_lowest_slot_sanitize() {
        let ls = LowestSlot::new(Pubkey::default(), 0, 0);
        let v = CrdsValue::new_unsigned(CrdsData::LowestSlot(0, ls.clone()));
        assert_eq!(v.sanitize(), Ok(()));

        let mut o = ls.clone();
        o.root = 1;
        let v = CrdsValue::new_unsigned(CrdsData::LowestSlot(0, o));
        assert_eq!(v.sanitize(), Err(SanitizeError::InvalidValue));

        let o = ls.clone();
        let v = CrdsValue::new_unsigned(CrdsData::LowestSlot(1, o));
        assert_eq!(v.sanitize(), Err(SanitizeError::ValueOutOfBounds));

        let mut o = ls.clone();
        o.slots.insert(1);
        let v = CrdsValue::new_unsigned(CrdsData::LowestSlot(0, o));
        assert_eq!(v.sanitize(), Err(SanitizeError::InvalidValue));

        let mut o = ls;
        o.stash.push(deprecated::EpochIncompleteSlots::default());
        let v = CrdsValue::new_unsigned(CrdsData::LowestSlot(0, o));
        assert_eq!(v.sanitize(), Err(SanitizeError::InvalidValue));
    }

    #[test]
    fn test_max_vote_index() {
        let mut rng = rand::thread_rng();
        let keypair = Keypair::new();
        let vote = Vote::new(keypair.pubkey(), new_test_vote_tx(&mut rng), timestamp()).unwrap();
        let vote = CrdsValue::new(CrdsData::Vote(OLD_MAX_VOTES, vote), &keypair);
        assert!(vote.sanitize().is_err());
    }

    #[test]
    fn test_vote_round_trip() {
        let mut rng = rand::thread_rng();
        let vote = vote_state::Vote::new(
            vec![1, 3, 7], // slots
            Hash::new_unique(),
        );
        let ix = vote_instruction::vote(
            &Pubkey::new_unique(), // vote_pubkey
            &Pubkey::new_unique(), // authorized_voter_pubkey
            vote,
        );
        let tx = Transaction::new_with_payer(
            &[ix],                       // instructions
            Some(&Pubkey::new_unique()), // payer
        );
        let vote = Vote::new(
            Pubkey::new_unique(), // from
            tx,
            rng.gen(), // wallclock
        )
        .unwrap();
        assert_eq!(vote.slot, Some(7));
        let bytes = bincode::serialize(&vote).unwrap();
        let other = bincode::deserialize(&bytes[..]).unwrap();
        assert_eq!(vote, other);
        assert_eq!(other.slot, Some(7));
        let bytes = bincode::options().serialize(&vote).unwrap();
        let other = bincode::options().deserialize(&bytes[..]).unwrap();
        assert_eq!(vote, other);
        assert_eq!(other.slot, Some(7));
    }

    #[test]
    fn test_max_epoch_slots_index() {
        let keypair = Keypair::new();
        let item = CrdsValue::new(
            CrdsData::EpochSlots(
                MAX_EPOCH_SLOTS,
                EpochSlots::new(keypair.pubkey(), timestamp()),
            ),
            &keypair,
        );
        assert_eq!(item.sanitize(), Err(SanitizeError::ValueOutOfBounds));
    }

    #[test]
    fn test_deprecated_values_fail_deserialization() {
        let keypair = Keypair::new();

        // NodeInstance
        let node_instance = CrdsData::NodeInstance(NodeInstance {
            from: keypair.pubkey(),
            wallclock: timestamp(),
            timestamp: 0,
            token: 0,
        });
        let bytes = bincode::serialize(&node_instance).unwrap();
        assert!(bincode::deserialize::<CrdsData>(&bytes[..]).is_err());

        #[derive(serde::Serialize)]
        struct LegacyVersion1Mirror {
            major: u16,
            minor: u16,
            patch: u16,
            commit: Option<u32>,
        }

        let legacy_v1: solana_version::LegacyVersion1 = {
            let bytes = bincode::serialize(&LegacyVersion1Mirror {
                major: 0,
                minor: 0,
                patch: 0,
                commit: None,
            })
            .unwrap();
            bincode::deserialize(&bytes).unwrap()
        };

        // LegacyVersion
        let legacy_version = CrdsData::LegacyVersion(LegacyVersion {
            from: keypair.pubkey(),
            wallclock: timestamp(),
            version: legacy_v1,
        });
        let bytes = bincode::serialize(&legacy_version).unwrap();
        assert!(bincode::deserialize::<CrdsData>(&bytes[..]).is_err());

        // Version
        let version = CrdsData::Version(Version {
            from: keypair.pubkey(),
            wallclock: timestamp(),
            version: solana_version::LegacyVersion2::default(),
        });
        let bytes = bincode::serialize(&version).unwrap();
        assert!(bincode::deserialize::<CrdsData>(&bytes[..]).is_err());

        // LegacyContactInfo
        let legacy_contact_info = CrdsData::LegacyContactInfo(LegacyContactInfo::default());
        let bytes = bincode::serialize(&legacy_contact_info).unwrap();
        assert!(bincode::deserialize::<CrdsData>(&bytes[..]).is_err());

        // AccountsHashes
        let mut rng = rand::thread_rng();
        let accounts_hashes =
            CrdsData::AccountsHashes(AccountsHashes::new_rand(&mut rng, Some(keypair.pubkey())));
        let bytes = bincode::serialize(&accounts_hashes).unwrap();
        assert!(bincode::deserialize::<CrdsData>(&bytes[..]).is_err());

        // LegacySnapshotHashes
        let legacy_snapshot_hashes = CrdsData::LegacySnapshotHashes(
            LegacySnapshotHashes::new_rand(&mut rng, Some(keypair.pubkey())),
        );
        let bytes = bincode::serialize(&legacy_snapshot_hashes).unwrap();
        assert!(bincode::deserialize::<CrdsData>(&bytes[..]).is_err());

        // LowestSlot(1, ...)
        let lowest_slot =
            CrdsData::LowestSlot(1, LowestSlot::new(keypair.pubkey(), 0, timestamp()));
        let bytes = bincode::serialize(&lowest_slot).unwrap();
        assert!(bincode::deserialize::<CrdsData>(&bytes[..]).is_err());

        // LowestSlot(0, ...) -> should be deserialized successfully
        let lowest_slot =
            CrdsData::LowestSlot(0, LowestSlot::new(keypair.pubkey(), 0, timestamp()));
        let bytes = bincode::serialize(&lowest_slot).unwrap();
        assert!(bincode::deserialize::<CrdsData>(&bytes[..]).is_ok());
    }
}
