Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit b99ae8f

Browse files
authored
Skip leader slots until a vote lands (#15607)
1 parent b041b55 commit b99ae8f

File tree

9 files changed

+117
-25
lines changed

9 files changed

+117
-25
lines changed

core/src/consensus.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,8 @@ pub mod test {
14551455
None,
14561456
&mut self.heaviest_subtree_fork_choice,
14571457
&mut BTreeMap::new(),
1458+
&mut true,
1459+
&mut Vec::new(),
14581460
)
14591461
}
14601462

core/src/replay_stage.rs

+48
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use solana_sdk::{
4141
genesis_config::ClusterType,
4242
hash::Hash,
4343
pubkey::Pubkey,
44+
signature::Signature,
4445
signature::{Keypair, Signer},
4546
timing::timestamp,
4647
transaction::Transaction,
@@ -63,6 +64,7 @@ pub const SUPERMINORITY_THRESHOLD: f64 = 1f64 / 3f64;
6364
pub const MAX_UNCONFIRMED_SLOTS: usize = 5;
6465
pub const DUPLICATE_LIVENESS_THRESHOLD: f64 = 0.1;
6566
pub const DUPLICATE_THRESHOLD: f64 = 1.0 - SWITCH_FORK_THRESHOLD - DUPLICATE_LIVENESS_THRESHOLD;
67+
const MAX_VOTE_SIGNATURES: usize = 200;
6668

6769
#[derive(PartialEq, Debug)]
6870
pub(crate) enum HeaviestForkFailures {
@@ -111,6 +113,7 @@ pub struct ReplayStageConfig {
111113
pub rewards_recorder_sender: Option<RewardsRecorderSender>,
112114
pub cache_block_time_sender: Option<CacheBlockTimeSender>,
113115
pub bank_notification_sender: Option<BankNotificationSender>,
116+
pub wait_for_vote_to_start_leader: bool,
114117
}
115118

116119
#[derive(Default)]
@@ -282,6 +285,7 @@ impl ReplayStage {
282285
rewards_recorder_sender,
283286
cache_block_time_sender,
284287
bank_notification_sender,
288+
wait_for_vote_to_start_leader,
285289
} = config;
286290

287291
trace!("replay stage");
@@ -312,6 +316,8 @@ impl ReplayStage {
312316
let mut skipped_slots_info = SkippedSlotsInfo::default();
313317
let mut replay_timing = ReplayTiming::default();
314318
let mut gossip_duplicate_confirmed_slots: GossipDuplicateConfirmedSlots = BTreeMap::new();
319+
let mut voted_signatures = Vec::new();
320+
let mut has_new_vote_been_rooted = !wait_for_vote_to_start_leader;
315321
loop {
316322
let allocated = thread_mem_usage::Allocatedp::default();
317323

@@ -523,6 +529,8 @@ impl ReplayStage {
523529
&cache_block_time_sender,
524530
&bank_notification_sender,
525531
&mut gossip_duplicate_confirmed_slots,
532+
&mut voted_signatures,
533+
&mut has_new_vote_been_rooted,
526534
);
527535
};
528536
voting_time.stop();
@@ -614,6 +622,7 @@ impl ReplayStage {
614622
&progress,
615623
&retransmit_slots_sender,
616624
&mut skipped_slots_info,
625+
has_new_vote_been_rooted,
617626
);
618627

619628
let poh_bank = poh_recorder.lock().unwrap().bank();
@@ -1020,7 +1029,12 @@ impl ReplayStage {
10201029
progress_map: &ProgressMap,
10211030
retransmit_slots_sender: &RetransmitSlotsSender,
10221031
skipped_slots_info: &mut SkippedSlotsInfo,
1032+
has_new_vote_been_rooted: bool,
10231033
) {
1034+
if !has_new_vote_been_rooted {
1035+
info!("Haven't landed a vote, so skipping my leader slot");
1036+
return;
1037+
}
10241038
// all the individual calls to poh_recorder.lock() are designed to
10251039
// increase granularity, decrease contention
10261040

@@ -1238,6 +1252,8 @@ impl ReplayStage {
12381252
cache_block_time_sender: &Option<CacheBlockTimeSender>,
12391253
bank_notification_sender: &Option<BankNotificationSender>,
12401254
gossip_duplicate_confirmed_slots: &mut GossipDuplicateConfirmedSlots,
1255+
vote_signatures: &mut Vec<Signature>,
1256+
has_new_vote_been_rooted: &mut bool,
12411257
) {
12421258
if bank.is_empty() {
12431259
inc_new_counter_info!("replay_stage-voted_empty_bank", 1);
@@ -1290,6 +1306,8 @@ impl ReplayStage {
12901306
highest_confirmed_root,
12911307
heaviest_subtree_fork_choice,
12921308
gossip_duplicate_confirmed_slots,
1309+
has_new_vote_been_rooted,
1310+
vote_signatures,
12931311
);
12941312
subscriptions.notify_roots(rooted_slots);
12951313
if let Some(sender) = bank_notification_sender {
@@ -1319,6 +1337,8 @@ impl ReplayStage {
13191337
last_vote,
13201338
&tower_slots,
13211339
switch_fork_decision,
1340+
vote_signatures,
1341+
*has_new_vote_been_rooted,
13221342
);
13231343
}
13241344

@@ -1330,6 +1350,8 @@ impl ReplayStage {
13301350
vote: Vote,
13311351
tower: &[Slot],
13321352
switch_fork_decision: &SwitchForkDecision,
1353+
vote_signatures: &mut Vec<Signature>,
1354+
has_new_vote_been_rooted: bool,
13331355
) {
13341356
if authorized_voter_keypairs.is_empty() {
13351357
return;
@@ -1399,6 +1421,14 @@ impl ReplayStage {
13991421

14001422
let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
14011423

1424+
if !has_new_vote_been_rooted {
1425+
vote_signatures.push(vote_tx.signatures[0]);
1426+
if vote_signatures.len() > MAX_VOTE_SIGNATURES {
1427+
vote_signatures.remove(0);
1428+
}
1429+
} else {
1430+
vote_signatures.clear();
1431+
}
14021432
let blockhash = bank.last_blockhash();
14031433
vote_tx.partial_sign(&[node_keypair.as_ref()], blockhash);
14041434
vote_tx.partial_sign(&[authorized_voter_keypair.as_ref()], blockhash);
@@ -2125,13 +2155,27 @@ impl ReplayStage {
21252155
highest_confirmed_root: Option<Slot>,
21262156
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
21272157
gossip_duplicate_confirmed_slots: &mut GossipDuplicateConfirmedSlots,
2158+
has_new_vote_been_rooted: &mut bool,
2159+
voted_signatures: &mut Vec<Signature>,
21282160
) {
21292161
bank_forks.write().unwrap().set_root(
21302162
new_root,
21312163
accounts_background_request_sender,
21322164
highest_confirmed_root,
21332165
);
21342166
let r_bank_forks = bank_forks.read().unwrap();
2167+
let new_root_bank = &r_bank_forks[new_root];
2168+
if !*has_new_vote_been_rooted {
2169+
for signature in voted_signatures.iter() {
2170+
if new_root_bank.get_signature_status(signature).is_some() {
2171+
*has_new_vote_been_rooted = true;
2172+
break;
2173+
}
2174+
}
2175+
if *has_new_vote_been_rooted {
2176+
std::mem::take(voted_signatures);
2177+
}
2178+
}
21352179
progress.handle_new_root(&r_bank_forks);
21362180
heaviest_subtree_fork_choice.set_root(new_root);
21372181
let mut slots_ge_root = gossip_duplicate_confirmed_slots.split_off(&new_root);
@@ -2553,6 +2597,8 @@ pub(crate) mod tests {
25532597
None,
25542598
&mut heaviest_subtree_fork_choice,
25552599
&mut gossip_duplicate_confirmed_slots,
2600+
&mut true,
2601+
&mut Vec::new(),
25562602
);
25572603
assert_eq!(bank_forks.read().unwrap().root(), root);
25582604
assert_eq!(progress.len(), 1);
@@ -2609,6 +2655,8 @@ pub(crate) mod tests {
26092655
Some(confirmed_root),
26102656
&mut heaviest_subtree_fork_choice,
26112657
&mut BTreeMap::new(),
2658+
&mut true,
2659+
&mut Vec::new(),
26122660
);
26132661
assert_eq!(bank_forks.read().unwrap().root(), root);
26142662
assert!(bank_forks.read().unwrap().get(confirmed_root).is_some());

core/src/test_validator.rs

+1
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ impl TestValidator {
422422
warp_slot: config.warp_slot,
423423
bpf_jit: !config.no_bpf_jit,
424424
validator_exit: config.validator_exit.clone(),
425+
no_wait_for_vote_to_start_leader: true,
425426
..ValidatorConfig::default()
426427
};
427428

core/src/tvu.rs

+2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub struct TvuConfig {
8686
pub use_index_hash_calculation: bool,
8787
pub rocksdb_compaction_interval: Option<u64>,
8888
pub rocksdb_max_compaction_jitter: Option<u64>,
89+
pub wait_for_vote_to_start_leader: bool,
8990
}
9091

9192
impl Tvu {
@@ -259,6 +260,7 @@ impl Tvu {
259260
rewards_recorder_sender,
260261
cache_block_time_sender,
261262
bank_notification_sender,
263+
wait_for_vote_to_start_leader: tvu_config.wait_for_vote_to_start_leader,
262264
};
263265

264266
let replay_stage = ReplayStage::new(

core/src/validator.rs

+52-25
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ pub struct ValidatorConfig {
130130
pub accounts_db_use_index_hash_calculation: bool,
131131
pub tpu_coalesce_ms: u64,
132132
pub validator_exit: Arc<RwLock<ValidatorExit>>,
133+
pub no_wait_for_vote_to_start_leader: bool,
133134
}
134135

135136
impl Default for ValidatorConfig {
@@ -184,6 +185,7 @@ impl Default for ValidatorConfig {
184185
accounts_db_use_index_hash_calculation: true,
185186
tpu_coalesce_ms: DEFAULT_TPU_COALESCE_MS,
186187
validator_exit: Arc::new(RwLock::new(ValidatorExit::default())),
188+
no_wait_for_vote_to_start_leader: true,
187189
}
188190
}
189191
}
@@ -629,15 +631,20 @@ impl Validator {
629631
check_poh_speed(&genesis_config, None);
630632
}
631633

632-
if wait_for_supermajority(
634+
let waited_for_supermajority = if let Ok(waited) = wait_for_supermajority(
633635
config,
634636
&bank,
635637
&cluster_info,
636638
rpc_override_health_check,
637639
&start_progress,
638640
) {
641+
waited
642+
} else {
639643
abort();
640-
}
644+
};
645+
646+
let wait_for_vote_to_start_leader =
647+
!waited_for_supermajority && !config.no_wait_for_vote_to_start_leader;
641648

642649
let poh_service = PohService::new(
643650
poh_recorder.clone(),
@@ -725,6 +732,7 @@ impl Validator {
725732
use_index_hash_calculation: config.accounts_db_use_index_hash_calculation,
726733
rocksdb_compaction_interval: config.rocksdb_compaction_interval,
727734
rocksdb_max_compaction_jitter: config.rocksdb_compaction_interval,
735+
wait_for_vote_to_start_leader,
728736
},
729737
&max_slots,
730738
);
@@ -1292,30 +1300,41 @@ fn initialize_rpc_transaction_history_services(
12921300
}
12931301
}
12941302

1295-
// Return true on error, indicating the validator should exit.
1303+
#[derive(Debug, PartialEq)]
1304+
enum ValidatorError {
1305+
BadExpectedBankHash,
1306+
NotEnoughLedgerData,
1307+
}
1308+
1309+
// Return if the validator waited on other nodes to start. In this case
1310+
// it should not wait for one of it's votes to land to produce blocks
1311+
// because if the whole network is waiting, then it will stall.
1312+
//
1313+
// Error indicates that a bad hash was encountered or another condition
1314+
// that is unrecoverable and the validator should exit.
12961315
fn wait_for_supermajority(
12971316
config: &ValidatorConfig,
12981317
bank: &Bank,
12991318
cluster_info: &ClusterInfo,
13001319
rpc_override_health_check: Arc<AtomicBool>,
13011320
start_progress: &Arc<RwLock<ValidatorStartProgress>>,
1302-
) -> bool {
1321+
) -> Result<bool, ValidatorError> {
13031322
if let Some(wait_for_supermajority) = config.wait_for_supermajority {
13041323
match wait_for_supermajority.cmp(&bank.slot()) {
1305-
std::cmp::Ordering::Less => return false,
1324+
std::cmp::Ordering::Less => return Ok(false),
13061325
std::cmp::Ordering::Greater => {
13071326
error!(
13081327
"Ledger does not have enough data to wait for supermajority, \
13091328
please enable snapshot fetch. Has {} needs {}",
13101329
bank.slot(),
13111330
wait_for_supermajority
13121331
);
1313-
return true;
1332+
return Err(ValidatorError::NotEnoughLedgerData);
13141333
}
13151334
_ => {}
13161335
}
13171336
} else {
1318-
return false;
1337+
return Ok(false);
13191338
}
13201339

13211340
if let Some(expected_bank_hash) = config.expected_bank_hash {
@@ -1325,7 +1344,7 @@ fn wait_for_supermajority(
13251344
bank.hash(),
13261345
expected_bank_hash
13271346
);
1328-
return true;
1347+
return Err(ValidatorError::BadExpectedBankHash);
13291348
}
13301349
}
13311350

@@ -1350,7 +1369,7 @@ fn wait_for_supermajority(
13501369
sleep(Duration::new(1, 0));
13511370
}
13521371
rpc_override_health_check.store(false, Ordering::Relaxed);
1353-
false
1372+
Ok(true)
13541373
}
13551374

13561375
fn report_target_features() {
@@ -1641,17 +1660,21 @@ mod tests {
16411660
&cluster_info,
16421661
rpc_override_health_check.clone(),
16431662
&start_progress,
1644-
));
1663+
)
1664+
.unwrap());
16451665

16461666
// bank=0, wait=1, should fail
16471667
config.wait_for_supermajority = Some(1);
1648-
assert!(wait_for_supermajority(
1649-
&config,
1650-
&bank,
1651-
&cluster_info,
1652-
rpc_override_health_check.clone(),
1653-
&start_progress,
1654-
));
1668+
assert_eq!(
1669+
wait_for_supermajority(
1670+
&config,
1671+
&bank,
1672+
&cluster_info,
1673+
rpc_override_health_check.clone(),
1674+
&start_progress,
1675+
),
1676+
Err(ValidatorError::NotEnoughLedgerData)
1677+
);
16551678

16561679
// bank=1, wait=0, should pass, bank is past the wait slot
16571680
let bank = Bank::new_from_parent(&bank, &Pubkey::default(), 1);
@@ -1662,18 +1685,22 @@ mod tests {
16621685
&cluster_info,
16631686
rpc_override_health_check.clone(),
16641687
&start_progress,
1665-
));
1688+
)
1689+
.unwrap());
16661690

16671691
// bank=1, wait=1, equal, but bad hash provided
16681692
config.wait_for_supermajority = Some(1);
16691693
config.expected_bank_hash = Some(hash(&[1]));
1670-
assert!(wait_for_supermajority(
1671-
&config,
1672-
&bank,
1673-
&cluster_info,
1674-
rpc_override_health_check,
1675-
&start_progress,
1676-
));
1694+
assert_eq!(
1695+
wait_for_supermajority(
1696+
&config,
1697+
&bank,
1698+
&cluster_info,
1699+
rpc_override_health_check,
1700+
&start_progress,
1701+
),
1702+
Err(ValidatorError::BadExpectedBankHash)
1703+
);
16771704
}
16781705

16791706
#[test]

local-cluster/src/validator_configs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub fn safe_clone_config(config: &ValidatorConfig) -> ValidatorConfig {
5353
tpu_coalesce_ms: config.tpu_coalesce_ms,
5454
validator_exit: Arc::new(RwLock::new(ValidatorExit::default())),
5555
poh_hashes_per_batch: config.poh_hashes_per_batch,
56+
no_wait_for_vote_to_start_leader: config.no_wait_for_vote_to_start_leader,
5657
}
5758
}
5859

multinode-demo/bootstrap-validator.sh

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ args+=(
105105
--vote-account "$vote_account"
106106
--rpc-faucet-address 127.0.0.1:9900
107107
--no-poh-speed-test
108+
--no-wait-for-vote-to-start-leader
108109
)
109110
default_arg --gossip-port 8001
110111
default_arg --log -

run.sh

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ args=(
105105
--init-complete-file "$dataDir"/init-completed
106106
--snapshot-compression none
107107
--require-tower
108+
--no-wait-for-vote-to-start-leader
108109
)
109110
# shellcheck disable=SC2086
110111
solana-validator "${args[@]}" $SOLANA_RUN_SH_VALIDATOR_ARGS &

0 commit comments

Comments
 (0)