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): Implement a subjectiveHead atomic pointer on the node to use #108

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

renaynay
Copy link
Member

@renaynay renaynay commented Sep 19, 2023

Resolves #99

@codecov-commenter
Copy link

codecov-commenter commented Sep 20, 2023

Codecov Report

Merging #108 (91d975a) into main (e7d24b8) will increase coverage by 0.15%.
The diff coverage is 74.11%.

@@            Coverage Diff             @@
##             main     #108      +/-   ##
==========================================
+ Coverage   69.09%   69.25%   +0.15%     
==========================================
  Files          37       37              
  Lines        2938     2979      +41     
==========================================
+ Hits         2030     2063      +33     
- Misses        758      768      +10     
+ Partials      150      148       -2     
Files Coverage Δ
sync/sync_store.go 83.33% <100.00%> (+2.08%) ⬆️
sync/sync_head.go 71.12% <94.11%> (+1.89%) ⬆️
headertest/dummy_suite.go 87.50% <28.57%> (-12.50%) ⬇️
headertest/dummy_header.go 72.58% <40.00%> (-0.28%) ⬇️
sync/sync.go 73.52% <79.16%> (+0.38%) ⬆️

... and 1 file with indirect coverage changes

@renaynay renaynay marked this pull request as ready for review September 20, 2023 14:16
@@ -145,8 +161,12 @@ func (s *Syncer[H]) incomingNetworkHead(ctx context.Context, head H) error {
}

// TODO(@Wondertan):
// Implement setSyncTarget and use it for soft failures
s.setSubjectiveHead(ctx, head)
// Implement setSyncTarget and use it for soft failures // TODO @renaynay <-can remove this comment?
Copy link
Member

Choose a reason for hiding this comment

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

TODO @liamsi ask rene and hlib if these two todos can be removed now? 😅

// this sleep is necessary to allow the syncer to trigger a job
// as calling SyncWait prematurely may falsely return without error
// as the syncer has not yet registered a sync job.
//time.Sleep(time.Millisecond * 100)
Copy link
Member

Choose a reason for hiding this comment

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

can this be removed?

sync/fork_test/fork.go Outdated Show resolved Hide resolved
sync/fork_test/fork.go Outdated Show resolved Hide resolved

func (edv ErrDummyVerify) Error() string {
return edv.Reason
}

type DummyHeader struct {
Chainid string
PreviousHash header.Hash
HeightI uint64
Timestamp time.Time

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

sync/sync.go Outdated
// if there's no subjective head stored, syncer has already applied the
// subjective head to the store and the syncer has passed it
if sbjHead != nil {
sh := *sbjHead
Copy link
Contributor

Choose a reason for hiding this comment

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

why do you need to deref here? Can't you do it on sbjHead def or cast? either way, please do it on line 333 if possible

sync/sync.go Outdated
Comment on lines 362 to 368
if len(headers) == 0 {
s.metrics.recordTotalSynced(totalHeaders)
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary? I don't know why we can't just leave it how it was

Copy link

@cmwaters cmwaters left a comment

Choose a reason for hiding this comment

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

Looks good. Still trying to wrap my head around how the mechanism works

// headers between its storeHead --> subjectiveHead.
type eclipsedExchange struct {
trustedPeer header.Store[*headertest.DummyHeader]
eclipsedExchange header.Store[*headertest.DummyHeader]

Choose a reason for hiding this comment

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

nit: keep it consistent with trustedPeer so we understand there are two different peers

Suggested change
eclipsedExchange header.Store[*headertest.DummyHeader]
eclipsedPeer header.Store[*headertest.DummyHeader]

cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
err = cmd.Run()
require.Error(t, err)

Choose a reason for hiding this comment

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

Do you want to make a specific assertion to the error that is generated

Copy link
Member Author

Choose a reason for hiding this comment

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

the assertion is below

require.NoError(t, err)

cmd := exec.Command("go", "run", path)
cmd.Stdout = os.Stderr

Choose a reason for hiding this comment

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

Is it important to catch stdout?

Copy link
Member Author

Choose a reason for hiding this comment

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

nice to be able to catch the output, but not important

@@ -322,13 +331,44 @@ func (s *Syncer[H]) requestHeaders(

// storeHeaders updates store with new headers and updates current syncStore's Head.
func (s *Syncer[H]) storeHeaders(ctx context.Context, headers ...H) error {

Choose a reason for hiding this comment

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

Why is this check done in storeHeaders? I would have thought this would happen during verification (that we check if it's the height of the subjective head and compare the hashed)

Comment on lines +21 to +26
func TestForkFollowingPrevention(t *testing.T) {
path, err := filepath.Abs("./fork_test/fork.go")
require.NoError(t, err)

cmd := exec.Command("go", "run", path)
cmd.Stdout = os.Stderr
Copy link
Member

Choose a reason for hiding this comment

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

[complement]
This is smart, and I like it.

headers = headers[segmentSize:]

err := s.store.Append(ctx, mustApplySegment...)
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

[blocking]
This must be a verification error only.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree and also would prefer if we could also assess whether it failed specifically on the height sbjHead-1 against sbjHead

@@ -145,8 +161,12 @@ func (s *Syncer[H]) incomingNetworkHead(ctx context.Context, head H) error {
}

// TODO(@Wondertan):
// Implement setSyncTarget and use it for soft failures
s.setSubjectiveHead(ctx, head)
// Implement setSyncTarget and use it for soft failures // TODO @renaynay <-can remove this comment?
Copy link
Member

Choose a reason for hiding this comment

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

[discussion]
We can keep it until we make setSyncTarget method

sync/sync.go Outdated
Comment on lines 336 to 368
var sbjHeadHeight uint64
sbjHead := *s.sbjHead.Load()
// if there's no subjective head stored, syncer has already applied the
// subjective head to the store and the syncer has passed it
if !sbjHead.IsZero() {
sbjHeadHeight = sbjHead.Height()
// sanity check the height to check whether it's within the range
if sbjHeadHeight < headers[0].Height() || sbjHeadHeight > headers[len(headers)-1].Height() {
sbjHeadHeight = 0
}
}

// if the header is within range, create a segment that *must* be applied
// such that the syncer fails if the subjective head cannot be applied
if sbjHeadHeight != 0 {
segmentSize := sbjHeadHeight - headers[0].Height() + 1
mustApplySegment := make([]H, segmentSize)
copy(mustApplySegment, headers[:segmentSize])
headers = headers[segmentSize:]

err := s.store.Append(ctx, mustApplySegment...)
if err != nil {
log.Fatal(fmt.Errorf("failed to apply trusted head at height %d to synced chain segment, "+
"potentially following fork: %w", sbjHeadHeight, err))
}
// if success, then clear subjective head as it's already applied as the store head
s.sbjHead.Store(new(H))
}
Copy link
Member

Choose a reason for hiding this comment

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

[suggestion]
Separate this code block with { and } and add a TODO pointing to backwards sync issue

@renaynay
Copy link
Member Author

renaynay commented Oct 2, 2023

Something is borked with sync.

@renaynay renaynay marked this pull request as draft October 2, 2023 15:26
@renaynay
Copy link
Member Author

renaynay commented Oct 5, 2023

Nvm bug was in main

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.

Prevent long-range attacks
6 participants