限制参与
- 限制外部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);
}
},
}
}
}
fn listen_and_confirm_votes(
gossip_vote_txs_receiver: &VerifiedVoteTransactionsReceiver,
vote_tracker: &VoteTracker,
root_bank: &Bank,
subscriptions: &RpcSubscriptions,
gossip_verified_vote_hash_sender: &GossipVerifiedVoteHashSender,
verified_vote_sender: &VerifiedVoteSender,
replay_votes_receiver: &ReplayVoteReceiver,
bank_notification_sender: &Option<BankNotificationSender>,
duplicate_confirmed_slot_sender: &Option<DuplicateConfirmedSlotsSender>,
vote_processing_time: &mut Option<VoteProcessingTiming>,
latest_vote_slot_per_validator: &mut HashMap<Pubkey, Slot>,
) -> Result<ThresholdConfirmedSlots> {
let mut sel = Select::new();
sel.recv(gossip_vote_txs_receiver);
sel.recv(replay_votes_receiver);
let mut remaining_wait_time = Duration::from_millis(200);
while remaining_wait_time > Duration::ZERO {
let start = Instant::now();
// Wait for one of the receivers to be ready. `ready_timeout`
// will return if channels either have something, or are
// disconnected. `ready_timeout` can wake up spuriously,
// hence the loop
let _ = sel.ready_timeout(remaining_wait_time)?;
// Should not early return from this point onwards until `process_votes()`
// returns below to avoid missing any potential `optimistic_confirmed_slots`
let gossip_vote_txs: Vec<_> = gossip_vote_txs_receiver.try_iter().flatten().collect();
let replay_votes: Vec<_> = replay_votes_receiver.try_iter().collect();
if !gossip_vote_txs.is_empty() || !replay_votes.is_empty() {
return Ok(Self::filter_and_confirm_with_new_votes(
vote_tracker,
gossip_vote_txs,
replay_votes,
root_bank,
subscriptions,
gossip_verified_vote_hash_sender,
verified_vote_sender,
bank_notification_sender,
duplicate_confirmed_slot_sender,
vote_processing_time,
latest_vote_slot_per_validator,
));
}
remaining_wait_time = remaining_wait_time.saturating_sub(start.elapsed());
}
Ok(vec![])
}
#[allow(clippy::too_many_arguments)]
fn filter_and_confirm_with_new_votes(
vote_tracker: &VoteTracker,
gossip_vote_txs: Vec<Transaction>,
replayed_votes: Vec<ParsedVote>,
root_bank: &Bank,
subscriptions: &RpcSubscriptions,
gossip_verified_vote_hash_sender: &GossipVerifiedVoteHashSender,
verified_vote_sender: &VerifiedVoteSender,
bank_notification_sender: &Option<BankNotificationSender>,
duplicate_confirmed_slot_sender: &Option<DuplicateConfirmedSlotsSender>,
vote_processing_time: &mut Option<VoteProcessingTiming>,
latest_vote_slot_per_validator: &mut HashMap<Pubkey, Slot>,
) -> ThresholdConfirmedSlots {
let mut diff: HashMap<Slot, HashMap<Pubkey, bool>> = HashMap::new();
let mut new_optimistic_confirmed_slots = vec![];
// Process votes from gossip and ReplayStage
let mut gossip_vote_txn_processing_time = Measure::start("gossip_vote_processing_time");
let votes = gossip_vote_txs
.iter()
.filter_map(vote_parser::parse_vote_transaction)
.zip(repeat(/*is_gossip:*/ true))
.chain(replayed_votes.into_iter().zip(repeat(/*is_gossip:*/ false)));
for ((vote_pubkey, vote, _switch_proof, signature), is_gossip) in votes {
Self::track_new_votes_and_notify_confirmations(
vote,
&vote_pubkey,
signature,
vote_tracker,
root_bank,
subscriptions,
verified_vote_sender,
gossip_verified_vote_hash_sender,
&mut diff,
&mut new_optimistic_confirmed_slots,
is_gossip, // 是否通过gossip发送投票信息
bank_notification_sender,
duplicate_confirmed_slot_sender,
latest_vote_slot_per_validator,
);
}
#[allow(clippy::too_many_arguments)]
fn track_new_votes_and_notify_confirmations(
vote: VoteTransaction,
vote_pubkey: &Pubkey,
vote_transaction_signature: Signature,
vote_tracker: &VoteTracker,
root_bank: &Bank,
subscriptions: &RpcSubscriptions,
verified_vote_sender: &VerifiedVoteSender,
gossip_verified_vote_hash_sender: &GossipVerifiedVoteHashSender,
diff: &mut HashMap<Slot, HashMap<Pubkey, bool>>,
new_optimistic_confirmed_slots: &mut ThresholdConfirmedSlots,
is_gossip_vote: bool, // 是否通过gossip发送投票信息
bank_notification_sender: &Option<BankNotificationSender>,
duplicate_confirmed_slot_sender: &Option<DuplicateConfirmedSlotsSender>,
latest_vote_slot_per_validator: &mut HashMap<Pubkey, Slot>,
) {
if vote.is_empty() {
return;
}
let (last_vote_slot, last_vote_hash) = vote.last_voted_slot_hash().unwrap();
let latest_vote_slot = latest_vote_slot_per_validator
.entry(*vote_pubkey)
.or_insert(0);
let root = root_bank.slot();
let mut is_new_vote = false;
let vote_slots = vote.slots();
// If slot is before the root, ignore it
for slot in vote_slots.iter().filter(|slot| **slot > root).rev() {
let slot = *slot;
// if we don't have stake information, ignore it
let epoch = root_bank.epoch_schedule().get_epoch(slot);
let epoch_stakes = root_bank.epoch_stakes(epoch);
if epoch_stakes.is_none() {
continue;
}
let epoch_stakes = epoch_stakes.unwrap();
// The last vote slot, which is the greatest slot in the stack
// of votes in a vote transaction, qualifies for optimistic confirmation.
// We cannot count any other slots in this vote toward optimistic confirmation because:
// 1) There may have been a switch between the earlier vote and the last vote
// 2) We do not know the hash of the earlier slot
if slot == last_vote_slot {
let vote_accounts = epoch_stakes.stakes().vote_accounts();
let stake = vote_accounts.get_delegated_stake(vote_pubkey);
let total_stake = epoch_stakes.total_stake();
// 快速处理投票交易中的最后一个时段
// 以便可以尽快发送乐观确认通知。
let (reached_threshold_results, is_new) = Self::track_optimistic_confirmation_vote(
vote_tracker,
last_vote_slot,
last_vote_hash,
*vote_pubkey,
stake,
total_stake,
);
if is_gossip_vote && is_new && stake > 0 {
let _ = gossip_verified_vote_hash_sender.send(( // 通过Gossip发送投票信息
*vote_pubkey,
last_vote_slot,
last_vote_hash,
));
}
if reached_threshold_results[0] {
if let Some(sender) = duplicate_confirmed_slot_sender {
let _ = sender.send(vec![(last_vote_slot, last_vote_hash)]);
}
}
if reached_threshold_results[1] {
new_optimistic_confirmed_slots.push((last_vote_slot, last_vote_hash));
// Notify subscribers about new optimistic confirmation
if let Some(sender) = bank_notification_sender {
sender
.send(BankNotification::OptimisticallyConfirmed(last_vote_slot))
.unwrap_or_else(|err| {
warn!("bank_notification_sender failed: {:?}", err)
});
}
}
if !is_new && !is_gossip_vote {
// By now:
// 1) The vote must have come from ReplayStage,
// 2) We've seen this vote from replay for this hash before
// (`track_optimistic_confirmation_vote()` will not set `is_new == true`
// for same slot different hash), so short circuit because this vote
// has no new information
// Note gossip votes will always be processed because those should be unique
// and we need to update the gossip-only stake in the `VoteTracker`.
return;
}
is_new_vote = is_new;
}
if slot < *latest_vote_slot {
// 重要的是,我们在 `last_vote_slot` 检查之后进行过滤,因为即使这个投票
// 是旧的,我们仍然需要跟踪乐观确认。
// 但是,可以过滤下面传播检查跟踪的其余槽,
// 因为传播检查能够为后代汇总投票,这与乐观确认不同。
continue;
}
diff.entry(slot)
.or_default()
.entry(*vote_pubkey)
.and_modify(|seen_in_gossip_previously| {
*seen_in_gossip_previously = *seen_in_gossip_previously || is_gossip_vote
})
.or_insert(is_gossip_vote);
}
*latest_vote_slot = max(*latest_vote_slot, last_vote_slot);
if is_new_vote {
subscriptions.notify_vote(*vote_pubkey, vote, vote_transaction_signature);
let _ = verified_vote_sender.send((*vote_pubkey, vote_slots));
}
}
fn track_optimistic_confirmation_vote(
vote_tracker: &VoteTracker,
slot: Slot,
hash: Hash,
pubkey: Pubkey,
stake: u64,
total_epoch_stake: u64,
) -> (Vec<bool>, bool) {
let slot_tracker = vote_tracker.get_or_insert_slot_tracker(slot);
// Insert vote and check for optimistic confirmation
let mut w_slot_tracker = slot_tracker.write().unwrap();
w_slot_tracker
.get_or_insert_optimistic_votes_tracker(hash)
.add_vote_pubkey(pubkey, stake, total_epoch_stake, &THRESHOLDS_TO_CHECK)
}
core/src/consensus/vote_stake_tracker.rs
pub fn add_vote_pubkey(
&mut self,
vote_pubkey: Pubkey,
stake: u64,
total_stake: u64,
thresholds_to_check: &[f64],
) -> (Vec<bool>, bool) {
let is_new = !self.voted.contains(&vote_pubkey);
if is_new {
self.voted.insert(vote_pubkey);
let old_stake = self.stake;
let new_stake = self.stake + stake;
self.stake = new_stake;
let reached_threshold_results: Vec<bool> = thresholds_to_check
.iter()
.map(|threshold| {
let threshold_stake = (total_stake as f64 * threshold) as u64;
old_stake <= threshold_stake && threshold_stake < new_stake
})
.collect();
(reached_threshold_results, is_new)
} else {
(vec![false; thresholds_to_check.len()], is_new)
}
}
为指定账户增加质押,并返回是否为首次加入
综合分析
validator 启动时会启动gossip通信,接收和转发收到的质押信息
Vote账户如何创建
查看一下cli工具命令代码
cli/src/cli.rs
pub fn process_command(config: &CliConfig) -> ProcessResult {
if config.verbose && config.output_format == OutputFormat::DisplayVerbose {
println_name_value("RPC URL:", &config.json_rpc_url);
println_name_value("Default Signer Path:", &config.keypair_path);
if config.keypair_path.starts_with("usb://") {
let pubkey = config
.pubkey()
.map(|pubkey| format!("{pubkey:?}"))
.unwrap_or_else(|_| "Unavailable".to_string());
println_name_value("Pubkey:", &pubkey);
}
println_name_value("Commitment:", &config.commitment.commitment.to_string());
}
let rpc_client = if config.rpc_client.is_none() {
Arc::new(RpcClient::new_with_timeouts_and_commitment(
config.json_rpc_url.to_string(),
config.rpc_timeout,
config.commitment,
config.confirm_transaction_initial_timeout,
))
} else {
// Primarily for testing
config.rpc_client.as_ref().unwrap().clone()
};
match &config.command {
...
// Create vote account
CliCommand::CreateVoteAccount {
vote_account,
seed,
identity_account,
authorized_voter,
authorized_withdrawer,
commission,
sign_only,
dump_transaction_message,
blockhash_query,
ref nonce_account,
nonce_authority,
memo,
fee_payer,
compute_unit_price,
} => process_create_vote_account(
&rpc_client,
config,
*vote_account,
seed,
*identity_account,
authorized_voter,
*authorized_withdrawer,
*commission,
*sign_only,
*dump_transaction_message,
blockhash_query,
nonce_account.as_ref(),
*nonce_authority,
memo.as_ref(),
*fee_payer,
compute_unit_price.as_ref(),
)
...
}
}
创建Vote账户交易的组装和发送
#[allow(clippy::too_many_arguments)]
pub fn process_create_vote_account(
rpc_client: &RpcClient,
config: &CliConfig,
vote_account: SignerIndex,
seed: &Option<String>,
identity_account: SignerIndex,
authorized_voter: &Option<Pubkey>,
authorized_withdrawer: Pubkey,
commission: u8,
sign_only: bool,
dump_transaction_message: bool,
blockhash_query: &BlockhashQuery,
nonce_account: Option<&Pubkey>,
nonce_authority: SignerIndex,
memo: Option<&String>,
fee_payer: SignerIndex,
compute_unit_price: Option<&u64>,
) -> ProcessResult {
// vote_account账户信息获取
let vote_account = config.signers[vote_account];
let vote_account_pubkey = vote_account.pubkey();
let vote_account_address = if let Some(seed) = seed {
Pubkey::create_with_seed(&vote_account_pubkey, seed, &solana_vote_program::id())?
} else {
vote_account_pubkey
};
check_unique_pubkeys(
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(&vote_account_address, "vote_account".to_string()),
)?;
// identity账户信息获取
let identity_account = config.signers[identity_account];
let identity_pubkey = identity_account.pubkey();
check_unique_pubkeys(
(&vote_account_address, "vote_account".to_string()),
(&identity_pubkey, "identity_pubkey".to_string()),
)?;
// 所需余额计算
let required_balance = rpc_client
.get_minimum_balance_for_rent_exemption(VoteState::size_of())? // RPC链上获取 TODO 可以设置一个无限制?https://solana.com/docs/rpc/http/getminimumbalanceforrentexemption
.max(1);
let amount = SpendAmount::Some(required_balance);
let fee_payer = config.signers[fee_payer];
let nonce_authority = config.signers[nonce_authority];
let is_feature_active = (!sign_only)
.then(solana_sdk::feature_set::vote_state_add_vote_latency::id)
.and_then(|feature_address| rpc_client.get_account(&feature_address).ok())
.and_then(|account| feature::from_account(&account))
.map_or(false, |feature| feature.activated_at.is_some());
let space = VoteStateVersions::vote_state_size_of(is_feature_active) as u64;
let build_message = |lamports| { // 组装交易信息
let vote_init = VoteInit {
node_pubkey: identity_pubkey,
authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
authorized_withdrawer,
commission,
};
let mut create_vote_account_config = CreateVoteAccountConfig {
space,
..CreateVoteAccountConfig::default()
};
let to = if let Some(seed) = seed {
create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed));
&vote_account_address
} else {
&vote_account_pubkey
};
let ixs = vote_instruction::create_account_with_config( // 组装具体交易
&config.signers[0].pubkey(),
to,
&vote_init,
lamports,
create_vote_account_config,
)
.with_memo(memo)
.with_compute_unit_price(compute_unit_price);
if let Some(nonce_account) = &nonce_account {
Message::new_with_nonce(
ixs,
Some(&fee_payer.pubkey()),
nonce_account,
&nonce_authority.pubkey(),
)
} else {
Message::new(&ixs, Some(&fee_payer.pubkey()))
}
};
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
let (message, _) = resolve_spend_tx_and_check_account_balances(
rpc_client,
sign_only,
amount,
&recent_blockhash,
&config.signers[0].pubkey(),
&fee_payer.pubkey(),
build_message,
config.commitment,
)?;
if !sign_only {
if let Ok(response) =
rpc_client.get_account_with_commitment(&vote_account_address, config.commitment)
{
if let Some(vote_account) = response.value {
let err_msg = if vote_account.owner == solana_vote_program::id() {
format!("Vote account {vote_account_address} already exists")
} else {
format!(
"Account {vote_account_address} already exists and is not a vote account"
)
};
return Err(CliError::BadParameter(err_msg).into());
}
}
if let Some(nonce_account) = &nonce_account {
let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
}
let mut tx = Transaction::new_unsigned(message);
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers_with_config(
&tx,
&config.output_format,
&ReturnSignersConfig {
dump_transaction_message,
},
)
} else {
tx.try_sign(&config.signers, recent_blockhash)?; // signers 进行签名
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); // 发送交易
log_instruction_custom_error::<SystemError>(result, config)
}
}
pub fn create_account_with_config(
from_pubkey: &Pubkey,
vote_pubkey: &Pubkey,
vote_init: &VoteInit,
lamports: u64,
config: CreateVoteAccountConfig,
) -> Vec<Instruction> {
let create_ix =
system_instruction::create_account(from_pubkey, vote_pubkey, lamports, config.space, &id()); // 系统指令 类似于EVM native code 级别 SystemInstruction::CreateAccount
let init_ix = initialize_account(vote_pubkey, vote_init); // 同上 VoteInstruction::InitializeAccount(
vec![create_ix, init_ix]
}
sdk/program/src/system_instruction.rs
pub fn create_account(
from_pubkey: &Pubkey,
to_pubkey: &Pubkey,
lamports: u64,
space: u64,
owner: &Pubkey,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, true),
];
Instruction::new_with_bincode(
system_program::id(),
&SystemInstruction::CreateAccount {
lamports,
space,
owner: *owner,
},
account_metas,
)
}
总结
本想直接禁用底层的执行方法,和EVM还不一样,对应方法是通用的
pub fn create_account_with_activation_epoch(
authorized: &Pubkey,
voter_pubkey: &Pubkey,
vote_account: &AccountSharedData,
rent: &Rent,
lamports: u64,
activation_epoch: Epoch,
) -> AccountSharedData {
do_create_account(
authorized,
voter_pubkey,
vote_account,
rent,
lamports,
activation_epoch,
)
}
// TODO 还没分析完,先保存,稍后再继续
附加
使用 Solana CLI 质押 SOL
https://docs.anza.xyz/cli/examples/delegate-stake
投票账户管理
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 |