解答開發者最關心的以太坊 2.0 Staking 關鍵問題:用代碼解讀成爲以太坊 2.0 驗證人的過程,探祕信標鏈共識與激勵機制。

原文標題:《以太坊 2.0 Staking 硬核四問》
撰文:HashQuark 研究團隊

作爲廣受矚目的全球頂尖公鏈項目,以太坊 2.0 完全顛覆了從前的設計,旨在最大程度地同時實現去中心化和擴容目標。與以太坊 1.0 不同的是,以太坊 2.0 使用 PoS (權益證明)算法來推動區塊鏈的運行,並通過「信標鏈+多分片鏈」 的架構來提高可擴展性。

以太坊 2.0 的研發和部署計劃歷時已久。在所有客戶端均順利實現規範的最終版本 v0.12.1 後,6 月底將啓動一個實現最終版本規範的多客戶端測試網,7 月則可啓動最後的公共測試網。此後,最終版本的公共多客戶端測試網若能穩定運行兩至三個月,則可開始準備以太坊 2.0 的主網啓動工作。若一切順利,階段 0 將於 11 月上線。但若版本規範仍有待修復,且所有客戶端需再次實現新規範,則上線時間可能推遲到 2021 年。

本文將爲讀者展示 HashQuark 研究團隊從以太坊 2.0 技術層面出發、對最受技術人員關心的四個硬核問題給出的詳細回答。

如何成爲驗證人?

以太坊採用存款合約(deposit contract)作爲以太坊 1.0 與以太坊 2.0 之間的橋樑,當用戶向存款合約存入 32 ETH 後,便可以作爲以太坊 2.0 的驗證者參與工作,並獲得以太坊 2.0 獎勵。

以 prysm 官網教程爲例,加入測試網來直觀感受下如何成爲驗證人。

準備工作

prysm 的實現主要包含兩部分 : 信標鏈客戶端和驗證者客戶端。前者負責信標鏈的狀態管理,後者負責驗證者的出塊和⻅證。爲方便這一流程,prysm 提供了簡易腳本 prysm.sh 來下載安裝:

    mkdir prysm && cd prysm
    curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh && chmod +x prysm.sh

(向左滑動,查看完整代碼)

上述命令會在當前目錄新建 prysm 文件夾,並下載可執行文件 prysm.sh。

除此之外,我們還需要至少 32 ETH 的以太坊賬戶和瀏覽器插件 metamask 以便發送交易。可以在測試網上申請一些測試幣,如下圖所示:

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

創建驗證者密鑰對

一個驗證人需要創建兩對密鑰對,一對用作驗證人出塊和見證,另一對用於管理存入合約的資金。運行如下命令來創建密鑰:

./prysm.sh validator accounts create

命令將默認在~/.eth2validators/ 目錄下創建兩個 keystore 格式的文件,如下圖所示:

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

前者用於資金管理,後者用於出塊等。

創建完密鑰的命令會在終端輸出 Deposit Data:

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

這是根據上面的密鑰對生成的交易信息,我們將它複製到網頁上的交易數據部分:

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

啓動節點

採用如下命令在兩個終端分別啓動信標鏈節點和驗證者節點:

    ./prysm.sh beacon-chain./prysm.sh validator

發送存款交易

通過網頁調用 metamask 填充上面的 Deposit Data,方便用戶直接發送存款交易。

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

點擊圖中按鈕,metamask 會跳出確認對話框:

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

可以看出共發送了 32 ETH 給存款合約,交易的 Data 部分正是我們複製的 Deposit Data。點擊確認發送交易,發送成功後等待 4-5 小時即可成功激活驗證人。

用代碼解讀成爲驗證人的過程

創建驗證人的主要過程爲創建密鑰對生成數據調用合約合約執行信標鏈處理

創建密鑰對

創建驗證者密鑰對時,通常需要兩對密鑰(採用 BLS12-381 曲線):驗證者密鑰對(Validator PubKey, Validator PrivateKey)和提取存款的密鑰對(Withdrawal PubKey, Withdrawal PrivateKey)。

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

生成數據調用合約

