summaryrefslogtreecommitdiff
path: root/data/account/participationRegistry_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'data/account/participationRegistry_test.go')
-rw-r--r--data/account/participationRegistry_test.go769
1 files changed, 769 insertions, 0 deletions
diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go
new file mode 100644
index 000000000..d000f16cb
--- /dev/null
+++ b/data/account/participationRegistry_test.go
@@ -0,0 +1,769 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
+
+package account
+
+import (
+ "context"
+ "database/sql"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/algorand/go-algorand/util/db"
+)
+
+func getRegistry(t *testing.T) *participationDB {
+ rootDB, err := db.OpenPair(t.Name(), true)
+ require.NoError(t, err)
+
+ registry, err := makeParticipationRegistry(rootDB, logging.TestingLog(t))
+ require.NoError(t, err)
+ require.NotNil(t, registry)
+
+ return registry
+}
+
+func assertParticipation(t *testing.T, p Participation, pr ParticipationRecord) {
+ require.Equal(t, p.FirstValid, pr.FirstValid)
+ require.Equal(t, p.LastValid, pr.LastValid)
+ require.Equal(t, p.KeyDilution, pr.KeyDilution)
+ require.Equal(t, p.Parent, pr.Account)
+}
+
+func makeTestParticipation(addrID int, first, last basics.Round, dilution uint64) Participation {
+ p := Participation{
+ FirstValid: first,
+ LastValid: last,
+ KeyDilution: dilution,
+ Voting: &crypto.OneTimeSignatureSecrets{},
+ VRF: &crypto.VRFSecrets{},
+ }
+ binary.LittleEndian.PutUint32(p.Parent[:], uint32(addrID))
+ return p
+}
+
+func registryCloseTest(t *testing.T, registry *participationDB) {
+ start := time.Now()
+ registry.Close()
+ duration := time.Since(start)
+ assert.Less(t, uint64(duration), uint64(defaultTimeout))
+}
+
+// Insert participation records and make sure they can be fetched.
+func TestParticipation_InsertGet(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ p := makeTestParticipation(1, 1, 2, 3)
+ p2 := makeTestParticipation(2, 4, 5, 6)
+
+ insertAndVerify := func(part Participation) {
+ id, err := registry.Insert(part)
+ a.NoError(err)
+ a.Equal(part.ID(), id)
+
+ record := registry.Get(part.ID())
+ a.False(record.IsZero())
+ assertParticipation(t, part, record)
+ }
+
+ // Verify inserting some records.
+ insertAndVerify(p)
+ insertAndVerify(p2)
+
+ // Data should be available immediately
+ results := registry.GetAll()
+ a.Len(results, 2)
+ for _, record := range results {
+ if record.Account == p.Parent {
+ assertParticipation(t, p, record)
+ } else if record.Account == p2.Parent {
+ assertParticipation(t, p2, record)
+ } else {
+ a.Fail("unexpected account")
+ }
+ }
+
+ // Check that Flush works, re-initialize cache and verify GetAll.
+ a.NoError(registry.Flush(defaultTimeout))
+ a.NoError(registry.initializeCache())
+ results = registry.GetAll()
+ a.Len(results, 2)
+ for _, record := range results {
+ if record.Account == p.Parent {
+ assertParticipation(t, p, record)
+ } else if record.Account == p2.Parent {
+ assertParticipation(t, p2, record)
+ } else {
+ a.Fail("unexpected account")
+ }
+ }
+}
+
+// Make sure a record can be deleted by id.
+func TestParticipation_Delete(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ p := makeTestParticipation(1, 1, 2, 3)
+ p2 := makeTestParticipation(2, 4, 5, 6)
+
+ id, err := registry.Insert(p)
+ a.NoError(err)
+ a.Equal(p.ID(), id)
+
+ id, err = registry.Insert(p2)
+ a.NoError(err)
+ a.Equal(p2.ID(), id)
+
+ err = registry.Delete(p.ID())
+ a.NoError(err)
+
+ results := registry.GetAll()
+ a.Len(results, 1)
+ assertParticipation(t, p2, results[0])
+
+ // Check that result was persisted.
+ a.NoError(registry.Flush(defaultTimeout))
+ a.NoError(registry.initializeCache())
+ results = registry.GetAll()
+ a.Len(results, 1)
+ assertParticipation(t, p2, results[0])
+}
+
+func TestParticipation_DeleteExpired(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ for i := 10; i < 20; i++ {
+ p := makeTestParticipation(i, 1, basics.Round(i), 1)
+ id, err := registry.Insert(p)
+ a.NoError(err)
+ a.Equal(p.ID(), id)
+ }
+
+ err := registry.DeleteExpired(15)
+ a.NoError(err)
+
+ a.Len(registry.GetAll(), 5, "The first 5 should be deleted.")
+
+ // Check persisting. Verify by re-initializing the cache.
+ a.NoError(registry.Flush(defaultTimeout))
+ a.NoError(registry.initializeCache())
+ a.Len(registry.GetAll(), 5, "The first 5 should be deleted.")
+}
+
+// Make sure the register function properly sets effective first/last for all effected records.
+func TestParticipation_Register(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ // Overlapping keys.
+ p := makeTestParticipation(1, 250000, 3000000, 1)
+ p2 := makeTestParticipation(1, 200000, 4000000, 2)
+
+ id, err := registry.Insert(p)
+ a.NoError(err)
+ a.Equal(p.ID(), id)
+
+ id, err = registry.Insert(p2)
+ a.NoError(err)
+ a.Equal(p2.ID(), id)
+
+ verifyEffectiveRound := func(id ParticipationID, first, last int) {
+ record := registry.Get(id)
+ a.False(record.IsZero())
+ require.Equal(t, first, int(record.EffectiveFirst))
+ require.Equal(t, last, int(record.EffectiveLast))
+ }
+
+ // Register the first key.
+ err = registry.Register(p.ID(), 500000)
+ a.NoError(err)
+ verifyEffectiveRound(p.ID(), 500000, int(p.LastValid))
+
+ // Register second key.
+ err = registry.Register(p2.ID(), 2500000)
+ a.NoError(err)
+ verifyEffectiveRound(p.ID(), 500000, 2499999)
+ verifyEffectiveRound(p2.ID(), 2500000, int(p2.LastValid))
+}
+
+// Test error when registering a non-existing participation ID.
+func TestParticipation_RegisterInvalidID(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ p := makeTestParticipation(0, 250000, 3000000, 1)
+
+ err := registry.Register(p.ID(), 10000000)
+ a.EqualError(err, ErrParticipationIDNotFound.Error())
+}
+
+// Test error attempting to register a key with an invalid range.
+func TestParticipation_RegisterInvalidRange(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ p := makeTestParticipation(0, 250000, 3000000, 1)
+
+ id, err := registry.Insert(p)
+ a.NoError(err)
+ a.Equal(p.ID(), id)
+
+ // Register the first key.
+ err = registry.Register(p.ID(), 1000000000)
+ a.EqualError(err, ErrInvalidRegisterRange.Error())
+}
+
+// Test the recording function.
+func TestParticipation_Record(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ // Setup p
+ p := makeTestParticipation(1, 0, 3000000, 1)
+ // Setup some other keys to make sure they are not updated.
+ p2 := makeTestParticipation(2, 0, 3000000, 1)
+ p3 := makeTestParticipation(3, 0, 3000000, 1)
+
+ // Install and register all of the keys
+ for _, part := range []Participation{p, p2, p3} {
+ id, err := registry.Insert(part)
+ a.NoError(err)
+ a.Equal(part.ID(), id)
+ err = registry.Register(part.ID(), 0)
+ a.NoError(err)
+ }
+
+ a.NotNil(registry.GetAll())
+
+ a.NoError(registry.Record(p.Parent, 1000, Vote))
+ a.NoError(registry.Record(p.Parent, 2000, BlockProposal))
+ a.NoError(registry.Record(p.Parent, 3000, StateProof))
+
+ // Verify that one and only one key was updated.
+ test := func(registry ParticipationRegistry) {
+ records := registry.GetAll()
+ a.Len(records, 3)
+ for _, record := range records {
+ if record.ParticipationID == p.ID() {
+ require.Equal(t, 1000, int(record.LastVote))
+ require.Equal(t, 2000, int(record.LastBlockProposal))
+ require.Equal(t, 3000, int(record.LastStateProof))
+ } else {
+ require.Equal(t, 0, int(record.LastVote))
+ require.Equal(t, 0, int(record.LastBlockProposal))
+ require.Equal(t, 0, int(record.LastStateProof))
+ }
+ }
+ }
+
+ test(registry)
+ a.NoError(registry.Flush(defaultTimeout))
+ a.Len(registry.dirty, 0)
+
+ // Re-initialize
+ a.NoError(registry.initializeCache())
+ test(registry)
+}
+
+// Test that attempting to record an invalid action generates an error.
+func TestParticipation_RecordInvalidActionAndOutOfRange(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ p := makeTestParticipation(1, 0, 3000000, 1)
+ id, err := registry.Insert(p)
+ a.NoError(err)
+ err = registry.Register(id, 0)
+ a.NoError(err)
+
+ err = registry.Record(p.Parent, 0, ParticipationAction(9000))
+ a.EqualError(err, ErrUnknownParticipationAction.Error())
+
+ err = registry.Record(p.Parent, 3000000, ParticipationAction(9000))
+ a.EqualError(err, ErrUnknownParticipationAction.Error())
+
+ err = registry.Record(p.Parent, 3000001, ParticipationAction(9000))
+ a.EqualError(err, ErrActiveKeyNotFound.Error())
+}
+
+func TestParticipation_RecordNoKey(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ err := registry.Record(basics.Address{}, 0, Vote)
+ a.EqualError(err, ErrActiveKeyNotFound.Error())
+}
+
+// Test that an error is generated if the record function updates multiple records.
+// This would only happen if the DB was in an inconsistent state.
+func TestParticipation_RecordMultipleUpdates(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ // We'll test that recording at this round fails because both keys are active
+ testRound := basics.Round(5000)
+
+ p := makeTestParticipation(1, 0, 3000000, 1)
+ p2 := makeTestParticipation(1, 1, 3000000, 1)
+
+ _, err := registry.Insert(p)
+ a.NoError(err)
+ _, err = registry.Insert(p2)
+ a.NoError(err)
+ err = registry.Register(p.ID(), p.FirstValid)
+ a.NoError(err)
+
+ // Force the DB to have 2 active keys for one account by tampering with the private cache variable
+ recordCopy := registry.cache[p2.ID()]
+ recordCopy.EffectiveFirst = p2.FirstValid
+ recordCopy.EffectiveLast = p2.LastValid
+ registry.cache[p2.ID()] = recordCopy
+ registry.dirty[p2.ID()] = struct{}{}
+ a.NoError(registry.Flush(defaultTimeout))
+ a.Len(registry.dirty, 0)
+ a.NoError(registry.initializeCache())
+
+ // Verify bad state - both records are valid until round 3 million
+ a.NotEqual(p.ID(), p2.ID())
+ recordTest := make([]ParticipationRecord, 0)
+
+ recordP := registry.Get(p.ID())
+ a.False(recordP.IsZero())
+ recordTest = append(recordTest, recordP)
+
+ recordP2 := registry.Get(p2.ID())
+ a.False(recordP2.IsZero())
+ recordTest = append(recordTest, recordP2)
+
+ // Make sure both accounts are active for the test round
+ for _, record := range recordTest {
+ a.True(recordActive(record, testRound), "both records should be active")
+ }
+
+ err = registry.Record(p.Parent, testRound, Vote)
+ a.EqualError(err, ErrMultipleValidKeys.Error())
+}
+
+func TestParticipation_MultipleInsertError(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ p := makeTestParticipation(1, 1, 2, 3)
+
+ _, err := registry.Insert(p)
+ a.NoError(err)
+ _, err = registry.Insert(p)
+ a.Error(err, ErrAlreadyInserted.Error())
+}
+
+// This is a contrived test on every level. To workaround errors we setup the
+// DB and cache in ways that are impossible with public methods.
+//
+// Basically multiple records with the same ParticipationID are a big no-no and
+// it should be detected as quickly as possible.
+func TestParticipation_RecordMultipleUpdates_DB(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+
+ p := makeTestParticipation(1, 1, 2000000, 3)
+ id := p.ID()
+
+ // Insert the same record twice
+ // Pretty much copied from the Insert function without error checking.
+ err := registry.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
+ for i := 0; i < 2; i++ {
+ record := p
+ _, err := tx.Exec(
+ insertKeysetQuery,
+ id[:],
+ record.Parent[:],
+ record.FirstValid,
+ record.LastValid,
+ record.KeyDilution,
+ nil)
+ if err != nil {
+ return fmt.Errorf("unable to insert keyset: %w", err)
+ }
+
+ // Fetch primary key
+ var pk int
+ row := tx.QueryRow(selectLastPK, id[:])
+ err = row.Scan(&pk)
+ if err != nil {
+ return fmt.Errorf("unable to scan pk: %w", err)
+ }
+
+ // Create Rolling entry
+ _, err = tx.Exec(`INSERT INTO Rolling (pk, effectiveFirstRound, effectiveLastRound) VALUES (?, ?, ?)`, pk, 1, 200000)
+ if err != nil {
+ return fmt.Errorf("unable insert rolling: %w", err)
+ }
+
+ var num int
+ row = tx.QueryRow(`SELECT COUNT(*) FROM Keysets WHERE participationID=?`, id[:])
+ err = row.Scan(&num)
+ if err != nil {
+ return fmt.Errorf("unable to scan pk: %w", err)
+ }
+ }
+
+ return nil
+ })
+
+ a.NoError(err)
+
+ // Now that the DB has multiple records for one participation ID, check that all the methods notice.
+
+ // Initializing the cache
+ err = registry.initializeCache()
+ a.EqualError(err, ErrMultipleKeysForID.Error())
+
+ // Registering the ID - No error because it is already registered so we don't try to re-register.
+ registry.cache[id] = ParticipationRecord{
+ ParticipationID: id,
+ Account: p.Parent,
+ FirstValid: p.FirstValid,
+ LastValid: p.LastValid,
+ KeyDilution: p.KeyDilution,
+ EffectiveFirst: p.FirstValid,
+ EffectiveLast: p.LastValid,
+ }
+ err = registry.Register(id, 1)
+ a.NoError(err)
+
+ // Clear the first/last so that the no-op registration can't be detected
+ record := registry.cache[id]
+ record.EffectiveFirst = 0
+ record.EffectiveLast = 0
+ registry.cache[id] = record
+
+ err = registry.Register(id, 1)
+ a.NoError(err)
+ err = registry.Flush(defaultTimeout)
+ a.Error(err)
+ a.Contains(err.Error(), "unable to disable old key")
+ a.EqualError(errors.Unwrap(err), ErrMultipleKeysForID.Error())
+
+ // Flushing changes detects that multiple records are updated
+ registry.dirty[id] = struct{}{}
+ err = registry.Flush(defaultTimeout)
+ a.EqualError(err, ErrMultipleKeysForID.Error())
+ a.Len(registry.dirty, 1)
+
+ err = registry.Flush(defaultTimeout)
+ a.EqualError(err, ErrMultipleKeysForID.Error())
+
+ // Make sure the error message is logged when closing the registry.
+ var logOutput strings.Builder
+ registry.log.SetOutput(&logOutput)
+ registry.Close()
+ a.Contains(logOutput.String(), "participationDB unhandled error during Close/Flush")
+ a.Contains(logOutput.String(), ErrMultipleKeysForID.Error())
+}
+
+func TestParticipation_NoKeyToUpdate(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := assert.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ registry.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
+ record := ParticipationRecord{
+ ParticipationID: ParticipationID{},
+ Account: basics.Address{},
+ FirstValid: 1,
+ LastValid: 2,
+ KeyDilution: 3,
+ EffectiveFirst: 4,
+ EffectiveLast: 5,
+ }
+ err := updateRollingFields(ctx, tx, record)
+ a.EqualError(err, ErrNoKeyForID.Error())
+ return nil
+ })
+}
+
+// TestParticipion_Blobs adds some secrets to the registry and makes sure the same ones are returned.
+func TestParticipion_Blobs(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ access, err := db.MakeAccessor("writetest_root", false, true)
+ if err != nil {
+ panic(err)
+ }
+ root, err := GenerateRoot(access)
+ access.Close()
+ a.NoError(err)
+
+ access, err = db.MakeAccessor("writetest", false, true)
+ if err != nil {
+ panic(err)
+ }
+ part, err := FillDBWithParticipationKeys(access, root.Address(), 0, 101, config.Consensus[protocol.ConsensusCurrentVersion].DefaultKeyDilution)
+ access.Close()
+ a.NoError(err)
+
+ check := func(id ParticipationID) {
+ record := registry.Get(id)
+ a.NotEqual(ParticipationRecord{}, record)
+ a.Equal(id, record.ParticipationID)
+ a.Equal(part.VRF, record.VRF)
+ a.Equal(part.Voting.Snapshot(), record.Voting.Snapshot())
+ }
+
+ id, err := registry.Insert(part.Participation)
+ a.NoError(err)
+ a.NoError(registry.Flush(defaultTimeout))
+ a.Equal(id, part.ID())
+ // check the initial caching
+ check(id)
+
+ // check the re-initialized object
+ a.NoError(registry.initializeCache())
+ check(id)
+}
+
+// TestParticipion_EmptyBlobs makes sure empty blobs are set to nil
+func TestParticipion_EmptyBlobs(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := assert.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ access, err := db.MakeAccessor("writetest_root", false, true)
+ if err != nil {
+ panic(err)
+ }
+ root, err := GenerateRoot(access)
+ access.Close()
+ a.NoError(err)
+
+ access, err = db.MakeAccessor("writetest", false, true)
+ if err != nil {
+ panic(err)
+ }
+ part, err := FillDBWithParticipationKeys(access, root.Address(), 0, 101, config.Consensus[protocol.ConsensusCurrentVersion].DefaultKeyDilution)
+ access.Close()
+ a.NoError(err)
+ part.VRF = nil
+ part.Voting = nil
+
+ check := func(id ParticipationID) {
+ record := registry.Get(id)
+ a.NotEqual(ParticipationRecord{}, record)
+ a.Equal(id, record.ParticipationID)
+ a.True(record.VRF.MsgIsZero())
+ a.True(record.Voting.MsgIsZero())
+ }
+
+ id, err := registry.Insert(part.Participation)
+ a.NoError(err)
+ a.NoError(registry.Flush(defaultTimeout))
+ a.Equal(id, part.ID())
+ // check the initial caching
+ check(id)
+
+ // check the re-initialized object
+ a.NoError(registry.initializeCache())
+ check(id)
+}
+
+func TestRegisterUpdatedEvent(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := assert.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ p := makeTestParticipation(1, 1, 2, 3)
+ p2 := makeTestParticipation(2, 4, 5, 6)
+
+ id1, err := registry.Insert(p)
+ a.NoError(err)
+ a.Equal(p.ID(), id1)
+
+ id2, err := registry.Insert(p2)
+ a.NoError(err)
+ a.Equal(p2.ID(), id2)
+
+ record1 := registry.Get(id1)
+ a.False(record1.IsZero())
+ record2 := registry.Get(id2)
+ a.False(record2.IsZero())
+
+ // Delete the second one to make sure it can't be updated.
+ a.NoError(registry.Delete(id2))
+ a.NoError(registry.Flush(defaultTimeout))
+
+ // Ignore optional error
+ updates := make(map[ParticipationID]updatingParticipationRecord)
+ updates[id1] = updatingParticipationRecord{
+ ParticipationRecord: record1,
+ required: true,
+ }
+ updates[id2] = updatingParticipationRecord{
+ ParticipationRecord: record2,
+ required: false,
+ }
+
+ registry.writeQueue <- partDBWriteRecord{
+ registerUpdated: updates,
+ }
+
+ a.NoError(registry.Flush(defaultTimeout))
+
+ // This time, make it required and we should have an error
+ updates[id2] = updatingParticipationRecord{
+ ParticipationRecord: record2,
+ required: true,
+ }
+
+ registry.writeQueue <- partDBWriteRecord{
+ registerUpdated: updates,
+ }
+
+ err = registry.Flush(defaultTimeout)
+ a.Contains(err.Error(), "unable to disable old key when registering")
+ a.Contains(err.Error(), ErrNoKeyForID.Error())
+}
+
+// TestFlushDeadlock reproduced a deadlock when calling Flush repeatedly. This test reproduced the deadlock and
+// verifies the fix.
+func TestFlushDeadlock(t *testing.T) {
+ var wg sync.WaitGroup
+
+ partitiontest.PartitionTest(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ spam := func() {
+ defer wg.Done()
+ timeout := time.After(time.Second)
+ for {
+ select {
+ case <-timeout:
+ return
+ default:
+ // If there is a deadlock, this timeout will trigger.
+ assert.NoError(t, registry.Flush(2*time.Second))
+ }
+ }
+ }
+
+ // Start spammers.
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go spam()
+ }
+
+ wg.Wait()
+}
+
+func benchmarkKeyRegistration(numKeys int, b *testing.B) {
+ // setup
+ rootDB, err := db.OpenPair(b.Name(), true)
+ if err != nil {
+ b.Fail()
+ }
+ registry, err := makeParticipationRegistry(rootDB, logging.TestingLog(b))
+ if err != nil {
+ b.Fail()
+ }
+
+ // Insert records so that we can t
+ b.Run(fmt.Sprintf("KeyInsert_%d", numKeys), func(b *testing.B) {
+ for n := 0; n < b.N; n++ {
+ for key := 0; key < numKeys; key++ {
+ p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
+ registry.Insert(p)
+ }
+ }
+ })
+
+ // The first call to Register updates the DB.
+ b.Run(fmt.Sprintf("KeyRegistered_%d", numKeys), func(b *testing.B) {
+ for n := 0; n < b.N; n++ {
+ for key := 0; key < numKeys; key++ {
+ p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
+
+ // Unfortunately we need to repeatedly clear out the registration fields to ensure the
+ // db update runs each time this is called.
+ record := registry.cache[p.ID()]
+ record.EffectiveFirst = 0
+ record.EffectiveLast = 0
+ registry.cache[p.ID()] = record
+ registry.Register(p.ID(), 50)
+ }
+ }
+ })
+
+ // The keys should now be updated, so Register is a no-op.
+ b.Run(fmt.Sprintf("NoOp_%d", numKeys), func(b *testing.B) {
+ for n := 0; n < b.N; n++ {
+ for key := 0; key < numKeys; key++ {
+ p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
+ registry.Register(p.ID(), 50)
+ }
+ }
+ })
+}
+
+func BenchmarkKeyRegistration1(b *testing.B) { benchmarkKeyRegistration(1, b) }
+func BenchmarkKeyRegistration5(b *testing.B) { benchmarkKeyRegistration(5, b) }
+func BenchmarkKeyRegistration10(b *testing.B) { benchmarkKeyRegistration(10, b) }
+func BenchmarkKeyRegistration50(b *testing.B) { benchmarkKeyRegistration(50, b) }