-
Notifications
You must be signed in to change notification settings - Fork 248
feat(sync): sync from trusted height #3050
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+229
−39
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -59,6 +59,11 @@ type SyncService[H store.EntityWithDAHint[H]] struct { | |||||
| topicSubscription header.Subscription[H] | ||||||
|
|
||||||
| storeInitialized atomic.Bool | ||||||
|
|
||||||
| // trustedHeight tracks the configured trusted height for sync initialization | ||||||
| trustedHeight uint64 | ||||||
| // trustedHeaderHash, trustedDataHash is the expected hash of the trusted header | ||||||
| trustedHeaderHash, trustedDataHash string | ||||||
| } | ||||||
|
|
||||||
| // NewDataSyncService returns a new DataSyncService. | ||||||
|
|
@@ -198,6 +203,11 @@ func (syncService *SyncService[H]) Start(ctx context.Context) error { | |||||
| return fmt.Errorf("failed to create syncer: %w", err) | ||||||
| } | ||||||
|
|
||||||
| // Initialize trusted height configuration | ||||||
| syncService.trustedHeight = syncService.conf.P2P.TrustedHeight | ||||||
| syncService.trustedHeaderHash = syncService.conf.P2P.TrustedHeaderHash | ||||||
| syncService.trustedDataHash = syncService.conf.P2P.TrustedDataHash | ||||||
|
|
||||||
| // initialize stores from P2P (blocking until genesis is fetched for followers) | ||||||
| // Aggregators (no peers configured) return immediately and initialize on first produced block. | ||||||
| if err := syncService.initFromP2PWithRetry(ctx, peerIDs); err != nil { | ||||||
|
|
@@ -322,7 +332,7 @@ func (syncService *SyncService[H]) startSubscriber(ctx context.Context) error { | |||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| // Height returns the current height stored | ||||||
| // Height returns the current height storeda | ||||||
| func (s *SyncService[H]) Height() uint64 { | ||||||
| return s.store.Height() | ||||||
| } | ||||||
|
|
@@ -331,11 +341,19 @@ func (s *SyncService[H]) Height() uint64 { | |||||
| // It inspects the local store to determine the first height to request: | ||||||
| // - when the store already contains items, it reuses the latest height as the starting point; | ||||||
| // - otherwise, it falls back to the configured genesis height. | ||||||
| // - if trusted height is configured, it fetches from that height first and verifies the hash. | ||||||
| func (syncService *SyncService[H]) initFromP2PWithRetry(ctx context.Context, peerIDs []peer.ID) error { | ||||||
| if len(peerIDs) == 0 { | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| // If trusted height is configured, fetch from that height first | ||||||
| if syncService.trustedHeight > 0 { | ||||||
| if err := syncService.fetchAndVerifyTrustedHeader(ctx, peerIDs); err != nil { | ||||||
| return fmt.Errorf("failed to fetch trusted header at height %d: %w", syncService.trustedHeight, err) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| tryInit := func(ctx context.Context) (bool, error) { | ||||||
| var ( | ||||||
| trusted H | ||||||
|
|
@@ -346,7 +364,12 @@ func (syncService *SyncService[H]) initFromP2PWithRetry(ctx context.Context, pee | |||||
| head, headErr := syncService.store.Head(ctx) | ||||||
| switch { | ||||||
| case errors.Is(headErr, header.ErrNotFound), errors.Is(headErr, header.ErrEmptyStore): | ||||||
| heightToQuery = syncService.genesis.InitialHeight | ||||||
| // If we have a trusted header, use its height as the starting point | ||||||
| if syncService.trustedHeight > 0 { | ||||||
| heightToQuery = syncService.trustedHeight | ||||||
| } else { | ||||||
| heightToQuery = syncService.genesis.InitialHeight | ||||||
| } | ||||||
| case headErr != nil: | ||||||
| return false, fmt.Errorf("failed to inspect local store head: %w", headErr) | ||||||
| default: | ||||||
|
|
@@ -405,6 +428,37 @@ func (syncService *SyncService[H]) initFromP2PWithRetry(ctx context.Context, pee | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| // fetchAndVerifyTrustedHeader fetches the header at the trusted height from P2P | ||||||
| // and verifies it matches the trusted hash. If verification passes, it stores the header. | ||||||
| func (syncService *SyncService[H]) fetchAndVerifyTrustedHeader(ctx context.Context, peerIDs []peer.ID) error { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||
| syncService.logger.Info().Uint64("height", syncService.trustedHeight).Msg("fetching trusted header from P2P") | ||||||
|
|
||||||
| // Fetch the header from trusted height | ||||||
| trusted, err := syncService.ex.GetByHeight(ctx, syncService.trustedHeight) | ||||||
| if err != nil { | ||||||
| return fmt.Errorf("failed to fetch trusted header at height %d: %w", syncService.trustedHeight, err) | ||||||
| } | ||||||
|
|
||||||
| // Verify the hash matches | ||||||
| actualHash := trusted.Hash().String() | ||||||
| if actualHash != syncService.trustedHeaderHash && actualHash != syncService.trustedDataHash { | ||||||
| return fmt.Errorf("trusted header hash mismatch at height %d: expected %s or %s, got %s", | ||||||
| syncService.trustedHeight, syncService.trustedHeaderHash, syncService.trustedDataHash, actualHash) | ||||||
| } | ||||||
|
|
||||||
| syncService.logger.Info().Uint64("height", syncService.trustedHeight). | ||||||
| Str("hash", actualHash). | ||||||
| Msg("trusted header verified and stored") | ||||||
|
|
||||||
| if err := syncService.store.Append(ctx, trusted); err != nil { | ||||||
| return fmt.Errorf("failed to store trusted header: %w", err) | ||||||
| } | ||||||
|
|
||||||
| syncService.storeInitialized.Store(true) | ||||||
|
|
||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| // Stop is a part of Service interface. | ||||||
| // | ||||||
| // `store` is closed last because it's used by other services. | ||||||
|
|
||||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation for the new trusted sync feature appears to be inconsistent with the implementation in
pkg/config/config.go.trusted_heightandtrusted_header_hashunder async:section. However, the code defines these fields withinP2PConfig, which is mapped to ap2p:section in YAML.--evnode.sync.trusted_heightand--evnode.sync.trusted_header_hash. The implementation defines them as--evnode.p2p.trusted_heightand--evnode.p2p.trusted_header_hash.To avoid user confusion, the documentation should be updated to reflect the implementation. Here is a suggested correction:
Command-line Flags:
--evnode.p2p.trusted_height <uint64>- Block height to trust for sync initialization--evnode.p2p.trusted_header_hash <string>- Hash of the trusted header for security verification (hex-encoded)Example:
testapp start \ --evnode.p2p.trusted_height 100000 \ --evnode.p2p.trusted_header_hash "abc123def456..."