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

Forkchoice duplicate store #10840

Merged
merged 18 commits into from
Jun 16, 2022
Merged

Conversation

potuz
Copy link
Contributor

@potuz potuz commented Jun 7, 2022

This PR is the second step towards fixing #10734. In this PR we let forkchoice update its justification information when inserting a new node. Moving away from the dependence on the store package for this. This PR also makes forkchoice package track the best justification checkpoint.

  • doubly linked tree tests
  • protoarray tests
  • blockchain package tests
  • spec tests
  • e2e tests

@potuz potuz requested a review from a team as a code owner June 7, 2022 20:25
@potuz potuz requested review from kasey, terencechain and rkapka June 7, 2022 20:25
@potuz potuz force-pushed the forkchoice_duplicate_store branch from 1aa0738 to 98b6fb9 Compare June 8, 2022 13:45
@potuz potuz dismissed a stale review via 850a1b9 June 14, 2022 18:45
@potuz potuz force-pushed the forkchoice_duplicate_store branch from 850a1b9 to d9d5133 Compare June 14, 2022 18:49
@@ -63,58 +59,6 @@ type head struct {
state state.BeaconState // current head state.
}

// Determined the head from the fork choice service and saves its new data
// (head root, head block, and head state) to the local service cache.
func (s *Service) updateHead(ctx context.Context, balances []uint64) ([32]byte, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function is deprecated, it only simply calls to forkchoice.Head, as forkchoice now tracks the justified checkpoints and it is guaranteed to have them already (so the call to fill in missing blocks is irrelevant)

@@ -158,26 +162,6 @@ func TestCacheJustifiedStateBalances_CanCache(t *testing.T) {
require.DeepEqual(t, balances, state.Balances(), "Incorrect justified balances")
}

func TestUpdateHead_MissingJustifiedRoot(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test is irrelevant since forkchoice will not fail to have it's justified checkpoint

@@ -221,23 +221,14 @@ func (s *Service) onBlock(ctx context.Context, signed interfaces.SignedBeaconBlo
return err
}
s.store.SetJustifiedCheckptAndPayloadHash(postState.CurrentJustifiedCheckpoint(), h)
// Update Forkchoice checkpoints
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do not need to update on onBlock since the call to InsertNode above already updated the checkpoints within forkchoice.

if err := s.cfg.ForkChoiceStore.SetOptimisticToValid(ctx, genesisBlkRoot); err != nil {
log.Fatalf("Could not set optimistic status of genesis block to false: %v", err)
return errors.Wrap(err, "Could not set optimistic status of genesis block to false")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Better to error here since the node will stop anyway

@@ -59,7 +59,8 @@ func TestFFGUpdates_OneBranch(t *testing.T) {
// 2 <- head
// |
// 3
f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Root: indexToHash(2), Epoch: 1}
f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Root: indexToHash(1), Epoch: 1}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These changes on these tests seem scary, but they are mostly due to the impossibility of some of the tests setups: before these changes, we had to carefully check that head will return a different value if a different justified checkpoint was passed. Currently, forkchoice already tracks the right justified checkpoint, so we need to trick it into having different checkpoints to obtain the previous results.

}

// updateCheckpoints update the checkpoints when inserting a new node.
func (f *ForkChoice) updateCheckpoints(ctx context.Context, jc, fc *ethpb.Checkpoint) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current implementation in the blockchain package checks for a block in DB, but that is unnecessary to have as the current justified checkpoint is strictly a forkchoice feature.

@@ -484,3 +537,13 @@ func (f *ForkChoice) InsertOptimisticChain(ctx context.Context, chain []*forkcho
}
return nil
}

// sets the genesisTime tracked by forkchoice
Copy link
Contributor Author

Choose a reason for hiding this comment

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

forkchoice needs to track genesisTime to ascertain the current slot number. This is needed for the balancing attack mitigation.

}

// sets the genesis Block root
func (f *ForkChoice) SetOriginRoot(root [32]byte) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Forkchoice needs to keep track of the OriginRoot because it needs to deal with some checkpoint roots being zero, when they should be the genesis block hash.

