-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Forkchoice duplicate store #10840
Conversation
1aa0738
to
98b6fb9
Compare
850a1b9
to
d9d5133
Compare
@@ -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) { |
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.
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) { |
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.
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 |
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.
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") |
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.
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} |
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.
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 { |
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 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 |
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.
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) { |
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.
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") |
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.
This is an example of a test that was irrelevant.
@@ -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 |
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.
Is this really just the genesis block root? or it could be check point sync origin root too?
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.
Only genesis root, it's used to deal with zero hashes on checkpoints
Co-authored-by: terencechain <[email protected]>
// zeroHash. In this case it should be the root of forkchoice | ||
// tree. | ||
if s.justifiedCheckpoint.Epoch == params.BeaconConfig().GenesisEpoch { | ||
justifiedNode = s.treeRootNode |
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.
isnt returning zero hash here sufficient?
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.
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'}} |
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.
Why not epoch 0? Is it inconsequential for this test, or does it fail if 0?
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.
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
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.
Lgtm otherwise!
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} | ||
} | ||
} | ||
} |
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.
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 |
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.
Do we still need UpdateJustifiedCheckpoint
when everything is done?
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.
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
Co-authored-by: terencechain <[email protected]>
Co-authored-by: terencechain <[email protected]>
Co-authored-by: terencechain <[email protected]>
Co-authored-by: terencechain <[email protected]>
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.