限制参与
限制外部validator注册
限制外部质押->奖励分配
genesis初始化
从genesis初始化逻辑,切入validator注册和质押逻辑
--bootstrap-validator "$SOLANA_CONFIG_DIR"/bootstrap-validator/identity.json "$SOLANA_CONFIG_DIR"/bootstrap-validator/vote-account.json "$SOLANA_CONFIG_DIR"/bootstrap-validator/stake-account.json
支持多个validator,每三个一组,分别对应,且不能重复
identity_pubkey
vote_pubkey
stake_pubkey
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);
初始化配置
let bootstrap_validator_lamports = value_t_or_exit!(matches, "bootstrap_validator_lamports", u64); // 分配给引导验证器的 Lamport 数量 let bootstrap_validator_stake_lamports = rent_exempt_check( // 分配给引导验证者质押账户的 lampor 数量 &matches, "bootstrap_validator_stake_lamports", rent.minimum_balance(StakeStateV2::size_of()), )?; let bootstrap_stake_authorized_pubkey = pubkey_of(&matches, "bootstrap_stake_authorized_pubkey"); // 包含授权管理引导验证者权益的公钥的文件路径 [默认值:--bootstrap-validator IDENTITY_PUBKEY]
初始化
let commission = value_t_or_exit!(matches, "vote_commission_percentage", u8);
let mut bootstrap_validator_pubkeys_iter = bootstrap_validator_pubkeys.iter();
loop {
let Some(identity_pubkey) = bootstrap_validator_pubkeys_iter.next() else {
break;
};
let vote_pubkey = bootstrap_validator_pubkeys_iter.next().unwrap();
let stake_pubkey = bootstrap_validator_pubkeys_iter.next().unwrap();
genesis_config.add_account( //添加账户,并初始增加bootstrap_validator_lamports数量余额
*identity_pubkey,
AccountSharedData::new(bootstrap_validator_lamports, 0, &system_program::id()),
);
let vote_account = vote_state::create_account_with_authorized( // 初始化账户和余额,并VoteInit初始化VoteSigner
identity_pubkey,
identity_pubkey,
identity_pubkey,
commission,
VoteState::get_rent_exempt_reserve(&genesis_config.rent).max(1),
);
genesis_config.add_account(
*stake_pubkey,
stake_state::create_account( //
bootstrap_stake_authorized_pubkey
.as_ref()
.unwrap_or(identity_pubkey),
vote_pubkey,
&vote_account,
&genesis_config.rent,
bootstrap_validator_stake_lamports,
),
);
genesis_config.add_account(*vote_pubkey, vote_account);
}Validator初始化逻辑
pub fn create_account_with_authorized(
node_pubkey: &Pubkey,
authorized_voter: &Pubkey,
authorized_withdrawer: &Pubkey,
commission: u8,
lamports: u64,
) -> AccountSharedData {
let mut vote_account = AccountSharedData::new(lamports, VoteState::size_of(), &id()); // 初始化账户和余额
let vote_state = VoteState::new( // 初始化vote_state
&VoteInit {
node_pubkey: *node_pubkey,
authorized_voter: *authorized_voter,
authorized_withdrawer: *authorized_withdrawer,
commission,
},
&Clock::default(),
);
VoteState::serialize( // 序列化状态
&VoteStateVersions::Current(Box::new(vote_state)),
vote_account.data_as_mut_slice(),
)
.unwrap();
vote_account
}质押逻辑
fn do_create_account(
authorized: &Pubkey,
voter_pubkey: &Pubkey,
vote_account: &AccountSharedData,
rent: &Rent,
lamports: u64,
activation_epoch: Epoch,
) -> AccountSharedData {
let mut stake_account = AccountSharedData::new(lamports, StakeStateV2::size_of(), &id()); // 初始化账户和余额
let vote_state = vote_state::from(vote_account).expect("vote_state"); // 获取投票状态
let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len()); // genesis_config.rent 需要预留的余额
stake_account
.set_state(&StakeStateV2::Stake(
Meta {
authorized: Authorized::auto(authorized),
rent_exempt_reserve,
..Meta::default()
},
new_stake(
lamports - rent_exempt_reserve, // 总余额 - 需要预留的,等于本次可质押的
voter_pubkey,
&vote_state,
activation_epoch,
),
StakeFlags::empty(),
))
.expect("set_state");
stake_account
}综合分析
对应代码,Validator的注册和质押流程如下
Validator->VoteState::new(VoteInit)->VoteState::serialize
StakeStateV2::Stake
在对应下面的流程图
验证者注册与委托流程图

节点之间Vote消息是如何传递的
Validator
core/src/validator.rs
pub fn new(
mut node: Node,
identity_keypair: Arc<Keypair>,
ledger_path: &Path,
vote_account: &Pubkey,
authorized_voter_keypairs: Arc<RwLock<Vec<Arc<Keypair>>>>,
cluster_entrypoints: Vec<ContactInfo>,
config: &ValidatorConfig,
should_check_duplicate_instance: bool,
rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
start_progress: Arc<RwLock<ValidatorStartProgress>>,
socket_addr_space: SocketAddrSpace,
use_quic: bool,
tpu_connection_pool_size: usize,
tpu_enable_udp: bool, // 关注参数
admin_rpc_service_post_init: Arc<RwLock<Option<AdminRpcRequestMetadataPostInit>>>,
) -> Result<Self, String> {
...
let (tpu, mut key_notifies) = Tpu::new(
&cluster_info,
&poh_recorder,
entry_receiver,
retransmit_slots_receiver,
TpuSockets {
transactions: node.sockets.tpu,
transaction_forwards: node.sockets.tpu_forwards,
vote: node.sockets.tpu_vote,
broadcast: node.sockets.broadcast,
transactions_quic: node.sockets.tpu_quic,
transactions_forwards_quic: node.sockets.tpu_forwards_quic,
},
&rpc_subscriptions,
transaction_status_sender,
entry_notification_sender,
blockstore.clone(),
&config.broadcast_stage_type,
exit,
node.info.shred_version(),
vote_tracker,
bank_forks.clone(),
verified_vote_sender,
gossip_verified_vote_hash_sender,
replay_vote_receiver,
replay_vote_sender,
bank_notification_sender.map(|sender| sender.sender),
config.tpu_coalesce,
duplicate_confirmed_slot_sender,
&connection_cache,
turbine_quic_endpoint_sender,
&identity_keypair,
config.runtime_config.log_messages_bytes_limit,
&staked_nodes,
config.staked_nodes_overrides.clone(),
banking_tracer,
tracer_thread,
tpu_enable_udp, // 关注参数
&prioritization_fee_cache,
config.block_production_method.clone(),
config.generator_config.clone(),
);core/src/tpu.rs
impl Tpu {
#[allow(clippy::too_many_arguments)]
pub fn new(
cluster_info: &Arc<ClusterInfo>,
poh_recorder: &Arc<RwLock<PohRecorder>>,
entry_receiver: Receiver<WorkingBankEntry>,
retransmit_slots_receiver: Receiver<Slot>,
sockets: TpuSockets,
subscriptions: &Arc<RpcSubscriptions>,
transaction_status_sender: Option<TransactionStatusSender>,
entry_notification_sender: Option<EntryNotifierSender>,
blockstore: Arc<Blockstore>,
broadcast_type: &BroadcastStageType,
exit: Arc<AtomicBool>,
shred_version: u16,
vote_tracker: Arc<VoteTracker>,
bank_forks: Arc<RwLock<BankForks>>,
verified_vote_sender: VerifiedVoteSender,
gossip_verified_vote_hash_sender: GossipVerifiedVoteHashSender,
replay_vote_receiver: ReplayVoteReceiver,
replay_vote_sender: ReplayVoteSender,
bank_notification_sender: Option<BankNotificationSender>,
tpu_coalesce: Duration,
duplicate_confirmed_slot_sender: DuplicateConfirmedSlotsSender,
connection_cache: &Arc<ConnectionCache>,
turbine_quic_endpoint_sender: AsyncSender<(SocketAddr, Bytes)>,
keypair: &Keypair,
log_messages_bytes_limit: Option<usize>,
staked_nodes: &Arc<RwLock<StakedNodes>>,
shared_staked_nodes_overrides: Arc<RwLock<HashMap<Pubkey, u64>>>,
banking_tracer: Arc<BankingTracer>,
tracer_thread_hdl: TracerThread,
tpu_enable_udp: bool,
prioritization_fee_cache: &Arc<PrioritizationFeeCache>,
block_production_method: BlockProductionMethod,
_generator_config: Option<GeneratorConfig>, /* vestigial code for replay invalidator */
) -> (Self, Vec<Arc<dyn NotifyKeyUpdate + Sync + Send>>) {
...
let fetch_stage = FetchStage::new_with_sender(
transactions_sockets,
tpu_forwards_sockets,
tpu_vote_sockets,
exit.clone(),
&packet_sender,
&vote_packet_sender,
&forwarded_packet_sender,
forwarded_packet_receiver,
poh_recorder,
tpu_coalesce,
Some(bank_forks.read().unwrap().get_vote_only_mode_signal()),
tpu_enable_udp,
);
...
let (gossip_vote_sender, gossip_vote_receiver) =
banking_tracer.create_channel_gossip_vote();
let cluster_info_vote_listener = ClusterInfoVoteListener::new(
exit.clone(),
cluster_info.clone(),
gossip_vote_sender,
poh_recorder.clone(),
vote_tracker,
bank_forks.clone(),
subscriptions.clone(),
verified_vote_sender,
gossip_verified_vote_hash_sender,
replay_vote_receiver,
blockstore.clone(),
bank_notification_sender,
duplicate_confirmed_slot_sender,
);core/src/cluster_info_vote_listener.rs
pub fn new(
exit: Arc<AtomicBool>,
cluster_info: Arc<ClusterInfo>,
verified_packets_sender: BankingPacketSender,
poh_recorder: Arc<RwLock<PohRecorder>>,
vote_tracker: Arc<VoteTracker>,
bank_forks: Arc<RwLock<BankForks>>,
subscriptions: Arc<RpcSubscriptions>,
verified_vote_sender: VerifiedVoteSender,
gossip_verified_vote_hash_sender: GossipVerifiedVoteHashSender,
replay_votes_receiver: ReplayVoteReceiver,
blockstore: Arc<Blockstore>,
bank_notification_sender: Option<BankNotificationSender>,
duplicate_confirmed_slot_sender: DuplicateConfirmedSlotsSender,
) -> Self {
let (verified_vote_label_packets_sender, verified_vote_label_packets_receiver) =
unbounded();
let (verified_vote_transactions_sender, verified_vote_transactions_receiver) = unbounded();
let listen_thread = {
let exit = exit.clone();
let bank_forks = bank_forks.clone();
Builder::new()
.name("solCiVoteLstnr".to_string())
.spawn(move || {
let _ = Self::recv_loop(
exit,
&cluster_info,
&bank_forks,
verified_vote_label_packets_sender,
verified_vote_transactions_sender,
);
})
.unwrap()
};
let bank_send_thread = {
let exit = exit.clone();
Builder::new()
.name("solCiBankSend".to_string())
.spawn(move || {
let _ = Self::bank_send_loop(
exit,
verified_vote_label_packets_receiver,
poh_recorder,
&verified_packets_sender,
);
})
.unwrap()
};
let send_thread = Builder::new()
.name("solCiProcVotes".to_string())
.spawn(move || {
let _ = Self::process_votes_loop(
exit,
verified_vote_transactions_receiver,
vote_tracker,
bank_forks,
subscriptions,
gossip_verified_vote_hash_sender,
verified_vote_sender,
replay_votes_receiver,
blockstore,
bank_notification_sender,
duplicate_confirmed_slot_sender,
);
})
.unwrap();
Self {
thread_hdls: vec![listen_thread, send_thread, bank_send_thread],
}
}fn process_votes_loop(
exit: Arc<AtomicBool>,
gossip_vote_txs_receiver: VerifiedVoteTransactionsReceiver,
vote_tracker: Arc<VoteTracker>,
bank_forks: Arc<RwLock<BankForks>>,
subscriptions: Arc<RpcSubscriptions>,
gossip_verified_vote_hash_sender: GossipVerifiedVoteHashSender,
verified_vote_sender: VerifiedVoteSender,
replay_votes_receiver: ReplayVoteReceiver,
blockstore: Arc<Blockstore>,
bank_notification_sender: Option<BankNotificationSender>,
duplicate_confirmed_slot_sender: DuplicateConfirmedSlotsSender,
) -> Result<()> {
let mut confirmation_verifier =
OptimisticConfirmationVerifier::new(bank_forks.read().unwrap().root());
let mut latest_vote_slot_per_validator = HashMap::new();
let mut last_process_root = Instant::now();
let duplicate_confirmed_slot_sender = Some(duplicate_confirmed_slot_sender);
let mut vote_processing_time = Some(VoteProcessingTiming::default());
loop {
if exit.load(Ordering::Relaxed) {
return Ok(());
}
let root_bank = bank_forks.read().unwrap().root_bank();
if last_process_root.elapsed().as_millis() > DEFAULT_MS_PER_SLOT as u128 {
let unrooted_optimistic_slots = confirmation_verifier
.verify_for_unrooted_optimistic_slots(&root_bank, &blockstore);
// SlotVoteTracker's for all `slots` in `unrooted_optimistic_slots`
// should still be available because we haven't purged in
// `progress_with_new_root_bank()` yet, which is called below
OptimisticConfirmationVerifier::log_unrooted_optimistic_slots(
&root_bank,
&vote_tracker,
&unrooted_optimistic_slots,
);
vote_tracker.progress_with_new_root_bank(&root_bank);
last_process_root = Instant::now();
}
let confirmed_slots = Self::listen_and_confirm_votes(
&gossip_vote_txs_receiver,
&vote_tracker,
&root_bank,
&subscriptions,
&gossip_verified_vote_hash_sender,
&verified_vote_sender,
&replay_votes_receiver,
&bank_notification_sender,
&duplicate_confirmed_slot_sender,
&mut vote_processing_time,
&mut latest_vote_slot_per_validator,
);
match confirmed_slots {
Ok(confirmed_slots) => {
confirmation_verifier
.add_new_optimistic_confirmed_slots(confirmed_slots.clone(), &blockstore);
}
Err(e) => match e {
Error::RecvTimeout(RecvTimeoutError::Disconnected) => {
return Ok(());
}
Error::ReadyTimeout => (),
_ => {
error!("thread {:?} error {:?}", thread::current().name(), e);
}
},
}
}
}let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);0
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);1
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);2
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);3
core/src/consensus/vote_stake_tracker.rs
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);4
为指定账户增加质押,并返回是否为首次加入
综合分析
validator 启动时会启动gossip通信,接收和转发收到的质押信息
Vote账户如何创建
查看一下cli工具命令代码
cli/src/cli.rs
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);5
创建Vote账户交易的组装和发送
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);6
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);7
sdk/program/src/system_instruction.rs
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);8
总结
本想直接禁用底层的执行方法,和EVM还不一样,对应方法是通用的
let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap(); assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);9
// TODO 还没分析完,先保存,稍后再继续
附加
solana-cli 中与质押和验证者有关的参数
| 参数 | 解释 |
|---|---|
| close-vote-account | 关闭投票账户并提取所有剩余资金 |
| create-stake-account | 创建质押账户 |
| create-stake-account-checked | 创建一个质押账户,检查作为签名者的提现权限 |
| create-vote-account | 创建投票账户 |
| deactivate-stake | 从质押账户中取消委托质押 |
| delegate-stake | 将质押委托给投票账户 |
| split-stake | 复制一个质押账户,将代币拆分到两个账户中 |
| stake-account | 显示质押账户的内容 |
| stake-authorize | 为给定的质押账户授权新的签名密钥对 |
| stake-authorize-checked | 为给定的质押账户授权新的签名密钥对,检查作为签名者的权限 |
| stake-history | 显示质押历史记录 |
| stake-minimum-delegation | 获取质押最低委托额度 |
| stake-set-lockup | 设置质押账户锁仓 |
| stake-set-lockup-checked | 设置质押账户的 Lockup,检查作为签名者的新权限 |
| stakes | 显示质押账户信息 |
| validator-info | 发布~获取 Solana 上的验证者信息 |
| validators | 显示当前验证器的摘要信息 |
| verify-offchain-signature | 验证链下消息签名 |
| vote-account | 显示投票账户的内容 |
| vote-authorize-voter | 为给定的投票账户授权新的投票签名密钥对 |
| vote-authorize-voter-checked | 为给定的投票账户授权新的投票签名密钥对,检查作为签名者的新权限 |
| vote-authorize-withdrawer | 为给定的投票账户授权新的提款签名密钥对 |
| vote-authorize-withdrawer-checked | 为给定的投票账户授权新的提款签名密钥对,检查作为签名者的新权限 |
| vote-update-commission | 更新投票账户的佣金 |
| vote-update-validator | 更新投票账户的验证者身份 |
| wait-for-max-stake | 等待任何一个节点的最大股权降至总股权的一定百分比以下。 |
| withdraw-from-nonce-account | 从 nonce 账户中提取 SOL |
| withdraw-from-vote-account | 将投票账户中的 lamport 提现到指定账户 |
| withdraw-stake | 从质押账户中提取未质押的 SOL |

