Skip to content

Commit 9310821

Browse files
committed
changed example for type (ii.a) from InsertAndIndexResultApproval to IndexStateCommitment as recommended by reviewer
1 parent de1aa59 commit 9310821

File tree

2 files changed

+53
-36
lines changed

2 files changed

+53
-36
lines changed

storage/operation/Documentation-Guidelines.md

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -131,43 +131,52 @@ Analyze the implementation! Here, the method itself receives the ID (i.e. the cr
131131

132132
#### Type (ii.a) functions for writing data
133133

134-
As an example for functions of type (i.b), consider `operation.InsertAndIndexResultApproval`:
134+
As an example for functions of type (ii.a), consider `operation.IndexStateCommitment`:
135135

136136
```golang
137-
// InsertAndIndexResultApproval atomically performs the following storage operations:
138-
// 1. Store ResultApproval by its ID (in this step, accidental overwrites with inconsistent values
139-
// are prevented by using a collision-resistant hash to derive the key from the value)
140-
// 2. Index approval by the executed chunk, specifically the key pair (ExecutionResultID, chunk index).
141-
// - first, we ensure that no _different_ approval has already been indexed for the same key pair
142-
// - only if the prior check succeeds, we write the index to the database
137+
// IndexStateCommitment indexes a state commitment by the block ID whose execution results in that state.
138+
// The function ensures data integrity by first checking if a commitment already exists for the given block
139+
// and rejecting overwrites with different values. This function is idempotent, i.e. repeated calls with the
140+
// *initially* indexed value are no-ops.
143141
//
144142
// CAUTION:
145-
// - In general, the Flow protocol requires multiple approvals for the same chunk from different
146-
// verification nodes. In other words, there are multiple different approvals for the same chunk.
147-
// Therefore, this index Executed Chunk ➜ ResultApproval ID is *only safe* to be used by
148-
// Verification Nodes for tracking their own approvals (for the same ExecutionResult, a Verifier
149-
// will always produce the same approval)
150-
// - In order to make sure only one approval is indexed for the chunk, _all calls_ to
151-
// `InsertAndIndexResultApproval` must be synchronized by the higher-logic. Currently, we have the
152-
// lockctx.Proof to prove the higher logic is holding the lock inserting the approval after checking
153-
// that the approval is not already indexed.
143+
// - Confirming that no value is already stored and the subsequent write must be atomic to prevent data corruption.
144+
// The caller must acquire the [storage.LockInsertOwnReceipt] and hold it until the database write has been committed.
154145
//
155-
// Expected error returns during normal operations
156-
// - [storage.ErrDataMismatch] if a *different* approval for the same key pair (ExecutionResultID, chunk index) is already indexed
157-
func InsertAndIndexResultApproval(approval *flow.ResultApproval) func(lctx lockctx.Proof, rw storage.ReaderBatchWriter) error {
146+
// Expected error returns during normal operations:
147+
// - [storage.ErrDataMismatch] if a *different* state commitment is already indexed for the same block ID
148+
func IndexStateCommitment(lctx lockctx.Proof, rw storage.ReaderBatchWriter, blockID flow.Identifier, commit flow.StateCommitment) error {
149+
if !lctx.HoldsLock(storage.LockInsertOwnReceipt) {
150+
return fmt.Errorf("cannot index state commitment without holding lock %s", storage.LockInsertOwnReceipt)
151+
}
152+
153+
var existingCommit flow.StateCommitment
154+
err := LookupStateCommitment(rw.GlobalReader(), blockID, &existingCommit) // on happy path, i.e. nothing stored yet, we expect `storage.ErrNotFound`
155+
if err == nil { // Value for this key already exists! Need to check for data mismatch:
156+
if existingCommit == commit {
157+
return nil // The commit already exists, no need to index again
158+
}
159+
return fmt.Errorf("commit for block %v already exists with different value, (existing: %v, new: %v), %w", blockID, existingCommit, commit, storage.ErrDataMismatch)
160+
} else if !errors.Is(err, storage.ErrNotFound) {
161+
return fmt.Errorf("could not check existing state commitment: %w", err)
162+
}
163+
164+
return UpsertByKey(rw.Writer(), MakePrefix(codeCommit, blockID), commit)
165+
}
158166
```
159167
Analyze the implementation! Only functions that internally implement safeguards against overwriting a key-value pair with _different_ data for the same key are of type (ii.a)!
160168

161-
* We document the struct type that is stored, here ResultApproval. If applicable, we also document additional key-value pairs that are persisted as part of this function (here "Index approval by the executed chunk"). Analyze the implementation.
162-
* With a "CAUTION" statement (here, second bullet point), we document that the function must first attempt to read the current value to avoid overwriting and corrupting existing data. This requires synchronization, and hence locking. We document which locks are required to be held by the caller.
163-
* The first bullet point explains further application-specific context which node is intended to use this index. You may skip this explanation for the structures you are documenting.
164-
* We state which errors are expected during normal operations (here storage.ErrDataMismatch) and the condition under which they occur. Analyze the implementation to make the correct statements!
169+
* We document the struct type that is stored, here `flow.StateCommitment`. If applicable, we also document additional key-value pairs that are persisted as part of this function (here, none). Analyze the implementation.
170+
* We concisely document by which means the implementation ensures data integrity. For functions of type (ii.a), we typically just attempt to read the value for the respective key. You may adapt the explanation from this example to reflect the specifics of the implementation. Note that the behaviour might be different if a value has previously been stored. Analyze the implementation.
171+
* With a "CAUTION" statement, we concisely document the requirement that the read for the data integrity check and the subsequent write must happen atomically. This requires synchronization, and hence locking. We document which locks are required to be held by the caller.
172+
* Analyze the implementation to decide whether additional cautionary statements are required to reduce the probability of accidental bugs.
173+
* We state which errors are expected during normal operations (here `storage.ErrDataMismatch`) and the condition under which they occur. Analyze the implementation to make the correct statements!
165174

166175

167176

168177
#### Type (ii.b) functions for writing data
169178

170-
As an example for functions of type (i.b), consider `operation.IndexPayloadSeals`:
179+
As an example for functions of type (ii.b), consider `operation.IndexPayloadSeals`:
171180

172181
```golang
173182
// IndexPayloadSeals indexes the given Seal IDs by the block ID.

storage/operation/commits.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,47 @@ import (
1010
"github.com/onflow/flow-go/storage"
1111
)
1212

13-
// IndexStateCommitment indexes a state commitment.
13+
// IndexStateCommitment indexes a state commitment by the block ID whose execution results in that state.
14+
// The function ensures data integrity by first checking if a commitment already exists for the given block
15+
// and rejecting overwrites with different values. This function is idempotent, i.e. repeated calls with the
16+
// *initially* indexed value are no-ops.
1417
//
15-
// State commitments are keyed by the block whose execution results in the state with the given commit.
16-
// It returns [storage.ErrDataMismatch] if the commit already exists with a different value.
18+
// CAUTION:
19+
// - Confirming that no value is already stored and the subsequent write must be atomic to prevent data corruption.
20+
// The caller must acquire the [storage.LockInsertOwnReceipt] and hold it until the database write has been committed.
21+
//
22+
// Expected error returns during normal operations:
23+
// - [storage.ErrDataMismatch] if a *different* state commitment is already indexed for the same block ID
1724
func IndexStateCommitment(lctx lockctx.Proof, rw storage.ReaderBatchWriter, blockID flow.Identifier, commit flow.StateCommitment) error {
1825
if !lctx.HoldsLock(storage.LockInsertOwnReceipt) {
1926
return fmt.Errorf("cannot index state commitment without holding lock %s", storage.LockInsertOwnReceipt)
2027
}
2128

2229
var existingCommit flow.StateCommitment
23-
err := LookupStateCommitment(rw.GlobalReader(), blockID, &existingCommit)
24-
if err == nil {
30+
err := LookupStateCommitment(rw.GlobalReader(), blockID, &existingCommit) // on happy path, i.e. nothing stored yet, we expect `storage.ErrNotFound`
31+
if err == nil { // Value for this key already exists! Need to check for data mismatch:
2532
if existingCommit == commit {
26-
// The commit already exists, no need to index again
27-
return nil
33+
return nil // The commit already exists, no need to index again
2834
}
29-
return fmt.Errorf("commit for block %v already exists with different value, (existing: %v, new: %v), %w", blockID,
30-
existingCommit, commit, storage.ErrDataMismatch)
35+
return fmt.Errorf("commit for block %v already exists with different value, (existing: %v, new: %v), %w", blockID, existingCommit, commit, storage.ErrDataMismatch)
3136
} else if !errors.Is(err, storage.ErrNotFound) {
3237
return fmt.Errorf("could not check existing state commitment: %w", err)
3338
}
3439

3540
return UpsertByKey(rw.Writer(), MakePrefix(codeCommit, blockID), commit)
3641
}
3742

38-
// LookupStateCommitment gets a state commitment keyed by block ID
39-
//
40-
// State commitments are keyed by the block whose execution results in the state with the given commit.
43+
// LookupStateCommitment retrieves a state commitment by the block ID whose execution results in that state.
44+
// Expected error returns during normal operations:
45+
// - [storage.ErrNotFound] if no state commitment is indexed for the specified block ID
4146
func LookupStateCommitment(r storage.Reader, blockID flow.Identifier, commit *flow.StateCommitment) error {
4247
return RetrieveByKey(r, MakePrefix(codeCommit, blockID), commit)
4348
}
4449

4550
// RemoveStateCommitment removes the state commitment by block ID
51+
// CAUTION: this is for recovery purposes only, and should not be used during normal operations!
52+
// It returns nil if no execution result for the given blockID was previously indexed.
53+
// No errors are expected during normal operation.
4654
func RemoveStateCommitment(w storage.Writer, blockID flow.Identifier) error {
4755
return RemoveByKey(w, MakePrefix(codeCommit, blockID))
4856
}

0 commit comments

Comments
 (0)