限制参与
限制外部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 |