成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

以太坊POA共識機制Clique源碼分析

Stardustsky / 2986人閱讀

摘要:以太坊中除了基于運算能力的外,還有基于權(quán)利證明的共識機制,是以太坊的共識算法的實現(xiàn),這里主要對的相關(guān)源碼做一個解讀分析。檢查包頭中包含的簽名是否滿足共識協(xié)議

以太坊中除了基于運算能力的POW(Ethash)外,還有基于權(quán)利證明的POA共識機制,Clique是以太坊的POA共識算法的實現(xiàn),這里主要對POA的Clique相關(guān)源碼做一個解讀分析。

Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置為clique.Clique, 根據(jù)當(dāng)前節(jié)點的礦工地址(默認是acounts[0]), 配置clique的 簽名者 : clique.Authorize(eb, wallet.SignHash) ,其中簽名函數(shù)是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})    // 根據(jù)用它胡地址獲取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挖礦在主網(wǎng)是荒誕的,所以沒有人能碰到這個路徑,然而一旦CPU挖礦同步標(biāo)志完成以后,將保證私網(wǎng)工作也在一個獨立礦工結(jié)點。
        atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    }
    go s.miner.Start(eb)
    return nil
}

這個StartMining會在miner.start前調(diào)用,然后通過woker -> agent -> CPUAgent -> update -> seal 挖掘區(qū)塊和組裝(后面會寫多帶帶的文章來對挖礦過程做源碼分析)。

Clique的代碼塊在go-ethereum/consensus/clique路徑下。和ethash一樣,在clique.go 中實現(xiàn)了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()函數(shù)可對一個調(diào)用過 Finalize()的區(qū)塊進行授權(quán)或封印,成功時返回的區(qū)塊全部成員齊整,可視為一個正常區(qū)塊,可被廣播到整個網(wǎng)絡(luò)中,也可以被插入?yún)^(qū)塊鏈等。對于挖掘一個新區(qū)塊來說,所有相關(guān)代碼里 Engine.Seal()是其中最重要最復(fù)雜的一步,所以這里我們首先來看下Clique 結(jié)構(gòu)體:

type Clique struct {
    config *params.CliqueConfig // 共識引擎配置參數(shù)
    db     ethdb.Database       // 數(shù)據(jù)庫,用來存儲和獲取快照檢查點
    recents    *lru.ARCCache // 最近區(qū)塊快照,加速快照重組
    signatures *lru.ARCCache // 最近區(qū)塊簽名,加速挖礦
    proposals map[common.Address]bool // 目前正在推送的提案
    signer common.Address // 簽名者的以太坊地址
    signFn SignerFn       // 授權(quán)哈希的簽名方法
    lock   sync.RWMutex   // 用鎖來保護簽名字段
}

順便來看下CliqueConfig共識引擎的配置參數(shù)結(jié)構(gòu)體:

type CliqueConfig struct {
    Period uint64 `json:"period"` // 在區(qū)塊之間執(zhí)行的秒數(shù)(比如出塊秒數(shù)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共識注入一個簽名者的私鑰地址已經(jīng)簽名函數(shù)用來挖出新塊
    c.signer = signer
    c.signFn = signFn
}

再來看Clique的Seal()函數(shù)的具體實現(xiàn):

//通過本地簽名認證創(chuàng)建已密封的區(qū)塊
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
    header := block.Header()

    // 不密封創(chuàng)世塊
    number := header.Number.Uint64()
    if number == 0 {
        return nil, errUnknownBlock
    }
    // 不支持0-period的鏈,不支持空塊密封,沒有獎勵但是能夠密封
    if c.config.Period == 0 && len(block.Transactions()) == 0 {
        return nil, errWaitTransactions
    }
    // 在整個密封區(qū)塊的過程中不要持有signer簽名者字段
    c.lock.RLock()
    signer, signFn := c.signer, c.signFn //獲取簽名者和簽名方法
    c.lock.RUnlock()

    snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) //調(diào)用獲取快照
    if err != nil {
        return nil, err
    }
  //檢查我們是否被授權(quán)去簽名一個區(qū)塊
    if _, authorized := snap.Signers[signer]; !authorized {
        return nil, errUnauthorized
    }
    // 如果我們是在‘最近簽名者’中則等待下一個區(qū)塊
    for seen, recent := range snap.Recents {
        if recent == signer {
            // 當(dāng)前簽名者在‘最近簽名者’中,如果當(dāng)前區(qū)塊沒有剔除他的話只能等待(這里涉及到機會均等)
            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
            }
        }
    }
    // 好了,走到這說明協(xié)議已經(jīng)允許我們來簽名這個區(qū)塊,等待我們的時間
    delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple
    if header.Difficulty.Cmp(diffNoTurn) == 0 {
        // 這不是我們的輪次來簽名,延遲一點,隨機延遲,這樣對于每一個簽簽名者來說來允許并發(fā)簽名
        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簽名函數(shù)開始簽名
    sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes())
    if err != nil {
        return nil, err
    }
    //將簽名結(jié)果替換保存在區(qū)塊頭的Extra字段中
    copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
    //通過區(qū)塊頭重新組裝生成一個區(qū)塊
    return block.WithSeal(header), nil
}

Seal是共識引擎的入口之一,該函數(shù)通過clique.signer對區(qū)塊簽名

signer不在snapshot的signer中不允許簽名

signer不是本區(qū)塊的簽名者需要延時隨機一段時候后再簽名,是本區(qū)塊的簽名者則直接簽名

簽名存放在Extra的extraSeal的65個字節(jié)中

關(guān)于機會均等
為了使得出塊的負載(或者說是機會)對于每個認證節(jié)點盡量均等,同時避免某些惡意節(jié)點持續(xù)出塊,clique中規(guī)定每一個認證節(jié)點在連續(xù)SIGNER_LIMIT個區(qū)塊中,最多只能簽發(fā)一個區(qū)塊,也就是說,每一輪中,最多只有SIGNER_COUNT - SIGNER_LIMIT個認證節(jié)點可以參與區(qū)塊簽發(fā)。
其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示認證節(jié)點的個數(shù)。

//snap.Signers是所有的認證節(jié)點
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
        }
    }
}

在保證好節(jié)點的個數(shù)大于壞節(jié)點的前提下,好節(jié)點最少的個數(shù)為SIGNER_LIMIT(大于50%),壞節(jié)點最多的個數(shù)為SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一個節(jié)點在SIGNER_LIMIT這個時間窗口內(nèi)最多只能簽發(fā)一個區(qū)塊,這就使得惡意節(jié)點在不超過50%的情況下,從理論上無法一直掌握區(qū)塊的簽發(fā)權(quán)。

關(guān)于難度計算
為了讓每個認證節(jié)點都有均等的機會去簽發(fā)一個區(qū)塊,每個節(jié)點在簽發(fā)時都會判斷本節(jié)點是不是本輪的inturn節(jié)點,若是inturn節(jié)點,則該節(jié)點產(chǎn)生的區(qū)塊難度為2,否則為1。每一輪僅有一個節(jié)點為inturn節(jié)點。

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

當(dāng)inturn的結(jié)點離線時,其他結(jié)點會來競爭,難度值降為1。然而正常出塊時,limit中的所有認證結(jié)點包括一個inturn和其他noturn的結(jié)點,clique是采用了給noturn加延遲時間的方式來支持inturn首先出塊,避免noturn的結(jié)點無謂生成區(qū)塊,上面的延時代碼段已經(jīng)有提現(xiàn)了。
判斷是否為inturn的節(jié)點,將本地維護的認證節(jié)點按照字典序排序,若當(dāng)前區(qū)塊號除以認證節(jié)點個數(shù)的余數(shù)等于該節(jié)點的下標(biāo),則該節(jié)點為inturn節(jié)點。代碼實現(xiàn)在 snapshot.go中:

// 通過給定的區(qū)塊高度和簽發(fā)者返回該簽發(fā)者是否在輪次內(nèi)
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()代碼中有獲取快照,然后從快照中來檢查授權(quán)區(qū)塊簽名者的邏輯,那么我們繼續(xù)來看下Snapshot,首先看下Snapshot的結(jié)構(gòu)體:

// Snapshot對象是在給定時間點的一個認證投票的狀態(tài)
type Snapshot struct {
    config   *params.CliqueConfig // 共識引擎配置參數(shù)
    sigcache *lru.ARCCache        // 簽名緩存,最近的區(qū)塊簽名加速恢復(fù)。
    Number  uint64                      `json:"number"`  // 快照建立的區(qū)塊號
    Hash    common.Hash                 `json:"hash"`    // 快照建立的區(qū)塊哈希
    Signers map[common.Address]struct{} `json:"signers"` // 當(dāng)下認證簽名者的列表
    Recents map[uint64]common.Address   `json:"recents"` // 最近擔(dān)當(dāng)過數(shù)字簽名算法的signer 的地址
    Votes   []*Vote                     `json:"votes"`   // 按時間順序排列的投票名單。
    Tally   map[common.Address]Tally    `json:"tally"`   // 當(dāng)前的投票結(jié)果,避免重新計算。
}

快照Snapshot對象中存在投票的Votes和記票的Tally對象:

// Vote代表了一個獨立的投票,這個投票可以授權(quán)一個簽名者,更改授權(quán)列表。
type Vote struct {
    Signer    common.Address `json:"signer"`    // 已授權(quán)的簽名者(通過投票)
    Block     uint64         `json:"block"`     // 投票區(qū)塊號
    Address   common.Address `json:"address"`   // 被投票的賬戶,修改它的授權(quán)
    Authorize bool           `json:"authorize"` // 對一個被投票賬戶是否授權(quán)或解授權(quán)
}

// Tally是一個簡單的用來保存當(dāng)前投票分?jǐn)?shù)的計分器
type Tally struct {
    Authorize bool `json:"authorize"` // 授權(quán)true或移除false
    Votes     int  `json:"votes"`     // 該提案已獲票數(shù)
}

Snapshot是一個快照,不僅是一個緩存,而且存儲了最近簽名者的map
loadSnapshot用來從數(shù)據(jù)庫中加載一個已存在的快照:

func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
    //使用Database接口的Get方法通過Key來查詢緩存內(nèi)容
    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函數(shù)用于創(chuàng)建快照,這個方法沒有初始化最近的簽名者集合,所以只使用創(chuàng)世塊:

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
}

繼續(xù)看下snapshot函數(shù)的具體實現(xiàn):

// 快照會在給定的時間點檢索授權(quán)快照
func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    // 在內(nèi)存或者磁盤上查找一個快照來檢查檢查點checkpoints
    var (
        headers []*types.Header    //區(qū)塊頭
        snap    *Snapshot    //快照對象
    )
    for snap == nil {
        // 如果在內(nèi)存中找到快照時,快照對象從內(nèi)存中取
        if s, ok := c.recents.Get(hash); ok {
            snap = s.(*Snapshot)
            break
        }
        // 如果在磁盤檢查點找到快照時
        if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到數(shù)據(jù)庫的區(qū)塊的區(qū)塊號
            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
            }
        }
        // 如果在創(chuàng)世塊,則新建一個快照
        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
        }
        // 沒有對于這個區(qū)塊頭的快照,收集區(qū)塊頭并向后移
        var header *types.Header
        if len(parents) > 0 {
            // 如果我們有明確的父,從那里挑選(強制執(zhí)行)
            header = parents[len(parents)-1]
            if header.Hash() != hash || header.Number.Uint64() != number {
                return nil, consensus.ErrUnknownAncestor
            }
            parents = parents[:len(parents)-1]
        } else {
            // 沒有明確的父(或者沒有更多的父)轉(zhuǎn)到數(shù)據(jù)庫獲取
            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) //通過區(qū)塊頭生成一個新的快照
    if err != nil {
        return nil, err
    }
    c.recents.Add(snap.Hash, snap) //將當(dāng)前區(qū)塊的區(qū)塊hash保存到最近區(qū)塊快照,加速快照重組

    // 如果我們已經(jīng)生成一個新的檢查點快照,保存在磁盤上
    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通過區(qū)塊頭來創(chuàng)建一個新的快照,這個apply中主要做什么操作?

//apply將給定的區(qū)塊頭應(yīng)用于原始頭來創(chuàng)建新的授權(quán)快照。
func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
      //可以傳空區(qū)塊頭
    if len(headers) == 0 {
        return s, nil
    }
      //完整性檢查區(qū)塊頭可用性
    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
    }
      //迭代區(qū)塊頭,創(chuàng)建一個新的快照
    snap := s.copy()
    // 投票的處理核心代碼
    for _, header := range headers {
        // 刪除檢查點區(qū)塊的所有投票 
        number := header.Number.Uint64()
        // 如果區(qū)塊高度正好在Epoch結(jié)束,則清空投票和計分器,避免了維護統(tǒng)計信息無限增大的內(nèi)存開銷;
        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)
        }
        // 從區(qū)塊頭中解密出來簽名者地址
        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

        // 區(qū)塊頭認證,不管該簽名者之前的任何投票
        for i, vote := range snap.Votes {
            if vote.Signer == signer && vote.Address == header.Coinbase {
                // 從緩存計數(shù)器中移除該投票
                snap.uncast(vote.Address, vote.Authorize)

                // 從按時間排序的列表中移除投票
                snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
                break // 只允許一票
            }
        }
        // 從簽名者中計數(shù)新的投票
        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,
            })
        }
        // 判斷票數(shù)是否超過一半的投票者,如果投票通過,更新簽名者列表
        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 {                                    
                          // 從緩存計數(shù)器中移除該投票
                        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對象,首先從數(shù)字簽名中恢復(fù)出簽名所用公鑰,轉(zhuǎn)化為common.Address類型,作為signer地址。數(shù)字簽名(signagure)長度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未認證的,則直接退出本次迭代;如果是已認證的,則投票+1。所以一個父區(qū)塊可添加一張記名投票,signer作為投票方地址,Header.Coinbase作為被投票地址,投票內(nèi)容authorized可由Header.Nonce取值確定。更新投票統(tǒng)計信息。如果被投票地址的總投票次數(shù)達到已認證地址個數(shù)的一半,則通過之。該被投票地址的認證狀態(tài)立即被更改,根據(jù)是何種更改,相應(yīng)的更新緩存數(shù)據(jù),并刪除過時的投票信息。在所有Header對象都被處理完后,Snapshot內(nèi)部的Number,Hash值會被更新,表明當(dāng)前Snapshot快照結(jié)構(gòu)已經(jīng)更新到哪個區(qū)塊了。

區(qū)塊驗證的過程是普通節(jié)點在收到一個新區(qū)塊時,會從區(qū)塊頭的extraData字段中取出認證節(jié)點的簽名,利用標(biāo)準(zhǔn)的spec256k1橢圓曲線進行反解公鑰信息,并且從公鑰中截取出簽發(fā)節(jié)點的地址,若該節(jié)點是認證節(jié)點,且該節(jié)點本輪擁有簽名的權(quán)限,則認為該區(qū)塊為合法區(qū)塊。verifySeal是被SubmitWork(miner/remote_agent.go) 來調(diào)用,SubmitWork函數(shù)嘗試注入一個pow解決方案(共識引擎)到遠程代理,返回這個解決方案是否被接受。(不能同時是一個壞的pow也不能有其他任何錯誤,例如沒有工作被pending)解決方案有效時,返回到礦工并且通知接受結(jié)果。

// 檢查包頭中包含的簽名是否滿足共識協(xié)議要求。該方法接受一個可選的父頭的列表,這些父頭還不是本地區(qū)塊鏈的一部分,用于生成快照
func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
    // 不支持校檢創(chuàng)世塊
    number := header.Number.Uint64()
    if number == 0 {
        return errUnknownBlock
    }
    // 檢索出所需的區(qū)塊對象來校檢去開頭和將其緩存
    snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    if err != nil {
        return err
    }

    //解析授權(quán)密鑰并檢查簽署者,ecrecover方法從區(qū)塊頭中反解出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 {
            // 簽署者是最近的,只有當(dāng)前塊沒有移出時才會失敗,參見seal中的機會均等
            if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
                return errUnauthorized
            }
        }
    }
    // 設(shè)置區(qū)塊難度,參見上面的區(qū)塊難度部分
    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
}

前面已經(jīng)分析了Clique的認證節(jié)點的出塊和校檢的過程,那么如何來區(qū)分一個節(jié)點是認證節(jié)點還是一個普通節(jié)點?以及一個授權(quán)者列表是如何產(chǎn)生并如何全網(wǎng)同步的?

Clique通過投票機制來確認一個認證節(jié)點,投票的范圍在委員會中,委員會就是所有節(jié)點礦工集合,普通節(jié)點沒有區(qū)塊生成權(quán)利。礦工的投票流程如下:

委員會節(jié)點通過RPC調(diào)用Propose,對某節(jié)點狀態(tài)變更,從普通節(jié)點變成認證階段,或者相反,寫入到Clique.purposal集合中

// Propose注入一個新的授權(quán)提案,可以授權(quán)一個簽名者或者移除一個。
func (api *API) Propose(address common.Address, auth bool) {
    api.clique.lock.Lock()
    defer api.clique.lock.Unlock()

    api.clique.proposals[address] = auth// true:授權(quán),false:移除
}

本地認證節(jié)點在一次區(qū)塊打包的過程中,從purposal池中隨機挑選一條還未被應(yīng)用的purposal,并將信息填入?yún)^(qū)塊頭,將區(qū)塊廣播給其他節(jié)點;

//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))] //隨機挑選一條投票節(jié)點的地址賦值給區(qū)塊頭的Coinbase字段。
            // 通過提案內(nèi)容來組裝區(qū)塊頭的隨機數(shù)字段。
            if c.proposals[header.Coinbase] {
                copy(header.Nonce[:], nonceAuthVote)
            } else {
                copy(header.Nonce[:], nonceDropVote)
            }
        }

在挖礦開始以后,會在miner.start()中提交一個commitNewWork,其中調(diào)用上面Prepare

    if err := self.engine.Prepare(self.chain, header); err != nil {
        log.Error("Failed to prepare header for mining", "err", err)
        return
    }

其他節(jié)點在接收到區(qū)塊后,取出其中的信息,封裝成一個vote進行存儲,并將投票結(jié)果應(yīng)用到本地,若關(guān)于目標(biāo)節(jié)點的狀態(tài)更改獲得的一致投票超過1/2,則更改目標(biāo)節(jié)點的狀態(tài):若為新增認證節(jié)點,將目標(biāo)節(jié)點的地址添加到本地的認證節(jié)點的列表中;若為刪除認證節(jié)點,將目標(biāo)節(jié)點的地址從本地的認證節(jié)點列表中刪除。具體實現(xiàn)可以查看上面的Snapshot.apply()方法

轉(zhuǎn)載請注明: 轉(zhuǎn)載自Ryan是菜鳥 | LNMP技術(shù)棧筆記

如果覺得本篇文章對您十分有益,何不 打賞一下