@@ -82,32 +82,6 @@ func TestNoVote_CanFindHead(t *testing.T) {
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
r, err = f.Head(context.Background(), balances)
require.NoError(t, err)
assert.Equal(t, indexToHash(4), r, "Incorrect head for with justified epoch at 1")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an example of a test that was irrelevant.

@potuz potuz changed the title [WIP] Forkchoice duplicate store Forkchoice duplicate store Jun 15, 2022
@potuz potuz added the Ready For Review A pull request ready for code review label Jun 15, 2022
beacon-chain/blockchain/init_sync_process_block.go Outdated Show resolved Hide resolved
beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go Outdated Show resolved Hide resolved
beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go Outdated Show resolved Hide resolved
@@ -29,9 +30,11 @@ type Store struct {
nodeByRoot map[[fieldparams.RootLength]byte]*Node // nodes indexed by roots.
nodeByPayload map[[fieldparams.RootLength]byte]*Node // nodes indexed by payload Hash
slashedIndices map[types.ValidatorIndex]bool // the list of equivocating validator indices
originRoot [fieldparams.RootLength]byte // The genesis Block root
Copy link
Member

Choose a reason for hiding this comment

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

Is this really just the genesis block root? or it could be check point sync origin root too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only genesis root, it's used to deal with zero hashes on checkpoints

potuz and others added 2 commits June 15, 2022 17:39
// zeroHash. In this case it should be the root of forkchoice
// tree.
if s.justifiedCheckpoint.Epoch == params.BeaconConfig().GenesisEpoch {
justifiedNode = s.treeRootNode
Copy link
Contributor

Choose a reason for hiding this comment

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

isnt returning zero hash here sufficient?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will use the node with root = zerohash as the justified node. Head still needs to be computed

@@ -79,7 +79,7 @@ func TestForkChoice_HasNode(t *testing.T) {
func TestStore_Head_UnknownJustifiedRoot(t *testing.T) {
f := setup(0, 0)

f.store.justifiedCheckpoint.Root = [32]byte{'a'}
f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'a'}}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not epoch 0? Is it inconsequential for this test, or does it fail if 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Epoch zero is special in that I completely disregard the root and use zerohash if the justified epoch is genesis since the only possible justified checkpoint can be the genesis block root

Copy link
Member

@terencechain terencechain left a comment

Choose a reason for hiding this comment

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

Lgtm otherwise!

beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go Outdated Show resolved Hide resolved
Comment on lines +161 to +184
if slots.SinceEpochStarts(currentSlot) < params.BeaconConfig().SafeSlotsToUpdateJustified {
f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: jc.Epoch,
Root: bytesutil.ToBytes32(jc.Root)}
} else {
currentJcp := f.store.justifiedCheckpoint
currentRoot := currentJcp.Root
if currentRoot == params.BeaconConfig().ZeroHash {
currentRoot = f.store.originRoot
}
jSlot, err := slots.EpochStart(currentJcp.Epoch)
if err != nil {
return err
}
jcRoot := bytesutil.ToBytes32(jc.Root)
root, err := f.AncestorRoot(ctx, jcRoot, jSlot)
if err != nil {
return err
}
if root == currentRoot {
f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: jc.Epoch,
Root: jcRoot}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

A little weird to read for translating spec and shouldUpdateCurrentJustified to this if else condition but I don't mind it. It'll work

    if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED:
        return True
    justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch)
    if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root:
        return False
    return True

@@ -397,6 +447,10 @@ func (f *ForkChoice) UpdateJustifiedCheckpoint(jc *forkchoicetypes.Checkpoint) e
f.store.checkpointsLock.Lock()
defer f.store.checkpointsLock.Unlock()
f.store.justifiedCheckpoint = jc
bj := f.store.bestJustifiedCheckpoint
Copy link
Member

Choose a reason for hiding this comment

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

Do we still need UpdateJustifiedCheckpoint when everything is done?

Copy link
Contributor Author

@potuz potuz Jun 16, 2022

Choose a reason for hiding this comment

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

It's not entirely clear yet. In principle it can be gone, and I am hoping that it completely disappears, but I haven't yet cleaned up init sync, where we don't really have the state cause we do transition in batches

beacon-chain/forkchoice/protoarray/types.go Outdated Show resolved Hide resolved
beacon-chain/forkchoice/doubly-linked-tree/types.go Outdated Show resolved Hide resolved
beacon-chain/forkchoice/protoarray/store.go Outdated Show resolved Hide resolved
Co-authored-by: terencechain <[email protected]>
@potuz potuz dismissed a stale review via 052abde June 16, 2022 16:56
potuz and others added 3 commits June 16, 2022 13:56
Co-authored-by: terencechain <[email protected]>
Co-authored-by: terencechain <[email protected]>
Co-authored-by: terencechain <[email protected]>
@prylabs-bulldozer prylabs-bulldozer bot merged commit e439f4a into develop Jun 16, 2022
@delete-merged-branch delete-merged-branch bot deleted the forkchoice_duplicate_store branch June 16, 2022 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ready For Review A pull request ready for code review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants