use super::{error::*, wire::*, *};
pub const DEFAULT_BS_BLOCKS_PER_RESPONSE: u32 = 1;
pub const DEFAULT_BS_MAX_INFLIGHT: u32 = 32000;
pub const DEFAULT_BS_INITIAL_INFLIGHT: u32 = 64;
pub const MAX_BS_INFLIGHT_REQUESTS: u32 = 32_768;
pub const DEFAULT_BS_MAX_RESPONSE_BYTES: u32 = 32 * 1024 * 1024;
pub const DEFAULT_BS_MAX_INFLIGHT_BLOCK_BYTES: u64 = 6 * 1024 * 1024 * 1024;
pub const BS_PER_BLOCK_WORST_CASE_BYTES: u64 = block::MAX_BLOCK_BYTES;
pub const DEFAULT_BS_MAX_REORDER_LOOKAHEAD_BYTES: u64 =
DEFAULT_BS_MAX_INFLIGHT_BLOCK_BYTES - DEFAULT_BS_MAX_RESPONSE_BYTES as u64;
pub const MIN_BS_CHECKPOINT_SUBMITTED_BLOCK_APPLIES: usize =
zakura_chain::parameters::checkpoint::constants::MAX_CHECKPOINT_HEIGHT_GAP + 1;
pub const BS_CHECKPOINT_RANGE_BYTE_FLOOR: u64 =
MIN_BS_CHECKPOINT_SUBMITTED_BLOCK_APPLIES as u64 * BS_PER_BLOCK_WORST_CASE_BYTES;
pub const DEFAULT_BS_REQUEST_TIMEOUT: Duration = Duration::from_secs(8);
pub const DEFAULT_BS_FLOOR_RESCUE_TIMEOUT: Duration = Duration::from_secs(2);
const BLOCK_PROGRESS_TIMEOUT_REQUESTS: u32 = 4;
pub const DEFAULT_BS_INITIAL_BLOCK_PROBE_REQUESTS: u32 = 1;
pub const DEFAULT_BS_MAX_REQUESTS_WITHOUT_BLOCK_PROGRESS: u32 = 64;
pub const DEFAULT_BS_NO_PROGRESS_PEER_COOLDOWN: Duration = Duration::from_secs(180);
pub const DEFAULT_BS_FLOOR_PEER_AVOID_COOLDOWN: Duration = DEFAULT_BS_REQUEST_TIMEOUT;
pub const DEFAULT_BS_STATUS_REFRESH_INTERVAL: Duration = Duration::from_secs(30);
pub const DEFAULT_BS_SIZE_DEVIATION_TOLERANCE: u32 = 200;
pub const MAX_BS_RESPONSE_BYTES: u32 = DEFAULT_BS_MAX_RESPONSE_BYTES;
pub const DEFAULT_BS_BBR_CWND_GAIN_PERCENT: u32 = 300;
pub const DEFAULT_BS_BBR_PROBE_BW_GAIN_PERCENT: u32 = 125;
pub const DEFAULT_BS_BBR_PROBE_RTT_INTERVAL: Duration = Duration::from_secs(10);
pub const DEFAULT_BS_BBR_PROBE_RTT_DURATION: Duration = Duration::from_millis(200);
pub const DEFAULT_BS_BBR_RTPROP_WINDOW: Duration = Duration::from_secs(10);
pub const DEFAULT_BS_BBR_DELIVERY_RATE_WINDOW: Duration = Duration::from_secs(10);
pub const DEFAULT_BS_BBR_STARTUP_GROWTH_PERCENT: u32 = 200;
pub const DEFAULT_BS_BBR_MIN_CWND: u32 = 4;
pub const DEFAULT_BS_BBR_MIN_CWND_BYTES: u64 = block::MAX_BLOCK_BYTES + 512 * 1024;
pub const DEFAULT_BS_BBR_DELAY_GRADIENT_PERCENT: u32 = 150;
pub const DEFAULT_BS_BBR_RELIABILITY_WEIGHT_PERCENT: u32 = 100;
pub const DEFAULT_BS_FLOOR_BYPASS_SLOTS: u32 = 2;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CwndUnit {
Blocks,
#[default]
Bytes,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct BlockSyncStatus {
pub servable_low: block::Height,
pub servable_high: block::Height,
pub tip_hash: block::Hash,
pub max_blocks_per_response: u32,
pub max_inflight_requests: u32,
pub max_response_bytes: u32,
}
impl BlockSyncStatus {
pub(super) fn encode_to<W: Write>(&self, writer: &mut W) -> Result<(), BlockSyncWireError> {
write_height(writer, self.servable_low)?;
write_height(writer, self.servable_high)?;
self.tip_hash.zcash_serialize(&mut *writer)?;
writer.write_u32::<LittleEndian>(clamp_advertised_blocks(self.max_blocks_per_response))?;
writer.write_u32::<LittleEndian>(self.max_inflight_requests)?;
writer.write_u32::<LittleEndian>(self.max_response_bytes.max(1))?;
Ok(())
}
pub(super) fn decode_from<R: Read>(reader: &mut R) -> Result<Self, BlockSyncWireError> {
Ok(Self {
servable_low: read_height(reader)?,
servable_high: read_height(reader)?,
tip_hash: block::Hash::zcash_deserialize(&mut *reader)?,
max_blocks_per_response: clamp_advertised_blocks(reader.read_u32::<LittleEndian>()?),
max_inflight_requests: clamp_advertised_inflight(reader.read_u32::<LittleEndian>()?),
max_response_bytes: clamp_advertised_response_bytes(reader.read_u32::<LittleEndian>()?),
})
}
}
impl Default for BlockSyncStatus {
fn default() -> Self {
Self {
servable_low: block::Height::MIN,
servable_high: block::Height::MIN,
tip_hash: block::Hash([0; 32]),
max_blocks_per_response: DEFAULT_BS_BLOCKS_PER_RESPONSE,
max_inflight_requests: DEFAULT_BS_MAX_INFLIGHT,
max_response_bytes: DEFAULT_BS_MAX_RESPONSE_BYTES,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct ZakuraBlockSyncConfig {
#[doc(hidden)]
#[serde(
default,
skip_serializing,
deserialize_with = "deserialize_ignored_replace_legacy_syncer"
)]
pub replace_legacy_syncer: bool,
pub max_blocks_per_response: u32,
pub max_inflight_requests: u32,
pub initial_inflight_requests: u32,
pub max_response_bytes: u32,
pub max_inflight_block_bytes: u64,
pub max_reorder_lookahead_bytes: u64,
#[serde(with = "humantime_serde")]
pub floor_peer_avoid_cooldown: Duration,
pub max_submitted_block_applies: usize,
#[serde(with = "humantime_serde")]
pub request_timeout: Duration,
#[serde(with = "humantime_serde")]
pub floor_rescue_timeout: Duration,
#[serde(with = "humantime_serde")]
pub no_progress_peer_cooldown: Duration,
pub initial_block_probe_requests: u32,
pub max_requests_without_block_progress: u32,
#[serde(with = "humantime_serde")]
pub status_refresh_interval: Duration,
pub size_deviation_tolerance: u32,
pub bbr_cwnd_gain_percent: u32,
pub bbr_probe_bw_gain_percent: u32,
#[serde(with = "humantime_serde")]
pub bbr_probe_rtt_interval: Duration,
#[serde(with = "humantime_serde")]
pub bbr_probe_rtt_duration: Duration,
#[serde(with = "humantime_serde")]
pub bbr_rtprop_window: Duration,
#[serde(with = "humantime_serde")]
pub bbr_delivery_rate_window: Duration,
pub bbr_startup_growth_percent: u32,
pub bbr_min_cwnd: u32,
pub bbr_min_cwnd_bytes: u64,
pub bbr_delay_gradient_percent: u32,
pub bbr_reliability_weight_percent: u32,
pub bbr_cwnd_unit: CwndUnit,
pub floor_bypass_slots: u32,
pub peer_limits: ServicePeerLimits,
}
fn deserialize_ignored_replace_legacy_syncer<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
let _ = bool::deserialize(deserializer)?;
Ok(false)
}
impl Default for ZakuraBlockSyncConfig {
fn default() -> Self {
Self {
replace_legacy_syncer: false,
max_blocks_per_response: DEFAULT_BS_BLOCKS_PER_RESPONSE,
max_inflight_requests: DEFAULT_BS_MAX_INFLIGHT,
initial_inflight_requests: DEFAULT_BS_INITIAL_INFLIGHT,
max_response_bytes: DEFAULT_BS_MAX_RESPONSE_BYTES,
max_inflight_block_bytes: DEFAULT_BS_MAX_INFLIGHT_BLOCK_BYTES,
max_reorder_lookahead_bytes: DEFAULT_BS_MAX_REORDER_LOOKAHEAD_BYTES,
floor_peer_avoid_cooldown: DEFAULT_BS_FLOOR_PEER_AVOID_COOLDOWN,
max_submitted_block_applies: MIN_BS_CHECKPOINT_SUBMITTED_BLOCK_APPLIES,
request_timeout: DEFAULT_BS_REQUEST_TIMEOUT,
floor_rescue_timeout: DEFAULT_BS_FLOOR_RESCUE_TIMEOUT,
no_progress_peer_cooldown: DEFAULT_BS_NO_PROGRESS_PEER_COOLDOWN,
initial_block_probe_requests: DEFAULT_BS_INITIAL_BLOCK_PROBE_REQUESTS,
max_requests_without_block_progress: DEFAULT_BS_MAX_REQUESTS_WITHOUT_BLOCK_PROGRESS,
status_refresh_interval: DEFAULT_BS_STATUS_REFRESH_INTERVAL,
size_deviation_tolerance: DEFAULT_BS_SIZE_DEVIATION_TOLERANCE,
bbr_cwnd_gain_percent: DEFAULT_BS_BBR_CWND_GAIN_PERCENT,
bbr_probe_bw_gain_percent: DEFAULT_BS_BBR_PROBE_BW_GAIN_PERCENT,
bbr_probe_rtt_interval: DEFAULT_BS_BBR_PROBE_RTT_INTERVAL,
bbr_probe_rtt_duration: DEFAULT_BS_BBR_PROBE_RTT_DURATION,
bbr_rtprop_window: DEFAULT_BS_BBR_RTPROP_WINDOW,
bbr_delivery_rate_window: DEFAULT_BS_BBR_DELIVERY_RATE_WINDOW,
bbr_startup_growth_percent: DEFAULT_BS_BBR_STARTUP_GROWTH_PERCENT,
bbr_min_cwnd: DEFAULT_BS_BBR_MIN_CWND,
bbr_min_cwnd_bytes: DEFAULT_BS_BBR_MIN_CWND_BYTES,
bbr_delay_gradient_percent: DEFAULT_BS_BBR_DELAY_GRADIENT_PERCENT,
bbr_reliability_weight_percent: DEFAULT_BS_BBR_RELIABILITY_WEIGHT_PERCENT,
bbr_cwnd_unit: CwndUnit::Bytes,
floor_bypass_slots: DEFAULT_BS_FLOOR_BYPASS_SLOTS,
peer_limits: ServicePeerLimits::default(),
}
}
}
impl ZakuraBlockSyncConfig {
pub fn advertised_max_blocks_per_response(&self) -> u32 {
clamp_advertised_blocks(self.max_blocks_per_response)
}
pub fn advertised_max_inflight_requests(&self) -> u32 {
clamp_advertised_inflight(self.max_inflight_requests)
}
pub fn advertised_max_response_bytes(&self) -> u32 {
clamp_advertised_response_bytes(self.max_response_bytes)
}
pub fn submitted_apply_limit(&self) -> usize {
self.max_submitted_block_applies
.max(MIN_BS_CHECKPOINT_SUBMITTED_BLOCK_APPLIES)
}
pub fn effective_max_reorder_lookahead_bytes(&self) -> u64 {
self.max_reorder_lookahead_bytes.min(
self.max_inflight_block_bytes
.saturating_mul(super::admission::DESERIALIZED_MEM_FACTOR),
)
}
pub fn effective_floor_peer_avoid_cooldown(&self) -> Duration {
self.floor_peer_avoid_cooldown.max(Duration::from_millis(1))
}
pub(super) fn effective_liveness_timeout(&self) -> Duration {
self.request_timeout
.saturating_mul(BLOCK_PROGRESS_TIMEOUT_REQUESTS)
}
pub(super) fn effective_no_progress_peer_cooldown(&self) -> Duration {
self.no_progress_peer_cooldown.max(Duration::from_millis(1))
}
pub(super) fn effective_floor_rescue_timeout(&self) -> Duration {
self.floor_rescue_timeout
.clamp(Duration::from_millis(1), self.request_timeout)
}
pub fn floor_request_byte_reservation(&self) -> u64 {
let worst_case_blocks = u64::from(self.advertised_max_blocks_per_response())
.saturating_mul(BS_PER_BLOCK_WORST_CASE_BYTES);
u64::from(self.advertised_max_response_bytes()).max(worst_case_blocks)
}
pub fn validate(&self) -> Result<(), &'static str> {
if self.max_inflight_block_bytes == 0 {
return Err("max_inflight_block_bytes must be greater than zero");
}
if self.max_reorder_lookahead_bytes == 0 {
return Err("max_reorder_lookahead_bytes must be greater than zero");
}
if self.max_inflight_block_bytes <= self.floor_request_byte_reservation() {
return Err("max_inflight_block_bytes must exceed one floor request");
}
if self.request_timeout < Duration::from_millis(1) {
return Err("request_timeout must be at least 1ms");
}
if self.bbr_min_cwnd == 0 {
return Err("bbr_min_cwnd must be greater than zero");
}
if self.bbr_min_cwnd_bytes == 0 {
return Err("bbr_min_cwnd_bytes must be greater than zero");
}
if self.max_requests_without_block_progress == 0 {
return Err("max_requests_without_block_progress must be greater than zero");
}
if self.initial_block_probe_requests == 0 {
return Err("initial_block_probe_requests must be greater than zero");
}
if self.bbr_cwnd_gain_percent < 100
|| self.bbr_probe_bw_gain_percent < 100
|| self.bbr_startup_growth_percent < 100
|| self.bbr_delay_gradient_percent < 100
{
return Err("bbr gain/threshold percentages must be at least 100");
}
if self.bbr_reliability_weight_percent > 100 {
return Err("bbr_reliability_weight_percent must be at most 100");
}
if self.bbr_probe_rtt_interval <= self.bbr_probe_rtt_duration {
return Err("bbr_probe_rtt_interval must exceed bbr_probe_rtt_duration");
}
Ok(())
}
pub fn clamp_inflight_block_bytes_to_floor(&mut self) {
if self.max_inflight_block_bytes > 0
&& self.max_inflight_block_bytes < BS_CHECKPOINT_RANGE_BYTE_FLOOR
{
tracing::warn!(
configured_max_inflight_block_bytes = self.max_inflight_block_bytes,
checkpoint_range_byte_floor = BS_CHECKPOINT_RANGE_BYTE_FLOOR,
"zakura.block_sync.max_inflight_block_bytes is below the checkpoint-range \
floor; clamping it up so checkpoint sync cannot deadlock",
);
self.max_inflight_block_bytes = BS_CHECKPOINT_RANGE_BYTE_FLOOR;
}
}
pub fn clamp_reorder_lookahead_to_floor(&mut self) {
let resident_range_floor = BS_CHECKPOINT_RANGE_BYTE_FLOOR
.saturating_mul(super::admission::DESERIALIZED_MEM_FACTOR);
if self.max_reorder_lookahead_bytes > 0
&& self.max_reorder_lookahead_bytes < resident_range_floor
{
tracing::warn!(
configured_max_reorder_lookahead_bytes = self.max_reorder_lookahead_bytes,
resident_checkpoint_range_floor = resident_range_floor,
"zakura.block_sync.max_reorder_lookahead_bytes is below the resident \
checkpoint-range floor; clamping it up so checkpoint sync cannot deadlock",
);
self.max_reorder_lookahead_bytes = resident_range_floor;
}
}
pub fn initial_status(&self) -> BlockSyncStatus {
BlockSyncStatus {
max_blocks_per_response: self.advertised_max_blocks_per_response(),
max_inflight_requests: self.advertised_max_inflight_requests(),
max_response_bytes: self.advertised_max_response_bytes(),
..BlockSyncStatus::default()
}
}
}
pub fn clamp_advertised_blocks(count: u32) -> u32 {
count.clamp(1, MAX_BS_BLOCKS_PER_REQUEST)
}
pub fn clamp_advertised_inflight(count: u32) -> u32 {
count.clamp(1, MAX_BS_INFLIGHT_REQUESTS)
}
pub fn clamp_advertised_response_bytes(bytes: u32) -> u32 {
bytes.clamp(1, MAX_BS_RESPONSE_BYTES)
}
pub fn inbound_get_blocks_count_limit(config: &ZakuraBlockSyncConfig) -> u32 {
config
.advertised_max_blocks_per_response()
.clamp(1, MAX_BS_BLOCKS_PER_REQUEST)
}