区块链共识机制深入刨析(下)
字数 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)

基本介绍

共识压迫攻击是一种针对区块链网络共识机制的攻击方式,旨在干扰或破坏网络中的共识过程,削弱或破坏区块链的正常运行。

攻击手法

  1. 攻击目标:针对具有共识权力的节点,如PoS中的验证节点
  2. 拒绝服务攻击:发送大量无效或欺骗性共识消息,占用网络带宽
  3. 延迟攻击:故意延迟共识消息传播,阻碍共识过程
  4. 双重投票攻击:在同一时间段将验证权力分配给不同分支
  5. 合谋攻击:多个攻击者协同工作增加攻击规模和影响力

防御措施

  • 让验证者抵押资产以避免作恶
  • 避免使用容易被操控的信息产生随机数

2. 共识罚没机制(Consensus Slashing)

Filecoin的罚没机制

有三种情形会导致罚没:

  1. double-fork mining fault:同一矿工在相同高度产生两个CID不同的区块
  2. time-offset mining fault:矿工在不同高度的两个区块有相同的parent tipset
  3. parent-grinding fault:A、C高度相同且有相同parents,其中B的parents包含A但不包含C

漏洞示例

在Filecoin v1.3.0中修复了一处共识罚没机制设计错误:

if !types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.
区块链共识机制深入剖析:Clique算法与安全漏洞分析 文章前言 本教学文档基于先知社区《区块链共识机制深入刨析(下)》文章内容,深入解析Clique共识算法及其实现细节,并介绍几种区块链共识算法的安全漏洞。文档分为两大部分:Clique共识算法源码分析和共识安全漏洞剖析。 第一部分:Clique共识算法源码分析 1. Clique目录结构 Clique共识算法的实现主要包含以下文件: clique/api.go :RPC方法实现 clique/clique.go :核心共识逻辑 clique/snapshot.go :快照处理逻辑 2. 基本常量定义 3. 错误类型定义 4. 核心功能实现 4.1 地址提取(ecrecover) 4.2 引擎构造(New) 4.3 区块头验证(VerifyHeader) 4.4 区块头详细验证(verifyHeader) 4.5 级联字段验证(verifyCascadingFields) 5. 快照系统 5.1 快照数据结构 5.2 快照检索(snapshot) 5.3 快照应用(apply) 6. 出块机制 6.1 顺序判断(inturn) 6.2 签名者列表(signers) 6.3 签名验证(verifySeal) 6.4 区块准备(Prepare) 6.5 区块最终化(FinalizeAndAssemble) 6.6 私钥注入(Authorize) 6.7 区块签名(Seal) 第二部分:共识安全漏洞分析 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中修复了一处共识罚没机制设计错误: