Skip to content
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

feat(sync): bifurcation for syncTarget #219

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
75 changes: 64 additions & 11 deletions sync/sync_head.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sync
import (
"context"
"errors"
"fmt"
"time"

"github.com/celestiaorg/go-header"
Expand Down Expand Up @@ -173,22 +174,74 @@ func (s *Syncer[H]) verify(ctx context.Context, newHead H) (bool, error) {
}

var verErr *header.VerifyError
if errors.As(err, &verErr) && !verErr.SoftFailure {
logF := log.Warnw
if errors.Is(err, header.ErrKnownHeader) {
logF = log.Debugw
}
logF("invalid network header",
"height_of_invalid", newHead.Height(),
"hash_of_invalid", newHead.Hash(),
"height_of_subjective", sbjHead.Height(),
"hash_of_subjective", sbjHead.Hash(),
"reason", verErr.Reason)
if !errors.As(err, &verErr) {
return false, nil
}

if verErr.SoftFailure {
err := s.verifySkipping(ctx, sbjHead.Height(), newHead)
return false, err
}

logF := log.Warnw
if errors.Is(err, header.ErrKnownHeader) {
logF = log.Debugw
}
logF("invalid network header",
"height_of_invalid", newHead.Height(),
"hash_of_invalid", newHead.Hash(),
"height_of_subjective", sbjHead.Height(),
"hash_of_subjective", sbjHead.Hash(),
"reason", verErr.Reason,
)

return verErr.SoftFailure, err
}

/*
Subjective head is 500, network head is 1000.
Header at height 1000 does not have sufficient validator set overlap,
so the client downloads height 750 (which does have enough sufficient overlap),
verifies it against 500 and advances the subjective head to 750.

Client tries to apply height 1000 against 750 and if there is sufficient overlap,
it applies 1000 as the subjective head.
If not, it downloads the halfway point and retries the process.
*/
func (s *Syncer[H]) verifySkipping(ctx context.Context, subjHeight uint64, networkHeader H) error {
Copy link
Member

Choose a reason for hiding this comment

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

From sync discussion:

In the 500(sbjHead) and 1000(netHead) example, the current algo fetches 750 and verifies 1000. However, the 750 is taken for granted without verification with 500.

Instead, the algo should fetch 750, verify it, and set it as new sbjHead to initiate the sync loop fetching 500-750, while bifurcation continues until 1000 is reached.

Copy link
Member

Choose a reason for hiding this comment

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

There is one more minor question to answer. What happens to new incoming heads if bifurcation takes more than a block time? Do we discard them or queue for verification once the bifurcation finishes?

diff := networkHeader.Height() - subjHeight
if diff <= 0 {
panic(fmt.Sprintf("implementation bug: diff is %d", diff))
}

for diff > 0 {
diff = diff / 2
subjHeight += diff

subjHeader, err := s.getter.GetByHeight(ctx, subjHeight)
if err != nil {
return err
}

if err := header.Verify(subjHeader, networkHeader); err == nil {
return nil
}
}
return &NewValidatorSetCantBeTrustedError{
NetHeadHeight: networkHeader.Height(),
NetHeadHash: networkHeader.Hash(),
}
}

type NewValidatorSetCantBeTrustedError struct {
NetHeadHeight uint64
NetHeadHash []byte
}

func (e *NewValidatorSetCantBeTrustedError) Error() string {
return fmt.Sprintf("sync: new validator set cant be trusted: head %d, attempted %s", e.NetHeadHeight, e.NetHeadHash)
}

// isExpired checks if header is expired against trusting period.
func isExpired[H header.Header[H]](header H, period time.Duration) bool {
expirationTime := header.Time().Add(period)
Expand Down
Loading