您正在查看: 2024年12月
Solana 禁用原有初始化账户
需求
Solana原版代码会在初始化时,预先会创建一系列账户地址和余额
跟进代码,确定初始化地址用途,是否必须的,是否可禁掉
跟进代码
genesis/src/genesis_accounts.rs
fn add_stakes(
genesis_config: &mut GenesisConfig,
staker_infos: &[StakerInfo],
unlock_info: &UnlockInfo,
) -> u64 {
staker_infos
.iter()
.map(|staker_info| create_and_add_stakes(genesis_config, staker_info, unlock_info, None))
.sum::<u64>()
}
pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig, mut issued_lamports: u64) {
// add_stakes() and add_validators() award tokens for rent exemption and
// to cover an initial transfer-free period of the network
issued_lamports += add_stakes(
genesis_config,
CREATOR_STAKER_INFOS,
&UNLOCKS_HALF_AT_9_MONTHS,
) + add_stakes(
genesis_config,
SERVICE_STAKER_INFOS,
&UNLOCKS_ALL_AT_9_MONTHS,
) + add_stakes(
genesis_config,
FOUNDATION_STAKER_INFOS,
&UNLOCKS_ALL_DAY_ZERO,
) + add_stakes(genesis_config, GRANTS_STAKER_INFOS, &UNLOCKS_ALL_DAY_ZERO)
+ add_stakes(
genesis_config,
COMMUNITY_STAKER_INFOS,
&UNLOCKS_ALL_DAY_ZERO,
);
// "one thanks" (community pool) gets 500_000_000SOL (total) - above distributions
create_and_add_stakes(
genesis_config,
&StakerInfo {
name: "one thanks",
staker: "7vEAL3nS9CWmy1q6njUUyHE7Cf5RmyQpND6CsoHjzPiR",
lamports: (500_000_000 * LAMPORTS_PER_SOL).saturating_sub(issued_lamports),
withdrawer: Some("3FFaheyqtyAXZSYxDzsr5CVKvJuvZD1WE1VEsBtDbRqB"),
},
&UNLOCKS_ALL_DAY_ZERO,
None,
);
}
分析
- 创建多个质押锁仓地址,并初始化对应量余额
- 创建community pool,余额为 500_000_000 - (issued_lamports - faucet_lamports)
从功能来说不是私链初始化链所必须的
Diff
genesis/src/main.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/genesis/src/main.rs b/genesis/src/main.rs
index 6b7efd5e66..45154443ab 100644
--- a/genesis/src/main.rs
+++ b/genesis/src/main.rs
@@ -15,7 +15,7 @@ use {
},
},
solana_entry::poh::compute_hashes_per_tick,
- solana_genesis::{genesis_accounts::add_genesis_accounts, Base64Account},
+ solana_genesis::{Base64Account},
solana_ledger::{blockstore::create_new_ledger, blockstore_options::LedgerColumnOptions},
solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
@@ -597,7 +597,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.map(|account| account.lamports)
.sum::<u64>();
- add_genesis_accounts(&mut genesis_config, issued_lamports - faucet_lamports);
+ // add_genesis_accounts(&mut genesis_config, issued_lamports - faucet_lamports);
let parse_address = |address: &str, input_type: &str| {
address.parse::<Pubkey>().unwrap_or_else(|err| {
测试
- 重置链网络
- 查看初始化地址,是否有非预期地址
- 查看bootstrap-validator 出块,手续费接收等是否正常
Solana 区块浏览器定制网络配置
github: https://github.com/solana-labs/explorer
Diff
From 88f5c222a19c983fae1c950cc3612d6bbc53a31c Mon Sep 17 00:00:00 2001
From: Hendrik Hofstadt <hendrik@bool.capital>
Date: Sun, 18 Feb 2024 10:19:46 +0100
Subject: [PATCH] reduce cluster options to SPE
---
app/providers/cluster.tsx | 5 ++++-
app/providers/stats/SolanaPingProvider.tsx | 2 +-
app/utils/cluster.ts | 11 +++++++++--
3 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/app/providers/cluster.tsx b/app/providers/cluster.tsx
index deef8a2..04bcc6b 100644
--- a/app/providers/cluster.tsx
+++ b/app/providers/cluster.tsx
@@ -59,9 +59,12 @@ function parseQuery(searchParams: ReadonlyURLSearchParams | null): Cluster {
return Cluster.Devnet;
case 'testnet':
return Cluster.Testnet;
+ case 'localspe':
+ return Cluster.SPE;
case 'mainnet-beta':
- default:
return Cluster.MainnetBeta;
+ default:
+ return Cluster.SPE;
}
}
diff --git a/app/providers/stats/SolanaPingProvider.tsx b/app/providers/stats/SolanaPingProvider.tsx
index 76b2682..6d5df04 100644
--- a/app/providers/stats/SolanaPingProvider.tsx
+++ b/app/providers/stats/SolanaPingProvider.tsx
@@ -12,7 +12,7 @@ const FETCH_PING_INTERVAL = 60 * 1000;
function getPingUrl(cluster: Cluster) {
const slug = clusterSlug(cluster);
- if (slug === 'custom') {
+ if (slug === 'custom' || slug === 'localspe') {
return undefined;
}
diff --git a/app/utils/cluster.ts b/app/utils/cluster.ts
index f30963f..734f121 100644
--- a/app/utils/cluster.ts
+++ b/app/utils/cluster.ts
@@ -8,10 +8,11 @@ export enum Cluster {
MainnetBeta,
Testnet,
Devnet,
+ SPE,
Custom,
}
-export const CLUSTERS = [Cluster.MainnetBeta, Cluster.Testnet, Cluster.Devnet, Cluster.Custom];
+export const CLUSTERS = [Cluster.SPE, Cluster.Custom];
export function clusterSlug(cluster: Cluster): string {
switch (cluster) {
@@ -21,6 +22,8 @@ export function clusterSlug(cluster: Cluster): string {
return 'testnet';
case Cluster.Devnet:
return 'devnet';
+ case Cluster.SPE:
+ return 'localspe';
case Cluster.Custom:
return 'custom';
}
@@ -34,6 +37,8 @@ export function clusterName(cluster: Cluster): string {
return 'Testnet';
case Cluster.Devnet:
return 'Devnet';
+ case Cluster.SPE:
+ return 'Local SPE';
case Cluster.Custom:
return 'Custom';
}
@@ -59,9 +64,11 @@ export function clusterUrl(cluster: Cluster, customUrl: string): string {
return process.env.NEXT_PUBLIC_MAINNET_RPC_URL ?? modifyUrl(MAINNET_BETA_URL);
case Cluster.Testnet:
return process.env.NEXT_PUBLIC_TESTNET_RPC_URL ?? modifyUrl(TESTNET_URL);
+ case Cluster.SPE:
+ return "http://localhost:8899";
case Cluster.Custom:
return customUrl;
}
}
-export const DEFAULT_CLUSTER = Cluster.MainnetBeta;
+export const DEFAULT_CLUSTER = Cluster.SPE;
--
2.42.0
Solana 修改最小质押量
需求
自定义最小的质押量,限制外部的加入门槛
代码分析
sdk/program/src/stake/instruction.rs
#[error("delegation amount is less than the minimum")]
InsufficientDelegation,
programs/stake/src/stake_state.rs
fn validate_delegated_amount(
account: &BorrowedAccount,
meta: &Meta,
feature_set: &FeatureSet,
) -> Result<ValidatedDelegatedInfo, InstructionError> {
let stake_amount = account
.get_lamports()
.saturating_sub(meta.rent_exempt_reserve); // 确保委托的质押金额有效。这将检查帐户是否满足委托质押的最低余额要求。如果不满足,则返回错误。
// 质押账户可能会以低于最低委托额的质押额进行初始化,因此请在委托前检查是否满足最低要求。
if stake_amount < crate::get_minimum_delegation(feature_set) {
return Err(StakeError::InsufficientDelegation.into());
}
Ok(ValidatedDelegatedInfo { stake_amount })
}
/// 可以委托的最低股权金额,以 lampors 为单位。
/// 注意:这也用于计算股权账户的最低余额,即租金豁免储备加上最低股权委托。
#[inline(always)]
pub fn get_minimum_delegation(feature_set: &FeatureSet) -> u64 {
if feature_set.is_active(&feature_set::stake_raise_minimum_delegation_to_1_sol::id()) {
const MINIMUM_DELEGATION_SOL: u64 = 1;
MINIMUM_DELEGATION_SOL * LAMPORTS_PER_SOL
} else {
#[allow(deprecated)]
solana_sdk::stake::MINIMUM_STAKE_DELEGATION
}
}
总结
目前测试验证节点,直接硬编码,设置一个较大值来限制外部加入,修改测试代码如下
/// 可以委托的最低股权金额,以 lampors 为单位。
/// 注意:这也用于计算股权账户的最低余额,即租金豁免储备加上最低股权委托。
#[inline(always)]
pub fn get_minimum_delegation(feature_set: &FeatureSet) -> u64 {
const MINIMUM_DELEGATION_SOL: u64 = 1000000000000;
MINIMUM_DELEGATION_SOL * LAMPORTS_PER_SOL
}
Solana 如何限制外部注册和质押
限制参与
- 限制外部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
投票账户管理
- 创建投票账户: create-vote-account
- 更改验证者身份: vote-update-validator
- 改变投票权限: vote-authorize-voter-checked
- 更改授权提款人: vote-authorize-withdrawer-checked
- 更改佣金: vote-update-commission
- 从投票账户中提取资金: withdraw-from-vote-account
- 关闭投票账户: close-vote-account
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 |
最新回复
fzd: 请问这个解决了吗
StarkWare explained: layer 2 solution provider of dYdX and iMMUTABLE R11; BitKeep News: [...]Layer 2: https://...
一文读懂 StarkWare:dYdX 和 Immutable 背后的 L2 方案 R11; BitKeep 博客: [...]Layer 2:Comparing Laye...
http://andere.strikingly.com/: Regards, Great stuff!
surou: 需要先执行提案合约申请,等待出块节点地址同意后,才会进...
heco: WARN [11-19|11:26:09.459] N...
P: 你好,我在heco链上遇到了“tx fee excee...
Peng: 楼主安装成功了吗?我正在同步区块链,一天了,差不多才同...
joyhu: 你好,请问下安装好之后如何获取到bee.yaml配置文...
kaka: 支票最终怎么提币呢?
归档
January 2025December 2024November 2024October 2024September 2024August 2024July 2024June 2024May 2024April 2024March 2024January 2024December 2023November 2023October 2023September 2023August 2023July 2023June 2023April 2023March 2023February 2023January 2023December 2022November 2022October 2022August 2022July 2022June 2022May 2022March 2022February 2022January 2022December 2021November 2021October 2021September 2021August 2021July 2021June 2021May 2021April 2021March 2021February 2021January 2021December 2020November 2020October 2020September 2020July 2020June 2020May 2020April 2020March 2020February 2020January 2020December 2019November 2019October 2019September 2019August 2019July 2019June 2019May 2019April 2019March 2019February 2019January 2019December 2018November 2018October 2018September 2018August 2018July 2018June 2018