Skip to content

Commit 423eb5a

Browse files
authored
Use the latest computed power table instead of always ranging certs (#882)
Certificate store already keeps track of the latest power table, i.e., the power table of the next expected certificate. Use this pre-computed table whenever the next power table is asked for instead of re-computing it every time. This results in a much faster response to `GetPowerTable` for the most common case: getting the committee as part of monotonic progress of GPBFT instances. Fixes #881
1 parent ac4d25d commit 423eb5a

File tree

2 files changed

+40
-11
lines changed

2 files changed

+40
-11
lines changed

certstore/certstore.go

+25-7
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,31 @@ func (cs *Store) GetPowerTable(ctx context.Context, instance uint64) (gpbft.Powe
287287
return nil, fmt.Errorf("cannot return a power table before the first instance: %d", cs.firstInstance)
288288
}
289289

290-
lastPowerTable := cs.firstInstance
291-
if latestCert := cs.Latest(); latestCert != nil {
292-
lastPowerTable = latestCert.GPBFTInstance + 1
293-
}
294-
if instance > lastPowerTable {
295-
return nil, fmt.Errorf("cannot return future power table for instance %d > %d", instance, lastPowerTable)
296-
}
290+
// Copy a reference to both the latest cert and latest power table while holding
291+
// the lock to guarantee order in case the latest changes while the requested
292+
// power table is being retrieved.
293+
cs.mu.RLock()
294+
latestCert := cs.latestCertificate
295+
latestPowerTable := cs.latestPowerTable
296+
cs.mu.RUnlock()
297+
298+
nextCertInstance := cs.firstInstance
299+
if latestCert != nil {
300+
nextCertInstance = latestCert.GPBFTInstance + 1
301+
}
302+
if instance > nextCertInstance {
303+
return nil, fmt.Errorf("cannot return future power table for instance %d > %d", instance, nextCertInstance)
304+
}
305+
if instance == nextCertInstance && len(latestPowerTable) != 0 {
306+
// Note that the latestPowerTable may be nil/empty, which indicates the certstore
307+
// is being opened. Hence, the strict check for non-zero length.
308+
return latestPowerTable, nil
309+
}
310+
// Technically, it's possible to also optimize for the case where the instance is
311+
// equal to the latest certificate instance. This is by "subtracting" the latest
312+
// cert power table delta from the latest power table. But it's unclear if that
313+
// optimization is necessary. Observing the runtime metrics should make it clear
314+
// if it is. For now, YAGNI.
297315

298316
// We store every `powerTableFrequency` power tables. Find the nearest multiple smaller than
299317
// the requested instance.

certstore/certstore_test.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -409,20 +409,31 @@ func TestPowerNoData(t *testing.T) {
409409
t.Parallel()
410410
ctx := context.Background()
411411
ds := ds_sync.MutexWrap(datastore.NewMapDatastore())
412-
pt, _ := testPowerTable(20)
412+
pt, ptCid := testPowerTable(20)
413413

414414
cs, err := CreateStore(ctx, ds, 0, pt)
415415
require.NoError(t, err)
416416
cs.powerTableFrequency = 5
417-
// clobber the datastore.
417+
418+
// Add some certs to force GetPowerTable call to have to range certs.
419+
require.NoError(t, cs.Put(ctx, makeCert(0, gpbft.SupplementalData{PowerTable: ptCid})))
420+
require.NoError(t, cs.Put(ctx, makeCert(1, gpbft.SupplementalData{PowerTable: ptCid})))
421+
422+
// Clobber the datastore.
418423
cs.ds = ds_sync.MutexWrap(datastore.NewMapDatastore())
419424

420-
// Should fail to find any power tables.
425+
// Should fail to find any power table apart from next. Because, internally it is
426+
// cached.
421427
_, err = cs.GetPowerTable(ctx, 0)
422428
require.ErrorContains(t, err, "failed to load power table")
423429
_, err = cs.GetPowerTable(ctx, 1)
424-
require.ErrorContains(t, err, "cannot return future power table")
430+
require.ErrorContains(t, err, "failed to load power table")
431+
_, err = cs.GetPowerTable(ctx, 2)
432+
require.NoError(t, err)
425433

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

428439
func TestPowerEmptyPowerTable(t *testing.T) {

0 commit comments

Comments
 (0)