調用合約除了通常的合約地址、金額參數外,還需要構造要調用的合約方法的參數:pubkey (驗證者公鑰)、withdrawal_credentials (提取存款權限信息),signature (簽名)和 deposit_data_root (防止篡改標識)。在用戶生成兩對密鑰後,就可以生成上面這些參數來構造要發送的交易數據,參考下圖:

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

可以看到:

  • pubkey 是 Validator PubKey,這也是創建的驗證者的公鑰標識
  • withdrawal_crendentials 由一個固定前綴拼接 Withdrawal PubKey 的哈希(sha256)構成
  • amount 是本次合約發送的金額,至少爲 1 ETH
  • signature 是採用 Validator PrivateKey 對 pubkey、withdrawal_credentials、amount 的哈希(HashTreeRoot)結果的簽名
  • deposit_data_root 是前面 4 個參數的哈希(HashTreeRoot)

在生成上述的參數後,會按照合約接口編碼成一定的格式,然後發送給存款合約完成調用。

合約執行

合約的主要方法 deposit,定義了收到一筆存款交易時如何處理,存款交易正是通過調用這一方法來實現存款。

deposit 方法接受 pubkey (驗證者公鑰)、withdrawal_credentials (提取存款權限信息),signature (簽名)和 deposit_data_root (防止篡改標識)作爲參數。主要分成參數基礎校驗、觸發存款事件、檢驗數據完整性、更新數據結構幾個部分,如以下代碼所示:

@public
  def deposit(pubkey: bytes[PUBKEY_LENGTH],
              withdrawal_credentials: bytes[WITHDRAWAL_CREDENTIALS_LENGTH],
              signature: bytes[SIGNATURE_LENGTH],
              deposit_data_root: bytes32):

      ############## 1. 參數基礎校驗 ################
      # Avoid overflowing the Merkle tree (and prevent edge case in computing `self.branch`)
      assert self.deposit_count < MAX_DEPOSIT_COUNT

      # Check deposit amount
      deposit_amount: uint256 = msg.value / as_wei_value(1, "gwei")
      assert deposit_amount >= MIN_DEPOSIT_AMOUNT

      # Length checks for safety
      assert len(pubkey) == PUBKEY_LENGTH
      assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH
      assert len(signature) == SIGNATURE_LENGTH
      #########################################

      # Emit `DepositEvent` log
      amount: bytes[8] = self.to_little_endian_64(deposit_amount)



      ############## 2. 觸發存款事件 ################
      log.DepositEvent(pubkey, withdrawal_credentials, amount, signature, self.to_little_endian_64(self.deposit_count))



      ############## 3. 校驗數據完整性 ################
      # Compute deposit data root (`DepositData` hash tree root)
      zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
      pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes32, start=0, len=64 - PUBKEY_LENGTH)))
      signature_root: bytes32 = sha256(concat(
          sha256(slice(signature, start=0, len=64)),
          sha256(concat(slice(signature, start=64, len=SIGNATURE_LENGTH - 64), zero_bytes32)),
      ))
      node: bytes32 = sha256(concat(
          sha256(concat(pubkey_root, withdrawal_credentials)),
          sha256(concat(amount, slice(zero_bytes32, start=0, len=32 - AMOUNT_LENGTH), signature_root)),
      ))
      # Verify computed and expected deposit data roots match
      assert node == deposit_data_root
      ###########################################



      ############## 4. 更新 Merkle Tree ################
      # Add deposit data root to Merkle tree (update a single `branch` node)
      self.deposit_count += 1
      size: uint256 = self.deposit_count
      for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
          if bitwise_and(size, 1) == 1:  # More gas efficient than `size % 2 == 1`
              self.branch[height] = node
              break
          node = sha256(concat(self.branch[height], node))
          size /= 2

(向左滑動,查看完整代碼)

對於上述第 3 部分數據完整性的校驗與構造交易數據一致,校驗的邏輯關係參考下圖:

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

可以看到,由於合約接收的參數是固定的格式和語義的,所以直接將各部分 pack 成 32 bytes,然後做 merkleize 求值,最後與接收到的 deposit_data_root 進行比較,如果相同則說明數據沒有被篡改。

信標鏈的處理

當存款交易成功被以太坊 1.0 鏈上執行後,以太坊 2.0 的信標鏈接下來如何處理?

