Bitwise compress incomplete epoch slots (#8341)

This commit is contained in:
Pankaj Garg 2020-02-19 20:24:09 -08:00 committed by GitHub
parent 221866f74e
commit ea8d9d1aea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 109 additions and 94 deletions

View File

@ -12,6 +12,7 @@
//! * layer 2 - Everyone else, if layer 1 is `2^10`, layer 2 should be able to fit `2^20` number of nodes. //! * layer 2 - Everyone else, if layer 1 is `2^10`, layer 2 should be able to fit `2^20` number of nodes.
//! //!
//! Bank needs to provide an interface for us to query the stake weight //! Bank needs to provide an interface for us to query the stake weight
use crate::crds_value::EpochIncompleteSlots;
use crate::packet::limited_deserialize; use crate::packet::limited_deserialize;
use crate::streamer::{PacketReceiver, PacketSender}; use crate::streamer::{PacketReceiver, PacketSender};
use crate::{ use crate::{
@ -77,6 +78,8 @@ const MAX_PROTOCOL_HEADER_SIZE: u64 = 214;
/// 128MB/PACKET_DATA_SIZE /// 128MB/PACKET_DATA_SIZE
const MAX_GOSSIP_TRAFFIC: usize = 128_000_000 / PACKET_DATA_SIZE; const MAX_GOSSIP_TRAFFIC: usize = 128_000_000 / PACKET_DATA_SIZE;
const NUM_BITS_PER_BYTE: u64 = 8;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum ClusterInfoError { pub enum ClusterInfoError {
NoPeers, NoPeers,
@ -316,7 +319,7 @@ impl ClusterInfo {
) )
} }
pub fn compress_incomplete_slots(incomplete_slots: &BTreeSet<Slot>) -> (Slot, Vec<u8>) { pub fn compress_incomplete_slots(incomplete_slots: &BTreeSet<Slot>) -> EpochIncompleteSlots {
if !incomplete_slots.is_empty() { if !incomplete_slots.is_empty() {
let first_slot = incomplete_slots let first_slot = incomplete_slots
.iter() .iter()
@ -326,9 +329,18 @@ impl ClusterInfo {
.iter() .iter()
.next_back() .next_back()
.expect("expected to find last slot"); .expect("expected to find last slot");
let mut uncompressed = vec![0u8; (last_slot.saturating_sub(*first_slot) + 1) as usize]; let num_uncompressed_bits = last_slot.saturating_sub(*first_slot) + 1;
let num_uncompressed_bytes = if num_uncompressed_bits % NUM_BITS_PER_BYTE > 0 {
1
} else {
0
} + num_uncompressed_bits / NUM_BITS_PER_BYTE;
let mut uncompressed = vec![0u8; num_uncompressed_bytes as usize];
incomplete_slots.iter().for_each(|slot| { incomplete_slots.iter().for_each(|slot| {
uncompressed[slot.saturating_sub(*first_slot) as usize] = 1; let offset_from_first_slot = slot.saturating_sub(*first_slot);
let index = offset_from_first_slot / NUM_BITS_PER_BYTE;
let bit_index = offset_from_first_slot % NUM_BITS_PER_BYTE;
uncompressed[index as usize] |= 1 << bit_index;
}); });
if let Ok(compressed) = uncompressed if let Ok(compressed) = uncompressed
.iter() .iter()
@ -336,27 +348,33 @@ impl ClusterInfo {
.encode(&mut GZipEncoder::new(), Action::Finish) .encode(&mut GZipEncoder::new(), Action::Finish)
.collect::<std::result::Result<Vec<u8>, _>>() .collect::<std::result::Result<Vec<u8>, _>>()
{ {
(*first_slot, compressed) return EpochIncompleteSlots {
} else { first: *first_slot,
(0, vec![]) compressed_list: compressed,
};
} }
} else {
(0, vec![])
} }
EpochIncompleteSlots::default()
} }
pub fn decompress_incomplete_slots(first_slot: u64, compressed: &[u8]) -> BTreeSet<Slot> { pub fn decompress_incomplete_slots(slots: &EpochIncompleteSlots) -> BTreeSet<Slot> {
let mut old_incomplete_slots: BTreeSet<Slot> = BTreeSet::new(); let mut old_incomplete_slots: BTreeSet<Slot> = BTreeSet::new();
if let Ok(decompressed) = compressed if let Ok(decompressed) = slots
.compressed_list
.iter() .iter()
.cloned() .cloned()
.decode(&mut GZipDecoder::new()) .decode(&mut GZipDecoder::new())
.collect::<std::result::Result<Vec<u8>, _>>() .collect::<std::result::Result<Vec<u8>, _>>()
{ {
decompressed.iter().enumerate().for_each(|(i, val)| { decompressed.iter().enumerate().for_each(|(i, val)| {
if *val == 1 { if *val != 0 {
old_incomplete_slots.insert(first_slot + i as u64); (0..8).for_each(|bit_index| {
if (1 << bit_index & *val) != 0 {
let slot = slots.first + i as u64 * NUM_BITS_PER_BYTE + bit_index;
old_incomplete_slots.insert(slot as u64);
}
})
} }
}) })
} }
@ -372,19 +390,13 @@ impl ClusterInfo {
slots: BTreeSet<Slot>, slots: BTreeSet<Slot>,
incomplete_slots: &BTreeSet<Slot>, incomplete_slots: &BTreeSet<Slot>,
) { ) {
let (first_missing_slot, compressed_map) = let compressed = Self::compress_incomplete_slots(incomplete_slots);
Self::compress_incomplete_slots(incomplete_slots);
let now = timestamp(); let now = timestamp();
let entry = CrdsValue::new_signed( let entry = CrdsValue::new_signed(
CrdsData::EpochSlots(EpochSlots::new( CrdsData::EpochSlots(
id, 0,
root, EpochSlots::new(id, root, min, slots, vec![compressed], now),
min, ),
slots,
first_missing_slot,
compressed_map,
now,
)),
&self.keypair, &self.keypair,
); );
self.gossip self.gossip
@ -2221,15 +2233,17 @@ mod tests {
for i in 0..128 { for i in 0..128 {
btree_slots.insert(i); btree_slots.insert(i);
} }
let value = CrdsValue::new_unsigned(CrdsData::EpochSlots(EpochSlots { let value = CrdsValue::new_unsigned(CrdsData::EpochSlots(
from: Pubkey::default(), 0,
root: 0, EpochSlots {
lowest: 0, from: Pubkey::default(),
slots: btree_slots, root: 0,
first_missing: 0, lowest: 0,
stash: vec![], slots: btree_slots,
wallclock: 0, stash: vec![],
})); wallclock: 0,
},
));
test_split_messages(value); test_split_messages(value);
} }
@ -2240,15 +2254,17 @@ mod tests {
let payload: Vec<CrdsValue> = vec![]; let payload: Vec<CrdsValue> = vec![];
let vec_size = serialized_size(&payload).unwrap(); let vec_size = serialized_size(&payload).unwrap();
let desired_size = MAX_PROTOCOL_PAYLOAD_SIZE - vec_size; let desired_size = MAX_PROTOCOL_PAYLOAD_SIZE - vec_size;
let mut value = CrdsValue::new_unsigned(CrdsData::EpochSlots(EpochSlots { let mut value = CrdsValue::new_unsigned(CrdsData::EpochSlots(
from: Pubkey::default(), 0,
root: 0, EpochSlots {
lowest: 0, from: Pubkey::default(),
slots: BTreeSet::new(), root: 0,
first_missing: 0, lowest: 0,
stash: vec![], slots: BTreeSet::new(),
wallclock: 0, stash: vec![],
})); wallclock: 0,
},
));
let mut i = 0; let mut i = 0;
while value.size() <= desired_size { while value.size() <= desired_size {
@ -2260,15 +2276,17 @@ mod tests {
desired_size desired_size
); );
} }
value.data = CrdsData::EpochSlots(EpochSlots { value.data = CrdsData::EpochSlots(
from: Pubkey::default(), 0,
root: 0, EpochSlots {
lowest: 0, from: Pubkey::default(),
slots, root: 0,
first_missing: 0, lowest: 0,
stash: vec![], slots,
wallclock: 0, stash: vec![],
}); wallclock: 0,
},
);
i += 1; i += 1;
} }
let split = ClusterInfo::split_gossip_messages(vec![value.clone()]); let split = ClusterInfo::split_gossip_messages(vec![value.clone()]);
@ -2408,15 +2426,17 @@ mod tests {
let other_node_pubkey = Pubkey::new_rand(); let other_node_pubkey = Pubkey::new_rand();
let other_node = ContactInfo::new_localhost(&other_node_pubkey, timestamp()); let other_node = ContactInfo::new_localhost(&other_node_pubkey, timestamp());
cluster_info.insert_info(other_node.clone()); cluster_info.insert_info(other_node.clone());
let value = CrdsValue::new_unsigned(CrdsData::EpochSlots(EpochSlots::new( let value = CrdsValue::new_unsigned(CrdsData::EpochSlots(
other_node_pubkey,
peer_root,
peer_lowest,
BTreeSet::new(),
0, 0,
vec![], EpochSlots::new(
timestamp(), other_node_pubkey,
))); peer_root,
peer_lowest,
BTreeSet::new(),
vec![],
timestamp(),
),
));
let _ = cluster_info.gossip.crds.insert(value, timestamp()); let _ = cluster_info.gossip.crds.insert(value, timestamp());
} }
// only half the visible peers should be eligible to serve this repair // only half the visible peers should be eligible to serve this repair
@ -2482,26 +2502,26 @@ mod tests {
let mut incomplete_slots: BTreeSet<Slot> = BTreeSet::new(); let mut incomplete_slots: BTreeSet<Slot> = BTreeSet::new();
assert_eq!( assert_eq!(
(0, vec![]), EpochIncompleteSlots::default(),
ClusterInfo::compress_incomplete_slots(&incomplete_slots) ClusterInfo::compress_incomplete_slots(&incomplete_slots)
); );
incomplete_slots.insert(100); incomplete_slots.insert(100);
let (first, compressed) = ClusterInfo::compress_incomplete_slots(&incomplete_slots); let compressed = ClusterInfo::compress_incomplete_slots(&incomplete_slots);
assert_eq!(100, first); assert_eq!(100, compressed.first);
let decompressed = ClusterInfo::decompress_incomplete_slots(first, &compressed); let decompressed = ClusterInfo::decompress_incomplete_slots(&compressed);
assert_eq!(incomplete_slots, decompressed); assert_eq!(incomplete_slots, decompressed);
incomplete_slots.insert(104); incomplete_slots.insert(104);
let (first, compressed) = ClusterInfo::compress_incomplete_slots(&incomplete_slots); let compressed = ClusterInfo::compress_incomplete_slots(&incomplete_slots);
assert_eq!(100, first); assert_eq!(100, compressed.first);
let decompressed = ClusterInfo::decompress_incomplete_slots(first, &compressed); let decompressed = ClusterInfo::decompress_incomplete_slots(&compressed);
assert_eq!(incomplete_slots, decompressed); assert_eq!(incomplete_slots, decompressed);
incomplete_slots.insert(80); incomplete_slots.insert(80);
let (first, compressed) = ClusterInfo::compress_incomplete_slots(&incomplete_slots); let compressed = ClusterInfo::compress_incomplete_slots(&incomplete_slots);
assert_eq!(80, first); assert_eq!(80, compressed.first);
let decompressed = ClusterInfo::decompress_incomplete_slots(first, &compressed); let decompressed = ClusterInfo::decompress_incomplete_slots(&compressed);
assert_eq!(incomplete_slots, decompressed); assert_eq!(incomplete_slots, decompressed);
} }
} }

View File

@ -15,6 +15,8 @@ use std::{
pub type VoteIndex = u8; pub type VoteIndex = u8;
pub const MAX_VOTES: VoteIndex = 32; pub const MAX_VOTES: VoteIndex = 32;
pub type EpochSlotIndex = u8;
/// CrdsValue that is replicated across the cluster /// CrdsValue that is replicated across the cluster
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CrdsValue { pub struct CrdsValue {
@ -58,7 +60,13 @@ impl Signable for CrdsValue {
pub enum CrdsData { pub enum CrdsData {
ContactInfo(ContactInfo), ContactInfo(ContactInfo),
Vote(VoteIndex, Vote), Vote(VoteIndex, Vote),
EpochSlots(EpochSlots), EpochSlots(EpochSlotIndex, EpochSlots),
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct EpochIncompleteSlots {
pub first: Slot,
pub compressed_list: Vec<u8>,
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
@ -67,8 +75,7 @@ pub struct EpochSlots {
pub root: Slot, pub root: Slot,
pub lowest: Slot, pub lowest: Slot,
pub slots: BTreeSet<Slot>, pub slots: BTreeSet<Slot>,
pub first_missing: Slot, pub stash: Vec<EpochIncompleteSlots>,
pub stash: Vec<u8>,
pub wallclock: u64, pub wallclock: u64,
} }
@ -78,8 +85,7 @@ impl EpochSlots {
root: Slot, root: Slot,
lowest: Slot, lowest: Slot,
slots: BTreeSet<Slot>, slots: BTreeSet<Slot>,
first_missing: Slot, stash: Vec<EpochIncompleteSlots>,
stash: Vec<u8>,
wallclock: u64, wallclock: u64,
) -> Self { ) -> Self {
Self { Self {
@ -87,7 +93,6 @@ impl EpochSlots {
root, root,
lowest, lowest,
slots, slots,
first_missing,
stash, stash,
wallclock, wallclock,
} }
@ -160,21 +165,21 @@ impl CrdsValue {
match &self.data { match &self.data {
CrdsData::ContactInfo(contact_info) => contact_info.wallclock, CrdsData::ContactInfo(contact_info) => contact_info.wallclock,
CrdsData::Vote(_, vote) => vote.wallclock, CrdsData::Vote(_, vote) => vote.wallclock,
CrdsData::EpochSlots(vote) => vote.wallclock, CrdsData::EpochSlots(_, vote) => vote.wallclock,
} }
} }
pub fn pubkey(&self) -> Pubkey { pub fn pubkey(&self) -> Pubkey {
match &self.data { match &self.data {
CrdsData::ContactInfo(contact_info) => contact_info.id, CrdsData::ContactInfo(contact_info) => contact_info.id,
CrdsData::Vote(_, vote) => vote.from, CrdsData::Vote(_, vote) => vote.from,
CrdsData::EpochSlots(slots) => slots.from, CrdsData::EpochSlots(_, slots) => slots.from,
} }
} }
pub fn label(&self) -> CrdsValueLabel { pub fn label(&self) -> CrdsValueLabel {
match &self.data { match &self.data {
CrdsData::ContactInfo(_) => CrdsValueLabel::ContactInfo(self.pubkey()), CrdsData::ContactInfo(_) => CrdsValueLabel::ContactInfo(self.pubkey()),
CrdsData::Vote(ix, _) => CrdsValueLabel::Vote(*ix, self.pubkey()), CrdsData::Vote(ix, _) => CrdsValueLabel::Vote(*ix, self.pubkey()),
CrdsData::EpochSlots(_) => CrdsValueLabel::EpochSlots(self.pubkey()), CrdsData::EpochSlots(_, _) => CrdsValueLabel::EpochSlots(self.pubkey()),
} }
} }
pub fn contact_info(&self) -> Option<&ContactInfo> { pub fn contact_info(&self) -> Option<&ContactInfo> {
@ -199,7 +204,7 @@ impl CrdsValue {
pub fn epoch_slots(&self) -> Option<&EpochSlots> { pub fn epoch_slots(&self) -> Option<&EpochSlots> {
match &self.data { match &self.data {
CrdsData::EpochSlots(slots) => Some(slots), CrdsData::EpochSlots(_, slots) => Some(slots),
_ => None, _ => None,
} }
} }
@ -283,15 +288,10 @@ mod test {
let key = v.clone().vote().unwrap().from; let key = v.clone().vote().unwrap().from;
assert_eq!(v.label(), CrdsValueLabel::Vote(0, key)); assert_eq!(v.label(), CrdsValueLabel::Vote(0, key));
let v = CrdsValue::new_unsigned(CrdsData::EpochSlots(EpochSlots::new( let v = CrdsValue::new_unsigned(CrdsData::EpochSlots(
Pubkey::default(),
0, 0,
0, EpochSlots::new(Pubkey::default(), 0, 0, BTreeSet::new(), vec![], 0),
BTreeSet::new(), ));
0,
vec![],
0,
)));
assert_eq!(v.wallclock(), 0); assert_eq!(v.wallclock(), 0);
let key = v.clone().epoch_slots().unwrap().from; let key = v.clone().epoch_slots().unwrap().from;
assert_eq!(v.label(), CrdsValueLabel::EpochSlots(key)); assert_eq!(v.label(), CrdsValueLabel::EpochSlots(key));
@ -312,15 +312,10 @@ mod test {
)); ));
verify_signatures(&mut v, &keypair, &wrong_keypair); verify_signatures(&mut v, &keypair, &wrong_keypair);
let btreeset: BTreeSet<Slot> = vec![1, 2, 3, 6, 8].into_iter().collect(); let btreeset: BTreeSet<Slot> = vec![1, 2, 3, 6, 8].into_iter().collect();
v = CrdsValue::new_unsigned(CrdsData::EpochSlots(EpochSlots::new( v = CrdsValue::new_unsigned(CrdsData::EpochSlots(
keypair.pubkey(),
0, 0,
0, EpochSlots::new(keypair.pubkey(), 0, 0, btreeset, vec![], timestamp()),
btreeset, ));
0,
vec![],
timestamp(),
)));
verify_signatures(&mut v, &keypair, &wrong_keypair); verify_signatures(&mut v, &keypair, &wrong_keypair);
} }