ceremonyclient/node/consensus/global/frame_chain_checker.go
Cassandra Heart 8dc7e0d526
v2.1.0.14 (#484)
* v2.1.0.14

* release notes
2025-12-03 23:56:34 -06:00

136 lines
3.6 KiB
Go

package global
import (
"bytes"
"encoding/hex"
"errors"
"go.uber.org/zap"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
"source.quilibrium.com/quilibrium/monorepo/protobufs"
"source.quilibrium.com/quilibrium/monorepo/types/store"
)
// FrameChainReader captures the minimal subset of clock store functionality
// required for sequential frame verification.
type FrameChainReader interface {
GetGlobalClockFrame(uint64) (*protobufs.GlobalFrame, error)
GetGlobalClockFrameCandidate(uint64, []byte) (*protobufs.GlobalFrame, error)
}
// FrameChainChecker verifies whether a proposal's parent chain can be linked
// through stored frame candidates or sealed frames.
type FrameChainChecker struct {
store FrameChainReader
logger *zap.Logger
}
// NewFrameChainChecker creates a new FrameChainChecker.
func NewFrameChainChecker(
store FrameChainReader,
logger *zap.Logger,
) *FrameChainChecker {
if store == nil {
return nil
}
if logger == nil {
logger = zap.NewNop()
}
return &FrameChainChecker{store: store, logger: logger}
}
// CanProcessSequentialChain returns true if the proposal's ancestors can be
// chained back to an existing sealed frame or the finalized state.
func (c *FrameChainChecker) CanProcessSequentialChain(
finalized *models.State[*protobufs.GlobalFrame],
proposal *protobufs.GlobalProposal,
) bool {
if c == nil || c.store == nil || proposal == nil ||
proposal.State == nil || proposal.State.Header == nil {
return false
}
parentSelector := proposal.State.Header.ParentSelector
if len(parentSelector) == 0 {
return false
}
frameNumber := proposal.State.Header.FrameNumber
if frameNumber == 0 {
return false
}
for frameNumber > 0 && len(parentSelector) > 0 {
frameNumber--
if sealed, err := c.store.GetGlobalClockFrame(frameNumber); err == nil &&
sealed != nil &&
bytes.Equal([]byte(sealed.Identity()), parentSelector) {
c.logger.Debug(
"frame chain linked to sealed frame",
zap.Uint64("sealed_frame_number", frameNumber),
)
return true
} else if err != nil && !errors.Is(err, store.ErrNotFound) {
c.logger.Warn(
"failed to read sealed frame during chain validation",
zap.Uint64("frame_number", frameNumber),
zap.Error(err),
)
return false
}
candidate, err := c.store.GetGlobalClockFrameCandidate(
frameNumber,
parentSelector,
)
if err == nil && candidate != nil {
if candidate.Header == nil ||
candidate.Header.FrameNumber != frameNumber {
c.logger.Debug(
"candidate frame had mismatched header",
zap.Uint64("frame_number", frameNumber),
)
return false
}
c.logger.Debug(
"frame chain matched candidate",
zap.Uint64("candidate_frame_number", frameNumber),
)
parentSelector = candidate.Header.ParentSelector
continue
}
if err != nil && !errors.Is(err, store.ErrNotFound) {
c.logger.Warn(
"failed to read candidate frame during chain validation",
zap.Uint64("frame_number", frameNumber),
zap.Error(err),
)
return false
}
if finalized != nil && finalized.State != nil &&
(*finalized.State).Header != nil &&
frameNumber == (*finalized.State).Header.FrameNumber &&
bytes.Equal([]byte(finalized.Identifier), parentSelector) {
c.logger.Debug(
"frame chain linked to finalized frame",
zap.Uint64("finalized_frame_number", frameNumber),
)
return true
}
c.logger.Debug(
"missing ancestor frame while validating chain",
zap.Uint64("missing_frame_number", frameNumber),
zap.String(
"expected_parent_selector",
hex.EncodeToString(parentSelector),
),
)
return false
}
return false
}