6

mina - 源代码导读(基础篇)

 3 years ago
source link: https://learnblockchain.cn/article/2521
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
mina - 源代码导读(基础篇) | 登链社区 | 深入浅出区块链技术

Mina链上的区块数据永远“压缩”在22k。Mina是一种新的区块链形式,解决了一般链式区块链数据爆炸的问题。Mina采用PoS的共识机制,“最长/最重”链确认为主链。为了压缩链上的数据,Mina世界状态包括证明(Snarked)和非证明(Staged)的状态。非证明的状态通过链下的零知识证明的计算生成证明,证明证明状态正确。

Mina是个有意思的项目。链上的区块数据永远“压缩”在22k。Mina是一种新的区块链形式,解决了一般链式区块链数据爆炸的问题。看别人的设计,是一种享受。看别人创造出一种新的思路,新事物,真心感叹,是一种美。很早就想看看Mina的设计,网络上的资料几乎都是一些简单的宣传资料,不够深入。这些资料不足以领略Mina的技术。

https://minaprotocol.com

在查看Mina源代码之前,建议看看Mina的技术文档:

https://docs.minaprotocol.com

Mina项目是链下验证,链上计算的极致体现。在链上永远只存储最新的证明信息。一些显而易见的问题就出现在脑海中,Coda的共识机制是什么?链上的证明信息证明了什么?当出现新的区块的时候,如何证明新的区块的合法性?如何结合链上的证明,生成新的证明?带着问题看代码。

内容比较多,分成两篇文章。这篇是基础篇,讲述链的结构以及共识,后面会详细分析零知识证明技术在mina中的应用原理和相关细节。

本文中使用的Mina代码的最后一次提交信息如下:

commit ce31b9dfe5f8e148837b4bf2cd19de6f97da663b (HEAD -> develop, origin/develop, origin/HEAD)
Merge: 49ef908cd 7f46d759e
Author: Matthew Ryan <[email protected]>
Date:   Tue Mar 9 01:20:24 2021 +0000

    Merge pull request #8172 from MinaProtocol/compatible
    
    Merge back to develop

Mina项目使用OCaml语言开发。对OCaml语言不熟悉的小伙伴,可以查看以下链接学习OCaml语言。

http://dev.realworldocaml.org/toc.html

https://ocaml.org/learn/tutorials/index.zh.html

01 Address (地址管理)

介绍账户管理之前简单介绍一下Tick/Tock。Tick/Tock是Pickles证明系统两条曲线的代号。Tick和Tock两条曲线的Fp和Fq是对称的。账户的创建基于Tick曲线。

私钥是在Tick的Scalar范围内的随机数。原始公钥是私钥对应的Tick曲线上的点。真实公钥是三者数据的base58编码结果:1/ 版本信息 2/ 原始公钥信息 3/ 原始公钥的checksum信息。具体的逻辑可以查看lib/cli_lib/commands.ml的generate_keypair函数。

02 Transaction(交易)

交易分为几种类型,比较容易理解:

  • User Command (常见交易)

    User Command又分为两种:1/ 一种是普通交易 2/ 一种是snapp交易。

  • Payment(支付交易)

  • Stake_delegation(质押交易)

  • Create_new_token(创建新代币交易)

  • Create_token_account (创建代币对应账户)

  • Mint_tokens (代币转账)

  • 普通交易:

  • snapp交易

  • Fee Transfer (交易费用转账交易)

  • Coinbase (铸币交易)

详细的逻辑可以查看lib/mina_base/transaction.ml中的定义。

交易签名采用的是schorr算法,详细请查看lib/signature_lib/schnorr.ml的sign函数。

03 Account (账户管理)

一个账户信息定义在lib/mina_base/account.ml,由如下信息组成:

  • 基本信息
  • 包括公钥信息,Nonce信息
  • 权限信息(是否具有发送/接收权限,是否具有质押权限等等)
  • 时效信息(是否有时效?释放的时间参数等等),具体查看lib/mina_base/account_timing.ml。
  • Token信息
  • 包括Token编号,权限以及余额。Token编号从1开始 (defult token - mina)。
  • Receipt chain hash信息
  • 所有交易的信息的hash信息
  • 质押信息
  • 质押账户的公钥信息等等
  • Snapp信息

