Substrate中session模块分析
Session模块能够让验证者去管理它们的session key,提供一个方法去改变session长度,和处理session轮流。
-
Session:会话是一段有一组常量出块认证者的时间。出块认证者只能在会话更改时加入或退出验证器集。它是用区块数来度量的。某个区块里会话结束由
ShouldSessionEnd
Trait决定。当会话结束时,可以通过OnSessionEnding
实现选择一个新的区块认证者集合。 -
Session key:一个会话密钥实际上是几个保存在一起的密钥,它们提供了网络出块认证者在执行其职责时所需的各种签名功能。
-
Validator ID:每个账号有一个相对应的validator ID,对于一些简单的抵押系统,这能够跟account ID发挥一样的作用,对于使用stash/控制者模型的抵押系统中,validator ID将是控制者的stash账号ID。
-
Session key configuration process:使用
set_key
设置会话密钥以供下一个会话使用。它存储在NextKeyFor
中,这是调用者的ValidatorId
和提供的会话密钥之间的mapping。set_key
允许用户在被选择为出块认证者之前设置他们的会话密钥。它是一个公共调用,因为它使用ensure_signed
,它检查源帐户是否是签名帐户。因此,存储在NextKeyFor
中的原始帐户ID不一定与出块作者或者出块认证者关联。帐户的会话密钥在帐户余额为零时被删除。 -
Validator set session key configuration process:在每个会话中,我们迭代当前出块认证者帐户id集,以检查是否在前一个会话中使用
set_key
为它创建了会话密钥。如果当时我们调用set_authority
并将其传递给一组会话密钥(每一个相关的帐户ID)作为新的会话密钥出块认证者集合。最后,如果当前出块节点的会话密钥不匹配任何会话密钥,存储其出块验证节点索引在AuthorityStorageVec
mapping中,然后更新mapping会话密钥和更新保存的原始出块节点列表在必要时(见https://github.com/paritytech/substrate/issues/1290)。注意:出块节点存储在Consensus模块中。它们由来自session模块的出块认证者帐户ID索引表示,并使用会话密钥分配会话长度。 -
Session length change process:在下一个会话开始时,我们分配一个会话索引并记录会话启动时的时间戳。如果‘NextSessionLength’记录在前一个会话中,我们将它记录为新会话长度。另外,如果新会话的长度与下一个会话的长度不同,那么我们将记录“LastLengthChange”。
-
Session rotation configration:配置为
normal
(可奖励的session,奖励将可以被应用)或exceptional
(可惩罚)会话轮换。 -
Session rotation process:使用
on_finalize
方法在当前会话的最后一个区块结束时更改会话。它可以由一个origin调用,也可以从每个区块末尾的另一个运行时模块内部调用。
Session模块在Substrate中被设计用来做如下的事情:
-
为验证者集合参与下一轮session设置session keys。
-
设置session的长度。
-
在normal或者exceptional session轮换中配置和切换。
-
set_key
:在下轮session中为验证者设置session key。 -
set_length
:在下一轮session改变的时候设置一个新的被应用的session长度。 -
force_new_session
:强制开启一个新的session,其应该被认为是normal或者exception轮换。 -
on_finalize
:当一个区块要finalized的时候被调用,如果这个区块是这个session的最后一个区块,那么要转换session。 -
validator_count
: 获取现在的验证者数量。 -
last_length_change
: 当session长度上一次改变时,获取当时的区块高度。 -
apply_force_new_session
:强制开启一个新的session,能够被其它runtime的modules调用。 -
set_validators
: 设置现在的验证者集合,只能够被抵押模块调用。 -
check_rotate_session
: 转换session和应用奖励,如果有必要的话。当抵押模块更新认证出块者到新的验证者人集合后,此方法能够被调用。 -
rotate_session
:更改到下一个session,注册新的认证出块人集合,更新session keys,如果可被应用的session长度被改变了,要颁布这种改变。 -
ideal_session_duration
:获取一个理想session的时间。 -
blocks_remaining
:现在这个session还剩下多少区块高度就要进入下一个session了。
- trait SholdEndSession
- fn should_end_session(now: BlockNumber) -> bool;
/// Period是session的固定区块时间,第一个session会有`Offset`的长度,接下来的session会有`period`的长度。随着session不断增长,Offset是不断增加的。
pub struct PeriodicSessions<Period,Offset,>(PhantomData<(Period, Offset)>);
- pub trait OnSessionEnding<ValidatorId>
- fn on_session_ending(ending_index:SessionIdex,will_apply_at:SessionIndex) -> Option<Vec<ValidatorId>>
-
pub trait OneSessionHandler<validatorId>
- fn on_genesis_session
- fn on_new_session
- fn on_before_session_ending
- fn on_disabled
-
pub trait SelectInitialValidators<ValidatorId>
- fn select_inital_validators() -> Option<vec<ValidatorId>>
- pub trait OneSessionHandler<ValidatorId>: BoundToRuntimeAppPublic
- fn on_genesis_session
- fn on_new_session
- fn on_before_session_ending
- fn on_disabled
Store
/// 现在的出块验证者集合
Validators get(fn validators): Vec<T::ValidatorId>;
/// 现在session的索引
CurrentIndex get(fn current_index): SessionIndex;
/// 如果底层经济特征或者出块验证者权重友改变,在这个出块认证者队列中,返回True
QueuedChanged: bool;
/// 下一个session的队列keys,当下一个session开启,这些keys会被用来决定队列中出块认证者的session keys
QueuedKeys get(fn queued_keys): Vec<(T::ValidatorId, T::Keys)>;
/// 失效出块认证者的目录
/// 这个集合会被清理掉,当`on_session_ending`返回一个新的身份集合
DisabledValidators get(fn disabled_validators): Vec<u32>;
/// 下一个session,某个出块验证者的session keys
NextKeys: double_map hasher(twox_64_concat) Vec<u8>, blake2_256(T::ValidatorId) => Option<T::Keys>;
/// 一个session key的所有者
KeyOwner: double_map hasher(twox_64_concat) Vec<u8>, blake2_256((KeyTypeId, Vec<u8>)) => Option<T::ValidatorId>;
Event
decl_event!(
pub enum Event {
/// 新的session发生了
NewSession(SessionIndex),
}
);
Module
decl_module! {
fn set_keys(origin,keys:T::Keys,proof: vec<u8>) ->Result
fn on_initialize(n: T::BlockNumber){
if T::ShouldEndSession::should_end_session(n) {
Self::rotate_session();
}
}
}
session模块有一个辅助性模块historical
模块。用来追踪历史性session记录的,在实现区块链的时候,需要在之前若干个session中,出块认证者都能够被保留可被惩罚的状态,这是需要记录历史session记录的。
相比于记录所有session数据,historical
采用了只记录默克尔tries的roots数据,这些roots保留了session数据。
这些roots和所包含的证明能够在任何时候被生成,在现在session的过程中,然后,在报告不诚实行为的时候,这些证明能够反哺共识模块。
/// 历史session目录,key是session的索引,value是session-data,主要是root hash和validator数量
HistoricalSessions get(fn historical_root): map SessionIndex => Option<(T::Hash, ValidatorCount)>;
/// value是session的索引,value是废弃掉的validator及其FullIdentification
CachedObsolete get(fn cached_obsolete): map SessionIndex => Option<Vec<(T::ValidatorId, T::FullIdentification)>>;
/// 存储的session索引,[first,last)
StoredRange: Option<(SessionIndex, SessionIndex)>;
staking模块中:
impl<T: Trait> session::OnSessionEnding<T::AccountId> for Module<T> {
fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) -> Option<Vec<T::AccountId>> {
Self::new_session(start_session - 1).map(|(new, _old)| new)
}
}
impl<T: Trait> OnSessionEnding<T::AccountId, Exposure<T::AccountId, BalanceOf<T>>> for Module<T> {
fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex)
-> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
{
Self::new_session(start_session - 1)
}
}
重点调用Self::new_session()
:
/// 上一个session刚刚结束,为下一个session提供validator集合,如果是一个era的结束,还提供前一个session的validator集合的exposure
fn new_session(session_index: SessionIndex)
-> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
{
let era_length = session_index.checked_sub(Self::current_era_start_session_index()).unwrap_or(0);
match ForceEra::get() {
Forcing::ForceNew => ForceEra::kill(),
Forcing::ForceAlways => (),
Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (),
_ => return None,
}
let validators = T::SessionInterface::validators();
let prior = validators.into_iter()
.map(|v| { let e = Self::stakers(&v); (v, e) })
.collect();
Self::new_era(session_index).map(move |new| (new, prior))
}
Substrate几个核心问题
-
出块节点的门槛?
fn bond(origin, controller: <T::Lookup as StaticLookup>::Source, #[compact] value: BalanceOf<T>, payee: RewardDestination ) { let stash = ensure_signed(origin)?; if <Bonded<T>>::exists(&stash) { return Err("stash already bonded") } let controller = T::Lookup::lookup(controller)?; if <Ledger<T>>::exists(&controller) { return Err("controller already paired") } // reject a bond which is considered to be _dust_. if value < T::Currency::minimum_balance() { return Err("can not bond with value less than minimum balance") } // You're auto-bonded forever, here. We might improve this by only bonding when // you actually validate/nominate and remove once you unbond __everything__. <Bonded<T>>::insert(&stash, &controller); <Payee<T>>::insert(&stash, payee); let stash_balance = T::Currency::free_balance(&stash); let value = value.min(stash_balance); let item = StakingLedger { stash, total: value, active: value, unlocking: vec![] }; Self::update_ledger(&controller, &item); }
网友评论