成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

以太坊POA共識(shí)機(jī)制Clique源碼分析

Stardustsky / 3100人閱讀

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

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

Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置為clique.Clique, 根據(jù)當(dāng)前節(jié)點(diǎn)的礦工地址(默認(rèn)是acounts[0]), 配置clique的 簽名者 : clique.Authorize(eb, wallet.SignHash) ,其中簽名函數(shù)是SignHash,對(duì)給定的hash進(jìn)行簽名。

func (s *Ethereum) StartMining(local bool) error {
    eb, err := s.Etherbase()//用戶(hù)地址
    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共識(shí)算法
        wallet, err := s.accountManager.Find(accounts.Account{Address: eb})    // 根據(jù)用它胡地址獲取wallet對(duì)象
        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對(duì)象獲取簽名方法
    }
    if local {
        // 如果本地CPU已開(kāi)始挖礦,我們可以禁用引入的交易拒絕機(jī)制來(lái)加速同步時(shí)間。CPU挖礦在主網(wǎng)是荒誕的,所以沒(méi)有人能碰到這個(gè)路徑,然而一旦CPU挖礦同步標(biāo)志完成以后,將保證私網(wǎng)工作也在一個(gè)獨(dú)立礦工結(jié)點(diǎn)。
        atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    }
    go s.miner.Start(eb)
    return nil
}

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

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

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

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

type CliqueConfig struct {
    Period uint64 `json:"period"` // 在區(qū)塊之間執(zhí)行的秒數(shù)(比如出塊秒數(shù)15s)
    Epoch  uint64 `json:"epoch"`  // Epoch長(zhǎng)度,重置投票和檢查點(diǎn)(比如Epoch長(zhǎng)度是30000個(gè)block, 每次進(jìn)入新的epoch,前面的投票都被清空, 重新開(kāi)始記錄)
}

在上面的 StartMining中,通過(guò)Clique. Authorize來(lái)注入簽名者和簽名方法,先來(lái)看下Authorize:

func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    c.lock.Lock()
    defer c.lock.Unlock()
    // 這個(gè)方法就是為clique共識(shí)注入一個(gè)簽名者的私鑰地址已經(jīng)簽名函數(shù)用來(lái)挖出新塊
    c.signer = signer
    c.signFn = signFn
}

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

//通過(guò)本地簽名認(rè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的鏈,不支持空塊密封,沒(méi)有獎(jiǎng)勵(lì)但是能夠密封
    if c.config.Period == 0 && len(block.Transactions()) == 0 {
        return nil, errWaitTransactions
    }
    // 在整個(gè)密封區(qū)塊的過(guò)程中不要持有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)去簽名一個(gè)區(qū)塊
    if _, authorized := snap.Signers[signer]; !authorized {
        return nil, errUnauthorized
    }
    // 如果我們是在‘最近簽名者’中則等待下一個(gè)區(qū)塊
    for seen, recent := range snap.Recents {
        if recent == signer {
            // 當(dāng)前簽名者在‘最近簽名者’中,如果當(dāng)前區(qū)塊沒(méi)有剔除他的話(huà)只能等待(這里涉及到機(jī)會(huì)均等)
            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
            }
        }
    }
    // 好了,走到這說(shuō)明協(xié)議已經(jīng)允許我們來(lái)簽名這個(gè)區(qū)塊,等待我們的時(shí)間
    delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple
    if header.Difficulty.Cmp(diffNoTurn) == 0 {
        // 這不是我們的輪次來(lái)簽名,延遲一點(diǎn),隨機(jī)延遲,這樣對(duì)于每一個(gè)簽簽名者來(lái)說(shuō)來(lái)允許并發(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):
    }
    // 通過(guò)signFn簽名函數(shù)開(kāi)始簽名
    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)
    //通過(guò)區(qū)塊頭重新組裝生成一個(gè)區(qū)塊
    return block.WithSeal(header), nil
}

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

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

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

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

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

//snap.Signers是所有的認(rèn)證節(jié)點(diǎn)
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é)點(diǎn)的個(gè)數(shù)大于壞節(jié)點(diǎn)的前提下,好節(jié)點(diǎn)最少的個(gè)數(shù)為SIGNER_LIMIT(大于50%),壞節(jié)點(diǎn)最多的個(gè)數(shù)為SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一個(gè)節(jié)點(diǎn)在SIGNER_LIMIT這個(gè)時(shí)間窗口內(nèi)最多只能簽發(fā)一個(gè)區(qū)塊,這就使得惡意節(jié)點(diǎn)在不超過(guò)50%的情況下,從理論上無(wú)法一直掌握區(qū)塊的簽發(fā)權(quán)。

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

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

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

// 通過(guò)給定的區(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()代碼中有獲取快照,然后從快照中來(lái)檢查授權(quán)區(qū)塊簽名者的邏輯,那么我們繼續(xù)來(lái)看下Snapshot,首先看下Snapshot的結(jié)構(gòu)體:

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

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

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

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

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

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

func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
    //組裝一個(gè)Snapshot對(duì)象
    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ù)的具體實(shí)現(xiàn):

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

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

//apply將給定的區(qū)塊頭應(yīng)用于原始頭來(lái)創(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)建一個(gè)新的快照
    snap := s.copy()
    // 投票的處理核心代碼
    for _, header := range headers {
        // 刪除檢查點(diǎn)區(qū)塊的所有投票 
        number := header.Number.Uint64()
        // 如果區(qū)塊高度正好在Epoch結(jié)束,則清空投票和計(jì)分器,避免了維護(hù)統(tǒng)計(jì)信息無(wú)限增大的內(nèi)存開(kāi)銷(xiāo);
        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ū)塊頭中解密出來(lái)簽名者地址
        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ū)塊頭認(rèn)證,不管該簽名者之前的任何投票
        for i, vote := range snap.Votes {
            if vote.Signer == signer && vote.Address == header.Coinbase {
                // 從緩存計(jì)數(shù)器中移除該投票
                snap.uncast(vote.Address, vote.Authorize)

                // 從按時(shí)間排序的列表中移除投票
                snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
                break // 只允許一票
            }
        }
        // 從簽名者中計(jì)數(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ù)是否超過(guò)一半的投票者,如果投票通過(guò),更新簽名者列表
        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 {                                    
                          // 從緩存計(jì)數(shù)器中移除該投票
                        snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize)
                          // 從按時(shí)間排序的列表中移除投票    
                        snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)

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

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

// 檢查包頭中包含的簽名是否滿(mǎn)足共識(shí)協(xié)議要求。該方法接受一個(gè)可選的父頭的列表,這些父頭還不是本地區(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ū)塊對(duì)象來(lái)校檢去開(kāi)頭和將其緩存
    snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    if err != nil {
        return err
    }

    //解析授權(quán)密鑰并檢查簽署者,ecrecover方法從區(qū)塊頭中反解出Extra字段中簽名字符串來(lái)獲取簽名者地址
    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)前塊沒(méi)有移出時(shí)才會(huì)失敗,參見(jiàn)seal中的機(jī)會(huì)均等
            if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
                return errUnauthorized
            }
        }
    }
    // 設(shè)置區(qū)塊難度,參見(jiàn)上面的區(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的認(rèn)證節(jié)點(diǎn)的出塊和校檢的過(guò)程,那么如何來(lái)區(qū)分一個(gè)節(jié)點(diǎn)是認(rèn)證節(jié)點(diǎn)還是一個(gè)普通節(jié)點(diǎn)?以及一個(gè)授權(quán)者列表是如何產(chǎn)生并如何全網(wǎng)同步的?

Clique通過(guò)投票機(jī)制來(lái)確認(rèn)一個(gè)認(rèn)證節(jié)點(diǎn),投票的范圍在委員會(huì)中,委員會(huì)就是所有節(jié)點(diǎn)礦工集合,普通節(jié)點(diǎn)沒(méi)有區(qū)塊生成權(quán)利。礦工的投票流程如下:

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

// Propose注入一個(gè)新的授權(quán)提案,可以授權(quán)一個(gè)簽名者或者移除一個(gè)。
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:移除
}

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

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

在挖礦開(kāi)始以后,會(huì)在miner.start()中提交一個(gè)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é)點(diǎn)在接收到區(qū)塊后,取出其中的信息,封裝成一個(gè)vote進(jìn)行存儲(chǔ),并將投票結(jié)果應(yīng)用到本地,若關(guān)于目標(biāo)節(jié)點(diǎn)的狀態(tài)更改獲得的一致投票超過(guò)1/2,則更改目標(biāo)節(jié)點(diǎn)的狀態(tài):若為新增認(rèn)證節(jié)點(diǎn),將目標(biāo)節(jié)點(diǎn)的地址添加到本地的認(rèn)證節(jié)點(diǎn)的列表中;若為刪除認(rèn)證節(jié)點(diǎn),將目標(biāo)節(jié)點(diǎn)的地址從本地的認(rèn)證節(jié)點(diǎn)列表中刪除。具體實(shí)現(xiàn)可以查看上面的Snapshot.apply()方法

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

如果覺(jué)得本篇文章對(duì)您十分有益,何不 打賞一下

本文鏈接地址: 以太坊POA共識(shí)機(jī)制Clique源碼分析

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/24032.html

相關(guān)文章

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

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

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

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

    姘擱『 評(píng)論0 收藏0
  • 以太源碼分析—挖礦與共識(shí)

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

    walterrwu 評(píng)論0 收藏0
  • 360共享云路由香港開(kāi)賣(mài),售價(jià)669港幣

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

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

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

    Brenner 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<