Skip to content

feat(sync): sync from trusted height#3050

Draft
julienrbrt wants to merge 4 commits intomainfrom
julien/trustedheight
Draft

feat(sync): sync from trusted height#3050
julienrbrt wants to merge 4 commits intomainfrom
julien/trustedheight

Conversation

@julienrbrt
Copy link
Member

@julienrbrt julienrbrt commented Feb 4, 2026

Overview

Re-implement sync from trusted height. With the recent changes, this should be more stable, and a great alternative to snapshot syncing.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedFeb 4, 2026, 5:10 PM

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @julienrbrt, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the node synchronization mechanism by introducing a "trusted height" feature. This allows nodes to bootstrap their state from a known, verified block height, significantly reducing the time required for initial synchronization, especially in long-running networks. The changes encompass new configuration options, robust validation to ensure secure usage, and integration into the core synchronization and state initialization processes.

Highlights

  • Trusted Height Synchronization: Introduced the ability for nodes to start synchronization from a specified "trusted height" and corresponding header hash, rather than always from genesis.
  • Configuration Parameters: Added TrustedHeight and TrustedHeaderHash to the P2P configuration, allowing users to define a specific starting point for synchronization.
  • Security Validation: Implemented validation to ensure that if a TrustedHeight is provided, a TrustedHeaderHash must also be supplied to prevent syncing from an unverified or malicious state.
  • Core Sync Logic Integration: Modified the Syncer and SyncService components to incorporate the trusted height logic, including fetching and verifying the trusted header from the store and P2P, and initializing the chain state accordingly.
  • Documentation Update: Updated the docs/learn/config.md to provide comprehensive details, examples, and security considerations for the new trusted height synchronization feature.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • block/internal/syncing/syncer.go
    • Added getTrustedHeader function to load and verify a trusted header from the store.
    • Modified initializeState to conditionally initialize the chain state from a trusted header if configured, otherwise falling back to genesis.
  • docs/learn/config.md
    • Introduced a new 'Sync Configuration' section detailing trusted_height and trusted_header_hash.
    • Provided descriptions, YAML and command-line examples, and security considerations for the new sync options.
  • pkg/config/config.go
    • Defined FlagTrustedHeight and FlagTrustedHeaderHash constants for command-line flags.
    • Added TrustedHeight and TrustedHeaderHash fields to the P2PConfig struct.
    • Implemented validation in Validate() to enforce that TrustedHeaderHash is present when TrustedHeight is set.
    • Registered new command-line flags for TrustedHeight and TrustedHeaderHash.
  • pkg/config/config_test.go
    • Updated the expected flag count in TestAddFlags.
    • Added TestTrustedHeightValidation to test the new configuration validation logic.
  • pkg/config/defaults.go
    • Set default values for TrustedHeight and TrustedHeaderHash in DefaultConfig().
  • pkg/sync/sync_service.go
    • Added trustedHeight and trustedHeaderHash fields to the SyncService struct.
    • Initialized trusted height configuration from P2PConfig during service startup.
    • Modified initFromP2PWithRetry to prioritize fetching and verifying the trusted header from P2P if configured.
    • Adjusted heightToQuery logic to use trustedHeight when the store is empty and trusted height is enabled.
    • Implemented fetchAndVerifyTrustedHeader to fetch and validate a header from P2P at the specified trusted height.
Activity
  • The pull request introduces a new feature for syncing from a trusted height, as indicated by the feat(sync) conventional commit prefix.
  • The author, julienrbrt, has provided a standard PR template, but no additional specific description beyond the title.
  • The changes involve modifications across multiple core components (syncer.go, sync_service.go, config.go) and corresponding documentation and tests.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@claude
Copy link
Contributor

claude bot commented Feb 4, 2026

Claude finished @julienrbrt's task in 1m 54s —— View job


Code Review: Trusted Height Sync Feature

I've completed a comprehensive code review of the trusted height synchronization feature. This is a valuable addition that allows nodes to bootstrap from a known verified block height instead of syncing from genesis. Here are my findings:


Critical Issues

1. State Initialization Logic May Cause Startup Failure ⚠️

Location: block/internal/syncing/syncer.go:303-399

The initialization logic has a potential critical issue. After initializing the state from a trusted height (lines 309-338), the code proceeds to call execReplayer.SyncToHeight(ctx, state.LastBlockHeight) at line 394. This will likely fail when using trusted height because:

  1. The replayer is initialized with the original genesis information
  2. It will attempt to replay blocks from genesis.InitialHeight
  3. However, the store won't contain intermediate blocks between genesis and trusted height
  4. This causes GetBlockData to fail

Recommendation: Skip the execReplayer.SyncToHeight call when state is initialized from a trusted height, since exec.InitChain was already called to set the executor's state.

// After line 339, before line 392:
if s.config.P2P.TrustedHeight > 0 {
    // Skip replayer sync when initialized from trusted height
    // as exec.InitChain already set the executor state
    return nil
}

2. Configuration Validation Requires Both Hashes ⚠️

Location: pkg/config/config.go:387-390

The validation requires both TrustedHeaderHash AND TrustedDataHash, but the implementation in sync_service.go:444 treats them as alternatives (OR logic). This is inconsistent.

Recommendation:

  • If both are truly required, document why and ensure both are used properly
  • If they're alternatives, update the validation to use OR instead of AND
  • Current validation: (c.P2P.TrustedHeaderHash == "" || c.P2P.TrustedDataHash == "")
  • Consider: Should it be && (both required) or allow either one?

High Priority Issues

3. Documentation Inconsistency

Location: docs/learn/config.md:1003-1018

The documentation shows configuration under a sync: section with flags like --evnode.sync.trusted_height, but the implementation places these fields in P2PConfig with flags --evnode.p2p.trusted_height.

Correction needed:

p2p:
  trusted_height: 100000
  trusted_header_hash: "a1b2c3d4e5f6..."
  trusted_data_hash: "a1b2c3d4e5f6..."

Flags should be:

  • --evnode.p2p.trusted_height
  • --evnode.p2p.trusted_header_hash
  • --evnode.p2p.trusted_data_hash

4. Unused Parameter in Function

Location: pkg/sync/sync_service.go:433

The peerIDs parameter is declared but never used in fetchAndVerifyTrustedHeader. Remove it and update the call site at line 352.

func (syncService *SyncService[H]) fetchAndVerifyTrustedHeader(ctx context.Context) error {

Medium Priority Issues

5. Hash Verification Logic

Location: pkg/sync/sync_service.go:443-446

The hash verification uses OR logic: actualHash != syncService.trustedHeaderHash && actualHash != syncService.trustedDataHash. This means it passes if the hash matches EITHER value. However:

  • For header sync service, only trustedHeaderHash should be checked
  • For data sync service, only trustedDataHash should be checked
  • Current logic may allow mismatches to pass

Recommendation: Use sync type to determine which hash to verify:

var expectedHash string
if syncService.syncType == headerSync {
    expectedHash = syncService.trustedHeaderHash
} else {
    expectedHash = syncService.trustedDataHash
}

if actualHash != expectedHash {
    return fmt.Errorf("trusted %s hash mismatch at height %d: expected %s, got %s",
        syncService.syncType, syncService.trustedHeight, expectedHash, actualHash)
}

6. Missing Trusted Data Hash in Defaults

Location: pkg/config/defaults.go:59-64

The defaults only set TrustedHeight and TrustedHeaderHash, but TrustedDataHash is missing. This should be included for consistency.

P2P: P2PConfig{
    ListenAddress:     "/ip4/0.0.0.0/tcp/7676",
    Peers:             "",
    TrustedHeight:     0,
    TrustedHeaderHash: "",
    TrustedDataHash:   "", // Add this
},

7. Test Coverage Gap

Location: pkg/config/config_test.go:517-570

The TestTrustedHeightValidation only tests TrustedHeaderHash, not TrustedDataHash. Given that the validation requires both, tests should verify:

  • Only header hash provided (should fail)
  • Only data hash provided (should fail)
  • Both provided (should pass)

Code Quality & Architecture

Positive Aspects ✅

  1. Clean separation of concerns - Configuration, validation, and sync logic are well separated
  2. Security-first approach - Requires hash verification to prevent malicious state injection
  3. Good error messages - Clear, actionable error messages with context
  4. Comprehensive validation - Config validation catches misconfigurations early
  5. Test coverage - Good test coverage for configuration validation

Improvement Opportunities

8. Error Handling in Syncer Initialization

The error handling in initializeState could be more specific. When loading the trusted header fails, distinguish between:

  • Header not found (needs to be fetched from P2P first)
  • Store error (critical failure)
  • Header verification failure (critical security issue)

9. Logging Consistency

Add more structured logging around trusted height initialization:

s.logger.Info().
    Uint64("trusted_height", s.config.P2P.TrustedHeight).
    Str("trusted_header_hash", s.config.P2P.TrustedHeaderHash[:16]+"...").
    Msg("initializing from trusted height")

10. Race Condition Protection

In sync_service.go:457, storeInitialized.Store(true) is set after Append, but if Append fails, the flag remains true. Consider using defer or only setting on success.


Security Considerations

✅ Security Strengths

  1. Mandatory hash verification - Cannot use trusted height without providing hash
  2. Validation at config level - Prevents misconfiguration before startup
  3. Store verification - Loads and verifies header from store before use
  4. P2P verification - Fetches and verifies from P2P if not in store

⚠️ Security Concerns

  1. Hash verification logic (Issue Mempool #5) - OR logic may allow bypasses
  2. Replay attack surface (Issue ABCI interface #1) - Initialization flow needs hardening
  3. No hash format validation - Consider validating that hashes are valid hex strings

Testing & Reliability

Test Coverage Analysis

  • Config validation: ✅ Well tested (66 flag checks, validation tests)
  • Trusted height validation: ⚠️ Partial (only header hash tested)
  • Syncer integration: ❌ Missing (no tests for trusted height sync flow)
  • Network logic: ❌ Missing (no tests for P2P trusted header fetch)

Recommendations

  1. Add integration tests for the full trusted height sync flow
  2. Add tests for fetchAndVerifyTrustedHeader with various failure modes
  3. Test the syncer initialization path with trusted height
  4. Test behavior when trusted header is not available from P2P

Documentation

Issues Found

  • Documentation uses wrong config section (sync vs p2p)
  • Documentation missing trusted_data_hash field
  • No troubleshooting guide for common trusted height issues

Suggestions

  • Add example showing how to get trusted hash from RPC
  • Document the difference between trusted_header_hash and trusted_data_hash
  • Add FAQ section about when to use trusted height

Performance Considerations

The trusted height feature should significantly improve sync performance by skipping historical blocks. No performance concerns identified in the implementation itself.


Summary

This PR introduces a valuable feature for faster node bootstrapping. The core concept is sound, but there are several issues that should be addressed:

Must Fix Before Merge:

  1. Issue ABCI interface #1: State initialization replay logic
  2. Issue Peer discovery #2: Configuration validation consistency
  3. Issue Transaction gossiping #3: Documentation configuration path

Should Fix:
4. Issue #4: Remove unused parameter
5. Issue #5: Hash verification logic
6. Issue #6: Missing default value

Nice to Have:
7. Issue #7: Expanded test coverage
8-10. Code quality improvements

The 35% patch coverage (47 lines missing) reported by Codecov aligns with my findings - the core syncer and sync_service logic lacks test coverage. This should be addressed before merge.


Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a valuable feature for faster node synchronization by starting from a trusted height. The implementation is spread across configuration, synchronization, and storage logic, and is mostly well-executed. I've identified a potential critical issue in the state initialization logic that could prevent nodes from starting correctly with a trusted height, along with some inconsistencies in the documentation and a minor code cleanup opportunity. Once these points are addressed, this will be a solid contribution.

Comment on lines 336 to 365
if s.config.P2P.TrustedHeight > 0 {
s.logger.Info().Uint64("trusted_height", s.config.P2P.TrustedHeight).Msg("initializing state from trusted height")

// Load and verify the trusted header
trustedHeader, err := s.getTrustedHeader(s.ctx)
if err != nil {
return fmt.Errorf("failed to load trusted header: %w", err)
}

state = types.State{
ChainID: s.genesis.ChainID,
InitialHeight: s.genesis.InitialHeight,
LastBlockHeight: s.genesis.InitialHeight - 1,
LastBlockTime: s.genesis.StartTime,
DAHeight: s.genesis.DAStartHeight,
AppHash: stateRoot,
// Initialize new chain state from the trusted header
stateRoot, initErr := s.exec.InitChain(
s.ctx,
trustedHeader.Time(),
trustedHeader.Height(),
trustedHeader.ChainID(),
)
if initErr != nil {
return fmt.Errorf("failed to initialize execution client: %w", initErr)
}

state = types.State{
Version: types.InitStateVersion,
ChainID: trustedHeader.ChainID(),
InitialHeight: trustedHeader.Height(),
LastBlockHeight: trustedHeader.Height(),
LastBlockTime: trustedHeader.Time(),
LastHeaderHash: trustedHeader.Hash(), // Hash of the trusted header
DAHeight: s.genesis.DAStartHeight,
AppHash: stateRoot,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This new logic for initializing from a trusted height looks mostly correct, but there's a potential critical issue with a subsequent call outside this block. After this block, execReplayer.SyncToHeight is called at line 421. When initializing from a trusted height, this can lead to a failure.

The replayer is initialized with the original genesis information and will likely try to replay blocks from genesis.InitialHeight. However, for a trusted sync, the store will not contain these intermediate blocks, causing GetBlockData to fail.

Since s.exec.InitChain is already called to set the executor's state to the trusted height, the subsequent replayer call seems unnecessary and problematic for this case. Consider restructuring the logic to skip the execReplayer.SyncToHeight call when the state is initialized from a trusted height.

Comment on lines +1002 to +1019
```yaml
sync:
trusted_height: 100000 # Block height to trust for sync initialization
trusted_header_hash: "a1b2c3d4e5f6..." # Hex-encoded hash of the header at trusted_height
```

---
**Command-line Flags:**

- `--evnode.sync.trusted_height <uint64>` - Block height to trust for sync initialization
- `--evnode.sync.trusted_header_hash <string>` - Hash of the trusted header for security verification (hex-encoded)

**Example:**

```bash
testapp start \
--evnode.sync.trusted_height 100000 \
--evnode.sync.trusted_header_hash "abc123def456..."
```
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The documentation for the new trusted sync feature appears to be inconsistent with the implementation in pkg/config/config.go.

  1. YAML Configuration: The documentation places trusted_height and trusted_header_hash under a sync: section. However, the code defines these fields within P2PConfig, which is mapped to a p2p: section in YAML.
  2. Command-line Flags: The documentation lists the flags as --evnode.sync.trusted_height and --evnode.sync.trusted_header_hash. The implementation defines them as --evnode.p2p.trusted_height and --evnode.p2p.trusted_header_hash.

To avoid user confusion, the documentation should be updated to reflect the implementation. Here is a suggested correction:

**YAML:**

```yaml
p2p:
  trusted_height: 100000 # Block height to trust for sync initialization
  trusted_header_hash: "a1b2c3d4e5f6..." # Hex-encoded hash of the header at trusted_height

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..."


// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The peerIDs parameter is unused within this function and should be removed to simplify the signature. The call site at line 351 should be updated accordingly by removing the peerIDs argument.

Suggested change
func (syncService *SyncService[H]) fetchAndVerifyTrustedHeader(ctx context.Context, peerIDs []peer.ID) error {
func (syncService *SyncService[H]) fetchAndVerifyTrustedHeader(ctx context.Context) error {

@codecov
Copy link

codecov bot commented Feb 4, 2026

Codecov Report

❌ Patch coverage is 36.48649% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.19%. Comparing base (d20b1ac) to head (907681b).

Files with missing lines Patch % Lines
block/internal/syncing/syncer.go 36.84% 22 Missing and 2 partials ⚠️
pkg/sync/sync_service.go 11.53% 23 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3050      +/-   ##
==========================================
- Coverage   56.38%   56.19%   -0.20%     
==========================================
  Files         118      118              
  Lines       12036    12092      +56     
==========================================
+ Hits         6787     6795       +8     
- Misses       4507     4554      +47     
- Partials      742      743       +1     
Flag Coverage Δ
combined 56.19% <36.48%> (-0.20%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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