-
Notifications
You must be signed in to change notification settings - Fork 176
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] Storage layer for KV store #5383
[Dynamic Protocol State] Storage layer for KV store #5383
Conversation
// - CAUTION: The updated state requires confirmation by a QC and will only become active at the child block, | ||
// _after_ validating the QC. |
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.
// - CAUTION: The updated state requires confirmation by a QC and will only become active at the child block, | |
// _after_ validating the QC. |
This is not true from protocol state machine, shouldn't be true for the kv store either. I'm guessing this is a copy-paste from some outdated comments.
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 make the same statement in the storage abstraction for the protocol state:
flow-go/storage/protocol_state.go
Lines 27 to 28 in 81266cf
// - CAUTION: The protocol state requires confirmation by a QC and will only become active at the child block, | |
// _after_ validating the QC. |
For sure, this is a simplified statement skipping over some details. Nevertheless, I feel the statement is still applicable, and a similarly worded statement is also in our notion doc Identity-Changing Operations
:
My interpretation of your comment is: "the consensus nodes don't need to wait for a QC, as they validate the correctness of the state transition by re-computing it locally" I am happy to discuss how we could reword the sentence, to make it more clear.
Nevertheless, I feel there is a super-important aspect in here:
The updated state [...] only become active at the child block, after [...] valid QC
This avoids some subtle but safety-critical misusage:
- using the resulting protocol state (kv-store) and apply it to the payload of the block (circular dependency)
- some off-chain logic uses the protocol state to determine signing authority. Without waiting for a valid QC, byzantine consensus participant could give a malicious block to a client where it tempered with the epoch participation information. Thereby, a client could be easily tricked into accepting malicious messages from unauthorized parties, or rejecting messages from honest parties.
On the one hand, the consensus follower handles this safely and protects higher-level business logic. On the other hand, I feel we should emphasize this safety-critical requirement because not all applications might be using the consensus follower and additionally we want to be reminded of this requirement when we work on the consensus follower in the future.
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.
how about we write (?)
// - CAUTION: The updated state requires confirmation by a QC and will only become active at the child block, | |
// _after_ validating the QC. | |
// - CAUTION: The updated state will only become active at the child block, | |
// _after_ its correctness has been affirmed by a valid QC. |
Does that address your wording concerns?
storage/protocol_kv_store.go
Outdated
// CAUTION: this store state requires confirmation by a QC and will only become active at the child block, | ||
// _after_ validating the QC. Protocol convention: | ||
// - Consider block B, whose ingestion might potentially lead to an updated KV store state. | ||
// For example, the state changes if we seal some execution results emitting specific service events. | ||
// - For the key `blockID`, we use the identity of block B which _proposes_ this updated KV store. As value, | ||
// the hash of the resulting state at the end of processing B is to be used. | ||
// - CAUTION: The updated state requires confirmation by a QC and will only become active at the child block, | ||
// _after_ validating the QC. |
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.
// CAUTION: this store state requires confirmation by a QC and will only become active at the child block, | |
// _after_ validating the QC. Protocol convention: | |
// - Consider block B, whose ingestion might potentially lead to an updated KV store state. | |
// For example, the state changes if we seal some execution results emitting specific service events. | |
// - For the key `blockID`, we use the identity of block B which _proposes_ this updated KV store. As value, | |
// the hash of the resulting state at the end of processing B is to be used. | |
// - CAUTION: The updated state requires confirmation by a QC and will only become active at the child block, | |
// _after_ validating the QC. | |
// Protocol convention: | |
// - Consider block B, whose ingestion might potentially lead to an updated KV store state. | |
// For example, the state changes if we seal some execution results emitting specific service events. | |
// - For the key `blockID`, we use the identity of block B which _proposes_ this updated KV store. As value, | |
// the hash of the resulting state at the end of processing B is to be used. |
} | ||
|
||
// ProtocolKVStore persists different snapshots of key-value stores. | ||
type ProtocolKVStore interface { |
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.
I'm wondering if it will be a clearer separation of concerns for this storage interface to keep KeyValueStoreData
private, and accept/return instances of kvstore.API
when storing and retrieving. My thinking is to keep the surface area where we need to worry about versioned types as small as possible (ideally only kvstore
and storage
to differing extents).
Not sure though, we can definitely merge this as-is and sort out the exact separation when hooking up to #5371.
Looks good, most comments are around documentation -- there are a few places where |
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## feature/protocol-state-kvstore #5383 +/- ##
==================================================================
- Coverage 56.00% 56.00% -0.01%
==================================================================
Files 1021 1025 +4
Lines 98524 98623 +99
==================================================================
+ Hits 55183 55229 +46
- Misses 39211 39253 +42
- Partials 4130 4141 +11
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Co-authored-by: Jordan Schalm <jordan@dapperlabs.com>
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.
first batch of comments:
storage/protocol_kv_store.go
Outdated
// StoreTx returns an anonymous function (intended to be executed as part of a badger transaction), | ||
// which persists the given model as part of a DB tx. | ||
// Expected errors of the returned anonymous function: | ||
// - storage.ErrAlreadyExists if a model with the given id is already stored |
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.
Overall, there is some ambiguity in the term "model". In my opinion, a data model is very similar to a schema: it specifies the data fields and their types. I would equate our golang type definition with the data model (if we had a protobuf representation of the same struct, it would be the same data model, just in a different language). For example, in Jordan's PR #5371, he defines the v0-data-model as
type modelv0 struct{} |
InvalidEpochTransitionAttempted
.
What we are storing here is not the data model in my opinion. For example all entries we store could be the same data model (e.g. they are all instances of modelv0
). I would call the thing we are storing either 'snapshots' (consistent with snapshots of the protocol state) or instances of a data model.
// - storage.ErrAlreadyExists if a model with the given id is already stored | |
// - storage.ErrAlreadyExists if a KV-store snapshot with the given id is already stored |
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.
Great work. Largely documentation questions.
I feel the only possibly controversial aspect is this sentence in the documentation:
CAUTION: The updated state requires confirmation by a QC and will only become active at the child block, after validating the QC.
See Jordan's comment. If you want to move ahead with this PR, I think it would be totally fine to merge when all other comments are addressed. We discuss on Tuesday and add a follow-up PR fixing the few lines of documentation if necessary.
Co-authored-by: Alexander Hentschel <alex.hentschel@axiomzen.co>
…flow-go into yurii/5292-storage-layer
Context
This PR implements storage capabilities for persisting KV store in the database.
It's implemented following the design doc.
Implementation is similar to the protocol state with the exception that it's a bit simpler and performs cache on store as well.