如果一个账户发送了若干个交易,这些交易是t_1,t_2,... t_n。每个交易对应的hash值为p_1,p_2,... p_n。Receipt chain hash信息的计算方式如下:
rn​=h(pn​,h(pn−1​,...))

简单的说,就是把历史交易信息的hash链起来,最后的hash记录在账户信息上。

04 Protocol State(世界状态)

mina的世界状态定义在lib/mina_state/protocol_state.ml。整个世界状态由四个状态组成:1/ genesis_state_hash(创世纪的状态)2/ blockchain_state (链的状态)3/consensus_state (共识状态)4/ constants(参数信息)。

module Poly = struct
   [%%versioned
   module Stable = struct
     module V1 = struct
       type ('state_hash, 'body) t =
         {previous_state_hash: 'state_hash; body: 'body}
       [@@deriving eq, ord, hash, sexp, yojson, hlist]
     end
   end]
 end
 
 module Body = struct
   module Poly = struct
     [%%versioned
     module Stable = struct
       module V1 = struct
         type ('state_hash, 'blockchain_state, 'consensus_state, 'constants) t =
           { genesis_state_hash: 'state_hash
           ; blockchain_state: 'blockchain_state
           ; consensus_state: 'consensus_state
           ; constants: 'constants }
         [@@deriving sexp, eq, compare, yojson, hash, version, hlist]
       end
     end]
   end

为了链接上一个区块的状态,世界状态表示为之前一个区块的世界状态和当前世界状态的hash值。

genesis_state_hash比较容易理解,就是创世纪的世界状态。先重点看看block_chain_state,共识状态在共识部分再介绍。block_chain_state中除了genesis ledger状态外,还包括如下的两个状态:

Staged ledger状态

所有的账户信息先通过Poseidon Hash计算,并且形成Merkle树。树根就代表当前所有账户的状态。这个状态也称为"Staged ledger"。

Snarked ledger状态

除了构建Staged ledger,mina还需要对当前的所有账户信息进行证明。由于证明的计算性能比较慢,为了避免证明计算性能拖慢整个链的性能,mina设计了snarked ledger。所谓的snarked ledger,就是已经证明过的所有账户信息。

05 Scan State (Parallel Scan State)

交易对应的状态变化的证明生成过程,称为Scan State。Scan State由多棵Merkle树组成,每棵Merkle树证明若干个交易的状态变化。

两个变量决定了Merkle树的个数以及每棵Merkle树的结点个数。

  • transaction_capacity_log_2
  • work_delay

每棵Merkle树的结点个数为:

max_no_of_transactions = 2^{transaction_capacity_log_2}

总共的Merkle树的个数为:

max_number_of_trees = (transaction_capacity_log_2 + 1) * (work_delay + 1) + 1

work_delay表明有多少棵Merkle树可以作为“buffer”,在work_delay内,可以不提供交易的证明。也就是说,超过了work_delay的交易,必须在生成区块时包含需要的交易证明。如果不能在区块中提供需要的证明,则区块中也不能添加交易。也就是说,在这种情况下,出的是“空块“(空块没有区块奖励)。

每棵树的叶子结点是对单个交易的证明,这样的证明任务称为base。每棵树的中间结点是两个证明的证明,这样的证明任务称为merge(将多个证明合并)。Scan State的叶子结点(交易)的证明可以并行处理,所以也称为Parallel Scan State。Base/Merge定义在lib/transaction_snark/transaction_snark.ml。

注意的是,Scan State上的交易并没有区块的概念。区块确定交易的顺序后,Scan State只需要按照顺序进行相应的证明即可。

06 Transition(区块)

每三分钟出一个区块。区块时间分成:Epoch和Slot。一个Epoch分为7140个Slot。区块分为两种:一种是External Transition,外部区块(从网络同步)和另外一种是Internal Transition,内部区块(节点自己创建的区块)。这两种区块的定义在lib/mina_transition/external_transition.ml和lib/mina_transition/internal_transition.ml。

External Transition

module Stable = struct                                                                            
    module V1 = struct
      type t =                                                                                      
        { protocol_state: Protocol_state.Value.Stable.V1.t                                          
        ; protocol_state_proof: Proof.Stable.V1.t sexp_opaque                                       
        ; staged_ledger_diff: Staged_ledger_diff.Stable.V1.t
        ; delta_transition_chain_proof:                                                             
            State_hash.Stable.V1.t * State_body_hash.Stable.V1.t list                               
        ; current_protocol_version: Protocol_version.Stable.V1.t                                    
        ; proposed_protocol_version_opt: Protocol_version.Stable.V1.t option                        
        ; mutable validation_callback: Validate_content.t }                                         
      [@@deriving sexp, fields]

External Transition主要包括两个重要信息:1/ protocol state以及相应的证明 2/ Transition中包含的交易信息(staged_ledger_diff)。其中的delta_transition_chain_proof是区块创建时的之前的区块状态转换信息。

Internal Transition

Internal Transition也需要包括区块中的交易信息(staged_ledger_diff)。除此之外,还包括:snark_transition(链的状态变化,包括链的状态和共识), ledger_proof(Snarked ledger的证明信息)和prover_state(共识相关的信息)。

module Stable = struct
   [@@@no_toplevel_latest_type]
 
   module V1 = struct
     type t =
       { snark_transition: Snark_transition.Value.Stable.V1.t
       ; ledger_proof: Ledger_proof.Stable.V1.t option
       ; prover_state: Consensus.Data.Prover_state.Stable.V1.t
       ; staged_ledger_diff: Staged_ledger_diff.Stable.V1.t }
 
     let to_latest = Fn.id
   end
 end]

Consensus.Data.Prover_state.Stable.V1.t定义在lib/consensus/stake_proof.ml:

[%%versioned
 module Stable = struct
   [@@@no_toplevel_latest_type]
 
   module V1 = struct
     type t =
       { delegator: Account.Index.Stable.V1.t
       ; delegator_pk: Public_key.Compressed.Stable.V1.t
       ; coinbase_receiver_pk: Public_key.Compressed.Stable.V1.t
       ; ledger: Sparse_ledger.Stable.V1.t
       ; producer_private_key: Private_key.Stable.V1.t
       ; producer_public_key: Public_key.Stable.V1.t }
 
     let to_latest = Fn.id
   end
 end]

Prover_state包括了delegator的账户信息,区块生成者的账户信息以及coinbase的接受者的信息。

07 Transition Frontier Controller

Transition Frontier Controller是核心逻辑。Transition Frontier Controller维护了整个链的世界状态,包括了如下的模块:

  • Transition Handler - 区块的验证(Validator)和处理(Processor)
  • Ledger Catchup - 区块同步逻辑
  • Transition Frontier - 维护区块的“cache”,区块和区块之间的依赖关系以及确定最长链。
  • Query Handler - 区块查询

这些模块和模块之间通过“pipe”异步通讯。

breadcrumb

lib/transition_frontier/frontier_base/breadcrumb.ml

type t =
     { validated_transition: External_transition.Validated.t
     ; staged_ledger: Staged_ledger.t sexp_opaque
     ; just_emitted_a_proof: bool
     ; transition_receipt_time: Time.t option }

breadcrumb是一个“wrapper”,主要包括了验证过的transition信息,区块中的交易执行后的账本信息以及区块时间信息。最近的一些区块breadcrumb组织成树状结构(KTree)。树上的每个节点定义为(lib/transition_frontier/full_frontier/full_frontier.ml):

module Node = struct
   type t =
     {breadcrumb: Breadcrumb.t; successor_hashes: State_hash.t list; length: int}

每个节点由一个breadcrumb组成,同时记录了该breadcrumb后续的状态以及个数。

当一个breadcrumb需要添加到Transition Frontier时,需要确认主链以及确定区块状态。对应的逻辑在lib/transition_frontier/full_frontier/full_frontier.ml的calculate_diffs函数中:

let diffs =
         if
           Consensus.Hooks.select
             ~constants:t.precomputed_values.consensus_constants
             ~existing:(Breadcrumb.consensus_state_with_hash current_best_tip)
             ~candidate:(Breadcrumb.consensus_state_with_hash breadcrumb)
             ~logger:
               (Logger.extend t.logger
                  [ ( "selection_context"
                    , `String "comparing new breadcrumb to best tip" ) ])
           = `Take
         then Full.E.E (Best_tip_changed breadcrumb_hash) :: diffs
         else diffs

Consensus.Hooks.select函数就是进行主链的确认,实现在lib/consensus/proof_of_stake.ml中:

let less_than_or_equal_when a b ~compare ~condition =
       let c = compare a b in
       c < 0 || (c = 0 && condition)
     in
     let candidate_hash_is_bigger =
       Mina_base.State_hash.(candidate_hash > existing_hash)
     in
     let candidate_vrf_is_bigger =
       let string_of_blake2 = Blake2.(Fn.compose to_raw_string digest_string) in
       let compare_blake2 a b =
         String.compare (string_of_blake2 a) (string_of_blake2 b)
       in
       less_than_or_equal_when existing.last_vrf_output
         candidate.last_vrf_output ~compare:compare_blake2
         ~condition:candidate_hash_is_bigger
     in
     let blockchain_length_is_longer =
       less_than_or_equal_when existing.blockchain_length
         candidate.blockchain_length ~compare:Length.compare
         ~condition:candidate_vrf_is_bigger
     in
     let long_fork_chain_quality_is_better =
       let max_slot =
         Global_slot.max candidate.curr_global_slot existing.curr_global_slot
       in
       let virtual_min_window_density (s : Consensus_state.Value.t) =
         if Global_slot.equal s.curr_global_slot max_slot then
           s.min_window_density
         else
           Min_window_density.update_min_window_density ~incr_window:false
             ~constants ~prev_global_slot:s.curr_global_slot
             ~next_global_slot:max_slot
             ~prev_sub_window_densities:s.sub_window_densities
             ~prev_min_window_density:s.min_window_density
           |> fst
       in
       less_than_or_equal_when
         (virtual_min_window_density existing)
         (virtual_min_window_density candidate)
         ~compare:Length.compare ~condition:blockchain_length_is_longer
     in
     let precondition_msg, choice_msg, should_take =
       if is_short_range existing candidate ~constants then
         ( "most recent finalized checkpoints are equal"
         , "candidate length is longer than existing length "
         , blockchain_length_is_longer )
       else
         ( "most recent finalized checkpoints are not equal"
         , "candidate virtual min-length is longer than existing virtual \
            min-length"
         , long_fork_chain_quality_is_better )
     in

主链的确定逻辑如下:

  • window density最大优先
  • 区块长度最长优先
  • VRF的计算结果大者优先
  • State Hash大者优先

08 Consensus(共识算法)

Mina采用的是PoS共识算法,相关逻辑实现在src/lib/consensus/proof_of_stake.ml。针对每个质押账户计算VRF结果,如果VRF结果满足一定的条件,节点即可出块。

VRF计算

针对每个质押账户,结合质押账户的公钥信息/区块高度和节点的私钥信息,采用Poseidon算法生成VRF。

Hashtbl.iteri
( Snapshot.delegators epoch_snapshot public_key_compressed
     |> Option.value ~default:(Core_kernel.Int.Table.create ()) )
     ~f:(fun ~key:delegator ~data:account ->
     let vrf_result =
     T.eval ~constraint_constants ~private_key
     {global_slot; seed; delegator}
     in
           
let eval ~constraint_constants ~private_key m =                                                   
let h = Message.hash_to_group ~constraint_constants m in                                        
let u = Group.scale h private_key in                                                            
Output_hash.hash ~constraint_constants m u

VRF的计算方法如下:

简单的说,如果VRF的结果“大于”节点质押占比,则该节点拥有出块权。

vrf_output / 2^256 <= c * (1 - (1 - f)^(amount / total_stake))

Mina链上的区块数据永远“压缩”在22k。Mina是一种新的区块链形式,解决了一般链式区块链数据爆炸的问题。Mina采用PoS的共识机制,“最长/最重”链确认为主链。为了压缩链上的数据,Mina世界状态包括证明(Snarked)和非证明(Staged)的状态。非证明的状态通过链下的零知识证明的计算生成证明,证明证明状态正确。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK