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] Querying of KV Store using Snapshot API #5650

Merged
merged 21 commits into from
Apr 12, 2024

Conversation

durkmurder
Copy link
Member

@durkmurder durkmurder commented Apr 9, 2024

#5316

Context

This PR exposes KV Store using the existing Snapshot API which allows querying data from Dynamic Protocol State by block ID.

Access to the KV Store has been structured to mirror existing implementation for epochs+identity table. In future we might want to consolidate them in one interface which could be a facade to the Dynamic Protocol State and allows querying sub-states using it. For now both KV Store and Epochs+IdentityTable are exposes equally in Snapshot interface.

I have renamed ProtocolState to EpochProtocolState in some places but I believe we should also address the naming as part of future refactoring.

Most of the tests that were previously disabled were enabled in this PR, but there are some tests still broken due to SealingSegment limitations(details in #5120).

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.

Looks great.

As you mentioned in the PR description, the ProtocolState naming remains a bit confusing, as what was previously "the whole protocol state" is now just a part of it, and stored separately from the rest. I am OK with addressing this when we move the epoch state into the KVStore, I've added a couple suggestions for noting this path in the comments / renaming a few other fields.

// ProtocolState returns the dynamic protocol state that the Head block commits to. The
// compliance layer guarantees that only valid blocks are appended to the protocol state.
// EpochProtocolState returns the epoch part of dynamic protocol state that the Head block commits to.
// The compliance layer guarantees that only valid blocks are appended to the protocol state.
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
// The compliance layer guarantees that only valid blocks are appended to the protocol state.
// The compliance layer guarantees that only valid blocks are appended to the protocol state.
// TODO: Conceptually the epoch protocol state is part of the overall Dynamic Protocol State
// (`ProtocolState` below), but it is separate here for historical reasons. Eventually we should
// move the epoch protocol state data into the protocol state KV Store.

Copy link
Member

@AlexHentschel AlexHentschel Apr 12, 2024

Choose a reason for hiding this comment

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

regarding your suggestion @jordanschalm:

  • As I understand your suggestion, you are commenting on a future revision of the implementation. It is not a suggestion to change the API (Snapshot interface), right?
  • Therefore, I would suggest to move your suggested TODO comment into EpochProtocolState()'s body (before line 332)

Given the importance of the Epoch portion of the protocol state, I feel it is very reasonable to retain the dedicated method EpochProtocolState() as a convenience function in the (Snapshot interface).

Copy link
Member

@jordanschalm jordanschalm Apr 12, 2024

Choose a reason for hiding this comment

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

No, It is a suggestion to change the API interface in a future revision (remove the top-level EpochProtocolState method).

I agree the Epoch portion of the protocol state is important, but at that point the data will be duplicated in 3 places in the Snapshot: .Epochs, .EpochProtocolState, and (for example) .KVStore.EpochState.

In general we should aim to have a collection of data in 1 place in the Snapshot structure, adding a shortcut in exceptional cases. Maybe 2 for deeply nested fields, definitely not 3 😅.

Copy link
Member

Choose a reason for hiding this comment

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

📝 added item to issue 5666

Comment on lines +65 to +70
// GetInvalidEpochTransitionAttempted returns a flag indicating whether epoch
// fallback mode has been tentatively triggered on this fork.
// Errors:
// - ErrKeyNotSupported if the respective entry does not exist in the
// Protocol State Snapshot that is backing the `Reader` interface.
GetInvalidEpochTransitionAttempted() (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.

I added this originally mainly as a way to have two different versions we could test with right away. But we still have the InvalidEpochTransitionAttempted field in the EpochStateContainer (where is was originally) and this is what is used for triggering EFM in the state machine.

I don't think we have an immediate plan to use this version of the flag instead, so I think we should remove it to avoid confusion.

However, it's still necessary for several tests (in particular the integration test in #5477) to have multiple versions to transition between before releasing the feature. Not sure what the best course of action is.

Suggestion: At the moment, I'm leaning toward having a v1 model version which is otherwise identical to v0, but returns a different version number.

This comment isn't really related to this PR - feel free to address here, otherwise I can in #5477.

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 will leave this to you as you will be able to check what is the best course of action when you will be working on the integration test.

Copy link
Member

Choose a reason for hiding this comment

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

📝 added item to issue 5666

state/protocol/protocol_state/state/protocol_state.go Outdated Show resolved Hide resolved
cmd/bootstrap/cmd/block.go Outdated Show resolved Hide resolved
// ProtocolState returns the dynamic protocol state that the Head block commits to. The
// compliance layer guarantees that only valid blocks are appended to the protocol state.
// EpochProtocolState returns the epoch part of dynamic protocol state that the Head block commits to.
// The compliance layer guarantees that only valid blocks are appended to the protocol state.
Copy link
Member

@AlexHentschel AlexHentschel Apr 12, 2024

Choose a reason for hiding this comment

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

regarding your suggestion @jordanschalm:

  • As I understand your suggestion, you are commenting on a future revision of the implementation. It is not a suggestion to change the API (Snapshot interface), right?
  • Therefore, I would suggest to move your suggested TODO comment into EpochProtocolState()'s body (before line 332)

Given the importance of the Epoch portion of the protocol state, I feel it is very reasonable to retain the dedicated method EpochProtocolState() as a convenience function in the (Snapshot interface).

Copy link
Member

@AlexHentschel AlexHentschel 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 code. Thanks.

With the progressive cleanups and continuing work, I feel some foot-guns in our current API (as it grew historically) are getting more clear. Probably out of scope for this PR, but wanting to flag for awareness.

Copy link
Member

@AlexHentschel AlexHentschel Apr 12, 2024

Choose a reason for hiding this comment

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

Probably beyond the scope of the PR, but I am a bit worried about about the safety of this API.

  • The DynamicProtocolState.Entry() method does not return the most recent dynamic protocol state, which is easily overlooked. The documentation is clear, but I am not sure that is enough.

    For example, I would guess that this code here does not take this into account. I am worried that this implementation would forget the "dynamic status" of ejected nodes, because it uses the initial status at the beginning of the spork!

I think the easiest (at least temporary) mitigation would be to rename the DynamicProtocolState.Entry() method to something more explicit like InitialEpochState()

Comment on lines 253 to 255
rootProtocolStateEntry := rootEpochState.Entry().ProtocolStateEntry
rootEpochStateID := rootProtocolStateEntry.ID()
err := protocolState.StoreTx(rootEpochStateID, rootProtocolStateEntry)(tx)
Copy link
Member

Choose a reason for hiding this comment

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

I am worried that his implementation is incorrect (more specifically, I think it doesn't ejected nodes correctly):

  • lets assume we bootstrap with a sealing segment where:
    • Bob was an authorized participant at the beginning of the epoch
    • However, during the epoch Bob got ejected (slashing or maybe self-ejection)
  • below (line 283), we index every block with the root state.

I guess in the end, this is more a flag for @jordanschalm's, that this code needs refactoring and to not use Snapshot.EpochProtocolState().Entry() as this always returns the state at the beginning of the Epoch (thereby "forgetting" ejection events).

Copy link
Member

@jordanschalm jordanschalm Apr 12, 2024

Choose a reason for hiding this comment

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

not use Snapshot.EpochProtocolState().Entry() as this always returns the state at the beginning of the Epoch

I don't think this is true.

  • epochPSForB := Snapshot.EpochProtocolState() returns the DynamicProtocolState for the snapshot's reference block B
  • psEntryForB := DynamicProtocolState.Entry() returns the ProtocolStateEntry associated with B
    • psEntryForB is the data structure representing the complete but minimal dynamic protocol state, including block-by-block state changes
    • In particular, ProtocolStateEntry contains EpochStateContainer which tracks the block-by-block identity lists in the field ActiveIdentities DynamicIdentityEntryList

The structure is confusing, though, because Entry() is part of InitialProtocolState, whose documentation states that it "returns constant data for given epoch", which is not true of Entry().

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 on Jordan's point, all instances of DynamicProtocolState are obtained using a concrete block:

Both of those places commit to a concrete block so they always return state at reference block.
There is no way to obtain InitialProtocolState right now.

That is a good argument to refactor it later.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks. I am glad the implementations are doing the right thing. However, I would like to emphasize that this is super confusing given the current API design:
Entry() is defined through the InitialProtocolState interface. The InitialProtocolState is very clear in its documentation that it (quote) "returns constant data for given epoch." Therefore, the only possible conclusion is also that Entry returns the DynamicProtocolState as specified initially by the Epoch Setup and Commit events.

Copy link
Member

Choose a reason for hiding this comment

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

📝 added item to issue 5666

state/protocol/inmem/convert.go Show resolved Hide resolved
state/protocol/kvstore.go Outdated Show resolved Hide resolved
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