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

Use the latest computed power table instead of always ranging certs #882

Merged
merged 1 commit into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions certstore/certstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,31 @@ func (cs *Store) GetPowerTable(ctx context.Context, instance uint64) (gpbft.Powe
return nil, fmt.Errorf("cannot return a power table before the first instance: %d", cs.firstInstance)
}

lastPowerTable := cs.firstInstance
if latestCert := cs.Latest(); latestCert != nil {
lastPowerTable = latestCert.GPBFTInstance + 1
}
if instance > lastPowerTable {
return nil, fmt.Errorf("cannot return future power table for instance %d > %d", instance, lastPowerTable)
}
// Copy a reference to both the latest cert and latest power table while holding
// the lock to guarantee order in case the latest changes while the requested
// power table is being retrieved.
cs.mu.RLock()
latestCert := cs.latestCertificate
latestPowerTable := cs.latestPowerTable
cs.mu.RUnlock()

nextCertInstance := cs.firstInstance
if latestCert != nil {
nextCertInstance = latestCert.GPBFTInstance + 1
}
if instance > nextCertInstance {
return nil, fmt.Errorf("cannot return future power table for instance %d > %d", instance, nextCertInstance)
}
if instance == nextCertInstance && len(latestPowerTable) != 0 {
Copy link
Member

Choose a reason for hiding this comment

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

Instead of checking len(latestPowerTable), can we check latestCert != nil? I guess that won't work for instance 0... meh, this works fine.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it would be problematic for firstInstance. An alternative is to explicitly check if the instance is the same as firstInstance. But that makes up a more complicated logic.

// Note that the latestPowerTable may be nil/empty, which indicates the certstore
// is being opened. Hence, the strict check for non-zero length.
return latestPowerTable, nil
}
// Technically, it's possible to also optimize for the case where the instance is
// equal to the latest certificate instance. This is by "subtracting" the latest
// cert power table delta from the latest power table. But it's unclear if that
// optimization is necessary. Observing the runtime metrics should make it clear
// if it is. For now, YAGNI.

// We store every `powerTableFrequency` power tables. Find the nearest multiple smaller than
// the requested instance.
Expand Down
19 changes: 15 additions & 4 deletions certstore/certstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,20 +409,31 @@ func TestPowerNoData(t *testing.T) {
t.Parallel()
ctx := context.Background()
ds := ds_sync.MutexWrap(datastore.NewMapDatastore())
pt, _ := testPowerTable(20)
pt, ptCid := testPowerTable(20)

cs, err := CreateStore(ctx, ds, 0, pt)
require.NoError(t, err)
cs.powerTableFrequency = 5
// clobber the datastore.

// Add some certs to force GetPowerTable call to have to range certs.
require.NoError(t, cs.Put(ctx, makeCert(0, gpbft.SupplementalData{PowerTable: ptCid})))
require.NoError(t, cs.Put(ctx, makeCert(1, gpbft.SupplementalData{PowerTable: ptCid})))

// Clobber the datastore.
cs.ds = ds_sync.MutexWrap(datastore.NewMapDatastore())

// Should fail to find any power tables.
// Should fail to find any power table apart from next. Because, internally it is
// cached.
_, err = cs.GetPowerTable(ctx, 0)
require.ErrorContains(t, err, "failed to load power table")
_, err = cs.GetPowerTable(ctx, 1)
require.ErrorContains(t, err, "cannot return future power table")
require.ErrorContains(t, err, "failed to load power table")
_, err = cs.GetPowerTable(ctx, 2)
require.NoError(t, err)

// Should fail to find power tables that are beyond the next expected instance.
_, err = cs.GetPowerTable(ctx, 3)
require.ErrorContains(t, err, "cannot return future power table")
}

func TestPowerEmptyPowerTable(t *testing.T) {
Expand Down
Loading