Conversation
prestonvanloon
left a comment
There was a problem hiding this comment.
Partial review with @saolyn. I'll review more later today.
beacon-chain/db/kv/blob.go
Outdated
| if len(scs) == 0 { | ||
| return errors.New("nil or empty blob sidecars") | ||
| } | ||
| slot := scs[0].Slot |
There was a problem hiding this comment.
Do you need to validate that all of the blobs in the argument scs have the same slot and block root?
Do you also need to validate that the sidecars are sorted by their index and in the correct position?
There was a problem hiding this comment.
Good questions. I asked them before on Slack and the response from @kasey was that we don't want to validate it in the db package. I would say otherwise, because we want to make sure the db doesn't get corrupted in any way.
There was a problem hiding this comment.
I was more opposed to the idea of verifying that the number of sidecars was less than the current value of MAX_BLOBS_PER_BLOCK than checking the consistency between slot and root. But yeah generally I do think that the db code checking the semantics within a value being stored is going a little too far and mixing concerns, especially since we're behind an abstract data storage interface here (kv). As opposed to for instance checking the integrity between values that refer to each other across atomic writes, which would definitely be the db's job, to ensure referential integrity.
There was a problem hiding this comment.
context: @rkapka is referring to my response to this comment: I think we should add some more validation though, like checking that the number of saved sidecars is not more than MaxBlobsPerBlock and that all of them have the same slot etc.
beacon-chain/db/kv/blob.go
Outdated
| // If there is no element stored at blob.slot % MAX_SLOTS_TO_PERSIST_BLOBS, then we simply | ||
| // store the blob by key and exit early. | ||
| if len(replacingKey) == 0 { | ||
| return bkt.Put(newKey, encodedBlobSidecar) | ||
| } | ||
|
|
||
| if err := bkt.Delete(replacingKey); err != nil { | ||
| log.WithError(err).Warnf("Could not delete blob with key %#x", replacingKey) | ||
| } | ||
| return bkt.Put(newKey, encodedBlobSidecar) |
There was a problem hiding this comment.
| // If there is no element stored at blob.slot % MAX_SLOTS_TO_PERSIST_BLOBS, then we simply | |
| // store the blob by key and exit early. | |
| if len(replacingKey) == 0 { | |
| return bkt.Put(newKey, encodedBlobSidecar) | |
| } | |
| if err := bkt.Delete(replacingKey); err != nil { | |
| log.WithError(err).Warnf("Could not delete blob with key %#x", replacingKey) | |
| } | |
| return bkt.Put(newKey, encodedBlobSidecar) | |
| if len(replacingKey) != 0 { | |
| if err := bkt.Delete(replacingKey); err != nil { | |
| log.WithError(err).Warnf("Could not delete blob with key %#x", replacingKey) | |
| } | |
| } | |
| return bkt.Put(newKey, encodedBlobSidecar) |
This removes the duplicate line of code bkt.Put(newKey, encodedBlobSidecar)
beacon-chain/db/kv/blob.go
Outdated
| // If there is no element stored at blob.slot % MAX_SLOTS_TO_PERSIST_BLOBS, then we simply | ||
| // store the blob by key and exit early. | ||
| if len(replacingKey) == 0 { | ||
| return bkt.Put(newKey, encodedBlobSidecar) | ||
| } | ||
|
|
||
| if err := bkt.Delete(replacingKey); err != nil { | ||
| log.WithError(err).Warnf("Could not delete blob with key %#x", replacingKey) | ||
| } | ||
| return bkt.Put(newKey, encodedBlobSidecar) |
There was a problem hiding this comment.
| // If there is no element stored at blob.slot % MAX_SLOTS_TO_PERSIST_BLOBS, then we simply | |
| // store the blob by key and exit early. | |
| if len(replacingKey) == 0 { | |
| return bkt.Put(newKey, encodedBlobSidecar) | |
| } | |
| if err := bkt.Delete(replacingKey); err != nil { | |
| log.WithError(err).Warnf("Could not delete blob with key %#x", replacingKey) | |
| } | |
| return bkt.Put(newKey, encodedBlobSidecar) | |
| if len(replacingKey) != 0 { | |
| if err := bkt.Delete(replacingKey); err != nil { | |
| log.WithError(err).Warnf("Could not delete blob with key %#x", replacingKey) | |
| } | |
| } | |
| return bkt.Put(newKey, encodedBlobSidecar) |
This removes the duplicate line of code bkt.Put(newKey, encodedBlobSidecar)
| for k, v := c.First(); k != nil; k, v = c.Next() { | ||
| if bytes.HasSuffix(k, root[:]) { | ||
| enc = v | ||
| break | ||
| } | ||
| } |
There was a problem hiding this comment.
This is going to be an O(n) lookup because you have to search every value in the bucket. Consider indexing the block roots in another bucket or putting the block root at the start of the string. Searching by prefix ought to be O(logn) if bbolt uses binary search. I'm not totally sure about the implementation yet.
Also consider simply acknowledging this in the godoc since the bucket is bounded to a max size, it may never be much of an issue.
There was a problem hiding this comment.
I considered multiple options and decided to remain the same to keep the implementation simple. I added a benchmark 9bcd36a to prove in the absolute worst case, every slot has the blob over the last 18 days (highly unlikely), to traverse through the list to get the last blob is no more than 2.3ms. This is entirely acceptable given this is not used in the hot path:
BenchmarkStore_BlobSidecarsByRoot-10 925645 1182 ns/op. // first slot
BenchmarkStore_BlobSidecarsByRoot-10 496 2340958 ns/op. // last slot
There was a problem hiding this comment.
It could be much faster but I'm OK with accepting it like this. Thanks for writing the benchmark and demonstrating the worst case. We can monitor these spans as well to see how much of an impact it is.
beacon-chain/db/kv/blob.go
Outdated
| if err := s.db.View(func(tx *bolt.Tx) error { | ||
| c := tx.Bucket(blobsBucket).Cursor() | ||
| // Bucket size is bounded and bolt cursors are fast. Moreover, a thin caching layer can be added. | ||
| for k, v := c.First(); k != nil; k, v = c.Next() { |
There was a problem hiding this comment.
It may be faster to recompute the prefix bytes(slot_to_rotating_buffer(blob.slot)) and use a prefix scan in boltdb. This should be O(logn) if using binary search. Again, im not 100% sure it does use BS.
https://github.com/etcd-io/bbolt#prefix-scans
There was a problem hiding this comment.
There was a problem hiding this comment.
This is a good optimization. I used the suggestion bytes(slot_to_rotating_buffer(blob.slot)) here:
8315171
|
OK I was able to confirm that seek does use binary search so that would make prefix lookups much faster. https://github.com/etcd-io/bbolt/blob/8b1ee10512ccb01d9d8744a620afc12939d26fb2/cursor.go#LL273C1-L273C1 |
There was a problem hiding this comment.
Changes to this file are not Deneb-specific. I would open a separate PR targeting develop with these improvements.
There was a problem hiding this comment.
I think it's OK to tag alone test utility enhancement in the same PR, and it's nicer to review them in the PR so the reviews get the complete picture. Shout out to @kasey as the original author for these assertion improvements. I'll let him decide if he wants to open them against develop
| return s.storeValidatorEntriesSeparately(ctx, tx, validatorsEntries) | ||
| } | ||
|
|
||
| func getPhase0PbState(rawState interface{}) (*ethpb.BeaconState, error) { |
There was a problem hiding this comment.
Had to do these to fix cognitive complexity 80 of func (*Store).saveStatesEfficientInternal is high (> 65)
45809a8 to
9970071
Compare
| return err | ||
| } | ||
| case *ethpb.BeaconStateDeneb: | ||
| pbState, err := getDenebPbState(rawType) |
There was a problem hiding this comment.
There doesn't appear to be any unit tests for either the capella or deneb cases
| case version.Capella: | ||
| fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateCapellaFieldCount) | ||
| case version.Deneb: | ||
| fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateCapellaFieldCount) |
There was a problem hiding this comment.
Is this correct? shouldn't we have a beacon state field count specific to Deneb?
I've seen we use the Capella one in quite a few places.
config/params/config.go
Outdated
| @@ -104,6 +104,7 @@ type BeaconChainConfig struct { | |||
| MaxWithdrawalsPerPayload uint64 `yaml:"MAX_WITHDRAWALS_PER_PAYLOAD" spec:"true"` // MaxWithdrawalsPerPayload defines the maximum number of withdrawals in a block. | |||
| MaxBlsToExecutionChanges uint64 `yaml:"MAX_BLS_TO_EXECUTION_CHANGES" spec:"true"` // MaxBlsToExecutionChanges defines the maximum number of BLS-to-execution-change objects in a block. | |||
| MaxValidatorsPerWithdrawalsSweep uint64 `yaml:"MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP" spec:"true"` //MaxValidatorsPerWithdrawalsSweep bounds the size of the sweep searching for withdrawals per slot. | |||
There was a problem hiding this comment.
| MaxValidatorsPerWithdrawalsSweep uint64 `yaml:"MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP" spec:"true"` //MaxValidatorsPerWithdrawalsSweep bounds the size of the sweep searching for withdrawals per slot. | |
| MaxValidatorsPerWithdrawalsSweep uint64 `yaml:"MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP" spec:"true"` // MaxValidatorsPerWithdrawalsSweep bounds the size of the sweep searching for withdrawals per slot. |
I know this isn't part of your modifications but it's hard to ignore
|
One more validation for |
They have tests. See |
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Adding deneb db methods
New
BlobSidecarsByRootretrieve blobs by a given rootBlobSidecarsBySlotretrieve blobs by a given slotSaveBlobSidecarsave blobsDeleteBlobSidecardelete blotsNote: we don't have caches built in for blob as we want to avoid premature optimization
Modified
Misc