信標鏈會一直監聽存款合約的 DepositEvent 事件,如果存在新的存款合約,那麼會啓動相應的處理程序。以下爲規範上對 deposit 的處理:

  def process_deposit(state: BeaconState, deposit: Deposit) -> None:

      ##################### 必要檢驗 ###################
      # Verify the Merkle branch
      assert is_valid_merkle_branch(
          leaf=hash_tree_root(deposit.data),
          branch=deposit.proof,
          depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1,  # Add 1 for the List length mix-in
          index=state.eth1_deposit_index,
          root=state.eth1_data.deposit_root,
      )

      # Deposits must be processed in order
      state.eth1_deposit_index += 1

      pubkey = deposit.data.pubkey
      amount = deposit.data.amount
      validator_pubkeys = [v.pubkey for v in state.validators]
      if pubkey not in validator_pubkeys:
          # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
          deposit_message = DepositMessage(
              pubkey=deposit.data.pubkey,
              withdrawal_credentials=deposit.data.withdrawal_credentials,
              amount=deposit.data.amount,
          )
          domain = compute_domain(DOMAIN_DEPOSIT)  # Fork-agnostic domain since deposits are valid across forks
          signing_root = compute_signing_root(deposit_message, domain)
          if not bls.Verify(pubkey, signing_root, deposit.data.signature):
              return
      #################################################


      ##################### 添加新驗證人 ###################
          # Add validator and balance entries
          state.validators.append(get_validator_from_deposit(state, deposit))
          state.balances.append(amount)
      #################################################
      else:

      ################ 同一個驗證人多次存入 ###############
          # Increase balance by deposit amount
          index = ValidatorIndex(validator_pubkeys.index(pubkey))
          increase_balance(state, index, amount)
      #################################################

(向左滑動,查看完整代碼)

可以看到,如果存入的已經是一個驗證者,只需增加其餘額便可。如果存入的是新的驗證者,會在進行必要的校驗後,在全局狀態註冊一個新的驗證者。

新建驗證者的設置如下:

def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator:
    amount = deposit.data.amount

    # 設置有效餘額
    effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)

    # 設置驗證者信息和生命週期相關參數
    return Validator(
        pubkey=deposit.data.pubkey,
        withdrawal_credentials=deposit.data.withdrawal_credentials,
        activation_eligibility_epoch=FAR_FUTURE_EPOCH,
        activation_epoch=FAR_FUTURE_EPOCH,
        exit_epoch=FAR_FUTURE_EPOCH,
        withdrawable_epoch=FAR_FUTURE_EPOCH,
        effective_balance=effective_balance,
      )

(向左滑動,查看完整代碼)

驗證者生命週期相關參數均設置爲 FAR_FUTURE_EPOCH。

之後再處理這些新註冊的驗證者:

  def process_registry_updates(state: BeaconState) -> None:
      # Process activation eligibility and ejections
      for index, validator in enumerate(state.validators):

          # 可否進入等待隊列
          if is_eligible_for_activation_queue(validator):
              validator.activation_eligibility_epoch = get_current_epoch(state) + 1

          # 可否成爲活躍的驗證者
          if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE:
              initiate_validator_exit(state, ValidatorIndex(index))

      # 限制每週期成爲驗證者數量
      # Queue validators eligible for activation and not yet dequeued for activation  15.      activation_queue = sorted([
          index for index, validator in enumerate(state.validators)
          if is_eligible_for_activation(state, validator)
          # Order by the sequence of activation_eligibility_epoch setting and then index
      ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index))
      # Dequeued validators for activation up to churn limit
      for index in activation_queue[:get_validator_churn_limit(state)]:
          validator = state.validators[index]
          validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state))

(向左滑動,查看完整代碼)

在處理新增加的驗證者時,會按照一定比例設置一個等待隊列,這會限制同一時間可以增加的新的驗證者數量,也能夠防止一瞬間涌入的大批新的驗證者對於網絡安全和協議的影響,保證一定的穩定和安全性。

知識點

密鑰對管理方案

驗證者密鑰對可以是隨機生成的兩對 BLS 密鑰對,但若要創建多個驗證人,密鑰對數量過多則會難於管理。一個可選的解決方案是通過一個種子密鑰來衍生出一對對相關的密鑰對,只記錄這個種子密鑰即可。也可以通過將種子密鑰映射成助記詞來方便記錄和保存。以太坊提案 EIP2333 和 EIP2334 給出了具體的規範說明。密鑰衍生示意圖參考如下:

      [m / 0] - [m / 0 / 0]
     /        \
    /           [m / 0 / 1]
[m] - [m / 1]
    \
     ...
      [m / i]

另一方面,對於單獨的一對密鑰對,相比直接存儲私鑰,存儲成 keystore 格式加上密碼驗證加密更加安全,存儲也更加方便,可以參考以太坊提案 EIP2335 給出了相關建議。

名詞解釋

哈希方法 HashTreeRoot

HashTreeRoot 提供了將一個對象按一定格式構建默克爾樹、並求得樹的默克爾根值的方法。HashTreeRoot 可以將一個對象(bit、bytes、vector、list、containers 等等)的各個部分按序排列然後構建默克爾樹,獲得根值。具體規範參考:https://github.com/ethereum/eth2.0-specs/blob/dev/ssz/simple-serialize.md

具體來講,在對 pubkey、withdrawal_credentials、amount、signature 求取 HashTreeRoot 時,會經歷以下過程:

  • 對 pubkey、withdrawal_credentials、amount、signature 進行 HashTreeRoot,求得 deposit_data_root

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

  • 對於整體的 HashTreeRoot,實際上是對各個部分分別求取 HashTreeRoot,最後一起 Merkleize

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

  • 每個部分的 HashTreeRoot 實際上是先 Pack 再將結果 Merkleize

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

  • 如何進行 Pack?

可以看到,Pack 會把值按照一定長度切割,如不夠就用零字節補充,這樣會得到一個個按序排列的 32 字節的數據塊,也爲接下來的 Merkleize 提供初始數據。

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

  • 如何進行 Merkleize?

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

可以看到,Merkleize 是上一個步驟產生的數據塊按照二叉樹的方式從底部兩兩哈希求值再拼接,一層一層向上計算得到根值,所以 HashTreeRoot 本質上講是數據按照一定長度排列,然後一層一層默克爾求值,直到獲得最上層的根值。

探祕信標鏈(Beacon Chain)共識

信標鏈是整個以太坊 2.0 的核心,它是一條與當前以太坊 PoW 鏈並行的一條獨立鏈。它負責存儲和維護驗證者註冊表,處理分片鏈和信標鏈之間的交聯(Crosslink),以及完成信標鏈共識。想要了解信標鏈的共識過程,就需要了解最重要的幾個概念:間隙(Slot)、時段(Epoch)、驗證人(Validator)、⻅證消息(attestation)、驗證人委員會 (Committee)、檢查點(Checkpoint)、合理化(justified)、敲定的(Finalized)、Casper FFG (Casper, the Friendly Finality Gadget)、LMD GHOST 分叉規則。

基礎知識

間隙(Slot)和時段(Epoch)

Slot 和 Epoch 表示信標鏈的出塊時間和共識結算週期。按最新的信標鏈的技術規範 v0.12,一個 Slot 的時間是 12 秒。每一個 Epoch 由 32 個 Slot 組成,大約 6.4 分鐘。也就是說在正常情況下,信標鏈每 12 秒就產出一個區塊。每 6.4 分鐘是一個新的共識週期。

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制來源:https://ethos.dev/beacon-chain/

驗證人(Validator)

信標鏈啓動時需要至少 16,384 個 Validator (一共 524,288 個 ETH),才能成功激活信標鏈。驗證人們負責對信標鏈和分片鏈 (目前未實現,將在 Phase1 實現) 的最新區塊進行投票共識。

驗證人委員會(Committee)

每一個 Epoch 開始時,信標鏈都會通過 RANDAO 僞隨機算法爲信標鏈和分片選舉出由至少 128 個驗證人組成的 Committee。每一個 Slot 都會有一個 Committee 和一個出塊者 (Proposer)共同完成出塊。

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制來源:https://ethos.dev/beacon-chain/

⻅證消息(Attestation)

驗證人的投票在信標鏈中稱爲⻅證消息(Attestation),在標準中一條⻅證消息由三個投票組成:

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

  • 使用 LMD Ghost 分叉算法,選出主鏈。
  • 使用 Casper FFG 進行檢查點(Checkpoint)的敲定。
  • 對分片鏈狀態(Crosslink)的投票(在 Phase 0 階段還沒有實現))

LMD GHOST 和 Casper FFG

所有 PoS 類型的區塊鏈都面臨着兩個最重要的安全問題 :

*無利害關係(Nothing-at-Stake)

在 PoS 共識機制中,礦工可以在所有分叉上進行挖礦而沒有成本,從而達到收益最大化。

*⻓程攻擊 (Long-Range-Attack)

攻擊者首先獲得一些私鑰,只要這些私鑰曾獲得足夠多的股權,便可以從這一時刻開始分叉進行 51% 攻擊,製造一條分叉鏈。而由於 PoS 的出塊不需要進行工作量證明,攻擊者可以短時間內讓重寫歷史的分叉鏈追趕上原本的主鏈,從而造成 PoS 鏈和安全性威脅。

以太坊 2.0 就是通過 LMD GHOST 和 Capser FFG 一起來保證鏈上的共識的完成。

LMD GHOST

在比特幣 PoW 共識算法中,分叉規則遵循的是最⻓鏈原則,即積累算力最多的鏈,也稱爲主鏈;其他則被稱爲分叉鏈。隨着共識的不斷進行,主鏈積累的算力也有可能被其他分叉超過,成爲分叉鏈。

LMD 讓消息 (messages) 發揮了作用,即以太坊 2.0 鏈上的最終性是由最新消息驅動的。消息即證明 (attestation),總結來說,擁有最多投票的分叉鏈將被認爲是主鏈。

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

上圖體現了由最新消息驅動的分叉選擇規則:綠色區塊表示經由 LMD GHOST 分叉選擇規則證明了的區塊,笑臉符號表示最新的驗證者證明 (attestations),某個區塊中的 證明總量 (笑臉總數) 就是該區塊的權重,用區塊中的數字表示。

儘管位於上方的分叉鏈是最⻓的鏈,但下方由綠色區塊組成的鏈纔是主鏈,因爲綠色區塊包含了最多的證明,也就是擁有最多的驗證者投票。

Casper FFG

Casper FFG 全稱爲「Casper the Friendly Finality Gadget (Casper 友好的最終性小工具)」 ,是 Vitalik 提出的一個 PoW/PoS 混合的算法,目的是使 Ethereum 平滑過渡到純 PoS。

Vitalik 總結了四條規則,任何違反此四條規則的行爲都要被取走押金。

  • 提交(commit_req):收到 2/3 節點的預備訊息後才能提交。
  • 預備(prepare_req):每個預備訊息只能指向某個也具有 2/3 節點預備訊息的高度(Epoch),且這些預備訊息也必須都指向同一個高度。
  • 預備提交一致性(prepare_commit_consistency):任何新的預備訊息只能指向最後一個已提交的或其他比其更新的高度。
  • 不重複預備(no_double_prepare):不能在同一個高度送出兩次預備。

這四條規則可以進一步簡化爲兩條 :

某驗證節點 v 必不可發出兩個相異的投票 : <ν, s1, t1, h(s1), h(t1)> 及

<ν, s2, t2, h(s2), h(t2)>, 且使下列任一條件成立 :

1. h(t1) = h(t2)

驗證節點必不可對某高度發出兩個相異投票。

2. h(s1) < h(s2) < h(t2) < h(t1)

驗證節點必不可投出高度圍繞 / 被圍繞於另一投票高度的投票。

Casper FFG 運作

Casper FFG 通過檢查點(Checkpoint)的合理化(Justified)和敲定(Finalized))來完成共識。

在共識的過程中,驗證人除了對每個 Slot 進行共識出塊,還要對 Epoch 的檢查點進行投票,Epoch 的檢查點一般爲第一個 Slot 的區塊。每個驗證節點都要對檢查點進行投票,投票的內容是由兩個 Epoch 的檢查點組成的連接(Link),連接的起點稱爲源頭(Source),終點稱爲目標(Target);若投票給某個連接的票數超過 2/3,則該連接被稱爲絕對多數連接(Supermajority Link)。

開發者必讀:代碼解讀成爲以太坊 2.0 驗證人,探索信標鏈激勵機制

由根檢查點開始,若某連接爲一個絕對多數連接,則該連接的目標進入爲已合理化(Justified)狀態,該連接的源頭進入已敲定(Finalized)狀態。進入敲定狀態的交易不 可逆。

信標鏈的激勵機制

信標鏈的獎勵由五部分組成:

  • ⻅證被包含在最新的區塊中
  • ⻅證中包括了正確的檢查點的投票
  • ⻅證中包含了正確的最新區塊
  • ⻅證被很快地包含到鏈上
  • ⻅證包含正確的分片區塊(Phase1 階段實現)
    *
def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
    """
    Return attestation reward/penalty deltas for each validator.
    """
    source_rewards, source_penalties = get_source_deltas(state)  # 計算檢查點 source 獎懲
    target_rewards, target_penalties = get_target_deltas(state)  # 計算檢查點 target 獎懲
    head_rewards, head_penalties = get_head_deltas(state)  # 計算最新區塊獎懲
    inclusion_delay_rewards,_= get_inclusion_delay_deltas(state)  # 計算入塊延遲獎懲
   _, inactivity_penalties = get_inactivity_penalty_deltas(state)

    rewards = [
        source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i]
        for i in range(len(state.validators))
    ]

    penalties = [
        source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i]
        for i in range(len(state.validators))
    ]

    return rewards, penalties

(向左滑動,查看完整代碼)

上面的代碼段就是信標鏈最新的標準。驗證人的獎勵實際上獎勵由基礎獎勵 (B) * 執行正確投票的驗證人比例 (P) 構成,任何一個沒有正確投票的驗證人都將受到-B 的懲罰。這樣一來,做出正確投票的人越多,大家得到的獎勵就會越多,從而抑制作惡的投票。

目前決定一個驗證人基礎獎勵的計算公式如下標準代碼所示:

*

def get_attestation_component_deltas(state: BeaconState,
                                     attestations: Sequence[PendingAttestation]
                                     ) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
    """
    Helper with shared logic for use by get source, target, and head deltas functions
    """
    rewards = [Gwei(0)] * len(state.validators)
    penalties = [Gwei(0)] * len(state.validators)
    total_balance = get_total_active_balance(state)
    unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations)
    attesting_balance = get_total_balance(state, unslashed_attesting_indices)
    for index in get_eligible_validator_indices(state):
        if index in unslashed_attesting_indices:
            increment = EFFECTIVE_BALANCE_INCREMENT  # Factored out from balance totals to avoid uint64 overflow
            if is_in_inactivity_leak(state):
                # Since full base reward will be canceled out by inactivity penalty deltas,
                # optimal participation receives full base reward compensation here.
                rewards[index] += get_base_reward(state, index)
            else:
                reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
                rewards[index] += reward_numerator // (total_balance // increment)
        else:
            penalties[index] += get_base_reward(state, index)

(向左滑動,查看完整代碼)

核心代碼行:

reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
rewards[index] += reward_numerator // (total_balance // increment)

(向左滑動,查看完整代碼)

在理想狀態下驗證人的收益就是 4 倍的 BaseReward ,而 BaseReward 的計算公式如下:

def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
    total_balance = get_total_active_balance(state)
    effective_balance = state.validators[index].effective_balance
    return Gwei(effective_balance * BASE_REWARD_FACTOR // integer_squareroot(total_balance) // BASE_REWARDS_PER_EPOCH)
    # BASE_REWARD_FACTOR = 64 基礎獎勵倍數
    # BASE_REWARDS_PER_EPOCH = 4 每個 Epoch 的基礎獎勵
    # effective_balance 驗證人的有效餘額
    # integer_squareroot(total_balance) 所有有效餘額的開平方

(向左滑動,查看完整代碼)

出塊人將會得到 BaseReward / 8 的出塊獎勵:

def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei:
    return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT)

(向左滑動,查看完整代碼)

參考資料:

https://www.chainnews.com/articles/502750353901.htm

https://prylabs.net/participate

https://github.com/ethereum/eth2.0-specs/blob/dev/deposit_contract/contracts/validator_registration.vy

https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md

https://ethos.dev/beacon-chain/

來源鏈接:mp.weixin.qq.com