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

[Dynamic Protocol State] Protocol State Mutator #4597

Merged

Conversation

durkmurder
Copy link
Member

https://github.com/dapperlabs/flow-go/issues/6802

Context

This PR implements protocol state mutator. It's a dedicated component that will be used by compliance layer to create protocol state updater and later commit protocol state into storage. To maintain a correct index of blockID -> protocol state previously described logic has to be executed for EACH block.

Base automatically changed from yurii/6801-protocol-state-updater to yurii/5513-read-only-identity-table August 8, 2023 17:51
return func(tx *transaction.Tx) error {
updatedState, updatedStateID, hasChanges := updater.Build()
if hasChanges {
err := m.protocolStateDB.StoreTx(updatedStateID, updatedState)(tx)
Copy link
Member

Choose a reason for hiding this comment

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

Suppose we have two protocol state changes, $\delta_1$, $\delta_2$, and applying them sequentially to an initial state $\Delta$ results in $\Delta$ again.

$\Delta + \delta_1 = \Delta'$
$\Delta' + \delta_2 = \Delta$

For example, if the state changes subtract, then add, a constant value to Weight.

In that case, we will have an unhandled storage.ErrAlreadyExists error here, treated as irrecoverable. I don't think we should make the assumption at this layer that protocol states can never repeat, even if it happens to be true.

Suggestion: Handle ErrAlreadyExists here, and consider as a no-op.

Copy link
Member

Choose a reason for hiding this comment

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

+1

We should certainly document possible error returns in detail for the interface ProtocolState. See my comment below for a detailed suggestion

Copy link
Member

Choose a reason for hiding this comment

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

⚠️ Approving the PR - but please address this before merging.

Copy link
Member Author

Choose a reason for hiding this comment

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

but in such case hasChanges has to be false, so it shouldn't happen. Nevertheless I will make it accept such case. 7266c8f (#4597)

candidate := unittest.BlockHeaderFixture()
s.protocolStateDB.On("ByBlockID", candidate.ParentID).Return(nil, storerr.ErrNotFound)
updater, err := s.mutator.CreateUpdater(candidate)
require.ErrorIs(s.T(), err, storerr.ErrNotFound)
Copy link
Member

Choose a reason for hiding this comment

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

We should document this as an expected error, or wrap as irrecoverable.

Copy link
Member

Choose a reason for hiding this comment

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

I think from the perspective of the Mutator the error should be benign, as it allows (under certain conditions, e.g. access node trying to serve an API request) to continue.

See my comment below for a detailed suggestion on documenting error returns of interface ProtocolState.

Copy link
Member

Choose a reason for hiding this comment

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

⚠️ Approving the PR - but please address this before merging.

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've updated godoc so we expect ErrNotFound a1969a0 (#4597)

@AlexHentschel
Copy link
Member

AlexHentschel commented Sep 7, 2023

Not sure if I mentioned this in one of the preceding PR reviews:

  • I feel the goDoc of interface ProtocolState could be extended
  • Regarding
    // StoreTx allows us to store protocol state as part of a DB tx, while still going through the caching layer.
    StoreTx(id flow.Identifier, protocolState *flow.ProtocolStateEntry) func(*transaction.Tx) error

    If I understand correctly, the implementation does not (contrary to the interface documentation) go through the caching layer, right?

What do you think about extending the goDoc as follows:

// ProtocolState represents persistent storage for protocol state entries.
type ProtocolState interface {
	// StoreTx returns an anonymous function (intended to be executed as part of a badger transaction),
	// which persists the given protocol state as part of a DB tx.
	// Expected errors of the returned anonymous function:
	//   - storage.ErrAlreadyExists if a protocol state snapshot has already been stored under the given id.
	StoreTx(id flow.Identifier, protocolState *flow.ProtocolStateEntry) func(*transaction.Tx) error

	// Index returns an anonymous function that is intended to be executed as part of a database transaction.
	// In a nutshell, we want to maintain a map from `blockID` to `protocolStateID`. Upon call, the
	// anonymous function persists the specific map entry in the node's database.
	// Protocol convention:
	//   - Consider block B, whose ingestion might potentially lead to an updated protocol state. For example,
	//     the protocol state changes if we seal some execution results emitting service events.
	//   - For the key `blockID`, we use the identity of block B. As value, the hash of the resulting protocol
	//     state at the end of processing B is to be used.
	// Expected errors during normal operations:
	//   - storage.ErrAlreadyExists if the key already exists in the database.
	Index(blockID flow.Identifier, protocolStateID flow.Identifier) func(*transaction.Tx) error

	// ByID returns the protocol state by its ID.
	// Expected errors during normal operations:
	//  * `storage.ErrNotFound` if no protocol state snapshot with the given Identifier is known.
	ByID(id flow.Identifier) (*flow.RichProtocolStateEntry, error)

	// ByBlockID returns the protocol state by block ID.
	// Expected errors during normal operations:
	//  * `storage.ErrNotFound` if no snapshot of the protocol state has been indexed for the given block.
	ByBlockID(blockID flow.Identifier) (*flow.RichProtocolStateEntry, error)
}

state/protocol/protocol_state.go Show resolved Hide resolved
state/protocol/protocol_state/mutator.go Outdated Show resolved Hide resolved
return func(tx *transaction.Tx) error {
updatedState, updatedStateID, hasChanges := updater.Build()
if hasChanges {
err := m.protocolStateDB.StoreTx(updatedStateID, updatedState)(tx)
Copy link
Member

Choose a reason for hiding this comment

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

+1

We should certainly document possible error returns in detail for the interface ProtocolState. See my comment below for a detailed suggestion

Copy link
Member

@jordanschalm jordanschalm left a comment

Choose a reason for hiding this comment

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

Commented on my outstanding points which we should address before merging.

Base automatically changed from yurii/5513-read-only-identity-table to feature/dynamic-protocol-state September 8, 2023 13:32
@codecov-commenter
Copy link

codecov-commenter commented Sep 8, 2023

Codecov Report

Patch coverage: 70.83% and project coverage change: +7.67% 🎉

Comparison is base (b361d16) 54.90% compared to head (a1969a0) 62.58%.

Additional details and impacted files
@@                        Coverage Diff                         @@
##           feature/dynamic-protocol-state    #4597      +/-   ##
==================================================================
+ Coverage                           54.90%   62.58%   +7.67%     
==================================================================
  Files                                 923      386     -537     
  Lines                               86103    33982   -52121     
==================================================================
- Hits                                47277    21267   -26010     
+ Misses                              35227    11005   -24222     
+ Partials                             3599     1710    -1889     
Flag Coverage Δ
unittests 62.58% <70.83%> (+7.67%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Changed Coverage Δ
state/protocol/protocol_state/mutator.go 70.83% <70.83%> (ø)

... and 542 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@durkmurder durkmurder merged commit 47a94f0 into feature/dynamic-protocol-state Sep 11, 2023
36 checks passed
@durkmurder durkmurder deleted the yurii/6802-protocol-state-mutator branch September 11, 2023 09:39
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.

4 participants