本文鏈接地址: 以太坊POA共識機制Clique源碼分析

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/24032.html

相關(guān)文章

  • 以太源碼分析共識(2)引擎

    摘要:前言是以太坊封定義的一個接口,它的功能可以分為類驗證區(qū)塊類,主要用在將區(qū)塊加入到區(qū)塊鏈前,對區(qū)塊進行共識驗證。輔助類生成以太坊共識相關(guān)的。被使用,是以太坊狀態(tài)管理服務(wù),當(dāng)報告數(shù)據(jù)的時候,需要獲取區(qū)塊的信息。 前言 engine是以太坊封定義的一個接口,它的功能可以分為3類: 驗證區(qū)塊類,主要用在將區(qū)塊加入到區(qū)塊鏈前,對區(qū)塊進行共識驗證。 產(chǎn)生區(qū)塊類,主要用在挖礦時。 輔助類。 接下...

    YuboonaZhang 評論0 收藏0
  • 以太創(chuàng)世區(qū)塊與鏈配置載入分析

    摘要:本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接以太坊創(chuàng)世區(qū)塊與鏈配置載入分析,原文已更新,請讀者前往原文閱讀。以太坊允許通過創(chuàng)世配置文件來初始化創(chuàng)世區(qū)塊,也可使用選擇使用內(nèi)置的多個網(wǎng)絡(luò)環(huán)境的創(chuàng)世配置。再準(zhǔn)備兩個以太坊賬戶,以便在創(chuàng)世時存入資產(chǎn)。 本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接:以太坊創(chuàng)世區(qū)塊與鏈配置載入分析,原文已更新,請讀者前往原文閱讀。 創(chuàng)世區(qū)塊作為第零個區(qū)塊,其他區(qū)塊直接或間接引用到...

    姘擱『 評論0 收藏0
  • 以太源碼分析—挖礦與共識

    摘要:下面來看看具體是怎么實現(xiàn)接口的可以看到,啟動了多個線程調(diào)用函數(shù),當(dāng)有線程挖到時,會通過傳入的通道傳出結(jié)果。可以看到在主要循環(huán)中,不斷遞增的值,調(diào)用函數(shù)計算上面公式中的左邊,而則是公式的右邊。 前言 挖礦(mine)是指礦工節(jié)點互相競爭生成新區(qū)塊以寫入整個區(qū)塊鏈獲得獎勵的過程.共識(consensus)是指區(qū)塊鏈各個節(jié)點對下一個區(qū)塊的內(nèi)容形成一致的過程在以太坊中, miner包向外提供挖...

    walterrwu 評論0 收藏0
  • 360共享云路由香港開賣,售價669港幣

    摘要:月日上午點,共享云路由器在香港正式開售,售價港幣,用戶可使用香港本地手機號碼在香港電視注冊后進行選購。目前云鉆已上線競拍和區(qū)塊貓等落地應(yīng)用,已有用戶使用云鉆成功競拍獲得。6月22日上午10點,360共享云路由器在香港正式開售,售價699港幣,用戶可使用香港本地手機號碼在香港電視 HKTVmall 注冊后進行選購。消息一經(jīng)公開,便吸引了眾多關(guān)注。作為360的首款區(qū)塊鏈硬件產(chǎn)品,360共享云路由...

    馬永翠 評論0 收藏0
  • 以太客戶端Geth命令用法-參數(shù)詳解

    摘要:本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接以太坊客戶端命令用法參數(shù)詳解原文已更新,請讀者前往原文閱讀在以太坊智能合約開發(fā)中最常用的工具必備開發(fā)工具,一個多用途的命令行工具。如果你還不知道是什么,請先閱讀入門篇以太坊是什么。 本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接:以太坊客戶端Geth命令用法-參數(shù)詳解原文已更新,請讀者前往原文閱讀 Geth在以太坊智能合約開發(fā)中最常用的工具(必備開發(fā)工具),一...

    Brenner 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<