Skip to content

Comments

Feature/implement posv snapshot#20

Open
thinhC98 wants to merge 7 commits intoBuildOnViction:developfrom
thinhC98:feature/implement-posv-snapshot
Open

Feature/implement posv snapshot#20
thinhC98 wants to merge 7 commits intoBuildOnViction:developfrom
thinhC98:feature/implement-posv-snapshot

Conversation

@thinhC98
Copy link
Contributor

@thinhC98 thinhC98 commented Feb 9, 2026

This pull request implements the snapshot and voting logic for the PoSV (Proof-of-Stake Voting) consensus engine, completing previously stubbed-out methods and integrating snapshot management into the consensus flow. It also introduces several bug fixes, code cleanups, and minor refactoring for type consistency. The most important changes are grouped below.

PoSV Snapshot and Voting Logic Implementation

  • Fully implements the Snapshot struct methods in consensus/posv/snapshot.go, including snapshot creation, loading, storing, copying, voting, unvoting, applying headers, and retrieving signers. This enables the PoSV engine to track authorized signers, tally votes, and update the set of signers as blocks are processed.
  • Adds logic to apply a sequence of headers to a snapshot, updating signers and vote tallies, handling checkpoint blocks, and enforcing voting rules and signer rotation.
  • Implements the inturn function to determine if a signer is "in-turn" for a given block, which is important for block production rules.

Consensus Engine Integration

  • Implements the snapshot method in consensus/posv/verifier.go to retrieve or reconstruct snapshots from memory, disk, or by replaying headers, ensuring the PoSV engine can verify blocks against the current set of authorized signers.
  • Updates error handling and timestamp validation to use the correct error variables, improving the accuracy and clarity of error reporting in the consensus process.

Type and Naming Consistency

  • Refactors the signersAscending type to SignersAscending in consensus/clique/snapshot.go and updates all usages to improve naming consistency and export the type for use in other packages, such as PoSV.
  • Updates the PoSV snapshot logic to use clique.SignersAscending for sorting signers, ensuring consistency with the Clique consensus implementation.

License Header Cleanup

  • Removes redundant or duplicate license headers from several files for clarity and to avoid repetition.

New Error Types

  • Adds new error variables in consensus/posv/posv.go for more granular error handling, such as errInvalidVotingChain and errRecentlySigned, which are used in the snapshot and voting logic.

These changes collectively enable the PoSV consensus engine to maintain and update the set of authorized signers, process votes, and validate blocks according to the PoSV protocol.

@imterryyy imterryyy requested a review from Copilot February 9, 2026 09:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements PoSV snapshot persistence/reconstruction and voting application logic, integrating it into header verification and adding unit tests.

Changes:

  • Implemented PoSV snapshot lifecycle (create/load/store/copy/apply) including voting + signer set updates.
  • Added snapshot reconstruction in the verifier by replaying headers and persisting checkpoint snapshots.
  • Added a comprehensive snapshot test suite and introduced new PoSV-specific error variables.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
consensus/posv/verifier.go Implements snapshot retrieval/rebuild flow and adjusts timestamp error usage.
consensus/posv/snapshot.go Adds full snapshot + voting logic, including signer sorting and in-turn calculation.
consensus/posv/snapshot_test.go Introduces unit tests for snapshot creation, persistence, voting tallying, and header application.
consensus/posv/posv.go Adds/renames errors used by snapshot/verifier logic.
consensus/errors.go Removes duplicate/redundant license header lines.
common/slice.go Removes redundant license header block.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// at a checkpoint block without a parent (light client CHT), or we have piled
// up more headers than allowed to be reorged (chain reinit from a freezer),
// consider the checkpoint trusted and snapshot it.
if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checkpoint handling elsewhere in this method uses (number+c.config.Gap)%c.config.Epoch == 0, but this trusted-checkpoint condition uses number%c.config.Epoch == 0. If Gap is non-zero, the logic will disagree on what constitutes a checkpoint, potentially treating non-checkpoint blocks as trusted checkpoints (or missing actual ones). Align this condition with the same (number+c.config.Gap)%c.config.Epoch rule.

Suggested change
if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
if number == 0 || ((number+c.config.Gap)%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {

Copilot uses AI. Check for mistakes.
Comment on lines +159 to +180
copy := snap.copy()

// Verify the copy
if copy.Number != snap.Number {
t.Errorf("Expected number %d, got %d", snap.Number, copy.Number)
}

if len(copy.Signers) != len(snap.Signers) {
t.Errorf("Expected %d signers, got %d", len(snap.Signers), len(copy.Signers))
}

if len(copy.Recents) != len(snap.Recents) {
t.Errorf("Expected %d recents, got %d", len(snap.Recents), len(copy.Recents))
}

if len(copy.Votes) != len(snap.Votes) {
t.Errorf("Expected %d votes, got %d", len(snap.Votes), len(copy.Votes))
}

// Modify the copy and ensure the original is unchanged
copy.Recents[100] = signers[0]
if len(snap.Recents) == len(copy.Recents) {
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local variable name copy shadows Go's predeclared copy function, which can make the test harder to read and can lead to accidental misuse later in the function. Rename it to something like snapCopy or cloned.

Suggested change
copy := snap.copy()
// Verify the copy
if copy.Number != snap.Number {
t.Errorf("Expected number %d, got %d", snap.Number, copy.Number)
}
if len(copy.Signers) != len(snap.Signers) {
t.Errorf("Expected %d signers, got %d", len(snap.Signers), len(copy.Signers))
}
if len(copy.Recents) != len(snap.Recents) {
t.Errorf("Expected %d recents, got %d", len(snap.Recents), len(copy.Recents))
}
if len(copy.Votes) != len(snap.Votes) {
t.Errorf("Expected %d votes, got %d", len(snap.Votes), len(copy.Votes))
}
// Modify the copy and ensure the original is unchanged
copy.Recents[100] = signers[0]
if len(snap.Recents) == len(copy.Recents) {
snapCopy := snap.copy()
// Verify the copy
if snapCopy.Number != snap.Number {
t.Errorf("Expected number %d, got %d", snap.Number, snapCopy.Number)
}
if len(snapCopy.Signers) != len(snap.Signers) {
t.Errorf("Expected %d signers, got %d", len(snap.Signers), len(snapCopy.Signers))
}
if len(snapCopy.Recents) != len(snap.Recents) {
t.Errorf("Expected %d recents, got %d", len(snap.Recents), len(snapCopy.Recents))
}
if len(snapCopy.Votes) != len(snap.Votes) {
t.Errorf("Expected %d votes, got %d", len(snap.Votes), len(snapCopy.Votes))
}
// Modify the copy and ensure the original is unchanged
snapCopy.Recents[100] = signers[0]
if len(snap.Recents) == len(snapCopy.Recents) {

Copilot uses AI. Check for mistakes.
Comment on lines 84 to +86
// ErrInvalidTimestamp is returned if the timestamp of a block is lower than
// the previous block's timestamp + the minimum block period.
ErrInvalidTimestamp = errors.New("invalid timestamp")
errInvalidTimestamp = errors.New("invalid timestamp")
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes ErrInvalidTimestamp from an exported identifier to an unexported one (errInvalidTimestamp). If external callers rely on posv.ErrInvalidTimestamp for error checks, this is a breaking API change. Consider keeping ErrInvalidTimestamp as an exported alias (possibly deprecated) that references the internal variable.

Copilot uses AI. Check for mistakes.
Comment on lines +245 to +250
// If the vote passed, update the list of signers
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)
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signer-set mutation logic here (especially deauthorizing signers) needs explicit tests for edge cases introduced by the new implementation. Add unit tests to ensure (1) the last signer cannot be removed, and (2) a signer cannot accumulate multiple concurrent votes for different addresses (a new vote should replace the previous one).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant