Bitwise compress incomplete epoch slots (#8341)
This commit is contained in:
parent
221866f74e
commit
ea8d9d1aea
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue