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

Add execution_optimistic field to API responses #10389

Merged
merged 21 commits into from
Mar 25, 2022

Conversation

rkapka
Copy link
Contributor

@rkapka rkapka commented Mar 17, 2022

What type of PR is this?

Feature

What does this PR do? Why is it needed?

Adds a new execution_optimistic field to API responses. The design is based on ethereum/beacon-APIs#190

@rkapka rkapka added API Api related tasks Merge PRs related to the great milestone the merge labels Mar 17, 2022
@rkapka rkapka requested a review from a team as a code owner March 17, 2022 18:01
rauljordan
rauljordan previously approved these changes Mar 17, 2022
@@ -67,6 +67,10 @@ func (bs *Server) GetBlockHeader(ctx context.Context, req *ethpbv1.BlockRequest)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not determine if block root is canonical: %v", err)
}
isOptimistic, err := bs.HeadFetcher.IsOptimistic(ctx)
Copy link
Member

Choose a reason for hiding this comment

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

This is wrong.

The spec says:

- description: "True if the response references an unverified execution payload. Optimistic information may be invalidated at a later time. If the field is not present, assume the False value."

The isOptimistic should reference to the responded block header not to the current head. Same for other places

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. I changed the code to use IsOptimisticForRoot everywhere. The only two places where we look at the head are head and chain_reorg events.

@@ -229,7 +229,7 @@ func (s *Service) onBlock(ctx context.Context, signed block.SignedBeaconBlock, b
if err := s.cfg.ForkChoiceStore.Prune(ctx, fRoot); err != nil {
return errors.Wrap(err, "could not prune proto array fork choice nodes")
}
isOptimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(ctx, s.headRoot())
isOptimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(ctx, fRoot)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this is better inside the goroutine, I don't think it makes a big difference but something worth considering

Copy link
Contributor

Choose a reason for hiding this comment

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

The call above locks on notifyFockchoiceUpdate so this one is guaranteed to return immediately.

Comment on lines 80 to 87
_, blocks, err := bs.BeaconDB.BlocksBySlot(ctx, st.Slot())
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get blocks for state slot: %v", err)
}
isOptimistic, err := bs.blocksAreOptimistic(ctx, blocks)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if blocks are optimistic: %v", err)
}
Copy link
Member

Choose a reason for hiding this comment

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

Here's a better and more accurate algorithm:

	header := st.LatestBlockHeader()
	header.StateRoot = stateRoot[:]
	headRoot, err := header.HashTreeRoot()
	if err != nil {
		return nil, errors.Wrap(err, "error while computing block root using state data")
	}
        isOptimistic, err = bs.HeadFetcher.IsOptimisticForRoot(ctx, headRoot)
		if err != nil {
			return false, errors.Wrap(err, "could not check if block is optimistic")
		}

"Block proposal received via RPC")
bs.BlockNotifier.BlockFeed().Send(&feed.Event{
Type: blockfeed.ReceivedBlock,
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedPhase0Blk, IsOptimistic: isOptimistic},
Copy link
Member

Choose a reason for hiding this comment

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

phase0 and altair blocks will always be non-optimistic. Optimistic is a concept introduced for Bellatrix

Suggested change
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedPhase0Blk, IsOptimistic: isOptimistic},
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedPhase0Blk, IsOptimistic: false},

Copy link
Contributor

Choose a reason for hiding this comment

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

+1, in addition the check for isOptimistic should not be there.

"Block proposal received via RPC")
bs.BlockNotifier.BlockFeed().Send(&feed.Event{
Type: blockfeed.ReceivedBlock,
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedAltairBlk, IsOptimistic: isOptimistic},
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedAltairBlk, IsOptimistic: isOptimistic},
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedAltairBlk, IsOptimistic: false},

@@ -327,6 +366,7 @@ func (bs *Server) GetBlockV2(ctx context.Context, req *ethpbv2.BlockRequestV2) (
Message: &ethpbv2.SignedBeaconBlockContainerV2_Phase0Block{Phase0Block: v1Blk.Block},
Signature: v1Blk.Signature,
},
ExecutionOptimistic: isOptimistic,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ExecutionOptimistic: isOptimistic,
ExecutionOptimistic: false,

@@ -349,6 +389,7 @@ func (bs *Server) GetBlockV2(ctx context.Context, req *ethpbv2.BlockRequestV2) (
Message: &ethpbv2.SignedBeaconBlockContainerV2_AltairBlock{AltairBlock: v2Blk},
Signature: blk.Signature(),
},
ExecutionOptimistic: isOptimistic,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ExecutionOptimistic: isOptimistic,
ExecutionOptimistic: false,

Comment on lines 348 to 355
root, err := blk.Block().HashTreeRoot()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get block root: %v", err)
}
isOptimistic, err := bs.HeadFetcher.IsOptimisticForRoot(ctx, root)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if block is optimistic: %v", err)
}
Copy link
Member

Choose a reason for hiding this comment

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

Move these before bellatrixBlk, err := blk.PbBellatrixBlock()

You can save HTR if it's phase 0 or Altair

@@ -577,7 +635,8 @@ func (bs *Server) ListBlockAttestations(ctx context.Context, req *ethpbv1.BlockR
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
}
return &ethpbv1.BlockAttestationsResponse{
Data: v2Blk.Body.Attestations,
Data: v2Blk.Body.Attestations,
ExecutionOptimistic: isOptimistic,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ExecutionOptimistic: isOptimistic,
ExecutionOptimistic: false,

@@ -35,3 +37,21 @@ func ValidateSync(ctx context.Context, syncChecker sync.Checker, headFetcher blo
}
return status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond")
}

func IsOptimistic(ctx context.Context, st state.BeaconState, headFetcher blockchain.HeadFetcher) (bool, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Godoc?

@@ -109,9 +109,15 @@ func (vs *Server) GetAttesterDuties(ctx context.Context, req *ethpbv1.AttesterDu
return nil, status.Errorf(codes.Internal, "Could not get dependent root: %v", err)
}

isOptimistic, err := rpchelpers.IsOptimistic(ctx, s, vs.HeadFetcher)
Copy link
Member

Choose a reason for hiding this comment

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

You want to move this before s, err = advanceState(ctx, s, req.Epoch, currentEpoch) or else state s gets mutated

@@ -171,9 +177,15 @@ func (vs *Server) GetProposerDuties(ctx context.Context, req *ethpbv1.ProposerDu
return nil, status.Errorf(codes.Internal, "Could not get dependent root: %v", err)
}

isOptimistic, err := rpchelpers.IsOptimistic(ctx, s, vs.HeadFetcher)
Copy link
Member

Choose a reason for hiding this comment

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

Same. Move this before advanceState(ctx, s, req.Epoch, currentEpoch)

@@ -245,8 +257,15 @@ func (vs *Server) GetSyncCommitteeDuties(ctx context.Context, req *ethpbv2.SyncC
} else if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get duties: %v", err)
}

isOptimistic, err := rpchelpers.IsOptimistic(ctx, st, vs.HeadFetcher)
Copy link
Member

Choose a reason for hiding this comment

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

This one is good

isOptimistic, err := s.IsOptimistic(ctx)
if err != nil {
return errors.Wrap(err, "could not check if node is optimistically synced")
}
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a short time lapse between we set the head and the optimistic status of the node is settled. How much of a race issue can there be here if we return optimistic when soon after the head will become VALID?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't say how event subscribers will use this info. Is there an easy way to sync things? If not, I would just not worry about it at this point.

isOptimistic, err := s.IsOptimistic(ctx)
if err != nil {
return errors.Wrap(err, "could not check if node is optimistically synced")
}
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, is this function being called right after setting head or right after the return from the engine?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this a question for me?

@@ -229,7 +229,7 @@ func (s *Service) onBlock(ctx context.Context, signed block.SignedBeaconBlock, b
if err := s.cfg.ForkChoiceStore.Prune(ctx, fRoot); err != nil {
return errors.Wrap(err, "could not prune proto array fork choice nodes")
}
isOptimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(ctx, s.headRoot())
isOptimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(ctx, fRoot)
Copy link
Contributor

Choose a reason for hiding this comment

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

The call above locks on notifyFockchoiceUpdate so this one is guaranteed to return immediately.

@@ -126,6 +132,12 @@ func (bs *Server) ListBlockHeaders(ctx context.Context, req *ethpbv1.BlockHeader
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not determine if block root is canonical: %v", err)
}
if !isOptimistic {
Copy link
Contributor

Choose a reason for hiding this comment

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

Here there's an assumption that all blocks in blks have the same optimistic status. Is this guaranteed to hold?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If at least one block is optimistic, then isOptimistic will become true and stay this way.

"Block proposal received via RPC")
bs.BlockNotifier.BlockFeed().Send(&feed.Event{
Type: blockfeed.ReceivedBlock,
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedPhase0Blk, IsOptimistic: isOptimistic},
Copy link
Contributor

Choose a reason for hiding this comment

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

+1, in addition the check for isOptimistic should not be there.

beacon-chain/rpc/eth/beacon/state.go Show resolved Hide resolved
beacon-chain/rpc/eth/helpers/sync.go Show resolved Hide resolved
// Do not block proposal critical path with debug logging or block feed updates.
defer func() {
log.WithField("blockRoot", fmt.Sprintf("%#x", bytesutil.Trunc(root[:]))).Debugf(
"Block proposal received via RPC")
bs.BlockNotifier.BlockFeed().Send(&feed.Event{
Type: blockfeed.ReceivedBlock,
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedBellatrixBlk},
Data: &blockfeed.ReceivedBlockData{SignedBlock: wrappedBellatrixBlk, IsOptimistic: false},
Copy link
Member

Choose a reason for hiding this comment

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

This one is bellatrix block, have we decided to just return false anyway?
Because we don't care about this endpoint?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@potuz mentioned that a proposed block is always optimistic. My thinking is that in this case we should always return true here. I must have copy/pasted false by mistake.

Copy link
Member

Choose a reason for hiding this comment

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

Is it? Why would a validator propose an optimistic block? That's a violation of the spec

cc @potuz

potuz
potuz previously approved these changes Mar 25, 2022
# Conflicts:
#	beacon-chain/rpc/eth/beacon/blocks.go
#	proto/eth/v1/beacon_chain.pb.go
#	proto/eth/v2/beacon_state.pb.go
@prylabs-bulldozer prylabs-bulldozer bot merged commit ed07f4b into develop Mar 25, 2022
@delete-merged-branch delete-merged-branch bot deleted the api-execution-optimistic branch March 25, 2022 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Api related tasks Merge PRs related to the great milestone the merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants