Substrate中session模块分析

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(PhantomData<(Period, Offset)>);
  • pub trait OnSessionEnding
    • fn on_session_ending(ending_index:SessionIdex,will_apply_at:SessionIndex) -> Option>
  • pub trait OneSessionHandler

    • fn on_genesis_session
    • fn on_new_session
    • fn on_before_session_ending
    • fn on_disabled
  • pub trait SelectInitialValidators

    • fn select_inital_validators() -> Option>
  • pub trait OneSessionHandler: BoundToRuntimeAppPublic
    • fn on_genesis_session
    • fn on_new_session
    • fn on_before_session_ending
    • fn on_disabled

Store

/// 现在的出块验证者集合
Validators get(fn validators): Vec;

/// 现在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;

/// 下一个session,某个出块验证者的session keys
NextKeys: double_map hasher(twox_64_concat) Vec, blake2_256(T::ValidatorId) => Option;

/// 一个session key的所有者
KeyOwner: double_map hasher(twox_64_concat) Vec, blake2_256((KeyTypeId, Vec)) => Option;

Event

decl_event!(
    pub enum Event {
        /// 新的session发生了
        NewSession(SessionIndex),
    }
);

Module

decl_module! {
    fn set_keys(origin,keys:T::Keys,proof: vec) ->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>;
/// 存储的session索引,[first,last)
StoredRange: Option<(SessionIndex, SessionIndex)>;

staking模块中:

impl session::OnSessionEnding for Module {
    fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) -> Option> {
        Self::new_session(start_session - 1).map(|(new, _old)| new)
    }
}

impl OnSessionEnding>> for Module {
    fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex)
        -> Option<(Vec, Vec<(T::AccountId, Exposure>)>)>
    {
        Self::new_session(start_session - 1)
    }
}

重点调用Self::new_session():

/// 上一个session刚刚结束,为下一个session提供validator集合,如果是一个era的结束,还提供前一个session的validator集合的exposure
fn new_session(session_index: SessionIndex)
    -> Option<(Vec, Vec<(T::AccountId, Exposure>)>)>
{
    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: ::Source,
          #[compact] value: BalanceOf,
          payee: RewardDestination
      ) {
          let stash = ensure_signed(origin)?;
    
          if >::exists(&stash) {
              return Err("stash already bonded")
          }
    
          let controller = T::Lookup::lookup(controller)?;
    
          if >::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__.
          >::insert(&stash, &controller);
          >::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);
      }

你可能感兴趣的:(Substrate中session模块分析)