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

Move service events to execution receipt #399

Merged

Conversation

jordanschalm
Copy link
Member

@jordanschalm jordanschalm commented Feb 11, 2021

This PR removes service events from seals so they live only in execution results.

Changes

  • Convert flow.Event (ie. Cadence) typed service events immediately to flow.ServiceEvent in execution node, so we only deal with one representation as much as possible.
    • Change type of ExecutionResult.ServiceEvent to flow.ServiceEvent
  • Update bootstrapping to add service event to root result, rather than root seal
  • Change when protocol state change caused by a service take place
    • For the minimal sealing chain (A <- B(resultA) <- C(sealA) <- D) where a (e.g.) EpochSetup event is emitted in block A, the epoch would previously become accessible at block C, when the seal is incorporated. Now the epoch becomes accessible at block D, after we have a QC for the block containing the seal.
    • Protocol events are still emitted at the same time (ie. when C is finalized)
  • Update EpochBuilder test utility
    • Expose an API for querying information about epochs built by the utility

@jordanschalm jordanschalm changed the base branch from master to feature/serializable-snapshots March 5, 2021 17:19
@jordanschalm jordanschalm marked this pull request as ready for review March 12, 2021 02:43
Copy link
Member

@durkmurder durkmurder left a comment

Choose a reason for hiding this comment

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

Very clean, great work!

Copy link
Member

@zhangchiqing zhangchiqing left a comment

Choose a reason for hiding this comment

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

Nice work! Just have 1 comment

Comment on lines +763 to +768
// we will apply service events from blocks which are sealed by this block's PARENT
parent, err := m.blocks.ByID(block.Header.ParentID)
if err != nil {
return nil, fmt.Errorf("could not get parent (id=%x): %w", block.Header.ParentID, err)
}

Copy link
Member

@zhangchiqing zhangchiqing Mar 12, 2021

Choose a reason for hiding this comment

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

  1. [optional] since we are handling events for its parent, what about passing the parent to this function instead of letting this function query the parent?
  2. ⚠️ we might handle events for the same parent again when inserting a second child of that parent, would that raise error? Are we able to exit earlier?

Copy link
Member Author

Choose a reason for hiding this comment

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

1 since we are handling events for its parent, what about passing the parent to this function

Would this be for performance? The parent is effectively guaranteed to be cached , I don't think there would be much performance impact by passing it as an argument. We also don't already have the parent at the callsite

blockID := candidate.ID()
m.tracer.StartSpan(blockID, trace.ProtoStateMutatorExtendDBInsert)
defer m.tracer.FinishSpan(blockID, trace.ProtoStateMutatorExtendDBInsert)
// SIXTH: epoch transitions and service events
// (i) Determine protocol state for block's _current_ Epoch.
// As we don't have slashing yet, the protocol state is fully
// determined by the Epoch Preparation events.
// (ii) Determine protocol state for block's _next_ Epoch.
// In case any of the payload seals includes system events,
// we need to check if they are valid and must apply them
// to the protocol state as needed.
ops, err := m.handleServiceEvents(candidate)
if err != nil {
return fmt.Errorf("could not handle service events: %w", err)
}

2 we might handle events for the same parent again when inserting a second child of that parent

Good point 🙇 . For a sealing chain like A <- B(RA) <- C(SA) <- D, when inserting conflicting children of C we always need to insert the epoch status for the child D. This will not cause conflicts because it is keyed by the block ID of D. We also need to insert the service events, which may have conflicts. (They can have conflicts beyond this particular case of multiple children of block C, since there may be multiple ERs containing the same service event in different forks, as well as different seals.)

I think the best way to handle all these cases is to allow duplicates directly in the storage layer of EpochSetups and EpochCommits -- I'll add this and a test case.

Copy link
Member

Choose a reason for hiding this comment

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

Would this be for performance?

Not for performance. Just to note that I was suggesting this:

	parent, err := m.blocks.ByID(candidate.Header.ParentID)
	if err != nil {
		return nil, fmt.Errorf("could not get parent (id=%x): %w", block.Header.ParentID, err)
	}
 	// SIXTH: epoch transitions...
 	ops, err := m.handleServiceEvents(parent) 
 	if err != nil { 
 	 	return fmt.Errorf("could not handle service events: %w", err) 
  	} 

when inserting conflicting children of C

I'm not sure if we are talking about the same case. The case I'm talking about is this:

A <- B(RA) <- C(SA) <- D
              ^------- E

Block C has 2 children: D and E. When we receive D first, we will call handleServiceEvents(D) which store the service events for its parent block (C). And then when we receive E later, we will call handleServiceEvents(E) again which will store the service events for its parent block (C) again. And storing the service events will store the epoch setup event, which seems doesn't allow SkipDuplication, which means, it will fail, right?

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 see. We can't only pass in the parent block, because we insert the epoch status for the block being inserted (ie. block D), not the parent block.

// we always index the epoch status, even when there are no service events
ops = append(ops, m.epoch.statuses.StoreTx(block.ID(), epochStatus))

Block C has 2 children: D and E

That is the same case I'm thinking of 👍 , added SkipDuplicate to the service event storage APIs in 132e243 and test case in 9e5bdaa.

Comment on lines 907 to 914
// we should be able to have conflicting forks with two duplicate instances of
// the same service event for the same epoch
//
// /--B1<--B3(R1)<--B5(S1)<--B7
// ROOT <--+
// \--B2<--B4(R2)<--B6(S2)<--B8
//
func TestExtendDuplicateEpochEvents(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

This comment seems is duplicated with the TestExtendConflictingEpochEvents

Copy link
Member Author

Choose a reason for hiding this comment

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

It's very close but it says two duplicate instances rather than two different instances

Comment on lines +910 to +912
// /--B1<--B3(R1)<--B5(S1)<--B7
// ROOT <--+
// \--B2<--B4(R2)<--B6(S2)<--B8
Copy link
Member

Choose a reason for hiding this comment

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

is the following case same as this one? Or has any test case covered it already?

ROOT <--B1 <- B2(R1) <-- B3(S1) <--B4
                            ^----- B5

This is to test we are able to process S1 twice.

Copy link
Member Author

Choose a reason for hiding this comment

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

TestExtendDuplicateEpochEvents will cover processing the same service event twice, since we create the same service event on two different forks, and handle service events on both forks.

@jordanschalm jordanschalm merged commit 933295e into feature/serializable-snapshots Mar 15, 2021
@jordanschalm jordanschalm deleted the jordan/4866-handle-service-events branch March 15, 2021 19:07
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.

3 participants