精华内容
下载资源
问答
  • 以太坊的共识机制

    千次阅读 2018-08-13 14:50:49
    在开始之前,我们补充一点基础知识。   第一个概念是哈希。简单理解,哈希是一个函数。它的作用是将任意长度的数据作为输入,转变为固定长度的一个字符串作为输出。...第二个补充知识是,以太坊的区块结构。...

    在开始之前,我们补充一点基础知识。

     

    第一个概念是哈希。简单理解,哈希是一个函数。它的作用是将任意长度的数据作为输入,转变为固定长度的一个字符串作为输出。这个函数有两个主要特点

    1. 过程不可逆
    2. 对输入做微小改动,输出就会完全不一样。

    哈希函数有好多种,但都满足上面的特点。几乎任何加密货币都会用到哈希算法,以太坊采用的哈希算法是ethash算法

     

    第二个补充知识是,以太坊的区块结构。一个以太坊区块包含区块头和区块内容

    区块内容就是区块所包含的交易列表。而区块头中包含了如下信息:

    前一个区块的哈希、区块序号(n)、随机数(nonce)、目标值(target)、时间戳(timestamp)、难度值(difficulty)、矿工地址(address)等内容

     

    好了,介绍完上述基础,我们正式开始本文的内容。

     

    以太坊共有四个阶段,即Frontier(前沿)、Homestead(家园)、Metropolis(大都会)、Serenity(宁静)。以太坊前三个阶段采用的是POW共识机。第四个阶段将采用自己创建的POS机制,名为Casper投注共识,这种机制增加了惩罚机制,并基于POS的思想在记账节点中选取验证人。

     

    首先介绍以太坊前三阶段使用的POW机制。我们在上节课中讲到,POW机制的基本原理是下面这个公式:

    计算值<目标值

     

    下面我们具体来看。在以太坊中

    f(h, n) <M / d

     

    我们先看目标值

    M / d

    其中M是一个常数,数值非常非常大,取2^256-1

    d是当前区块的难度值,

    d=pd + pd//2048*max( 1-(t-pt)//10,-99 ) + int(  2** (n//100000-2)  )

    其中,pd:前一个区块的难度值

    t:当前区块的时间戳

    pt:前一个区块的时间戳

    n:当前区块的区块序号

    其中//为整数除法运算符,a//b,即先计算a/b,然后取不大于a/b的最大整数。

    调整难度的目的,是为了使挖矿时间保持在10-19s期间内,如果低于10s增大挖矿难度,如果大于19s将减小难度。另外,计算出的当前区块难度不应低于以太坊创世区块难度,即131072

     

    难度系数变大,目标值M / d就越小,所以要计算出满足要求的计算值就越难,这就是难度系数的意思。

     

    再看计算值f(h, n)f()这个函数表示一个概念函数,代表一系列的复杂运算。其中输入参数,hn分别是,区块头Header的哈希值、以及Header中的随机数Nonce

    计算机的整个挖矿过程,大致可以认为是计算机不断调整尝试Nonce的值,再将计算出的f(h, n)与目标值比较。这就是工作量证明,证明你一直在进行计算工作。因为我们默认,只有你在不算尝试计算,才能找到那个对的nonce。虽然这其中有一定的运气成分,但我们现实生活中也有很多地方是看结果来证明你的工作量,这很好理解啊,你拿到了文凭,我就默认你花过时间学习了。

    这就是以太坊的pow过程,具体的源代码,详见文末。

     

    好,我们接下来看以太坊第四阶段的共识机制casper。这就是pos的一种。但它有自己的特点:

    在有些情况下,矿工的币不但不会增加,反而会减少。为什么呢,我们来看看

     

    在casper共识机制中,矿工要拿出保证金对他认为的大概率胜出的区块进行下注。如果赌对了,他们就可以拿回保证金外加区块中的交易费用,也许还会有一些新发的货币;如果下注没有迅速达成一致,他们只能拿回部分保证金,相当于损失了一些保证金。因此数个回合之后矿工的下注分布就会收敛。一旦结果出来,那些选错的矿工就会输掉他们的保证金。此外如果矿工过于显著的改变下注,例如先是赌某个块有很高概率胜出,然后又改赌另外一个块有高概率胜出,他将被严惩。如此朝三暮四的人在区块链中也是不受欢迎的啊。这条规则确保了矿工只有在非常确信其他人也认为某个块有高概率胜出时才下注。我们通过这个机制来确保不会出现下注先收敛于一个结果然后又收敛到另外一个结果的情况。(将该段的情况简单体现在一个PPT页面里)。这就是casper的大致工作机理,由于以太坊目前还没有发布第四个版本,更多细节以后有机会再交流。

     

     

     

    源代码:

    func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) {

        // Extract some data from the header

        var (

            header = block.Header()

            hash   = header.HashNoNonce().Bytes()

            //target,即M / d,即(2^256-1)/Difficulty

            target = new(big.Int).Div(maxUint256, header.Difficulty)

     

            number  = header.Number.Uint64()

            dataset = ethash.dataset(number)

        )

        // Start generating random nonces until we abort or find a good one

        var (

            attempts = int64(0)

            nonce    = seed

        )

        logger := log.New("miner", id)

        logger.Trace("Started ethash search for new nonces", "seed", seed)

        for {

            select {

            case <-abort:

                // Mining terminated, update stats and abort

                logger.Trace("Ethash nonce search aborted", "attempts", nonce-seed)

                ethash.hashrate.Mark(attempts)

                return

     

            default:

                // We don‘t have to update hash rate on every nonce, so update after after 2^X nonces

                attempts++

                if (attempts % (1 << 15)) == 0 {

                    ethash.hashrate.Mark(attempts)

                    attempts = 0

                }

                //hashimotoFullRAND(h, n)所代表的一系列的复杂运算

                digest, result := hashimotoFull(dataset, hash, nonce)

                //result满足RAND(h, n)  <=  M / d

                if new(big.Int).SetBytes(result).Cmp(target) <= 0 {

                    // Correct nonce found, create a new header with it

                    header = types.CopyHeader(header)

                    header.Nonce = types.EncodeNonce(nonce)

                    header.MixDigest = common.BytesToHash(digest)

     

                    // Seal and return a block (if still needed)

                    select {

                    case found <- block.WithSeal(header):

                        logger.Trace("Ethash nonce found and reported", "attempts", nonce-seed, "nonce", nonce)

                    case <-abort:

                        logger.Trace("Ethash nonce found but discarded", "attempts", nonce-seed, "nonce", nonce)

                    }

                    return

                }

                //不断变更Nonce

                nonce++

            }

        }

    }

    //代码位置consensus/ethash/sealer.go

     

    想了解更多?关注我们就够了。

    公众号:ipfscom
    微信(Q):18191727
    Q群:71789361
    微信扫一扫,加入知识星球


     

    展开全文
  • 18 以太坊的共识机制

    2021-01-08 04:26:18
    以太坊的共识机制 以太坊把出块时间降低到十几秒,但是这样也带来了很多问题,较频繁的出现分叉,这对于共识协议来说有什么影响呢? 在比特币中,只有最长合法链的中才是合法链, GHOST协议:假如一条区块链没有成为...
  • 以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。 Clique的初始化在 Ethereum.StartMining中,如果...
        

    以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。

    Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。

    func (s *Ethereum) StartMining(local bool) error {
        eb, err := s.Etherbase()//用户地址
        if err != nil {
            log.Error("Cannot start mining without etherbase", "err", err)
            return fmt.Errorf("etherbase missing: %v", err)
        }
    
        if clique, ok := s.engine.(*clique.Clique); ok {
            //如果是clique共识算法
            wallet, err := s.accountManager.Find(accounts.Account{Address: eb})    // 根据用它胡地址获取wallet对象
            if wallet == nil || err != nil {
                log.Error("Etherbase account unavailable locally", "err", err)
                return fmt.Errorf("signer missing: %v", err)
            }
            clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
        }
        if local {
            // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
            atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
        }
        go s.miner.Start(eb)
        return nil
    }

    这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。

    Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:

    type Engine interface {
        Author(header *types.Header) (common.Address, error)
    
        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
    
        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)
    
        VerifyUncles(chain ChainReader, block *types.Block) error
    
        VerifySeal(chain ChainReader, header *types.Header) error
    
        Prepare(chain ChainReader, header *types.Header) error
    
        Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
            uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)
    
        Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error)
    
        CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int
    
        APIs(chain ChainReader) []rpc.API
    }

    Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:

    type Clique struct {
        config *params.CliqueConfig // 共识引擎配置参数
        db     ethdb.Database       // 数据库,用来存储和获取快照检查点
        recents    *lru.ARCCache // 最近区块快照,加速快照重组
        signatures *lru.ARCCache // 最近区块签名,加速挖矿
        proposals map[common.Address]bool // 目前正在推送的提案
        signer common.Address // 签名者的以太坊地址
        signFn SignerFn       // 授权哈希的签名方法
        lock   sync.RWMutex   // 用锁来保护签名字段
    }

    顺便来看下CliqueConfig共识引擎的配置参数结构体:

    type CliqueConfig struct {
        Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
        Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    }

    在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:

    func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
        c.lock.Lock()
        defer c.lock.Unlock()
        // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
        c.signer = signer
        c.signFn = signFn
    }

    再来看Clique的Seal()函数的具体实现:

    //通过本地签名认证创建已密封的区块
    func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
        header := block.Header()
    
        // 不密封创世块
        number := header.Number.Uint64()
        if number == 0 {
            return nil, errUnknownBlock
        }
        // 不支持0-period的链,不支持空块密封,没有奖励但是能够密封
        if c.config.Period == 0 && len(block.Transactions()) == 0 {
            return nil, errWaitTransactions
        }
        // 在整个密封区块的过程中不要持有signer签名者字段
        c.lock.RLock()
        signer, signFn := c.signer, c.signFn //获取签名者和签名方法
        c.lock.RUnlock()
    
        snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) //调用获取快照
        if err != nil {
            return nil, err
        }
      //检查我们是否被授权去签名一个区块
        if _, authorized := snap.Signers[signer]; !authorized {
            return nil, errUnauthorized
        }
        // 如果我们是在‘最近签名者’中则等待下一个区块
        for seen, recent := range snap.Recents {
            if recent == signer {
                // 当前签名者在‘最近签名者’中,如果当前区块没有剔除他的话只能等待(这里涉及到机会均等)
                if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
                    log.Info("Signed recently, must wait for others")
                    <-stop
                    return nil, nil
                }
            }
        }
        // 好了,走到这说明协议已经允许我们来签名这个区块,等待我们的时间
        delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple
        if header.Difficulty.Cmp(diffNoTurn) == 0 {
            // 这不是我们的轮次来签名,延迟一点,随机延迟,这样对于每一个签签名者来说来允许并发签名
            wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime
            delay += time.Duration(rand.Int63n(int64(wiggle)))
    
            log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
        }
        log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
    
        select {
        case <-stop:
            return nil, nil
        case <-time.After(delay):
        }
        // 通过signFn签名函数开始签名
        sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes())
        if err != nil {
            return nil, err
        }
        //将签名结果替换保存在区块头的Extra字段中
        copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
        //通过区块头重新组装生成一个区块
        return block.WithSeal(header), nil
    }

    Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名

    • signer不在snapshot的signer中不允许签名
    • signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
    • 签名存放在Extra的extraSeal的65个字节中

    关于机会均等
    为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。
    其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。

    //snap.Signers是所有的认证节点
    for seen, recent := range snap.Recents {
        if recent == signer {
            if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
                log.Info("Signed recently, must wait for others")
                <-stop
                return nil, nil
            }
        }
    }

    在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。

    关于难度计算
    为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。

    diffInTurn = big.NewInt(2) 
    diffNoTurn = big.NewInt(1) 

    当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。
    判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:

    
    // 通过给定的区块高度和签发者返回该签发者是否在轮次内
    func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
        signers, offset := s.signers(), 0
        for offset < len(signers) && signers[offset] != signer {
            offset++
        }
        return (number % uint64(len(signers))) == uint64(offset)
    }
    

    Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:

    // Snapshot对象是在给定时间点的一个认证投票的状态
    type Snapshot struct {
        config   *params.CliqueConfig // 共识引擎配置参数
        sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
        Number  uint64                      `json:"number"`  // 快照建立的区块号
        Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
        Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表
        Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址
        Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
        Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
    }

    快照Snapshot对象中存在投票的Votes和记票的Tally对象:

    // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
    type Vote struct {
        Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
        Block     uint64         `json:"block"`     // 投票区块号
        Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权
        Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    }
    
    // Tally是一个简单的用来保存当前投票分数的计分器
    type Tally struct {
        Authorize bool `json:"authorize"` // 授权true或移除false
        Votes     int  `json:"votes"`     // 该提案已获票数
    }

    Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map
    loadSnapshot用来从数据库中加载一个已存在的快照:

    func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
        //使用Database接口的Get方法通过Key来查询缓存内容
        blob, err := db.Get(append([]byte("clique-"), hash[:]...))
        if err != nil {
            return nil, err
        }
        snap := new(Snapshot)
        if err := json.Unmarshal(blob, snap); err != nil {
            return nil, err
        }
        snap.config = config
        snap.sigcache = sigcache
    
        return snap, nil
    }

    newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:

    func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
        //组装一个Snapshot对象
        snap := &Snapshot{
            config:   config,
            sigcache: sigcache,
            Number:   number,
            Hash:     hash,
            Signers:  make(map[common.Address]struct{}),
            Recents:  make(map[uint64]common.Address),
            Tally:    make(map[common.Address]Tally),
        }
        for _, signer := range signers {
            snap.Signers[signer] = struct{}{}
        }
        return snap
    }

    继续看下snapshot函数的具体实现:

    // 快照会在给定的时间点检索授权快照
    func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
        // 在内存或者磁盘上查找一个快照来检查检查点checkpoints
        var (
            headers []*types.Header    //区块头
            snap    *Snapshot    //快照对象
        )
        for snap == nil {
            // 如果在内存中找到快照时,快照对象从内存中取
            if s, ok := c.recents.Get(hash); ok {
                snap = s.(*Snapshot)
                break
            }
            // 如果在磁盘检查点找到快照时
            if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
                if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
                    log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
                    snap = s
                    break
                }
            }
            // 如果在创世块,则新建一个快照
            if number == 0 {
                genesis := chain.GetHeaderByNumber(0)
                if err := c.VerifyHeader(chain, genesis, false); err != nil {
                    return nil, err
                }
                signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
                for i := 0; i < len(signers); i++ {
                    copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:])
                }
                snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers)
                if err := snap.store(c.db); err != nil {
                    return nil, err
                }
                log.Trace("Stored genesis voting snapshot to disk")
                break
            }
            // 没有对于这个区块头的快照,收集区块头并向后移
            var header *types.Header
            if len(parents) > 0 {
                // 如果我们有明确的父,从那里挑选(强制执行)
                header = parents[len(parents)-1]
                if header.Hash() != hash || header.Number.Uint64() != number {
                    return nil, consensus.ErrUnknownAncestor
                }
                parents = parents[:len(parents)-1]
            } else {
                // 没有明确的父(或者没有更多的父)转到数据库获取
                header = chain.GetHeader(hash, number)
                if header == nil {
                    return nil, consensus.ErrUnknownAncestor
                }
            }
            headers = append(headers, header)
            number, hash = number-1, header.ParentHash
        }
        // 找到了之前的快照,将所有的pedding块头放在它上面
        for i := 0; i < len(headers)/2; i++ {
            headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i]
        }
        snap, err := snap.apply(headers) //通过区块头生成一个新的快照
        if err != nil {
            return nil, err
        }
        c.recents.Add(snap.Hash, snap) //将当前区块的区块hash保存到最近区块快照,加速快照重组
    
        // 如果我们已经生成一个新的检查点快照,保存在磁盘上
        if snap.Number%checkpointInterval == 0 && len(headers) > 0 {
            if err = snap.store(c.db); err != nil {
                return nil, err
            }
            log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
        }
        return snap, err
    }

    在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?

    //apply将给定的区块头应用于原始头来创建新的授权快照。
    func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
          //可以传空区块头
        if len(headers) == 0 {
            return s, nil
        }
          //完整性检查区块头可用性
        for i := 0; i < len(headers)-1; i++ {
            if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 {
                return nil, errInvalidVotingChain
            }
        }
        if headers[0].Number.Uint64() != s.Number+1 {
            return nil, errInvalidVotingChain
        }
          //迭代区块头,创建一个新的快照
        snap := s.copy()
        // 投票的处理核心代码
        for _, header := range headers {
            // 删除检查点区块的所有投票 
            number := header.Number.Uint64()
            // 如果区块高度正好在Epoch结束,则清空投票和计分器,避免了维护统计信息无限增大的内存开销;
            if number%s.config.Epoch == 0 {
                snap.Votes = nil
                snap.Tally = make(map[common.Address]Tally)
            }
              //从最近的签名者列表中删除最旧的签名者以允许它再次签名
            if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
                delete(snap.Recents, number-limit)
            }
            // 从区块头中解密出来签名者地址
            signer, err := ecrecover(header, s.sigcache)
            if err != nil {
                return nil, err
            }
            if _, ok := snap.Signers[signer]; !ok {
                return nil, errUnauthorized
            }
            for _, recent := range snap.Recents {
                if recent == signer {
                    return nil, errUnauthorized
                }
            }
            snap.Recents[number] = signer
    
            // 区块头认证,不管该签名者之前的任何投票
            for i, vote := range snap.Votes {
                if vote.Signer == signer && vote.Address == header.Coinbase {
                    // 从缓存计数器中移除该投票
                    snap.uncast(vote.Address, vote.Authorize)
    
                    // 从按时间排序的列表中移除投票
                    snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
                    break // 只允许一票
                }
            }
            // 从签名者中计数新的投票
            var authorize bool
            switch {
            case bytes.Equal(header.Nonce[:], nonceAuthVote):
                authorize = true
            case bytes.Equal(header.Nonce[:], nonceDropVote):
                authorize = false
            default:
                return nil, errInvalidVote
            }
            if snap.cast(header.Coinbase, authorize) {
                snap.Votes = append(snap.Votes, &Vote{
                    Signer:    signer,
                    Block:     number,
                    Address:   header.Coinbase,
                    Authorize: authorize,
                })
            }
            // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
            if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
                if tally.Authorize {
                    snap.Signers[header.Coinbase] = struct{}{}
                } else {
                    delete(snap.Signers, header.Coinbase)
                      // 签名者列表缩减,删除最近剩余的缓存
                    if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
                        delete(snap.Recents, number-limit)
                    }
                    for i := 0; i < len(snap.Votes); i++ {
                        if snap.Votes[i].Signer == header.Coinbase {                                    
                              // 从缓存计数器中移除该投票
                            snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize)
                              // 从按时间排序的列表中移除投票    
                            snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    
                            i--
                        }
                    }
                }
                // 不管之前的任何投票,直接改变账户
                for i := 0; i < len(snap.Votes); i++ {
                    if snap.Votes[i].Address == header.Coinbase {
                        snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
                        i--
                    }
                }
                delete(snap.Tally, header.Coinbase)
            }
        }
        snap.Number += uint64(len(headers))
        snap.Hash = headers[len(headers)-1].Hash()
    
        return snap, nil
    }

    Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。

    区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。

    // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
    func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
        // 不支持校检创世块
        number := header.Number.Uint64()
        if number == 0 {
            return errUnknownBlock
        }
        // 检索出所需的区块对象来校检去开头和将其缓存
        snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
        if err != nil {
            return err
        }
    
        //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址
        signer, err := ecrecover(header, c.signatures)
        if err != nil {
            return err
        }
        if _, ok := snap.Signers[signer]; !ok {
            return errUnauthorized
        }
        for seen, recent := range snap.Recents {
            if recent == signer {
                // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等
                if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
                    return errUnauthorized
                }
            }
        }
        // 设置区块难度,参见上面的区块难度部分
        inturn := snap.inturn(header.Number.Uint64(), signer)
        if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
            return errInvalidDifficulty
        }
        if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
            return errInvalidDifficulty
        }
        return nil
    }

    前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?

    Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:

    • 委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中
    // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    func (api *API) Propose(address common.Address, auth bool) {
        api.clique.lock.Lock()
        defer api.clique.lock.Unlock()
    
        api.clique.proposals[address] = auth// true:授权,false:移除
    }
    
    • 本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;
    //Clique.Prepare
    
            // 抓取所有有意义投票的提案
            addresses := make([]common.Address, 0, len(c.proposals))
            for address, authorize := range c.proposals {
                if snap.validVote(address, authorize) {
                    addresses = append(addresses, address)
                }
            }
            // If there's pending proposals, cast a vote on them
            if len(addresses) > 0 {
                header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。
                // 通过提案内容来组装区块头的随机数字段。
                if c.proposals[header.Coinbase] {
                    copy(header.Nonce[:], nonceAuthVote)
                } else {
                    copy(header.Nonce[:], nonceDropVote)
                }
            }

    在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare

        if err := self.engine.Prepare(self.chain, header); err != nil {
            log.Error("Failed to prepare header for mining", "err", err)
            return
        }
    • 其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法

    转载请注明: 转载自Ryan是菜鸟 | LNMP技术栈笔记

    如果觉得本篇文章对您十分有益,何不 打赏一下

    谢谢打赏

    本文链接地址: 以太坊POA共识机制Clique源码分析

    展开全文
  • 从比特币诞生以来,POW设计理念成为数字货币和区块链主流共识机制,但是由于其对能源消耗巨大,2013年点点币(Peercoin)系统中提出并实现了另一种共识机制——权益证明。目前点点币和量子链等都采用POS共识...

    部分原创,部分转自https://www.jianshu.com/p/953558bb5e64

    区块链共识技术二:pos共识机制

    从比特币诞生以来,POW的设计理念成为数字货币和区块链的主流共识机制,但是由于其对能源的消耗巨大,2013年点点币(Peercoin)系统中提出并实现了另一种共识机制——权益证明。目前点点币和量子链等都采用POS共识机制,ETH正在积极探索对POS的应用。

    POS最早出现于NXT社区,该社区出来很多现在很牛的技术讨论,比如IOTA的DAG也是出自于此,打算之后重点关注下。

    POS(proof of stake,),即权益证明,类似于股东机制,没有挖矿过程,在创世区块内写明了股权分配比例,之后通过不断地转让和交易,逐渐分散到不同的用户手里,并通过利息的方式产生新增货币,实现对节点的奖励。

    1、 pos的实现原理及公式

          POS的公式为:

            hash(block_header) =<target * coinage

            币龄的计算:coinage = 币的个数*币的剩余使用时间

            其中,coinage表示币龄,这将意味着,币龄越大,越容易得到答案。而其中币龄的计算是通过挖矿者拥有的币乘以每个币的剩下使用时间得到,这也将意味着拥有的币越多,也越容易得到答案。这样,pos解决了pow中浪费资源的问题,同时挖矿者不可能拥有全网51%的币,所以也解决了51%攻击的问题。

    2、pos的缺陷

            pos的实现原理和实现算法公式上分析,pos很完美的解决了pow的算力以及51%攻击问题,那么,pos是不是真的就很完美了呢?

        缺陷一:

           我们再从pos的实现算法公式去看,币龄的计算公式中,假如一开始挖矿,只有创始区块中有币,也就是说其他矿机是没法参与挖矿的,因为币的个数这个值对他们来说永远是零,这也就是pos机制的缺陷之一币无法发行的问题。

        缺陷二:

            同样是从pos的实现算法公式可以分析到,币龄其实就是时间,一旦挖矿者囤积一定的币,很久很久之后发起攻击,这样他也将很容易拿到记账权,所以我们得给每个币设计一个时间上限。

        缺陷三:

        设计时间上限后,虽然解决掉了部分挖矿者囤积币的缺陷,从公式中仍然看到还会面临一个问题,也就是币的数量这个因素还是会影响我们拿到记账权,很多挖矿者还会囤积代币,给代币造成流通上的缺陷。目前有些平台引入币龄按时间衰弱的方案来解决这一缺陷(例如:瑞迪币)。

        缺陷四:

            接下来让我们来看看pos共识机制还有哪方面的不足,即使上面的各种缺陷都多少有些解决方案,但例如挖矿者挖一段时间后离线,此时,时间将不纳入币龄减弱计算,这样,挖矿者通过离线时间长来囤积挖矿,同样面临灾难。

      .pos的发展历程

        由于pos存在以上四大缺陷,所以pos的发展历程经历了三个版本,即pos1.0pos2.0以及pos3.0,其中pos2.0在算法公式中使用的是币的数量,这样,上述缺陷二到四就不再是问题,可这样以后却导致了pos共识机制面临了无成本利益的问题(即 nothing at stake,这也将意味着很容易产生分叉。

      .casper协议

        第三章节我们说到pos的发展历程时,提到其为了解决其中4个缺陷,引出很多版本,也就是使用了pos2.0,也会无法避免的引发无成本利益导致很容易分叉的问题,而以太坊的共识机制就是使用的pos共识机制,那么我们来看看以太坊是如何解决这个问题的?

        1.什么是无成本利益关系问题

        在解决无成本利益关系这个问题前,我们先来看看什么是无成本利益关系问题,因此,我们可以先模拟下这种场景,如下图所示:

    区块链分叉图

            假设我们处在上面的这种情况下,有一条蓝色的主链和一条红色的从主链中分出来的链条,如何禁止一个恶意的矿工在红色区块上挖矿然后推动一次硬分叉(Hard Fork)呢?

            在一个工作量证明系统上,这一风险是可以被减轻的。

            假设恶意矿工想在红色链上挖矿。即便她投入了她所有的哈希算力,也不会有任何矿工加入她在新链上挖矿。每个其他人都将继续在蓝色链上挖矿,因为在最长的链上挖矿收益更可观,而且没有风险。

            记住,工作量证明在资源方面是非常昂贵的。对一个矿工来说,花费许多资源在一个将会被网络拒绝的区块上是没有任何意义的。因此,链分裂在一个工作量证明系统中是被避免了的,因为攻击者将不得不付出大量金钱。

            但是,当你把这种情形放到到权益证明下的时候,事情看起来就有些不一样了。如果你是一个验证者,你可以简单地把钱投到红蓝两条链上,完全无需担心间接的不良后果。不管发生什么事,你都总是可以赢,不会失去任何东西,不管你的行为有多恶意。

            这就是所谓的无成本利益关系(Nothing at Stake问题,也是以太坊必须解决的问题。他们需要一种协议,可以实行权益证明,同时减少无成本利益关系问题。

        2.引入casper协议解决无成本利益关系问题

            Csaper是以太坊选择实行的PoS协议,既然有人恶意去使得我们的区块链产生分叉,那么我们想方设法去对恶意制造者加以惩罚,这样不就可以解决我们说的无成本利益关系问题了吗?Csaper协议正式奔着这样的做法去实现的,那我们来看看Csaper是如何去做的呢?

           i. 验证者押下一定比例的他们拥有的以太币作为保证金。

           ii.然后,他们将开始验证区块。也就是说,当他们发现一个可以他们认为可以被加到链上的区块的时候,他们将以通过押下赌注来验证它。

           iii.如果该区块被加到链上,然后验证者们将得到一个跟他们的赌注成比例的奖励。

            iV.但是,如果一个验证者采用一种恶意的方式行动、试图做无利害关系的事,他们将立即遭到惩罚,他们所有的权益都会被砍掉。

            正是利用了这样的对赌协议,帮我们对恶意制造者加以了惩罚,使得我们的区块链尽量保障不会产生分叉。



    转自:https://ethfans.org/posts/ethereum-casper

    科普 | 什么是以太坊 Casper 协议?

    Ajian   |   7. Dec, 2017   |   2952 次阅读

    引介 PoS 权益证明 共识算法 casper

    (编者按:在12月3号的亚太区以太坊培训和交流Meetup上,Jon Choi为大家贡献了关于加密经济学和Casper的演讲。这篇文章同样是关于Casper的,而且从行文中可以看出本文的作者受到Jon Choi的解释的启发,阅读本文应可增进对Casper的理解。而Jon Choi的《Casper 101》,见EthFans上网站的中译。)


    一般来说,如果你对以太坊(Ethereum)和加密货币感兴趣,你一定听过了权益证明(Proof of Stake)和以太坊Casper这两个名词。没有什么东西可以离得开它们,它们充斥每一个角落。就因为有这么材料,每一个对这些主题感兴趣的人也许都正在经历“分析的泥淖”。所以,在这份指南中,我们向你展示关于Casper的终极向导以及它将如何永远地改变以太坊。

    什么是工作量证明(Proof of Work)?

    大多数加密货币包括比特币都使用“工作量证明”。工作量证明的过程具有下列步骤:

    • 矿工们解答密码学谜题以“挖出”一个区块并加入到区块链上。
    • 这一个过程要求大量的电力和运算。在系统中,这些谜题已经被设计成艰难而又繁重。
    • 当一个矿工解决一个谜题的时候,他们发布他们的区块到网络上接受验证。
    • 验证一个区块是否属于一条链是一个非常简单的过程。

    工作量证明系统,在本质上说就是这样的。解决谜题是困难的,但检查答案是否正确则是容易的。它也是比特币(Bitcoin)和Ethereum(直到现在)一直在用的系统。但是,这套系统存在一些根本的缺陷。

    工作量证明的问题

    事实证明,工作量证明存在非常多的问题。

    • 首先,也是最重要的,工作量证明是个极端低效的系统,因为它消耗大量的电力和能量。
    • 可以购买更快更强劲的ASIC设备的人们和机构通常可以比其他人拥有更高的概率挖到区块。
    • 上一点导致的结果是,比特币并没有像它希望的那样分散化。让我们看看这张哈希算力分布表:


    -图片来源:Blockchain.info-

    你可以看到,约有65%的算力是被5家矿池瓜分的!

    • 理论上来说,这5个大矿池可以联合起来并在比特币网络上发动51%攻击。

    为了解决这个问题,以太坊开始关注权益证明。

    什么是权益证明?

    权益证明将让整个挖矿过程虚拟化,并以验证者取代矿工。

    以下是权益证明的运行过程:

    • 验证者必须锁定一些他们拥有的币作为保证金。
    • 在此之后,他们将开始验证区块。同时,当他们发现一个他们认为可以被加到链上的区块时,他们会通过下赌注来验证它。
    • 如果该区块成功上链,验证者就将得到一个与他们的赌注成比例的奖励。

    权益证明最大的障碍

    以太坊开发者一直计划着最终转移到权益证明。他们的计划一直是这样的。但是,在他们可以这样做之前,他们不得不解决一个权益证明的一个最大的缺陷。

    考虑一下这种场景:

    假设我们处在上面的这种情况下,有一条蓝色的主链和一条红色的从主链中分出来的链条,如何禁止一个恶意的矿工在红色区块上挖矿然后推动一次硬分叉(Hard Fork)呢?

    在一个工作量证明系统上,这一风险是可以被减轻的。

    假设恶意矿工Alice想在红色链上挖矿。即便她投入了她所有的哈希算力,也不会有任何矿工加入她在新链上挖矿。每个其他人都将继续在蓝色链上挖矿,因为在最长的链上挖矿收益更可观,而且没有风险。

    记住,工作量证明在资源方面是非常昂贵的。对一个矿工来说,花费许多资源在一个将会被网络拒绝的区块上是没有任何意义的。因此,链分裂在一个工作量证明系统中是被避免了的,因为攻击者将不得不付出大量金钱。

    但是,当你把这种情形放到到权益证明下的时候,事情看起来就有些不一样了。如果你是一个验证者,你可以简单地把钱投到红蓝两条链上,完全无需担心间接的不良后果。不管发生什么事,你都总是可以赢,不会失去任何东西,不管你的行为有多恶意。

    这就是所谓的“无利害关系(Nothing at Stake)”问题,也是以太坊必须解决的问题。他们需要一种协议,可以实行权益证明,同时减少“无利害关系”问题。

    进入以太坊Casper

    Csaper是以太坊选择实行的PoS协议。虽然已经有一整个团队在忙着创建它,Vlad Zamfir还是常常被认为是“Casper的头面人物”。


    -图片来源:Blocknomi-

    所以,Casper跟其他PoS协议到底哪里不一样呢?

    Casper实施了一个进程,使得它可以惩罚所有的恶意因素。这就是权益证明在Casper下是如何工作的:

    • 验证者押下一定比例的他们拥有的以太币作为保证金。
    • 然后,他们将开始验证区块。也就是说,当他们发现一个可以他们认为可以被加到链上的区块的时候,他们将以通过押下赌注来验证它。
    • 如果该区块被加到链上,然后验证者们将得到一个跟他们的赌注成比例的奖励。
    • 但是,如果一个验证者采用一种恶意的方式行动、试图做“无利害关系”的事,他们将立即遭到惩罚,他们所有的权益都会被砍掉。

    正如你可以看到的,Casper被设计成可以在一个无需信任的系统上工作,并且是更加拜占庭容错的。

    任何人,如果以一种恶意的,或者说是拜占庭叛徒式的方式行动,就会立即受到惩罚、失去他们的保证金。这就是它不同于其他权益证明协议的地方。恶意元素会失去一些东西,所以,“无利害关系”是不可能的。

    这不是唯一一个Casper可以惩罚验证者的地方。

    正如Hudson James以及Joris Bontje在“StackExchange”的回答中标注的那样,Casper设计了苛刻的激励来保证网络的安全,包括惩罚离线的矿工,不管ta是有意还是无意的。

    这意味着验证者将不得不变得对他们的节点正常运行时间小心翼翼。粗心或者懒惰都将导致他们失去自己保证金。这一属性减少了对交易和整体利用率的审查。围绕着所有这些,这种“惩罚”属性同样给予了Casper相对标准工作量证明协议的明显优势。

    让我们再次放上链图解:

    在一个工作量证明协议中,一个矿工会在蓝色链上还是红色链上挖矿并不重要。因为诚实的和恶意的矿工都将花费同等数量的资源。

    然而,在Casper中,如果一个诚实的验证者在蓝色链上挖矿,他们将得到与他们的赌注成比例的奖励,但一个恶意的矿工将因为下注在红色链条上而失去他们的保证金。

    以太坊Casper VS 以太坊Casper?


    -图片来源: Medium-

    Casper不是一个具体的项目。它是两个研究项目的融合,这两个在最近一直由以太坊开发者团队承担:

    • Casper the Friendly Finality Gadget(FFG)
    • Casper the Friendly GHOST: Correct-by-Construction(CBC)

    Casper FFG

    Casper FFG也就是众所周知的Vitalik版Casper,是一个混合PoW/PoS共识机制。它是正准备进行初步应用的版本,也是被精心设计好来缓冲权益证明的转变过程的。设计的方式是,一个权益证明协议被叠加在正常的以太坊版工作量证明协议上。虽然区块仍将通过工作量证明来挖出,每50个区块就将有一个权益证明检查点,也就是网络中验证者评估确定性(Finality)的地方。

    什么是确定性(Finality)?

    确定性(Finality),从一个非常宽松的意义上来说,意味着一旦一个特定的操作完成,它将永远被蚀刻在历史上,没有任何东西可以逆转这个操作。在处理金融事务的领域,这是非常重要的。想象Alice在一个公司里拥有特定数目的一种资产。就算公司的某些进程中出了一点小故障,她也不应该需要恢复对该资产的所有权(因为小故障并不影响她的所有权)。

    有人说,工作量证明是唯一一种在区块链上实现确定性的方式。但是,这并不是必然正确的。真相远比这个要复杂很多。

    正如Vitalic Buterin提出的,世界上没有一个系统可以提供100%的确定性。黑进一个系统,或者物理上破解一份注册表并篡改数字以改变一个人的资产负债表,都是有可能的。这也是中心化机构的一个大问题。但是,分布式的系统也会面临同样的问题。

    实际上,比特币,工作量证明机制的典范,至少三次曾经面临确定性问题。在一个例子中,链必须分叉,因为一个Bug存在于软件的一个版本中但在其它版本中并不存在。这在社区中导致了分裂,一部分人拒接接受被另一部分人所接受的链。这次分裂在6个小时中被解决。

    所以,问题在于,Casper FFG如何能够提供确定性?根据Vitalik的说法 ,因为下面三个理由,Casper保证可以提供比工作量证明更强的确定性:

    • 完全经济确定性。三分之二的验证者会下最大几率的赌注使区块达到最终一致。因此,对他们来说,串谋以及攻击网络的激励是非常小的,因为,如果他们这样做的话,他们将危及自己的保证金。Vlad Zamfir更好地解释了这一点,他说:“设想一种版本的工作量证明,如果你参与一场51%供给的话,你的矿机会烧毁。”
    • 假设整个网络由三个人组成:Alice,Bob,和Charlie。假设Alice和Bob将他们的保证金放在一种结论上,同时,Bob和Charlie把他们的保证金放在一个与之对立的结论上。不管发生什么事,Alice或者Charlie其中一人肯定会损失一大笔钱。所以,正如你可以看到的,验证者没有动机去串谋或用恶意的方式行动,因为他们总会失去一大笔钱。
    • 然而,如果双重确定性(Double Finality)发生的话,还有一种意外事故处理方案。如果双重确定性发生的话,用户可以选择他们想到哪条链上去。不管哪条链,得到多数票的就成为主链。基本上,在Casper上,双重确认会导致硬分叉而不是回滚。

    Casper CBC

    Casper CBC也就是我们所知的Vlad版Casper使用建构修正(correct-by-construction,CBC)协议。嗯,那什么是CBC协议呢?(大多数材料都来源于Vlad Zamfir的edcon presentation以及Jon Choi的文章《Ethererum Casper 101》)。
    https://www.youtube.com/watch?v=6boQSQ7IuNY

    这是一个普通的协议设计的样子:

    • 你正式指定协议。
    • 定义该协议必须满足的属性。
    • 证明该协议可以满足给定的属性。

    而CBC协议的样子是:

    • 你正式地但只是部分地指定协议。
    • 定义该协议必须指定的属性。
    • 从满足所有它被规定去指明的属性中推导出该协议。

    用大白话来说,你是动态地推导出该协议的。获得完整协议的其中一种方式是运行一种Vald称为“理想对手(ideal adversary)”的预估安全预言机(estimate safety oracle),它运行下列两者之一:

    • 提出一个合理估计的错误的例外情况。
    • 列出所有在未来可能发生的错误。

    所以,这个理想对手要做的事情,就是不断进行微调、让这个只是部分建构好的协议更加完美,直到它变成完全版。

    可以看到的是,以太坊开发者团队一直在努力地开发这两个Casper项目。很明显,这不会是最终版本,但不管这最终版本是什么,它肯定会受到Vlad的和Vitalik的Casper的深刻影响。正如刚刚提到过的,Vitalik的Casper将被初步运行以缓冲从PoW到PoS的转变。而Vlad的Casper,通过使用一个“理想对手”推导出一个安全性论证。

    为什么我们需要Casper?

    使用权益证明有多种好处。这些好处可以在下面这些分类中广泛地列举出来。(对Jon
    Choi及其文章的大喊)

    • 帮助实现分散化
    • 高能效
    • 经济安全
    • 扩展性
    • 转移到PoS

    实现分散化

    正如我们在上面看到的,工作量证明协议不再是真正对去中心化友好的了。让我们再一次看看比特币的哈希算力分布图:

    而这里是以太坊的矿池哈希算力分布:


    -图片来源:Etherchain.org-

    你可以看到,大部分哈希算力集中在几个特定的矿池,而这意味着无论发生什么事,他们总会比其他人有更大的机会挖到区块、获得奖励。

    因为他们可以获得更多钱,他们可以买得起更好更快的ASIC设备。这基本上意味着,无论发生什么,大矿池将总是比个人和小矿池拥有优势。换句话来说,富有的将变得更富有。

    权益证明通过让挖矿完全虚拟化使得这一切都无关紧要。然而,这不是权益证明减缓中心化效果的唯一途径。为了理解这个,首先我们需要弄懂“经济规模”意味着什么。(谢谢ACDCLeadership的Youtube频道提供的数据)

    在生产的时候,有两种类型:

    • 短期生产
    • 长期生产

    在短期生产中,至少投入的资源是固定的。想象一个只有一个烤箱的小披萨店,无论他们是要制作1个还是25个披萨,他们都只有一个烤箱可以用于制作。而在长期生产中,所有资源都是可变的,这就是规模经济产生影响的地方。

    在投入资源的数量都是可变的长期生产中,如果你投入两倍数量的话会怎么样?在这一场景中,只有三种结果,我们称为“规模报酬”。

    • 产出大于两倍。意味着一个递增的规模报酬。
    • 产出变成两倍。意味着一个不变的规模报酬。
    • 产出小于两倍。意味着一个递减的规模报酬。

    下面这幅图将向你展示一个对规模经济的图解:


    -图片来源:Wikipedia-

    到底发生了什么?

    当产量从Q增长到Q2的时候,生产的总成本从C降到了C1。这就是所谓的规模经济。在这之后,增速变得平均了一些。这是你获得恒定规模报酬的地方。再然后,当你越过这一点,成本就会随着你的产出超过Q2而上升。

    这里面的意思是,大的企业可以通过提高产量来减少他们产品的平均成本!(在到达限度之前)

    在这种意义上,产品越多反而越便宜!

    让我们用现实世界来举个例子。

    一个小规模的烘培作坊的初始成本明显少于一个大烘培厂的因为工厂必须在机器上投资很大。但是,在长期生产中,对工厂来说每一条面包的平均成本将比烘培作坊的更低。

    OK,就是这样了。但这怎么对应到矿池呢?

    跟大企业很像,大矿池可以通过下列途径减少他们投入资源的成本:

    • 在大规模运营中分摊固定成本。
    • 作为一个更大的经营主体,拥有议价能力。

    这意味着,一个大而有影响力的矿池可以一美元又一美元地比其他矿池生产更多哈希算力,即便他们投入了等量的资金。

    这一问题在权益证明中完全被消解了,因为一个简单的原因。在权益证明中你投资一份保证金。你不能轻易地结成矿池然后让你的保证金一美元又一美元地升值。在一天结束的时候,一美元还是一美元。规模经济在这里不起作用。

    能源效率

    工作量证明最大的问题是能源浪费。最糟糕的部分是,能源浪费是为了能源浪费。尤其是比特币,其对能源的欲望是非常贪婪的。看一下这个:


    -图片来源:Digicnomist-

    再来一些数字如何?(来自Digicnomist)

    你应该关注两个数字。

    比特币全球一年的挖矿成本是1,423,794,674美元。

    光是比特币,一年要消耗相当于28.48 TWh的电能。厄瓜多尔国(The country of Ecuador)的才消耗大约21 TWh!

    实际上,我们可以比较一下比特币和一些国家的能源消耗:

    比特币比爱尔兰(Ireland)、巴林国(Bahrain)和斯洛伐克共和国(Slovak Republic)消耗的能源还要多!

    好吧,让我们公平一点。

    不如比较一下比特币与其他支付系统,比如……Visa?

    比特币的能源消耗与Visa相比如何?

    嗯。看起来不是太好。

    所以,很明显,比特币使用太多电力了,太多钱被花在资源上了。但是,外部性成本呢?大量的电力消耗对环境产生的影响必定是巨大的。

    虽然毫无疑问地比特币和工作量证明已经造成了大量积极的社会影响,我们至少应该看看权益证明系统可以做到怎样的规模,以及,它是否可以在不消耗那么多电力的情况下工作。

    经济安全

    权益证明(尤其是Casper)的最大优势是它的经济安全性。想想看,假设你是一个验证者,并且你将你自己的钱作为保证金存入网络。以最大化网络利益的方式行事也是你自己的利益。知道了如果有恶意的行为你的保证金中的一大部分将被罚没,你为什么还会那样做呢?

    当你有很多钱锁定在里面的时候,你为什么还要攻击网络、损害币值呢?
    同样地,“惩罚效应”消除了Vitalik定义的“驻守重生点攻击(spawn camping attack)”的可能性。

    //重生点攻击(spawn camping attack):个人理解是51%的矿工持续攻击网络。


    -图片来源:Vitalik Buterin 推特-

    在权益证明中,重生点攻击可以被防止,因为一个简单的事实:一次攻击将导致惩罚,没收已投入的保证金。如果你没有投入任何保证金,你就不能加入PoS验证。

    扩展性

    权益证明可以提高扩展性的最显而易见的方式是允许分片(Sharding)。

    如同Vlad Zamfir所言,PoW加上分片是并不是不可能的,但很困难。什么是分片

    分片是一个从数据系统中产生出来的术语。让我们来看看对数据库来说分片意味着什么。假设你的网站有一个大而笨重的数据库。拥有一个笨重的数据库不仅意味着搜寻数据变得更慢,也限制住了你的可扩展性。在这种情况下你会怎么做?

    给你的数据做个水平分区(horizontal partition),把它们变成更小的表格并且储存在不同的数据库服务器中如何?


    -图片来源:Dzone-

    像上面这样?

    现在,你也许在问,为什么是水平分区而不是垂直分区呢?这是因为设计表格的方式。

    考虑一下。假设这是我们的主表格:

    你看到发生什么了么?当你垂直切分一个表格,他们将变成两个完全不同的表格。

    但是,如果我们水平切分他们的话:

    看到了吗?这是拥有更少数据的完全一样的表格/数据库。这些更小的数据库就是我们所知的更大数据库的片(shard)。每一片的表格结构都应该是完全相同的。

    如果我们在一个工作量证明协议中运行分片,会发生什么?

    通过将一个状态分为不同的片,分配可以加快运行过程。但是,如果我们使用工作量证明的话,较小的片将处在被恶意矿工掌控的危险之中,因为它的低哈希算力。实际上,这是也工作量证明区块链永远不能运行分片的最大的理由,任意一个小分片都有可能轻易被掌控。

    这种风险在权益证明中可以完全避免,因为它没有挖矿的概念。

    转换到PoS

    现在,这在很大程度上是一个哲学问题。

    普遍来说,人们,对改变并不是那么地有好感。当他们习惯了一些东西的时候,让他们离开舒适区是非常困难的。

    对于其价值完全基于大众感知的价值的货币制度而言尤其棘手。就在我写作的时候,以太坊的价值越是320亿美元。

    要把权益证明炸弹掷到人群中并期待他们会跟上时代,是不负责任的。这可能会减少公众对该系统的信任,而以太坊的市值也许会下降。因此,实施从PoW到PoS的温和转变是非常精明的,而后者是Casper FFG计划实现的东西。

    那么还会“富者愈富”吗?

    其中一种反复出现的对权益证明的批评是,它只会让富人变得更加富有。因为为了具备验证者的资格,你需要锁定一个你的(以太)资产中的可观的比例作为保证金;而且,除了这一点,你将获得的奖励是跟你押注的数额成比例的。所以,如果你在系统中有更多钱,你将得到更多钱。

    但是,Jon Choi在他的文章中否认了这一点:

    “这里的主要结论应该是权益证明是明显地更加平等主义的(比如,对拥有更多资本给予更少好处),相对比比特币现在基于工作量证明的算法。”

    而这背后的主要理由,就是我们已经做出的关于规模经济的讨论。在一个权益证明系统中,一美元还是一美元,大矿池无法在投入同样数量的钱时却拥有更多哈希算力而脱颖而出。

    以太坊Casper的未来

    最近,在我写作的时候,Vitalik Buterin说不仅Casper准备好接受测试了,它还可以在客户端更新代码时提供一个安全提升。虽然看起来并不像它已经准备好用来广泛普及了,但第一个Casper测试网发布的日子看起来是越来越近了。

    权益证明是否将要实施并不是一个问题,问题是什么时候能够实施。以太坊“宁静”(Serenity)应该是一个权益证明网络。此前我们并不是从未见过PoS的实行,Peercoin非常成功地运用了它。但是,我们从还从来没有看过在这个层面采用该协议。

    也许,如果Casper被成功运行的话,另一个加密货币会跟风并且做出转换。不管事情会变成什么样,Casper带来了非常多迷人的可能性。



    casper的发展历程,翻译自Vitalik的推特流。(截至2018.08.15)

    原文链接: https://twitter.com/VitalikButerin/status/1029900695925706753
    作者: Vitalik

    翻译转自:https://ethfans.org/posts/vitalik-on-casper-development-history-and-state

    1/ 今天我准备发一个推特流,解释一下以太坊 Casper 研究的历史和现状,包括 FFG 和 CBC 的分歧、(工作量/权益)混合共识到完全 PoS 的转变、随机性在 Casper 中的角色、机制设计挑战,以及其它。

    2/ 以太坊权益证明研究开始于 2014 年 1 月,是从剑手协议(Slasher Protocol)开始的 [1]。虽然算法远远称不上完善,这一研究还是给了我们一些很重要的观念,尤其是用惩罚措施来解决无利害关系问题(Nothing at Stake)[2]

    3/ 话虽如此,我当时使用的惩罚措施是非常轻的,只是取消了签名奖励(Signing Reward)。Vlad Zamfir 在 2014 年年中加入,他迅速地推进,指出要让验证者存储 保证金,而且数额要比奖励大很多,以此驱逐恶意行为。

    4/ 这是 Vlad 的回顾:[3](编者注:中译本见文末《Casper 的历史起源,Part-1》)

    5/ 2014 年下半年的大部分时间,我们都在试着处理“长程攻击(Long Range Attack)”:攻击者可以先取回他们质押在主链上的权益,然后创建一条替代性的“攻击链”,只要该链比主链有更多签名,他们便可愚弄客户端切换到攻击链。

    6/ 如果攻击链在一个离当下非常近的时间点偏离主链,这就不会是一个问题,因为如果验证者为两条相互冲突的链签名了两条相互冲突的消息,我们就可以此为证据惩罚他们并没收保证金。

    7/ 但如果这种偏转发生在很久以前的区块上(这就是为什么它被称作“长程攻击”),攻击者可以取出他们的保证金,在所有链上都免于受罚。

    8/ 最终我们确定,长程攻击是无可避免的,因为 PoW 支持者说的许多原因(例如:[4])。但我们不同意他们的结论。

    9/ 我们意识到,我们可以通过引入额外的安全假设来对付长程攻击:客户端每 4 个月登录至少一次(而保证金要 4 个月之后才能取出),那么客户端就可以直接拒绝比这个时间更长的回滚

    10/ 对 PoW 支持者来说这简直是大逆不道,因为这一假设似乎需要信任:在你第一次同步的时候,你需要从一些受信的来源获得区块链。

    11/ 但对我们这些“没有底线”的主观主义者来说,看起来这没什么大不了的;不论什么情况下,你都需要一些可信源头来告诉你区块链使用的共识规则(而且还有软件升级这回事呢)。所以这一 PoS 假设所要求的额外信任也不是什么大事。

    12/ 这是 Vlad 的回顾:[5](编者注:即《Casper 的历史起源,Part-2》)

    13/ 明确了要使用保证金和惩罚措施还不够,我们还必需决定保证金和惩罚措施是什么样的。我们知道我们想要“经济确定性(Economic Finality)”,也就是说:

    14/ 一旦某个区块得到验证者签名,被“敲定(Finalized)”,就没有任何 冲突 区块可以被敲定,除非大部分的验证者签署了与他们早先签署的消息相冲突的消息;此种签署方式可以被区块链检测到,因此这样的验证者也会受到惩罚。

    15/ 我在自己称作是“打赌共识(Consensus by Bet)”的方向上走了很远,但最终没有结果 [6](编者注:中译本见文末超链接《理解 Serenity,Part-2》)

    16/ 打赌共识是一种很有趣的结构:验证者是在打赌哪个块会被敲定,而这些赌注本身决定了共识偏爱哪条链。

    17/ 理论上来说,PoW 也具有这种属性,因为挖矿就是一种赌博,如果你押中了对的链,你就赚钱(奖励 - 挖矿成本);如果你押错了宝,你就会损失挖矿成本;只不过,在 PoS 中,我们可以将赌注的赔率推得更高。

    18/ 验证者一开始下注时赔率较低,但因为验证者看到其他验证者对某一个区块有越来越强的信心,每一个人的赔率都会互不干扰地迅速上升,直到最后,他们将自己全部的保证金都押在一个区块上,这就是“确定性”。

    19/ 与此同时,Vlad 开始研究机制设计,特别是希望 Casper 可以更强健地抵制寡头形成;而我们也开始关注受传统拜占庭容错理论启发的共识算法,比如 Tendermint

    20/ Vlad 认为,传统拜占庭容错理论是很差劲的(他尤其不喜欢硬上限,比如实用拜占庭容错算法(PBFT)和 Tendermint 中的 2/3),他想尝试高效地彻底改造 BFT 理论,运用他称之为“建构中修正(Correct by Construction,CBC)”的方法。

    21/ 这是 Vlad 自己说的 [7] [8] [9](编者注:Casper 历史起源的 Part-3、Part-4 和 Part-5)。

    22/ “建构中修正”的哲学迥异于传统的拜占庭容错理论,因为其“确定性”是完全主观的。在 CBC 哲学中,验证者签署消息,如果他们签署的消息与此前自己签署过的消息相冲突……

    23/ ……他们就不得不提交一份“辩护词”,证明在很大程度上,他们“更支持”新近投票支持的区块,因此他们有权利转投。

    24/ 要发现确定性,客户端就要找到这样的消息模式:可以证明大多数验证者确实把票投给了区块 B,并且没有办法脱离区块B,除非大量验证者“非法地”转投其它区块。

    25/ 举个例子,如果每个人都把票投给了 B,然后每个人都投票给了包含对 B 投票记录的区块,这就证明他们支持 B 并且意识到了其他所有人也都支持 B,因此他们也就没有合法理由从 B 转投到其它区块上。

    26/ 我最终放弃了“打赌共识”,因为这一方案看起来实在是太过冒险了,那么我就转过去试图理解 PBFT 的工作原理。虽然花了不少时间,但几个月之后我就弄清楚了。

    27/ 我简化了 PBFT [10],然后将它化用到区块链环境中,转述为 4 条“罚没条件”。这些条件规定了哪些消息组合是自我矛盾的因此是非法的:[11]

    28/ 我定义了一条规则来确定什么时候区块会敲定,并证实了关键的“安全性”和“合理活性(Plausible Liveness)”:(i)一旦某个区块被敲定,除非 >= 1/3 的权益违反罚没条件,否则冲突区块不能被敲定……

    29/ (ii)一旦某个区块被敲定,2/3 诚实验证者可以必定联合起来敲定一个新区块。所以只要诚实验证者权重超过 2/3 ,算法就既不能“自相矛盾”,也不能“卡死”。

    30/ 我最终把最少罚没条件从 4 条简化为 2 条,那时候就出现了 Casper FFG(Friendly Finality Gadget),它被设计为可以覆盖在任何 PoW 或 PoS 或其它类型的区块上,用于增加确定性保证。

    31/ 确定性是一个非常重要的进步:一旦某区块被敲定,它就是安全的,无论网络延迟如何(不像 PoW 还需要后续区块确认);并且回滚区块要求 >= 1/3 的验证者作弊,但作弊可以被发现,作弊的证据可用于罚没他们的保证金。

    32/ 因此,回滚确定性的成本可以上升到数十亿美元。Casper CBC 和 Casper FFG 方案都实现了确定性,虽然用了不一样的技术。

    33/ 注意,Casper CBC 和 Casper FFG 都是覆盖层,需要应用在已有的分叉选择规则上,尽管抽象工作的方式有所不同。

    34/ 用大白话来说,在 Casper CBC 中,确定性覆盖层适应分叉选择规则;但在 Casper FFG 中,分叉选择规则适应确定性覆盖层。

    35/ Vlad 最初对分叉选择规则的偏好是“最新消息驱动型 GHOST”,GHOST 用于权益证明的一种改良版本 [12];我一开始的偏好是从混合 PoS 开始,使用工作量证明作为基本的分叉选择规则。

    36/ 在 Casper FFG 的最初版本中,工作量证明会一个一个区块驱动链生长,而权益证明会随后将它们敲定。Casper CBC 从一开始就是完全的权益证明。

    37/ 与此同时,Vlad 和我都开始提出各自的思想流派关于共识算法 激励 理论的观点。

    38/ 此处,一个非常重要的分歧是在“可独占归因型错误”与“不可独占归因型错误(Non-uniquely-attributable Faults )”之间的。前一种情境中,你可以分辨出谁对错误负有责任,因此也可以惩罚他;在后一种情境中,可能是多方中的一方对错误负有责任

    39/ 不可独占归因型错误的经典情形是是下线 vs 审查,也被称为“说者-听者错误等价(Speaker-listener Fault Equivalence)”。

    40/ 惩罚可独占归因型错误(例如,Casper FFG 罚没条件)是容易的。但惩罚不可独占归因型错误就很难。

    41/ 如果你没办法分辨出区块敲定中止是因为少数人下线了还是因为多数人审查了少数人,那会怎么样?

    42/ 对这一问题有三种流派的观点:(i)轻微惩罚双方;(ii)严惩双方(Vlad 偏爱这种方案);(iii)将链一分为二,各自只惩罚一方,让市场来决定哪条链更有价值(我偏爱这种方案)。

    43/ 可以看看我的想法:[13]

    44/ 在 2017 年 11 月, Casper FFG 罚没条件加上我用“二次泄压(Quadratic Leak)”机制解决“1/3 下线问题”的想法,变成了一篇论文 [14]

    45/ 当然,我很清楚地意识到,诉诸社会层面来解决 51% 攻击不是一个非常好的事情,所以我开始寻找一种办法,至少能让在线的客户端 自动地 检测到哪条链是“合法的”,哪条是正在发动“攻击”的。

    46/ 这是我早期的想法中的一种:[15]。它算是成型了,但仍不够理想;除非网络延迟绝对是 0,否则只能保证一点:不同客户端对同一链的怀疑值最多会有 δ 的差异,但客户端无法达成一致。

    47/ 与此同时,我对 Vlad 模型的主要批评在于“威吓攻击(Discouragement Attack)”:攻击者们可以可信地威胁发动导致所有人都遭受损失的 51% 攻击,让其他所有人都逃离,然后几乎不费吹灰之力接管整条链。

    48/ Vlad(以及 Georgios Piliouras)开始建立一些经济模型来估计这样一种攻击的实际成本。

    49/ 值得指出的是,所有这些问题都不是权益证明独有的。实际上,在工作量证明中,人们倾向于放弃并假设从 51% 攻击中恢复是绝对不可能的,因此 51% 攻击是必须不计代价阻止的世界末日。

    50/ 但是,正如以太坊一贯以来的传统,Vlad 和我都没有意识到,“雄心勃勃(ambitious)”一词绝对不是恭维,因此都用我们各自的方法不懈工作,致力于压制和缓解 51% 攻击,以及从中恢复的方法。

    51/ 在 2018 年上半年,Vlad 在 CBC 中的工作开始快速推进,在安全性证明上有了长足的进展。要了解 2018 年 3 月的进度,请看这个两小时的演讲 [16]

    52/ 与此同时,Casper FFG 也向前迈了一大步。我们决定用发布到以太坊区块链的合约来实现它,以让开发工作变得更加简单。2017 年 12 月 31 日 23:40,我们放出了用 Python 写成的测试网:[17]

    53/ 不幸的是,FFG 的开发工作随后便慢下来。用合约来实现 FFG 的决定确实让一些事情变得简单,但也让另一些事情变得更难,这也意味着:从 EVM 最终切换到 EWASM,以及从单链 Casper 到分片 Casper,会变得更难。

    54/ 此外,团队工作开始分化为“主链 Casper”和“分片链 Casper”;事情明摆着,在 Casper 团队和分片团队之间游走会导致很多不必要的重复工作。

    55/ 在 2018 年 6 月,我们做了一个攸关生死的决定:彻底放弃“用合约实现混合 Casper FFG”的想法代之以追求完全 Casper 的独立链,改变设计让整合分片变得更加容易。

    56/ 转向完全的权益证明让我开始更加努力地思考权益证明的分叉选择规则。

    57/ Casper FFG(以及 CBC)都要求,在每一个“轮次(epoch)”都有 完整 的验证者集来投票敲定区块;这意味着每秒钟都会有数以千计的签名要通过网络发送。BLS 签名聚合方案让这一点至少在计算开销上是可以接受的……

    58/ ……但我想试着利用所有这些额外的签名方案,让整条链可以更“稳定”,在几秒内获得价值“100 个确认”的安全性。

    59/ 这里是我最初的尝试:[18] [19]

    60/ 然而,所有这些方法作为分叉选择规则都有一个弱点将验证者区分为“见证者(Attester)”和“提议者(Proposer)”,而提议者变成了区块生产的关键一环,拥有了过于巨大的权力。

    61/ 这当然不是我想要的,因为它使得我们必须拥有一个强大的链上随机数源来公正地选出提议者。而链上随机性是 难以实现的,像 RANDAO 这样简单的方案可以发现越来越多的问题 [20]

    62/ Justin Drake 和我用两种方式来解决这个问题Justin 使用可验证延迟函数(Verifiable Delay Function,VDF),这一函数有确定且可验证的输出,不过要花大量不可并行化的连续时间来计算,消除了提前操纵的可能……

    63/ ……我自己则对 Vlad 神教TM 做了重大让步,使用了基于 GHOST 的分叉选择规则来大幅减少对提议者的依赖,这使得即便 >90% 的提议者都是恶意的,只要 >50% 的见证者是诚实的,链就可以不受干预地生长。

    64/ 那时候 Vlad 非常开心,不过还不能算开心得飞起:他偏爱的 GHOST 版本基于验证者的 最新消息,而我偏爱的版本基于 即时消息[21]

    65/ 大约同一时间,我还想出一种办法来“管道化” Casper FFG,将实现确定性的时间从 2.5 个轮次减少到理论上最优的 2 个轮次:[22]

    66/ 我非常高兴 RPJ 分叉选择规则(在那以后我该称其为“即时消息驱动型 GHOST”)可以用一种别的算法都做不到的方式很好地兼容 Casper FFG……

    67/ …… 并且它也有非常重要的“稳定”属性:从当前分叉选择中可以很好地预见未来分叉时的选择。看起来很明显,但分叉选择规则很容易不小心就 丧失 这种属性。

    68/ 所有这一切的最新进展是:最新消息驱动型 GHOST 也许,因为技术原因,在两轮内只有 25% 的容错能力,而即时消息驱动型 GHOST(加上 FFG 或 CBC)可以有完全的 33%(虽然还没有写出来)。

    69/ FFG 和 CBC 之间的主要权衡是,理论上 CBC 具有更好的属性,但 FFG 看起来更容易实现。

    70/ 另一方面,可验证延迟函数方面出现了 大量 进展:[23]

    71/ 同样地,我最近决定深入学习 Lamport 1982 年的旧论文,里面有一种共识算法可以实现 99% 的容错能力,只要你加入一个假设:所有节点,包括观察者,全部都在线且网络延迟很低[24]

    72/ 网络延迟假设可能使它不适合作为一种首要的共识算法,不过,在一种场景下它可以运作得 非常 好:作为怀疑值的替代方案,用于发现 51% 审查。

    73/ 基本上,如果 51% 的权益开始联合审查区块,其它验证者和客户端可以发现这一点,然后使用 99% 容错算法来达成共识,联合发动少数方分叉。

    74/ 这一研究的长期目标是尽可能减少对社会层面的依赖,最大化扰乱链运行的成本,使其必须回到社会层面来。

    75/ 那么还剩下什么议题呢?在 FFG 这一方面,包括形式化验证、改进技术规范、实现上的不断推进(已经有 >= 3 支团队开始了),还有对安全和迅速部署的关注。在 CBC 这一方面,大体相似。快高长大吧!


    一些网友对 Vitalik 的回应:

    -我的推特客户端验证了这是推特历史上最长的推文,因此称为最有效的推链!
    内容很有意思,非常感谢!-

    -回复第73条:如果大于 51% 的联合是大集团与政府的联合呢?如果你正在使用的某个被中心化机构控制的 app 选择了在谷歌链上运行呢?如果你只是一名用户,你关心的是什么?-

    -又一个对于以太坊发展蓝图的天才计划!大神请不要停下来~-

    -这可能是 V 神承认 “Vald 是对的” 最长的一次解释。Vlad 肯定在偷笑,“早就告诉你我是对的啦!”不过说正经的,还有很多人都很感谢你们作出的贡献,虽然我们没有那些诋毁者叫得大声。非常感谢~ -



     

    展开全文
  • Combining GHOST and Casper以太坊2.0共识机制Gasper:part 1 共识机制设计理念 正如以太坊基金会成员Vald zamfir,所说Casper设计出发点,源于对系统最差情况经济分析,这也是是公链PoS共识算法唯一路径。...

    Combining GHOST and Casper以太坊2.0共识机制Gasper:part 1

    共识机制设计理念

    正如以太坊基金会成员Vald zamfir,所说的Casper的设计出发点,源于对系统最差情况的经济分析,这也是是公链PoS共识算法的唯一路径。也因此,Casper希望通过引入惩罚措施来最大化拜占庭容错。

    并且,Casper是为轻节点而生,为此需要提供两大特性:

    • 最终性(敲定的块就不能再改了)
    • 低延迟

    那么针对这些特性,就需要在指定分叉规则的同时,还要有最终性的共识算法,那么这该如何解决呢?

    在2020年5月修改版的《Combining GHOST and Casper》一文中,V神的团队给出了详细的答案及解释。

    设计目标

    Eth2.0的共识算法设计目标就是让PoS就有一定的安全性和可用性(certain safety and liveness claims).

    对应着这个目标,提出了两大组件来定义分叉规则和最终性:

    1 最终性规则Casper FFG

    Buterin 和Griffith提出了Casper the Friendly Finality Gadget (FFG),引入**验证和敲定(**justification and finalization)来定义最终性,什么意思呢?其实有点像检查点,简单来说就是,在特定的区块高度需要有验证节点的签名信息A->B(其中A,B代表检查点区块),表明我A举手赞成B成为下一任党委书记,也就是赞成B成为下一个检查点,这样就不要再从创世区块去算状态保证安全性了,因为检查点之后的就改不了了。道理其实很简单,当上了党委书记就有话语权了,政策就不能再由着下面的人乱改了嘛。

    同时,Casper也引入了惩罚条件(slashing conditions),来判定谁是坏人谁是好人,违反这两个规则的人就得被开罚单:

    1. 验证者对同一个高度的检查点,只能给一个完全一致的证明
    2. 验证者在两个不相邻的检查点之间只能给一个完全一致的证明,也就是说这两个检查点之间的检查点不能再给不一样的证明了

    可以看出,Casper只是规定了检查点是怎么确定下来的,也就是说最终性是如何确定的,而把分叉规则单独最为一个组件,这么做也是为了解耦这两部分。

    2 分叉规则LMD GHOST

    LMD由Sompolinsky等人提出的The Greediest Heaviest Observed SubTree rule (参见通俗的解释GHOST)改进而来,由于PoS的共识算法需要消息驱动来获取投票的权重,因此称之为Latest Message Driven Greediest Heaviest Observed SubTree(LMD GHOST).

    算法如下所示:

    图片

    LMD GHOST算法规则

    举例来说就是,假设每个人拥有一个stake代表一票的权重(下图中用圆圈表示),那么按照这个算法就确定了途中蓝色的主链:

    图片

    LMD GHOST举例

    那么这两个协议是如何结合到一起的呢,以太坊提出了Gasper(Ghost第一个字母G+Casper的后四个字母)协议:

    Gasper

    为了理解Gasper,我们先要了解三个基本概念:

    • 纪元边界(Epoch boundary):Gasper把12秒定义为了一个基本时间槽(slot),然后把64个slot定义为一个**纪元(epoch).**那么每个纪元开始的那个区块就成为边界区块,用EBB(B,j)=C表示,第j个epoch的区块B的边界区块是区块C
    • 委员会:所有的验证者被分配到每一个epoch中,构成了一个大的委员会,并且对于每一个slot从这个大的委员会选出一个特定的委员会。再每一个slot中,由一个验证者来打包出块,该委员会的其他成员使用HLMD GHOST(LMD GHOST的一个轻微变种,后面会讲)附上认可这个区块的证明。
    • 验证和敲定(Justification and Finalization):这个其实和Casper FFG很像,不过这里不是对单个检查点操作,而是对所有的纪元边界区块进行操作。

    纪元边界区块验证

    验证规则定义,如果这个投票的总和大于总staking的2/3,那么就认为边界区块被验证了。

    具体而言,对于下面的例子:

    图片

    这是一个验证者的视图,由于网路延迟等问题,他之前很多区块没收到,因此64号区块就是193号区块Epoch1和Epoch2的边界区块,因此他再这里写入投票α*,表示(64,2)是(180,3)的边界区块。同时根据GHOST规则可以看出需要投193号区块为当前的链头。*

    敲定

    而敲定相对于验证是一个更强一点的概念,如果说区块B0构成的边界区块(B0,j)被敲定了,那么要么他是创世区块,要么存在一个k>=1的数满足以下三个条件:

    1. (B0, j), (B1, j + 1), . . . , (Bk, j + k) 是相邻的epoch边界
    2. (B0, j), (B1, j + 1), . . . , (Bk−1, j + k − 1) 都已经被验证了
    3. (B0, j)被 (Bk, j + k)验证了

    其实总的来说就是B0,需要被其他区块验证!

    举个例子:

    图片

    蓝色的区块代表被敲定了,带箭头的边表示被验证了(思考k=2时,B1为什么没有被敲定?)

    分叉规则Hybrid LMD GHOST(HLMD)

    验证了边界区块之后,只要没有超过1/3的攻击者stake就不能篡改这个区块,在这个假设基础上就能唯一确定边界区块,从而定义epoch内部的分叉规则了,算法如下:

    图片

    这个算法思路其实表简单,就是先选取最新验证的epoch边界区块Bj,然后再根据GHOST规则来找到当前主链的链头区块。

    细心的你可能发现这里有个问题就是,最新被验证的区块可能会变,因为被验证了不等于finalize了,只要还没被完全敲定就有可能会变;同时,每个验证节点的视图可能不一致,就导致我们自己的这个区块Bj可能跟别人的不一样!

    那么对于这两个问题如何解决呢?还有整个系统的安全性和可用性如何呢?且听下回分解…

    展开全文
  • 以太坊的POS共识机制

    千次阅读 2018-03-19 20:50:56
    Original post by Vitalik Buterin, on December 28th, 2015特别感谢Vlad Zamfir,他提出了按块达成共识这个想法,并且说服我认同它和Casper其它一些核心想法价值;以及Vlad Zamfir和Greg Meredith,为他们在这...
  • 2014年9月份我开始了研究和设计以太坊POS(proof-of-stake, 权益证明)架构工作。目前Vitalik和我对于Serenity阶段POS协议应该长什么样已经有了许多共识,只剩一些细节方面分歧。我们称它为友善小精灵Casper...
  • 2014年9月份我开始了研究和设计以太坊POS(proof-of-stake, 权益证明)架构工作。目前Vitalik和我对于Serenity阶段POS协议应该长什么样已经有了许多共识,只剩一些细节方面分歧。我们称它为友善小精灵Casper...
  • 前序 本人版暂名为《区块链以太坊DApp实战开发》一书,即将完成,是一... 以太坊目前所使用的共识算法 Ghost 协议 选择最优链 分叉块处理 Casper PoS变种共识机制 如何成为验证人 验证人如何获取保证金...
  • 以太坊的POS共识机制(二)

    千次阅读 2018-07-29 12:18:59
    特别感谢Vlad Zamfir,他提出了按块达成共识这个想法,并且说服我认同它和Casper其它一些核心想法价值;以及Vlad Zamfir和Greg Meredith,为他们在这个协议上持续努力。 在这个系列上一篇中,我们讨论了...
  • 特别感谢Vlad Zamfir,他提出了按块达成共识这个想法,并且说服我认同它和Casper其它一些核心想法价值;以及Vlad Zamfir和Greg Meredith,为他们在这个协议上持续努力。 在这个系列上一篇中,我们讨论了...
  • 作者:林冠宏 / 指尖下幽灵 博客:http://www.cnblogs.com/linguanh/ GitHub : https://github.com/af913337456/ 掘金:...以太坊 2.0 参与者 区块生命周期 优化共识算法 2.0 共识的流...
  • POW是最早的共识机制。POW是指工作者通过一定的工作量,提供一个能被大多数验证者快速验证的答案,从而得到奖励的机制。 原理 讲原理之前,先给大家普及两个概念:难度值和nonce 难度值     &...
  • 以太坊打算在未来转向POS机制(权益证明)。如果你还不了解什么是POW和POS,可搜索什么是POS,什么是POW?...’与比特币类似,以太坊的共识机制是PoW机制(工作量证明),以确保区块链的安全。 以太坊在比特币的基础...
  • 作者:林冠宏 / 指尖下幽灵 掘金:juejin.im/user/587f0d… 博客:www.cnblogs.com/linguanh/ GitHub : github.com/af913337456… 腾讯云专栏: cloud.tencent.com/developer/u… 虫洞区块链专栏:...
  • 我们已经听过很长一段时间了,在同步网络中可以达到50%容错的共识,其中任何诚实节点广播的消息都保证在某个已知时间段内被所有其他诚实节点接收(如果攻击者有更超过50%,就可以执行“51%的攻击”,而有这种用于...
  • 以太坊2.0主要变化之一就是其共识机制的变化,它由PoW迈向PoS(Casper),PoW和PoS在最终性有什么不同?它会比PoW更安全吗?本文作者Alex Stoke...
  • 作者:林冠宏 / 指尖下幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguanh/ GitHub : https://github.com/af913337456/ 腾讯云专栏: ...
  • 2013年年末,以太坊创始人Vitalik Buterin发布了以太坊初版白皮书,这个有着智能合约功能的公共区块链平台...在以太坊的“升级”中,需要经历四个主要阶段:前沿(Frontier)、家园(Homestead)、大都会(Metrop...
  • 以太坊GHOST协议

    2020-01-09 15:41:29
    以太坊的共识机制用的是ghost协议,以太坊把出块时间降至十几秒,对于降低反映时间很有帮助,但是带来了一些新的问题,比特币和以太坊都是运行在应用层,底层是overlay network,这个overlay network 本身传输的时间...
  • 摘要:比特币诞生给我们带来了一个可以自运转网络...在第一代区块链账本(比特币)基础上,以太坊加入了智能合约概念,但继续沿用了POW共识机制,直到最近(5月8日),以太坊网络发布了一项酝酿和讨论了很...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 388
精华内容 155
关键字:

以太坊的共识机制