Go-ethereum 源碼解析之 consensus/clique/snapshot.go

Go-ethereum 源碼解析之 consensus/clique/snapshot.go

package clique

import (
    "bytes"
    "encoding/json"
    "sort"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/ethdb"
    "github.com/ethereum/go-ethereum/params"
    lru "github.com/hashicorp/golang-lru"
)

// Vote represents a single vote that an authorized signer made to modify the
// list of authorizations.
type Vote struct {
    Signer    common.Address `json:"signer"`    // Authorized signer that cast this vote
    Block     uint64         `json:"block"`     // Block number the vote was cast in (expire old votes)
    Address   common.Address `json:"address"`   // Account being voted on to change its authorization
    Authorize bool           `json:"authorize"` // Whether to authorize or deauthorize the voted account
}

// Tally is a simple vote tally to keep the current score of votes. Votes that
// go against the proposal aren't counted since it's equivalent to not voting.
type Tally struct {
    Authorize bool `json:"authorize"` // Whether the vote is about authorizing or kicking someone
    Votes     int  `json:"votes"`     // Number of votes until now wanting to pass the proposal
}

// Snapshot is the state of the authorization voting at a given point in time.
type Snapshot struct {
    config   *params.CliqueConfig // Consensus engine parameters to fine tune behavior
    sigcache *lru.ARCCache        // Cache of recent block signatures to speed up ecrecover

    Number  uint64                      `json:"number"`  // Block number where the snapshot was created
    Hash    common.Hash                 `json:"hash"`    // Block hash where the snapshot was created
    Signers map[common.Address]struct{} `json:"signers"` // Set of authorized signers at this moment
    Recents map[uint64]common.Address   `json:"recents"` // Set of recent signers for spam protections
    Votes   []*Vote                     `json:"votes"`   // List of votes cast in chronological order
    Tally   map[common.Address]Tally    `json:"tally"`   // Current vote tally to avoid recalculating
}

// signers implements the sort interface to allow sorting a list of addresses
type signers []common.Address

func (s signers) Len() int           { return len(s) }
func (s signers) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 }
func (s signers) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// newSnapshot creates a new snapshot with the specified startup parameters. This
// method does not initialize the set of recent signers, so only ever use if for
// the genesis block.
func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *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
}

Appendix A. 總體批注

文件 clique/snapshot.go 主要是用于描述 Clique 共識(shí)算法中關(guān)于授權(quán)簽名者列表生成的快照信息炫刷,以及授權(quán)簽名者對(duì)給定區(qū)塊頭列表如何進(jìn)行具體簽名的規(guī)則。

假設(shè)授權(quán)簽名者列表的長(zhǎng)度為 K,當(dāng)前進(jìn)行投票的區(qū)塊編號(hào)為 N苦银,給定區(qū)塊頭中的投票簽名者在有序授權(quán)簽名者列表中的偏移為 P什往,偏移從 0 開始左敌。

快照中包含的主要信息有:

  • 創(chuàng)建快照時(shí)的區(qū)塊編號(hào)
  • 創(chuàng)建快照時(shí)的區(qū)塊哈希
  • 授權(quán)簽名者集合
  • 最近 K/2 + 1 個(gè)區(qū)塊中各區(qū)塊編號(hào)對(duì)應(yīng)的簽名者集合
  • 按區(qū)塊編號(hào)順序投票的投票列表
  • 以及各被投票簽名者的得票計(jì)數(shù)器始苇。

授權(quán)簽名者的具體簽名規(guī)則:

  • 待應(yīng)用簽名的區(qū)塊頭列表需要滿足要求:區(qū)塊的編號(hào)是連續(xù)的绰沥。
  • K 個(gè)簽名者各自在最近連續(xù)的 K/2 + 1 個(gè)區(qū)塊最多只能投出一票瓦宜。
  • 第 P 個(gè)簽名者只能在滿足 N % K == P 條件的區(qū)塊中進(jìn)行投票蔚万。
  • 對(duì)于一個(gè)投票,得票數(shù)需要超過 K/2临庇,不包括 K/2反璃。

??? 第 1 個(gè)疑問:大多數(shù)時(shí)候在區(qū)塊頭中并不會(huì)進(jìn)行投票,而區(qū)塊頭列表又需要滿足連續(xù)性這個(gè)條件苔巨,但是看代碼中對(duì)于不包含投票的區(qū)塊頭并沒有直接過濾的操作版扩。

??? 第 2 個(gè)疑問:根據(jù)授權(quán)簽名者的具體簽名規(guī)則,在知道 K 的時(shí)候侄泽,能夠推斷出在區(qū)塊 N 中進(jìn)行投票的簽名者為 P礁芦。這在 PoA 聯(lián)盟鏈中會(huì)不會(huì)導(dǎo)致安全漏洞。

!!! 一個(gè) BUG:在投票解除授權(quán)簽名者時(shí)悼尾,存在一個(gè)問題柿扣。當(dāng)授權(quán)簽名者列表中只剩下一個(gè)簽名者,且該簽名者投票解除自己的授權(quán)時(shí)闺魏,會(huì)觸發(fā)此問題未状,導(dǎo)致授權(quán)簽名者列表為空,引起之后用授權(quán)簽名者列表長(zhǎng)度作分母時(shí)的代碼報(bào)除 0 錯(cuò)誤析桥。
- 真正有問題的代碼司草,具體代碼見方法 Snapshot.apply() 中的 delete(snap.Signers, header.Coinbase)。
- 觸發(fā)問題的代碼泡仗,具體代碼見方法 Snapshot.inturn() 中的 return (number % uint64(len(signers))) == uint64(offset)

定義了多種數(shù)據(jù)結(jié)構(gòu)埋虹,如:

  • 數(shù)據(jù)結(jié)構(gòu) Vote 用于描述一次具體的投票信息。
  • 數(shù)據(jù)結(jié)構(gòu) Tally 用于描述一個(gè)簡(jiǎn)單的投票計(jì)數(shù)器娩怎。
  • 數(shù)據(jù)結(jié)構(gòu) Snapshot 用于描述指定時(shí)間點(diǎn)的授權(quán)投票狀態(tài)搔课。
  • 數(shù)據(jù)結(jié)構(gòu) signers 用于描述授權(quán)簽名者列表的封裝器,并實(shí)現(xiàn)了排序接口截亦。數(shù)據(jù)結(jié)構(gòu) singers 支持對(duì)授權(quán)簽名者列表進(jìn)行升序排序爬泥,因此可以計(jì)算出給定簽名者在整個(gè)授權(quán)簽名者列表的有序偏移 P柬讨。

1. type Vote struct

數(shù)據(jù)結(jié)構(gòu) Vote 表示授權(quán)簽名者為了修改授權(quán)列表而進(jìn)行的一次投票。

  • Signer common.Address: 投票的授權(quán)簽名者
  • Block uint6: 投票的區(qū)塊編號(hào)(投票過期)
  • Address common.Address: 被投票的帳戶袍啡,以更改其授權(quán)
  • Authorize bool: 表示是否授權(quán)或取消對(duì)已投票帳戶的授權(quán)

2. type Tally struct

數(shù)據(jù)結(jié)構(gòu) Tally 是一個(gè)簡(jiǎn)單的投票計(jì)數(shù)器踩官,以保持當(dāng)前的投票得分。投票反對(duì)該提案不計(jì)算在內(nèi)葬馋,因?yàn)樗韧诓煌镀薄?/p>

  • Authorize bool: 投票是關(guān)于授權(quán)還是踢某人
  • Votes int: 到目前為止希望通過提案的投票數(shù)

3. type Snapshot struct

數(shù)據(jù)結(jié)構(gòu) Snapshot 表示指定時(shí)間點(diǎn)的授權(quán)投票狀態(tài)卖鲤。

  • config *params.CliqueConfig: 共識(shí)引擎參數(shù)以微調(diào)行為

  • sigcache *lru.ARCCache: 緩存最近的塊簽名以加速函數(shù) ecrecover()

  • Number uint64: 創(chuàng)建快照的區(qū)塊編號(hào)

  • Hash common.Hash: 創(chuàng)建快照的區(qū)塊哈希

  • Signers map[common.Address]struct{}: 這一刻的授權(quán)簽名者集合

  • Recents map[uint64]common.Address: 一組最近的簽名者集,用于防止 spam 攻擊畴嘶。分別記錄最近 k/2 + 1 次的區(qū)塊編號(hào)對(duì)應(yīng)的簽名者蛋逾。

  • Votes []*Vote: 按區(qū)塊編號(hào)順序投票的投票列表

  • Tally map[common.Address]Tally: 目前的投票計(jì)數(shù)器,以避免重新計(jì)算

  • 通過構(gòu)造函數(shù)
    newSnapshot() 使用指定的啟動(dòng)參數(shù)創(chuàng)建新快照窗悯。這種方法不會(huì)初始化最近的簽名者集区匣,所以只能用于創(chuàng)世塊。

  • 通過函數(shù) loadSnapshot() 從數(shù)據(jù)庫加載已經(jīng)存在的快照蒋院。

  • 通過方法 store() 將快照插入數(shù)據(jù)庫亏钩。

  • 通過方法 copy() 會(huì)創(chuàng)建快照的深層副本,但不會(huì)創(chuàng)建單獨(dú)的投票欺旧。

  • 通過方法 validVote() 返回在給定的快照上下文中投出的特定投票是否有意義(例如姑丑,不要嘗試添加已經(jīng)授權(quán)的簽名者)。

  • 通過方法 cast() 往投票計(jì)數(shù)器 Snapshot.tally 中增加新的投票辞友。

  • 通過方法 uncast() 從投票計(jì)數(shù)器 Snapshot.tally 中移除之前的一次投票栅哀。

  • 通過方法 apply() 通過將給定的區(qū)塊頭列表應(yīng)用于原始的快照來生成新的授權(quán)快照。

  • 通過方法 signers() 按升序返回授權(quán)簽名者列表称龙。

  • 通過方法 inturn() 返回簽名者在給定區(qū)塊高度是否是 in-turn 的留拾。

4. type signers []common.Address

封裝器 signers 實(shí)現(xiàn)了排序接口,以允許排序地址列表鲫尊。

  • 通過方法 Len() 返回列表中元素的個(gè)數(shù)痴柔。
  • 通過方法 Less() 比較列表中第 i 個(gè)元素是否比第 j 個(gè)元素的小,如果是返回 true疫向。
  • 通過方法 Swap() 交換列表中第 i 個(gè)元素和第 j 個(gè)元素咳蔚。

Appendix B. 詳細(xì)批注

1. type Vote struct

數(shù)據(jù)結(jié)構(gòu) Vote 表示授權(quán)簽名者為了修改授權(quán)列表而進(jìn)行的一次投票。

  • Signer common.Address: 投票的授權(quán)簽名者
  • Block uint6: 投票的區(qū)塊編號(hào)(投票過期)
  • Address common.Address: 被投票的帳戶搔驼,以更改其授權(quán)
  • Authorize bool: 表示是否授權(quán)或取消對(duì)已投票帳戶的授權(quán)

2. type Tally struct

數(shù)據(jù)結(jié)構(gòu) Tally 是一個(gè)簡(jiǎn)單的投票計(jì)數(shù)器谈火,以保持當(dāng)前的投票得分。投票反對(duì)該提案不計(jì)算在內(nèi)匙奴,因?yàn)樗韧诓煌镀薄?/p>

  • Authorize bool: 投票是關(guān)于授權(quán)還是踢某人
  • Votes int: 到目前為止希望通過提案的投票數(shù)

3. type Snapshot struct

數(shù)據(jù)結(jié)構(gòu) Snapshot 表示指定時(shí)間點(diǎn)的授權(quán)投票狀態(tài)堆巧。

  • config *params.CliqueConfig: 共識(shí)引擎參數(shù)以微調(diào)行為

  • sigcache *lru.ARCCache: 緩存最近的塊簽名以加速函數(shù) ecrecover()

  • Number uint64: 創(chuàng)建快照的區(qū)塊編號(hào)

  • Hash common.Hash: 創(chuàng)建快照的區(qū)塊哈希

  • Signers map[common.Address]struct{}: 這一刻的授權(quán)簽名者集合

  • Recents map[uint64]common.Address: 一組最近的簽名者集妄荔,用于防止 spam 攻擊泼菌。分別記錄最近 k/2 + 1 次的區(qū)塊編號(hào)對(duì)應(yīng)的簽名者谍肤。

  • Votes []*Vote: 按區(qū)塊編號(hào)順序投票的投票列表

  • Tally map[common.Address]Tally: 目前的投票計(jì)數(shù)器,以避免重新計(jì)算

1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot

構(gòu)造函數(shù)
newSnapshot() 使用指定的啟動(dòng)參數(shù)創(chuàng)建新快照哗伯。這種方法不會(huì)初始化最近的簽名者集荒揣,所以只能用于創(chuàng)世塊。

2. func loadSnapshot(config *params.CliqueConfig, sigcache lru.ARCCache, db ethdb.Database, hash common.Hash) (Snapshot, error)

函數(shù) loadSnapshot() 從數(shù)據(jù)庫加載已經(jīng)存在的快照焊刹。

主要的實(shí)現(xiàn)細(xì)節(jié)如下:

  • 調(diào)用方法 db.Get() 從數(shù)據(jù)庫加載 JSON 數(shù)據(jù)流
  • 調(diào)用方法 json.Unmarshal() 從 JSON 數(shù)據(jù)流中解碼出對(duì)象 clique.Snapshot
  • 與方法 Snapshot.store() 的功能相反系任。

3. func (s *Snapshot) store(db ethdb.Database) error

方法 store() 將快照插入數(shù)據(jù)庫。

主要的實(shí)現(xiàn)細(xì)節(jié)如下:

  • 調(diào)用方法 json.Marshal() 將對(duì)象 clique.Snapshot 編碼成 JSON 數(shù)據(jù)流虐块。
  • 調(diào)用方法 db.Put() 將 JSON 數(shù)據(jù)流插入數(shù)據(jù)庫俩滥。
  • 與函數(shù) loadSnapshot() 的功能相反。

4. func (s *Snapshot) copy() *Snapshot

方法 copy() 會(huì)創(chuàng)建快照的深層副本贺奠,但不會(huì)創(chuàng)建單獨(dú)的投票霜旧。

5. func (s *Snapshot) validVote(address common.Address, authorize bool) bool

方法 validVote() 返回在給定的快照上下文中投出的特定投票是否有意義(例如,不要嘗試添加已經(jīng)授權(quán)的簽名者)儡率。

主要的實(shí)現(xiàn)細(xì)節(jié)如下:

  • 當(dāng) authorize 為 true 時(shí)挂据,則 address 應(yīng)該不存在于 Snapshot.Signers;且當(dāng) authorize 為 false 時(shí)儿普,則 address 應(yīng)該存在于 Snapshot.Signers崎逃。這兩種情況都是有效的投票,否則為無效的投票眉孩。
  • 也就是當(dāng)投出剔除授權(quán)簽名者个绍,該簽名者應(yīng)該存在于授權(quán)簽名者列表。當(dāng)投出新增授權(quán)簽名者時(shí)勺像,該簽名者應(yīng)該不存在于授權(quán)簽名者列表障贸。
  • 判定算法有點(diǎn)繞
    • return (signer && !authorize) || (!signer && authorize)
func (s *Snapshot) validVote(address common.Address, authorize bool) bool {
    _, signer := s.Signers[address]
    return (signer && !authorize) || (!signer && authorize)
}

6. func (s *Snapshot) cast(address common.Address, authorize bool) bool

方法 cast() 往投票計(jì)數(shù)器 Snapshot.tally 中增加新的投票。

主要的實(shí)現(xiàn)細(xì)節(jié)如下:

  • 調(diào)用方法 Snapshot.validVote() 驗(yàn)證投票的有效性吟宦。
  • 需要考慮對(duì)指定地址的投票是全新的篮洁,還是只是增加得票數(shù)即可。

7. func (s *Snapshot) uncast(address common.Address, authorize bool) bool

方法 uncast() 從投票計(jì)數(shù)器 Snapshot.tally 中移除之前的一次投票殃姓。

主要的實(shí)現(xiàn)細(xì)節(jié)如下:

  • 需要確保此次投票和之前的投票一致袁波。
  • 返還投票時(shí)需要考慮返還后指定地址的得票數(shù)是否為 0.

8. func (s Snapshot) apply(headers []types.Header) (*Snapshot, error)

方法 apply() 通過將給定的區(qū)塊頭列表應(yīng)用于原始的快照來生成新的授權(quán)快照。

主要的實(shí)現(xiàn)細(xì)節(jié)如下:

  • 如果 len(headers) == 0蜗侈,則直接返回篷牌。允許傳入空 headers 以獲得更清晰的代碼。

  • 檢查區(qū)塊頭列表的完整性踏幻。即區(qū)塊頭列表中的區(qū)塊頭必須是連續(xù)的枷颊,且是根據(jù)區(qū)塊編號(hào)升序排序的。

  • 除了參數(shù) headers 必須是連續(xù)且升序之外,第一個(gè)區(qū)塊頭的區(qū)塊編號(hào)也必須是當(dāng)前快照所處的區(qū)塊編號(hào)的下一個(gè)區(qū)塊夭苗, 即 headers[0].Number.Uint64() != s.Number+1信卡。

  • 通過方法 Snapshot.copy() 創(chuàng)建要返回的新的快照,并在此新快照上依次應(yīng)用參數(shù)區(qū)塊頭列表中的區(qū)塊頭 header题造。

    • 檢查當(dāng)前區(qū)塊頭是否為檢查點(diǎn)區(qū)塊傍菇,如果是則清除所有的投票信息。
    • 從最近的簽名者列表(snap.Recents)中刪除最舊的簽名者以允許它再次簽名界赔。
      • 具體規(guī)則為:Snapshot.Recents 最多只會(huì)記錄 K/2 + 1 個(gè)最近的簽名者簽名記錄丢习,也就是簽名者在最近 K/2 + 1 個(gè)區(qū)塊中只能簽名一次。具體的計(jì)算規(guī)則是:假設(shè)當(dāng)前區(qū)塊的編號(hào)為 N淮悼,會(huì)刪除 Snapshot.Recents 中第 N - (K/2 + 1) 個(gè)元素咐低,之后 Snapshot.Recents 中的第 1 個(gè)元素為 N - (K/2 + 1) + 1,在 N - (K/2 + 1) + 1 和 N 之間存在 (N - (N - (K/2 + 1) + 1) + 1) = K/2 + 1袜腥。之所以是 number >= limit渊鞋,這里 limit = K/2 + 1,是由于第 1 個(gè)區(qū)塊的編號(hào)為 0瞧挤,由 0 到 limit - 1 正好包含 (limit - 1) - 0 + 1 = (K/2 + 1 - 1) - 0 + 1 = k/2 + 1 個(gè)區(qū)塊锡宋。
    • 調(diào)用函數(shù) ecrecover() 從區(qū)塊頭中恢復(fù)出簽名者 signer。
    • 檢查簽名者 signer 是否存在于授權(quán)簽名者列表(snap.Signers)特恬,不存在返回 clique.errUnauthorized执俩。
    • 檢查簽名者 singer 是否在最近 K/2 + 1 個(gè)區(qū)塊中已經(jīng)簽名過,即是否已經(jīng)存在于最近的簽名者列表(snap.Recents)中癌刽。已經(jīng)簽名過則返回 clique.errUnauthorized役首。
    • 更新最近的簽名者列表(snap.Recents),snap.Recents[number] = signer显拜。
    • 對(duì)于授權(quán)的區(qū)塊頭衡奥,丟棄簽名者以前的任何投票 vote。
      • 通過方法 snap.uncast(vote.Address, vote.Authorize) 從投票計(jì)數(shù)器(Snapshot.Tally)移除該投票远荠。
      • 通過 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) 從 Snapshot.Votes 中移除該投票矮固。
    • 從區(qū)塊頭 types.Header.Nonce 中計(jì)算是授權(quán)(nonceAuthVote)還是解除授權(quán)(nonceDropVote)投票,無效 Nonce 值則返回 clique.errInvalidVote譬淳。
    • 通過方法 Snapshot.cast() 更新投票計(jì)數(shù)器(Snapshot.Tally)档址。如果成功,則往 snap.Votes 添加新的投票邻梆。
    • 如果區(qū)塊頭 header 中的投票被通過守伸,則更新授權(quán)簽名者列表。一次投票被通過的條件是浦妄,得票數(shù)大于等于 K/2 + 1尼摹,其中 K 為授權(quán)簽名者個(gè)數(shù)见芹。
      • 如果投票是授權(quán)簽名者,則 snap.Signers[header.Coinbase] = struct{}{}
      • 如果投票是解除授權(quán)簽名者蠢涝,則:
        • delete(snap.Signers, header.Coinbase)辆童。
        • 簽名者列表縮小,刪除任何剩余的最近的簽名者列表(snap.Recents)緩存惠赫,這個(gè)操作是為了維持與 K/2 + 1 相關(guān)的這個(gè)規(guī)則。
        • 丟棄授權(quán)簽名者以前的任何投票故黑,即調(diào)用 snap.uncast 更新 snap.Votes儿咱,和通過 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) 更新 snap.Votes。注意具體實(shí)現(xiàn)時(shí)的 i-- 操作场晶,這是由于 snap.Votes 的長(zhǎng)度已經(jīng)縮小了 1.
      • 丟棄剛剛更改的帳戶(header.coinbase)的所有先前投票
        • 通過 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) 修改 snap.Votes混埠。同時(shí)注意與上述相似的 i-- 操作。
        • 通過 delete(snap.Tally, header.Coinbase) 直接從 snap.Tally 刪除 header.Coinbase 的整個(gè)計(jì)數(shù)器诗轻。
  • 更新當(dāng)前快照創(chuàng)建時(shí)的區(qū)塊編號(hào)钳宪。即將原快照創(chuàng)建時(shí)的區(qū)塊編號(hào)加上參數(shù) headers 中 types.Header 的個(gè)數(shù),具體實(shí)現(xiàn)為 snap.Number += uint64(len(headers))

  • 更新當(dāng)前快照創(chuàng)建時(shí)的區(qū)塊哈希扳炬。即參數(shù) headers 中最后一個(gè) types.Header 的哈希吏颖。snap.Hash = headers[len(headers)-1].Hash()

9. func (s *Snapshot) signers() []common.Address

方法 signers() 按升序返回授權(quán)簽名者列表。

主要的實(shí)現(xiàn)細(xì)節(jié)如下:

  • 通過方法 sort.Sort() 按升序排序授權(quán)簽名者列表恨樟。

10. func (s *Snapshot) inturn(number uint64, signer common.Address) bool

方法 inturn() 返回簽名者在給定區(qū)塊高度是否是 in-turn 的半醉。

這里可以理解 in-turn 為授權(quán)簽名者列表對(duì)于給定區(qū)塊判定采用哪個(gè)簽名者的規(guī)則。

假設(shè)區(qū)塊編號(hào)為 N劝术,也就是區(qū)塊的高度為 N缩多。授權(quán)簽名者列表的長(zhǎng)度為 K。簽名者在授權(quán)簽名者列表中的順序?yàn)?P养晋,從 0 開始偏移衬吆。則如果 (N % K) == P 就返回 true,表示 in-turn绳泉。

主要的實(shí)現(xiàn)細(xì)節(jié)如下:

  • 即實(shí)現(xiàn)上面的規(guī)則逊抡。

4. type signers []common.Address

封裝器 signers 實(shí)現(xiàn)了排序接口,以允許排序地址列表零酪。

(1) func (s signers) Len() int

方法 Len() 返回列表中元素的個(gè)數(shù)秦忿。

(2) func (s signers) Less(i, j int) bool

方法 Less() 比較列表中第 i 個(gè)元素是否比第 j 個(gè)元素的小,如果是返回 true蛾娶。

(3) func (s signers) Swap(i, j int)

方法 Swap() 交換列表中第 i 個(gè)元素和第 j 個(gè)元素灯谣。

Reference

  1. https://github.com/ethereum/go-ethereum/blob/master/consensus/clique/snapshot.go

Contributor

  1. Windstamp, https://github.com/windstamp
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蛔琅,隨后出現(xiàn)的幾起案子胎许,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辜窑,死亡現(xiàn)場(chǎng)離奇詭異钩述,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)穆碎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門牙勘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人所禀,你說我怎么就攤上這事方面。” “怎么了色徘?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵恭金,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我褂策,道長(zhǎng)横腿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任斤寂,我火速辦了婚禮耿焊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遍搞。我一直安慰自己搀别,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布尾抑。 她就那樣靜靜地躺著歇父,像睡著了一般。 火紅的嫁衣襯著肌膚如雪再愈。 梳的紋絲不亂的頭發(fā)上榜苫,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音翎冲,去河邊找鬼垂睬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抗悍,可吹牛的內(nèi)容都是我干的驹饺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼缴渊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赏壹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衔沼,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蝌借,失蹤者是張志新(化名)和其女友劉穎昔瞧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菩佑,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡自晰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稍坯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酬荞。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瞧哟,靈堂內(nèi)的尸體忽然破棺而出混巧,到底是詐尸還是另有隱情,我是刑警寧澤绢涡,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站遣疯,受9級(jí)特大地震影響雄可,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缠犀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一数苫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辨液,春花似錦虐急、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至燎悍,卻和暖如春敬惦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谈山。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工俄删, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奏路。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓畴椰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親鸽粉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斜脂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容