Skip to content

Deneb DB methods#12379

Merged
terencechain merged 9 commits intodeneb-integrationfrom
deneb-db
May 11, 2023
Merged

Deneb DB methods#12379
terencechain merged 9 commits intodeneb-integrationfrom
deneb-db

Conversation

@terencechain
Copy link
Collaborator

Adding deneb db methods

New

  • BlobSidecarsByRoot retrieve blobs by a given root
  • BlobSidecarsBySlot retrieve blobs by a given slot
  • SaveBlobSidecar save blobs
  • DeleteBlobSidecar delete blots

Note: we don't have caches built in for blob as we want to avoid premature optimization

Modified

  • Block with deneb support
  • State with deneb support

Misc

  • New config for max blobs per slot
  • New config for min epochs to service blobs sidecar
  • New test helpers on hydration methods for block and require pkg

@terencechain terencechain self-assigned this May 9, 2023
@terencechain terencechain requested a review from a team as a code owner May 9, 2023 23:36
@terencechain terencechain requested review from james-prysm, potuz and rkapka and removed request for a team May 9, 2023 23:36
Copy link
Member

@prestonvanloon prestonvanloon left a comment

Choose a reason for hiding this comment

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

Partial review with @saolyn. I'll review more later today.

if len(scs) == 0 {
return errors.New("nil or empty blob sidecars")
}
slot := scs[0].Slot
Copy link
Member

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added validations 8315171

Copy link
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Collaborator

Choose a reason for hiding this comment

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

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.

Comment on lines 55 to 64
// 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)
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
// 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)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines 55 to 64
// 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)
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
// 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)

Comment on lines +80 to +128
for k, v := c.First(); k != nil; k, v = c.Next() {
if bytes.HasSuffix(k, root[:]) {
enc = v
break
}
}
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Collaborator Author

@terencechain terencechain May 10, 2023

Choose a reason for hiding this comment

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

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

Copy link
Member

Choose a reason for hiding this comment

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

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.

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() {
Copy link
Member

Choose a reason for hiding this comment

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

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a good optimization. I used the suggestion bytes(slot_to_rotating_buffer(blob.slot)) here:
8315171

prestonvanloon

This comment was marked as duplicate.

prestonvanloon

This comment was marked as duplicate.

@prestonvanloon
Copy link
Member

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Changes to this file are not Deneb-specific. I would open a separate PR targeting develop with these improvements.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Had to do these to fix cognitive complexity 80 of func (*Store).saveStatesEfficientInternal is high (> 65)

return err
}
case *ethpb.BeaconStateDeneb:
pbState, err := getDenebPbState(rawType)
Copy link
Contributor

Choose a reason for hiding this comment

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

There doesn't appear to be any unit tests for either the capella or deneb cases

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

case version.Capella:
fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateCapellaFieldCount)
case version.Deneb:
fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateCapellaFieldCount)
Copy link
Contributor

@saolyn saolyn May 11, 2023

Choose a reason for hiding this comment

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

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rkapka
Copy link
Contributor

rkapka commented May 11, 2023

One more validation for verifySideCars: check that each index from 0 to len(scs) - 1 appears exactly once. And please add some unit tests for this function.

@terencechain
Copy link
Collaborator Author

terencechain commented May 11, 2023

One more validation for verifySideCars: check that each index from 0 to len(scs) - 1 appears exactly once. And please add some unit tests for this function.

They have tests. See TestStore_verifySideCars

terencechain added a commit that referenced this pull request May 26, 2023
terencechain added a commit that referenced this pull request May 31, 2023
terencechain added a commit that referenced this pull request Jun 7, 2023
terencechain added a commit that referenced this pull request Jun 12, 2023
terencechain added a commit that referenced this pull request Jun 12, 2023
terencechain added a commit that referenced this pull request Jun 16, 2023
terencechain added a commit that referenced this pull request Jun 27, 2023
terencechain added a commit that referenced this pull request Jul 7, 2023
terencechain added a commit that referenced this pull request Jul 9, 2023
terencechain added a commit that referenced this pull request Jul 10, 2023
kasey pushed a commit that referenced this pull request Jul 20, 2023
james-prysm pushed a commit that referenced this pull request Aug 4, 2023
terencechain added a commit that referenced this pull request Aug 16, 2023
kasey pushed a commit that referenced this pull request Aug 21, 2023
kasey pushed a commit that referenced this pull request Aug 22, 2023
kasey pushed a commit that referenced this pull request Aug 22, 2023
kasey pushed a commit that referenced this pull request Aug 22, 2023
kasey pushed a commit that referenced this pull request Aug 23, 2023
kasey pushed a commit that referenced this pull request Aug 23, 2023
kasey pushed a commit that referenced this pull request Aug 23, 2023
kasey pushed a commit that referenced this pull request Aug 24, 2023
kasey pushed a commit that referenced this pull request Aug 24, 2023
prestonvanloon pushed a commit that referenced this pull request Aug 24, 2023
prestonvanloon pushed a commit that referenced this pull request Aug 24, 2023
prestonvanloon pushed a commit that referenced this pull request Aug 24, 2023
prestonvanloon pushed a commit that referenced this pull request Aug 24, 2023
james-prysm pushed a commit that referenced this pull request Aug 25, 2023
prestonvanloon pushed a commit that referenced this pull request Aug 30, 2023
prestonvanloon pushed a commit that referenced this pull request Aug 30, 2023
prestonvanloon pushed a commit that referenced this pull request Aug 31, 2023
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.

5 participants