From 6b348cbc1bbe1e1d8a282f40d02f1d06fa9dbb21 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 23 Nov 2021 09:46:06 -0500 Subject: [PATCH 1/6] WIP: append keys. --- data/account/participationRegistry.go | 46 ++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index 213e2be505..5645c794b2 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -157,6 +157,11 @@ type ParticipationRegistry interface { // Insert adds a record to storage and computes the ParticipationID Insert(record Participation) (ParticipationID, error) + // PKI TODO: use a real type instead of []byte + // AppendKeys appends state proof keys to an existing Participation record. Keys can only be appended + // once, an error + AppendKeys(id ParticipationID, keys map[uint64][]byte) error + // Delete removes a record from storage. Delete(id ParticipationID) error @@ -256,8 +261,9 @@ const ( key BLOB NOT NULL, --* msgpack encoding of ParticipationAccount.BlockProof.SignatureAlgorithm PRIMARY KEY (pk, round) )` - insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf) VALUES (?, ?, ?, ?, ?, ?)` - insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)` + insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf, stateProof) VALUES (?, ?, ?, ?, ?, ?, ?)` + insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)` + insertStateProofKeysQuery = `INSERT INTO StateProofKeys (pk, round, key) VALUES (?, ?, ?)` // SELECT pk FROM Keysets WHERE participationID = ? selectPK = `SELECT pk FROM Keysets WHERE participationID = ? LIMIT 1` @@ -332,6 +338,7 @@ type updatingParticipationRecord struct { type partDBWriteRecord struct { insertID ParticipationID insert Participation + keys map[uint64][]byte registerUpdated map[ParticipationID]updatingParticipationRecord @@ -380,7 +387,12 @@ func (db *participationDB) writeThread() { if len(wr.registerUpdated) != 0 { err = db.registerInner(wr.registerUpdated) } else if !wr.insertID.IsZero() { - err = db.insertInner(wr.insert, wr.insertID) + if wr.insert != (Participation{}) { + err = db.insertInner(wr.insert, wr.insertID) + } + if len(wr.keys) != 0 { + err = db.inertKeysInner(wr.insertID, wr.keys) + } } else if !wr.delete.IsZero() { err = db.deleteInner(wr.delete) } else if wr.flushResultChannel != nil { @@ -413,9 +425,9 @@ func verifyExecWithOneRowEffected(err error, result sql.Result, operationName st } func (db *participationDB) insertInner(record Participation, id ParticipationID) (err error) { - var rawVRF []byte var rawVoting []byte + var rawStateProof []byte if record.VRF != nil { rawVRF = protocol.Encode(record.VRF) @@ -424,6 +436,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID) voting := record.Voting.Snapshot() rawVoting = protocol.Encode(&voting) } + // PKI TODO: Extract state proof from record. err = db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { result, err := tx.Exec( @@ -433,7 +446,8 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID) record.FirstValid, record.LastValid, record.KeyDilution, - rawVRF) + rawVRF, + rawStateProof) if err := verifyExecWithOneRowEffected(err, result, "insert keyset"); err != nil { return err } @@ -453,6 +467,13 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID) return err } +func (db *participationDB) inertKeysInner(id ParticipationID, keys map[uint64][]byte) (err error) { + // PKI TODO: Insert the keys + err = db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + + }) +} + func (db *participationDB) registerInner(updated map[ParticipationID]updatingParticipationRecord) error { var cacheDeletes []ParticipationID err := db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { @@ -619,6 +640,21 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err return } +func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64][]byte) error { + db.mutex.Lock() + defer db.mutex.Unlock() + + if _, ok := db.cache[id]; ok { + return ErrParticipationIDNotFound + } + + db.writeQueue <- partDBWriteRecord{ + + } + + return nil +} + func (db *participationDB) Delete(id ParticipationID) error { db.mutex.Lock() defer db.mutex.Unlock() From 02fbb3dcab890c8fb4927f113b9125b53ede87f2 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 30 Nov 2021 16:52:45 -0500 Subject: [PATCH 2/6] Implement functions. --- data/account/participationRegistry.go | 119 +++++++++++++++--- .../participationRegistryBench_test.go | 64 ++++++++++ data/account/participationRegistry_test.go | 102 ++++++++------- go.mod | 2 +- go.sum | 2 + 5 files changed, 226 insertions(+), 63 deletions(-) create mode 100644 data/account/participationRegistryBench_test.go diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index 5645c794b2..0ecb93457c 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -82,6 +82,22 @@ type ParticipationRecord struct { Voting *crypto.OneTimeSignatureSecrets } +// StateProofKey is a placeholder for the real state proof key type. +// PKI TODO: Replace this with a real object. +type StateProofKey []byte + +// ParticipationRecordForRound adds in the per-round state proof key. +type ParticipationRecordForRound struct { + ParticipationRecord + + StateProof StateProofKey +} + +// IsZero returns true if the object contains zero values. +func (r ParticipationRecordForRound) IsZero() bool { + return r.StateProof == nil && r.ParticipationRecord.IsZero() +} + var zeroParticipationRecord = ParticipationRecord{} // IsZero returns true if the object contains zero values. @@ -152,15 +168,17 @@ var ErrMultipleKeysForID = errors.New("multiple valid keys found for the same pa // ErrNoKeyForID there may be cases where a key is deleted and used at the same time, so this error should be handled. var ErrNoKeyForID = errors.New("no valid key found for the participationID") +// ErrSecretNotFound is used when attempting to lookup secrets for a particular round. +var ErrSecretNotFound = errors.New("the participation ID did not have secrets for the requested round") + // ParticipationRegistry contain all functions for interacting with the Participation Registry. type ParticipationRegistry interface { // Insert adds a record to storage and computes the ParticipationID Insert(record Participation) (ParticipationID, error) - // PKI TODO: use a real type instead of []byte // AppendKeys appends state proof keys to an existing Participation record. Keys can only be appended // once, an error - AppendKeys(id ParticipationID, keys map[uint64][]byte) error + AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error // Delete removes a record from storage. Delete(id ParticipationID) error @@ -174,6 +192,9 @@ type ParticipationRegistry interface { // GetAll of the participation records. GetAll() []ParticipationRecord + // GetWithSecrets fetches a record with all secrets for a particular round. + GetWithSecrets(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) + // Register updates the EffectiveFirst and EffectiveLast fields. If there are multiple records for the account // then it is possible for multiple records to be updated. Register(id ParticipationID, on basics.Round) error @@ -263,7 +284,7 @@ const ( )` insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf, stateProof) VALUES (?, ?, ?, ?, ?, ?, ?)` insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)` - insertStateProofKeysQuery = `INSERT INTO StateProofKeys (pk, round, key) VALUES (?, ?, ?)` + appendStateProofKeysQuery = `INSERT INTO StateProofKeys (pk, round, key) VALUES(?, ?, ?)` // SELECT pk FROM Keysets WHERE participationID = ? selectPK = `SELECT pk FROM Keysets WHERE participationID = ? LIMIT 1` @@ -276,6 +297,10 @@ const ( FROM Keysets k INNER JOIN Rolling r ON k.pk = r.pk` + selectStateProofKeys = `SELECT s.key + FROM StateProofKeys s + WHERE round=? + AND pk IN (SELECT pk FROM Keysets WHERE participationID=?)` deleteKeysets = `DELETE FROM Keysets WHERE pk=?` deleteRolling = `DELETE FROM Rolling WHERE pk=?` updateRollingFieldsSQL = `UPDATE Rolling @@ -338,7 +363,7 @@ type updatingParticipationRecord struct { type partDBWriteRecord struct { insertID ParticipationID insert Participation - keys map[uint64][]byte + keys map[uint64]StateProofKey registerUpdated map[ParticipationID]updatingParticipationRecord @@ -391,7 +416,7 @@ func (db *participationDB) writeThread() { err = db.insertInner(wr.insert, wr.insertID) } if len(wr.keys) != 0 { - err = db.inertKeysInner(wr.insertID, wr.keys) + err = db.appendKeysInner(wr.insertID, wr.keys) } } else if !wr.delete.IsZero() { err = db.deleteInner(wr.delete) @@ -448,7 +473,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID) record.KeyDilution, rawVRF, rawStateProof) - if err := verifyExecWithOneRowEffected(err, result, "insert keyset"); err != nil { + if err = verifyExecWithOneRowEffected(err, result, "insert keyset"); err != nil { return err } pk, err := result.LastInsertId() @@ -458,7 +483,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID) // Create Rolling entry result, err = tx.Exec(insertRollingQuery, pk, rawVoting) - if err := verifyExecWithOneRowEffected(err, result, "insert rolling"); err != nil { + if err = verifyExecWithOneRowEffected(err, result, "insert rolling"); err != nil { return err } @@ -467,11 +492,35 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID) return err } -func (db *participationDB) inertKeysInner(id ParticipationID, keys map[uint64][]byte) (err error) { - // PKI TODO: Insert the keys - err = db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { +func (db *participationDB) appendKeysInner(id ParticipationID, keys map[uint64]StateProofKey) error { + err := db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + // Fetch primary key + var pk int + row := tx.QueryRow(selectPK, id[:]) + err := row.Scan(&pk) + if err == sql.ErrNoRows { + // nothing to do. + return nil + } + if err != nil { + return fmt.Errorf("unable to scan pk: %w", err) + } + stmt, err := tx.Prepare(appendStateProofKeysQuery) + if err != nil { + return fmt.Errorf("unable to prepare state proof insert: %w", err) + } + + for k, v := range keys { + result, err := stmt.Exec(pk, k, v) + if err = verifyExecWithOneRowEffected(err, result, "append keys"); err != nil { + return err + } + } + + return nil }) + return err } func (db *participationDB) registerInner(updated map[ParticipationID]updatingParticipationRecord) error { @@ -523,12 +572,12 @@ func (db *participationDB) deleteInner(id ParticipationID) error { // Delete rows result, err := tx.Exec(deleteKeysets, pk) - if err := verifyExecWithOneRowEffected(err, result, "delete keyset"); err != nil { + if err = verifyExecWithOneRowEffected(err, result, "delete keyset"); err != nil { return err } result, err = tx.Exec(deleteRolling, pk) - if err := verifyExecWithOneRowEffected(err, result, "delete rolling"); err != nil { + if err = verifyExecWithOneRowEffected(err, result, "delete rolling"); err != nil { return err } @@ -599,6 +648,8 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err id = record.ID() if _, ok := db.cache[id]; ok { + // PKI TODO: Add a special case to set the StateProof public key if it is in the input + // but not in the cache. return id, ErrAlreadyInserted } @@ -640,18 +691,27 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err return } -func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64][]byte) error { +func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error { db.mutex.Lock() defer db.mutex.Unlock() - if _, ok := db.cache[id]; ok { + if _, ok := db.cache[id]; !ok { return ErrParticipationIDNotFound } - db.writeQueue <- partDBWriteRecord{ + keyCopy := make(map[uint64]StateProofKey) + for k, v := range keys { + keyCopy[k] = v // PKI TODO: Deep copy? + } + // Write to the DB asynchronously. + db.writeQueue <- partDBWriteRecord{ + insertID: id, + keys: keyCopy, } + // Keys not stored in cache, no more work to do. + return nil } @@ -665,6 +725,7 @@ func (db *participationDB) Delete(id ParticipationID) error { } delete(db.dirty, id) delete(db.cache, id) + // do the db part async db.writeQueue <- partDBWriteRecord{ delete: id, @@ -806,6 +867,34 @@ func (db *participationDB) GetAll() []ParticipationRecord { return results } +// GetWithSecrets fetches a record with all secrets for a particular round. +func (db *participationDB) GetWithSecrets(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) { + var result ParticipationRecordForRound + result.ParticipationRecord = db.Get(id) + if result.ParticipationRecord.IsZero() { + return ParticipationRecordForRound{}, ErrParticipationIDNotFound + } + + err := db.store.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + row := tx.QueryRow(selectStateProofKeys, round, id[:]) + err := row.Scan(&result.StateProof) + if err == sql.ErrNoRows { + return ErrSecretNotFound + } + if err != nil { + return fmt.Errorf("error while querying secrets: %w", err) + } + + return nil + }) + + if err != nil { + return ParticipationRecordForRound{}, fmt.Errorf("unable to lookup secrets: %w", err) + } + + return result, nil +} + // updateRollingFields sets all of the rolling fields according to the record object. func updateRollingFields(ctx context.Context, tx *sql.Tx, record ParticipationRecord) error { result, err := tx.ExecContext(ctx, updateRollingFieldsSQL, diff --git a/data/account/participationRegistryBench_test.go b/data/account/participationRegistryBench_test.go new file mode 100644 index 0000000000..4490baf7eb --- /dev/null +++ b/data/account/participationRegistryBench_test.go @@ -0,0 +1,64 @@ +package account + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/util/db" +) + +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) } diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go index d000f16cbb..4a49104865 100644 --- a/data/account/participationRegistry_test.go +++ b/data/account/participationRegistry_test.go @@ -433,6 +433,7 @@ func TestParticipation_RecordMultipleUpdates_DB(t *testing.T) { record.FirstValid, record.LastValid, record.KeyDilution, + nil, nil) if err != nil { return fmt.Errorf("unable to insert keyset: %w", err) @@ -714,56 +715,63 @@ func TestFlushDeadlock(t *testing.T) { 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() +func TestAddStateProofKeys(t *testing.T) { + partitiontest.PartitionTest(t) + a := assert.New(t) + registry := getRegistry(t) + defer registryCloseTest(t, registry) + + // Install a key to add StateProof keys. + max := uint64(1000) + p := makeTestParticipation(1, 0, basics.Round(max), 3) + id, err := registry.Insert(p) + a.NoError(err) + a.Equal(p.ID(), id) + + // Wait for async DB operations to finish. + err = registry.Flush(10 * time.Second) + a.NoError(err) + + // Initialize keys array. + keys := make(map[uint64]StateProofKey) + for i := uint64(0); i <= max; i++ { + bs := make([]byte, 8) + binary.LittleEndian.PutUint64(bs, i) + keys[i] = bs } - // 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) - } - } - }) + err = registry.AppendKeys(id, keys) + a.NoError(err) - // 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) - } - } - }) + // Wait for async DB operations to finish. + err = registry.Flush(10 * time.Second) + a.NoError(err) - // 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) - } - } - }) + // Make sure we're able to fetch the same data that was put in. + for i := uint64(0); i <= max; i++ { + r, err := registry.GetWithSecrets(id, basics.Round(i)) + a.NoError(err) + a.Equal(keys[i], r.StateProof) + number := binary.LittleEndian.Uint64(r.StateProof) + a.Equal(i, number) + } } -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) } +func TestSecretNotFound(t *testing.T) { + partitiontest.PartitionTest(t) + a := assert.New(t) + registry := getRegistry(t) + defer registryCloseTest(t, registry) + + // Install a key for testing + p := makeTestParticipation(1, 0, 2, 3) + id, err := registry.Insert(p) + a.NoError(err) + a.Equal(p.ID(), id) + + r, err := registry.GetWithSecrets(id, basics.Round(100)) + + a.True(r.IsZero()) + a.Error(err) + a.ErrorIs(err, ErrSecretNotFound) +} \ No newline at end of file diff --git a/go.mod b/go.mod index 00a9719d63..8c97054261 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/net v0.0.0-20200904194848-62affa334b73 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect diff --git a/go.sum b/go.sum index 0ac28a9455..c037979898 100644 --- a/go.sum +++ b/go.sum @@ -142,6 +142,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= From 129e3a0d9b22bf8a20947c53a1ae2e900c3b8880 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 30 Nov 2021 16:54:55 -0500 Subject: [PATCH 3/6] Add missing license header. --- data/account/participationRegistryBench_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/data/account/participationRegistryBench_test.go b/data/account/participationRegistryBench_test.go index 4490baf7eb..d1a97d400b 100644 --- a/data/account/participationRegistryBench_test.go +++ b/data/account/participationRegistryBench_test.go @@ -1,3 +1,19 @@ +// 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 . + package account import ( From 26d2afa7cc952a4de3f93d8ac816e01d9dd56974 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 30 Nov 2021 21:50:39 -0500 Subject: [PATCH 4/6] PR Feedback + another test. --- data/account/participationRegistry.go | 7 ++--- data/account/participationRegistry_test.go | 32 +++++++++++++++++++++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index 0ecb93457c..8703cea21c 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -177,7 +177,7 @@ type ParticipationRegistry interface { Insert(record Participation) (ParticipationID, error) // AppendKeys appends state proof keys to an existing Participation record. Keys can only be appended - // once, an error + // once, an error will occur when the data is flushed when inserting a duplicate key. AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error // Delete removes a record from storage. @@ -414,8 +414,7 @@ func (db *participationDB) writeThread() { } else if !wr.insertID.IsZero() { if wr.insert != (Participation{}) { err = db.insertInner(wr.insert, wr.insertID) - } - if len(wr.keys) != 0 { + } else if len(wr.keys) != 0 { err = db.appendKeysInner(wr.insertID, wr.keys) } } else if !wr.delete.IsZero() { @@ -699,7 +698,7 @@ func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64]StateP return ErrParticipationIDNotFound } - keyCopy := make(map[uint64]StateProofKey) + keyCopy := make(map[uint64]StateProofKey, len(keys)) for k, v := range keys { keyCopy[k] = v // PKI TODO: Deep copy? } diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go index 4a49104865..77b76f2eb7 100644 --- a/data/account/participationRegistry_test.go +++ b/data/account/participationRegistry_test.go @@ -774,4 +774,34 @@ func TestSecretNotFound(t *testing.T) { a.True(r.IsZero()) a.Error(err) a.ErrorIs(err, ErrSecretNotFound) -} \ No newline at end of file +} + +func TestAddingSecretTwice(t *testing.T) { + partitiontest.PartitionTest(t) + a := assert.New(t) + registry := getRegistry(t) + defer registryCloseTest(t, registry) + + // Install a key for testing + p := makeTestParticipation(1, 0, 2, 3) + id, err := registry.Insert(p) + a.NoError(err) + a.Equal(p.ID(), id) + + // Append key + keys := make(map[uint64]StateProofKey) + bs := make([]byte, 8) + binary.LittleEndian.PutUint64(bs, 10) + keys[0] = bs + + err = registry.AppendKeys(id, keys) + a.NoError(err) + + // The error doesn't happen until the data persists. + err = registry.AppendKeys(id, keys) + a.NoError(err) + + err = registry.Flush(10 * time.Second) + a.Error(err) + a.EqualError(err, "unable to execute append keys: UNIQUE constraint failed: StateProofKeys.pk, StateProofKeys.round") +} From 3632133663e4347c3939f68ae26ea32f4d3c6888 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 1 Dec 2021 09:53:06 -0500 Subject: [PATCH 5/6] Minor cleanup. --- data/account/participationRegistry.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index 8703cea21c..d63e3ea497 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -297,7 +297,7 @@ const ( FROM Keysets k INNER JOIN Rolling r ON k.pk = r.pk` - selectStateProofKeys = `SELECT s.key + selectStateProofKeys = `SELECT s.key FROM StateProofKeys s WHERE round=? AND pk IN (SELECT pk FROM Keysets WHERE participationID=?)` @@ -703,14 +703,11 @@ func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64]StateP keyCopy[k] = v // PKI TODO: Deep copy? } - // Write to the DB asynchronously. + // Update the DB asynchronously. db.writeQueue <- partDBWriteRecord{ insertID: id, keys: keyCopy, } - - // Keys not stored in cache, no more work to do. - return nil } From 8ad0dd54342baa8928b45dfd6eade45cb3c593db Mon Sep 17 00:00:00 2001 From: Will Winder Date: Fri, 3 Dec 2021 15:46:49 -0500 Subject: [PATCH 6/6] Rename GetWithRound to GetForRound. --- data/account/participationRegistry.go | 8 ++++---- data/account/participationRegistry_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index d63e3ea497..a4aa0629af 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -192,8 +192,8 @@ type ParticipationRegistry interface { // GetAll of the participation records. GetAll() []ParticipationRecord - // GetWithSecrets fetches a record with all secrets for a particular round. - GetWithSecrets(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) + // GetForRound fetches a record with all secrets for a particular round. + GetForRound(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) // Register updates the EffectiveFirst and EffectiveLast fields. If there are multiple records for the account // then it is possible for multiple records to be updated. @@ -863,8 +863,8 @@ func (db *participationDB) GetAll() []ParticipationRecord { return results } -// GetWithSecrets fetches a record with all secrets for a particular round. -func (db *participationDB) GetWithSecrets(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) { +// GetForRound fetches a record with all secrets for a particular round. +func (db *participationDB) GetForRound(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) { var result ParticipationRecordForRound result.ParticipationRecord = db.Get(id) if result.ParticipationRecord.IsZero() { diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go index 77b76f2eb7..e960317e73 100644 --- a/data/account/participationRegistry_test.go +++ b/data/account/participationRegistry_test.go @@ -749,7 +749,7 @@ func TestAddStateProofKeys(t *testing.T) { // Make sure we're able to fetch the same data that was put in. for i := uint64(0); i <= max; i++ { - r, err := registry.GetWithSecrets(id, basics.Round(i)) + r, err := registry.GetForRound(id, basics.Round(i)) a.NoError(err) a.Equal(keys[i], r.StateProof) number := binary.LittleEndian.Uint64(r.StateProof) @@ -769,7 +769,7 @@ func TestSecretNotFound(t *testing.T) { a.NoError(err) a.Equal(p.ID(), id) - r, err := registry.GetWithSecrets(id, basics.Round(100)) + r, err := registry.GetForRound(id, basics.Round(100)) a.True(r.IsZero()) a.Error(err)