Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion consensus/XDPoS/engines/engine_v2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,14 @@ func (x *XDPoS_v2) Initial(chain consensus.ChainReader, header *types.Header) er
x.lock.Lock()
defer x.lock.Unlock()

return x.initial(chain, header)
if err := x.initial(chain, header); err != nil {
return err
}
// Startup-only repair, skipped for chain readers that cannot open state.
if gapChain, ok := chain.(GapStateReader); ok {
x.RepairGapSnapshots(gapChain)
}
return nil
}

func (x *XDPoS_v2) initial(chain consensus.ChainReader, header *types.Header) error {
Expand Down
111 changes: 111 additions & 0 deletions consensus/XDPoS/engines/engine_v2/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package engine_v2

import (
"encoding/json"
"errors"
"fmt"

"github.com/XinFinOrg/XDPoSChain/common"
xdc_sort "github.com/XinFinOrg/XDPoSChain/common/sort"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log"
)
Expand Down Expand Up @@ -111,3 +115,110 @@ func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64, isGap
x.snapshots.Add(snap.Hash, snap)
return snap, nil
}

// GapStateReader is a chain reader that can also open historical state, which
// the startup repair needs to rebuild a snapshot from its gap block.
type GapStateReader interface {
consensus.ChainReader
StateAt(root common.Hash) (*state.StateDB, error)
}

var errNoCandidates = errors.New("no masternode candidates in state")

// BuildSnapshotFromState derives a gap block snapshot from the state committed
// at that block. The ordering must stay identical to core.BlockChain.UpdateM1
// and Downloader.generateSnapshot: a different equal-stake order yields a
// different masternode set.
func BuildSnapshotFromState(statedb *state.StateDB, number uint64, hash common.Hash) (*SnapshotV2, error) {
var ms []utils.Masternode
for _, candidate := range statedb.GetCandidates() {
if candidate.IsZero() {
continue
}
ms = append(ms, utils.Masternode{Address: candidate, Stake: statedb.GetCandidateCap(candidate)})
}
if len(ms) == 0 {
// An empty snapshot loads back fine and would permanently mask the hole.
return nil, errNoCandidates
}
xdc_sort.Slice(ms, func(i, j int) bool {
return ms[i].Stake.Cmp(ms[j].Stake) >= 0
})
Comment thread
gzliudan marked this conversation as resolved.

candidates := make([]common.Address, len(ms))
for i, m := range ms {
candidates[i] = m.Address
}
return NewSnapshot(number, hash, candidates), nil
}

// repairGapCandidates returns the gap block numbers at or below head whose
// snapshot can still matter to the running chain. getSnapshot maps head to the
// gap block between Gap and Gap+Epoch blocks back, which is always one of these.
func (x *XDPoS_v2) repairGapCandidates(head uint64) []uint64 {
epoch, gap := x.config.Epoch, x.config.Gap
if epoch == 0 || gap >= epoch {
return nil
}
offset := epoch - gap
if head < offset {
return nil
}
latest := head - (head-offset)%epoch
if latest < epoch {
return []uint64{latest}
}
return []uint64{latest - epoch, latest}
}

// RepairGapSnapshots restores gap block snapshots missing from the database,
// which happens when the process exits between writeHeadBlock and UpdateM1.
// Meant to run once at startup. Failures are only logged: a node that is still
// syncing legitimately has no state to rebuild from.
func (x *XDPoS_v2) RepairGapSnapshots(chain GapStateReader) {
head := chain.CurrentHeader()
if head == nil {
return
}
for _, gapNum := range x.repairGapCandidates(head.Number.Uint64()) {
// The snapshot at V2 SwitchBlock-Gap is owned by initial(), and gap
// blocks below the switch belong to the v1 engine.
if gapNum <= x.config.V2.SwitchBlock.Uint64() {
continue
}
gapHeader := chain.GetHeaderByNumber(gapNum)
if gapHeader == nil {
// gapNum is at or below the current head, so the canonical header must exist.
log.Warn("[RepairGapSnapshots] missing canonical gap header", "number", gapNum, "head", head.Number)
continue
Comment thread
gzliudan marked this conversation as resolved.
}
gapHash := gapHeader.Hash()
// Only a genuinely absent key is repaired. If we cannot reliably determine
// whether a snapshot is present (e.g. I/O error), skip repair to avoid
// overwriting a snapshot that may have been persisted through a reorg.
has, err := rawdb.HasXdposV2Snapshot(x.db, gapHash)
if err != nil {
log.Debug("[RepairGapSnapshots] cannot probe stored snapshot", "number", gapNum, "hash", gapHash, "err", err)
continue
}
if has {
continue
}
statedb, err := chain.StateAt(gapHeader.Root)
if err != nil {
log.Warn("[RepairGapSnapshots] gap block state unavailable", "number", gapNum, "hash", gapHash, "root", gapHeader.Root, "err", err)
continue
}
snap, err := BuildSnapshotFromState(statedb, gapNum, gapHash)
if err != nil {
log.Error("[RepairGapSnapshots] cannot derive snapshot", "number", gapNum, "hash", gapHash, "err", err)
continue
}
if err := StoreSnapshot(snap, x.db); err != nil {
log.Error("[RepairGapSnapshots] cannot store snapshot", "number", gapNum, "hash", gapHash, "err", err)
continue
}
x.snapshots.Add(snap.Hash, snap)
log.Warn("Repaired missing V2 gap snapshot", "number", gapNum, "hash", gapHash, "candidates", len(snap.NextEpochCandidates))
}
}
Loading