区块链共识机制深入刨析(下)
字数 1172 2025-08-22 12:22:59
区块链共识机制深入剖析:Clique算法与安全漏洞分析
文章前言
本教学文档基于先知社区《区块链共识机制深入刨析(下)》文章内容,深入解析Clique共识算法及其实现细节,并介绍几种区块链共识算法的安全漏洞。文档分为两大部分:Clique共识算法源码分析和共识安全漏洞剖析。
第一部分:Clique共识算法源码分析
1. Clique目录结构
Clique共识算法的实现主要包含以下文件:
clique/api.go:RPC方法实现clique/clique.go:核心共识逻辑clique/snapshot.go:快照处理逻辑
2. 基本常量定义
const (
checkpointInterval = 1024 // 保存投票快照到数据库的区块间隔
inmemorySnapshots = 128 // 内存中保留的最近投票快照数量
inmemorySignatures = 4096 // 内存中保留的最近区块签名数量
wiggleTime = 500 * time.Millisecond // 允许并发签名者的随机延迟
)
var (
epochLength = uint64(30000) // 检查点间隔和重置待处理投票的默认区块数
extraVanity = 32 // 为签名者预留的额外数据前缀字节数
extraSeal = crypto.SignatureLength // 为签名者预留的额外数据后缀字节数
nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // 添加新签名者的魔法nonce值
nonceDropVote = hexutil.MustDecode("0x0000000000000000") // 移除签名者的魔法nonce值
uncleHash = types.CalcUncleHash(nil) // 始终为Keccak256(RLP([])),因为PoA中uncles无意义
diffInTurn = big.NewInt(2) // 顺序签名的区块难度
diffNoTurn = big.NewInt(1) // 非顺序签名的区块难度
)
3. 错误类型定义
var (
errUnknownBlock = errors.New("unknown block")
errInvalidCheckpointBeneficiary = errors.New("beneficiary in checkpoint block non-zero")
errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f")
errInvalidCheckpointVote = errors.New("vote nonce in checkpoint block non-zero")
errMissingVanity = errors.New("extra-data 32 byte vanity prefix missing")
errMissingSignature = errors.New("extra-data 65 byte signature suffix missing")
errExtraSigners = errors.New("non-checkpoint block contains extra signer list")
errInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block")
errMismatchingCheckpointSigners = errors.New("mismatching signer list on checkpoint block")
errInvalidMixDigest = errors.New("non-zero mix digest")
errInvalidUncleHash = errors.New("non empty uncle hash")
errInvalidDifficulty = errors.New("invalid difficulty")
errWrongDifficulty = errors.New("wrong difficulty")
errInvalidTimestamp = errors.New("invalid timestamp")
errInvalidVotingChain = errors.New("invalid voting chain")
errUnauthorizedSigner = errors.New("unauthorized signer")
errRecentlySigned = errors.New("recently signed")
)
4. 核心功能实现
4.1 地址提取(ecrecover)
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
hash := header.Hash()
if address, known := sigcache.Get(hash); known {
return address.(common.Address), nil
}
if len(header.Extra) < extraSeal {
return common.Address{}, errMissingSignature
}
signature := header.Extra[len(header.Extra)-extraSeal:]
pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature)
if err != nil {
return common.Address{}, err
}
var signer common.Address
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
sigcache.Add(hash, signer)
return signer, nil
}
4.2 引擎构造(New)
func New(config *params.CliqueConfig, db ethdb.Database) *Clique {
conf := *config
if conf.Epoch == 0 {
conf.Epoch = epochLength
}
recents, _ := lru.NewARC(inmemorySnapshots)
signatures, _ := lru.NewARC(inmemorySignatures)
return &Clique{
config: &conf,
db: db,
recents: recents,
signatures: signatures,
proposals: make(map[common.Address]bool),
}
}
4.3 区块头验证(VerifyHeader)
func (c *Clique) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
return c.verifyHeader(chain, header, nil)
}
func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
abort := make(chan struct{})
results := make(chan error, len(headers))
go func() {
for i, header := range headers {
err := c.verifyHeader(chain, header, headers[:i])
select {
case <-abort:
return
case results <- err:
}
}
}()
return abort, results
}
4.4 区块头详细验证(verifyHeader)
func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
if header.Number == nil {
return errUnknownBlock
}
number := header.Number.Uint64()
if header.Time > uint64(time.Now().Unix()) {
return consensus.ErrFutureBlock
}
checkpoint := (number % c.config.Epoch) == 0
if checkpoint && header.Coinbase != (common.Address{}) {
return errInvalidCheckpointBeneficiary
}
if !bytes.Equal(header.Nonce[:], nonceAuthVote) && !bytes.Equal(header.Nonce[:], nonceDropVote) {
return errInvalidVote
}
if checkpoint && !bytes.Equal(header.Nonce[:], nonceDropVote) {
return errInvalidCheckpointVote
}
if len(header.Extra) < extraVanity {
return errMissingVanity
}
if len(header.Extra) < extraVanity+extraSeal {
return errMissingSignature
}
signersBytes := len(header.Extra) - extraVanity - extraSeal
if !checkpoint && signersBytes != 0 {
return errExtraSigners
}
if checkpoint && signersBytes%common.AddressLength != 0 {
return errInvalidCheckpointSigners
}
if header.MixDigest != (common.Hash{}) {
return errInvalidMixDigest
}
if header.UncleHash != uncleHash {
return errInvalidUncleHash
}
if number > 0 {
if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0) {
return errInvalidDifficulty
}
}
if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil {
return err
}
return c.verifyCascadingFields(chain, header, parents)
}
4.5 级联字段验证(verifyCascadingFields)
func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
number := header.Number.Uint64()
if number == 0 {
return nil
}
var parent *types.Header
if len(parents) > 0 {
parent = parents[len(parents)-1]
} else {
parent = chain.GetHeader(header.ParentHash, number-1)
}
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
return consensus.ErrUnknownAncestor
}
if parent.Time+c.config.Period > header.Time {
return errInvalidTimestamp
}
snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
if err != nil {
return err
}
if number%c.config.Epoch == 0 {
signers := make([]byte, len(snap.Signers)*common.AddressLength)
for i, signer := range snap.signers() {
copy(signers[i*common.AddressLength:], signer[:])
}
extraSuffix := len(header.Extra) - extraSeal
if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) {
return errMismatchingCheckpointSigners
}
}
return c.verifySeal(chain, header, parents)
}
5. 快照系统
5.1 快照数据结构
type Vote struct {
Signer common.Address `json:"signer"` // 投票的授权签名者
Block uint64 `json:"block"` // 投票所在的区块号(用于过期旧投票)
Address common.Address `json:"address"` // 被投票修改授权的账户
Authorize bool `json:"authorize"` // 是授权还是取消授权
}
type Tally struct {
Authorize bool `json:"authorize"` // 投票是关于授权还是踢出
Votes int `json:"votes"` // 当前希望提案通过的票数
}
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"` // 最近签名者集合(防垃圾保护)
Votes []*Vote `json:"votes"` // 按时间顺序排列的投票列表
Tally map[common.Address]Tally `json:"tally"` // 当前投票统计(避免重复计算)
}
5.2 快照检索(snapshot)
func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
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 {
if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
snap = s
break
}
}
if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
checkpoint := chain.GetHeaderByNumber(number)
if checkpoint != nil {
hash := checkpoint.Hash()
signers := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength)
for i := 0; i < len(signers); i++ {
copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:])
}
snap = newSnapshot(c.config, c.signatures, number, hash, signers)
if err := snap.store(c.db); err != nil {
return nil, err
}
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
}
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)
if snap.Number%checkpointInterval == 0 && len(headers) > 0 {
if err = snap.store(c.db); err != nil {
return nil, err
}
}
return snap, err
}
5.3 快照应用(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 i, header := range headers {
number := header.Number.Uint64()
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, errUnauthorizedSigner
}
for _, recent := range snap.Recents {
if recent == signer {
return nil, errRecentlySigned
}
}
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
}
6. 出块机制
6.1 顺序判断(inturn)
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)
}
6.2 签名者列表(signers)
func (s *Snapshot) signers() []common.Address {
sigs := make([]common.Address, 0, len(s.Signers))
for sig := range s.Signers {
sigs = append(sigs, sig)
}
sort.Sort(signersAscending(sigs))
return sigs
}
6.3 签名验证(verifySeal)
func (c *Clique) verifySeal(chain consensus.ChainHeaderReader, 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
}
signer, err := ecrecover(header, c.signatures)
if err != nil {
return err
}
if _, ok := snap.Signers[signer]; !ok {
return errUnauthorizedSigner
}
for seen, recent := range snap.Recents {
if recent == signer {
if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
return errRecentlySigned
}
}
}
if !c.fakeDiff {
inturn := snap.inturn(header.Number.Uint64(), signer)
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
return errWrongDifficulty
}
if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
return errWrongDifficulty
}
}
return nil
}
6.4 区块准备(Prepare)
func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
header.Coinbase = common.Address{}
header.Nonce = types.BlockNonce{}
number := header.Number.Uint64()
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil {
return err
}
if number%c.config.Epoch != 0 {
c.lock.RLock()
addresses := make([]common.Address, 0, len(c.proposals))
for address, authorize := range c.proposals {
if snap.validVote(address, authorize) {
addresses = append(addresses, address)
}
}
if len(addresses) > 0 {
header.Coinbase = addresses[rand.Intn(len(addresses))]
if c.proposals[header.Coinbase] {
copy(header.Nonce[:], nonceAuthVote)
} else {
copy(header.Nonce[:], nonceDropVote)
}
}
c.lock.RUnlock()
}
header.Difficulty = calcDifficulty(snap, c.signer)
if len(header.Extra) < extraVanity {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
}
header.Extra = header.Extra[:extraVanity]
if number%c.config.Epoch == 0 {
for _, signer := range snap.signers() {
header.Extra = append(header.Extra, signer[:]...)
}
}
header.Extra = append(header.Extra, make([]byte, extraSeal)...)
header.MixDigest = common.Hash{}
parent := chain.GetHeader(header.ParentHash, number-1)
if parent == nil {
return consensus.ErrUnknownAncestor
}
header.Time = parent.Time + c.config.Period
if header.Time < uint64(time.Now().Unix()) {
header.Time = uint64(time.Now().Unix())
}
return nil
}
6.5 区块最终化(FinalizeAndAssemble)
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
c.Finalize(chain, header, state, txs, uncles)
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil
}
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.UncleHash = types.CalcUncleHash(nil)
}
6.6 私钥注入(Authorize)
func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
c.lock.Lock()
defer c.lock.Unlock()
c.signer = signer
c.signFn = signFn
}
6.7 区块签名(Seal)
func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
header := block.Header()
number := header.Number.Uint64()
if number == 0 {
return errUnknownBlock
}
if c.config.Period == 0 && len(block.Transactions()) == 0 {
log.Info("Sealing paused, waiting for transactions")
return nil
}
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 err
}
if _, authorized := snap.Signers[signer]; !authorized {
return errUnauthorizedSigner
}
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")
return nil
}
}
}
delay := time.Unix(int64(header.Time), 0).Sub(time.Now())
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))
}
sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeClique, CliqueRLP(header))
if err != nil {
return err
}
copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
go func() {
select {
case <-stop:
return
case <-time.After(delay):
}
select {
case results <- block.WithSeal(header):
default:
log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header))
}
}()
return nil
}
第二部分:共识安全漏洞分析
1. 共识压迫攻击(Consensus Jamming Attack)
基本介绍
共识压迫攻击是一种针对区块链网络共识机制的攻击方式,旨在干扰或破坏网络中的共识过程,削弱或破坏区块链的正常运行。
攻击手法
- 攻击目标:针对具有共识权力的节点,如PoS中的验证节点
- 拒绝服务攻击:发送大量无效或欺骗性共识消息,占用网络带宽
- 延迟攻击:故意延迟共识消息传播,阻碍共识过程
- 双重投票攻击:在同一时间段将验证权力分配给不同分支
- 合谋攻击:多个攻击者协同工作增加攻击规模和影响力
防御措施
- 让验证者抵押资产以避免作恶
- 避免使用容易被操控的信息产生随机数
2. 共识罚没机制(Consensus Slashing)
Filecoin的罚没机制
有三种情形会导致罚没:
- double-fork mining fault:同一矿工在相同高度产生两个CID不同的区块
- time-offset mining fault:矿工在不同高度的两个区块有相同的parent tipset
- parent-grinding fault:A、C高度相同且有相同parents,其中B的parents包含A但不包含C
漏洞示例
在Filecoin v1.3.0中修复了一处共识罚没机制设计错误:
if !types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.