Skip to content

Commit 850f7c7

Browse files
authored
ParticipationRegistry - StateProof loading methods (#3261)
## Summary Add ParticipationRegistry methods for setting and retrieving state proof keys. Since they aren't in master yet there is a `type StateProofKey []byte` stub which will need to be updated later. ## Test Plan New unit tests.
1 parent 70ff3c7 commit 850f7c7

File tree

5 files changed

+297
-56
lines changed

5 files changed

+297
-56
lines changed

data/account/participationRegistry.go

Lines changed: 130 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ type ParticipationRecord struct {
8282
Voting *crypto.OneTimeSignatureSecrets
8383
}
8484

85+
// StateProofKey is a placeholder for the real state proof key type.
86+
// PKI TODO: Replace this with a real object.
87+
type StateProofKey []byte
88+
89+
// ParticipationRecordForRound adds in the per-round state proof key.
90+
type ParticipationRecordForRound struct {
91+
ParticipationRecord
92+
93+
StateProof StateProofKey
94+
}
95+
96+
// IsZero returns true if the object contains zero values.
97+
func (r ParticipationRecordForRound) IsZero() bool {
98+
return r.StateProof == nil && r.ParticipationRecord.IsZero()
99+
}
100+
85101
var zeroParticipationRecord = ParticipationRecord{}
86102

87103
// IsZero returns true if the object contains zero values.
@@ -152,11 +168,18 @@ var ErrMultipleKeysForID = errors.New("multiple valid keys found for the same pa
152168
// ErrNoKeyForID there may be cases where a key is deleted and used at the same time, so this error should be handled.
153169
var ErrNoKeyForID = errors.New("no valid key found for the participationID")
154170

171+
// ErrSecretNotFound is used when attempting to lookup secrets for a particular round.
172+
var ErrSecretNotFound = errors.New("the participation ID did not have secrets for the requested round")
173+
155174
// ParticipationRegistry contain all functions for interacting with the Participation Registry.
156175
type ParticipationRegistry interface {
157176
// Insert adds a record to storage and computes the ParticipationID
158177
Insert(record Participation) (ParticipationID, error)
159178

179+
// AppendKeys appends state proof keys to an existing Participation record. Keys can only be appended
180+
// once, an error will occur when the data is flushed when inserting a duplicate key.
181+
AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error
182+
160183
// Delete removes a record from storage.
161184
Delete(id ParticipationID) error
162185

@@ -169,6 +192,9 @@ type ParticipationRegistry interface {
169192
// GetAll of the participation records.
170193
GetAll() []ParticipationRecord
171194

195+
// GetForRound fetches a record with all secrets for a particular round.
196+
GetForRound(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error)
197+
172198
// Register updates the EffectiveFirst and EffectiveLast fields. If there are multiple records for the account
173199
// then it is possible for multiple records to be updated.
174200
Register(id ParticipationID, on basics.Round) error
@@ -256,8 +282,9 @@ const (
256282
key BLOB NOT NULL, --* msgpack encoding of ParticipationAccount.BlockProof.SignatureAlgorithm
257283
PRIMARY KEY (pk, round)
258284
)`
259-
insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf) VALUES (?, ?, ?, ?, ?, ?)`
260-
insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)`
285+
insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf, stateProof) VALUES (?, ?, ?, ?, ?, ?, ?)`
286+
insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)`
287+
appendStateProofKeysQuery = `INSERT INTO StateProofKeys (pk, round, key) VALUES(?, ?, ?)`
261288

262289
// SELECT pk FROM Keysets WHERE participationID = ?
263290
selectPK = `SELECT pk FROM Keysets WHERE participationID = ? LIMIT 1`
@@ -270,6 +297,10 @@ const (
270297
FROM Keysets k
271298
INNER JOIN Rolling r
272299
ON k.pk = r.pk`
300+
selectStateProofKeys = `SELECT s.key
301+
FROM StateProofKeys s
302+
WHERE round=?
303+
AND pk IN (SELECT pk FROM Keysets WHERE participationID=?)`
273304
deleteKeysets = `DELETE FROM Keysets WHERE pk=?`
274305
deleteRolling = `DELETE FROM Rolling WHERE pk=?`
275306
updateRollingFieldsSQL = `UPDATE Rolling
@@ -332,6 +363,7 @@ type updatingParticipationRecord struct {
332363
type partDBWriteRecord struct {
333364
insertID ParticipationID
334365
insert Participation
366+
keys map[uint64]StateProofKey
335367

336368
registerUpdated map[ParticipationID]updatingParticipationRecord
337369

@@ -380,7 +412,11 @@ func (db *participationDB) writeThread() {
380412
if len(wr.registerUpdated) != 0 {
381413
err = db.registerInner(wr.registerUpdated)
382414
} else if !wr.insertID.IsZero() {
383-
err = db.insertInner(wr.insert, wr.insertID)
415+
if wr.insert != (Participation{}) {
416+
err = db.insertInner(wr.insert, wr.insertID)
417+
} else if len(wr.keys) != 0 {
418+
err = db.appendKeysInner(wr.insertID, wr.keys)
419+
}
384420
} else if !wr.delete.IsZero() {
385421
err = db.deleteInner(wr.delete)
386422
} else if wr.flushResultChannel != nil {
@@ -413,9 +449,9 @@ func verifyExecWithOneRowEffected(err error, result sql.Result, operationName st
413449
}
414450

415451
func (db *participationDB) insertInner(record Participation, id ParticipationID) (err error) {
416-
417452
var rawVRF []byte
418453
var rawVoting []byte
454+
var rawStateProof []byte
419455

420456
if record.VRF != nil {
421457
rawVRF = protocol.Encode(record.VRF)
@@ -424,6 +460,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
424460
voting := record.Voting.Snapshot()
425461
rawVoting = protocol.Encode(&voting)
426462
}
463+
// PKI TODO: Extract state proof from record.
427464

428465
err = db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
429466
result, err := tx.Exec(
@@ -433,8 +470,9 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
433470
record.FirstValid,
434471
record.LastValid,
435472
record.KeyDilution,
436-
rawVRF)
437-
if err := verifyExecWithOneRowEffected(err, result, "insert keyset"); err != nil {
473+
rawVRF,
474+
rawStateProof)
475+
if err = verifyExecWithOneRowEffected(err, result, "insert keyset"); err != nil {
438476
return err
439477
}
440478
pk, err := result.LastInsertId()
@@ -444,7 +482,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
444482

445483
// Create Rolling entry
446484
result, err = tx.Exec(insertRollingQuery, pk, rawVoting)
447-
if err := verifyExecWithOneRowEffected(err, result, "insert rolling"); err != nil {
485+
if err = verifyExecWithOneRowEffected(err, result, "insert rolling"); err != nil {
448486
return err
449487
}
450488

@@ -453,6 +491,37 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
453491
return err
454492
}
455493

494+
func (db *participationDB) appendKeysInner(id ParticipationID, keys map[uint64]StateProofKey) error {
495+
err := db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
496+
// Fetch primary key
497+
var pk int
498+
row := tx.QueryRow(selectPK, id[:])
499+
err := row.Scan(&pk)
500+
if err == sql.ErrNoRows {
501+
// nothing to do.
502+
return nil
503+
}
504+
if err != nil {
505+
return fmt.Errorf("unable to scan pk: %w", err)
506+
}
507+
508+
stmt, err := tx.Prepare(appendStateProofKeysQuery)
509+
if err != nil {
510+
return fmt.Errorf("unable to prepare state proof insert: %w", err)
511+
}
512+
513+
for k, v := range keys {
514+
result, err := stmt.Exec(pk, k, v)
515+
if err = verifyExecWithOneRowEffected(err, result, "append keys"); err != nil {
516+
return err
517+
}
518+
}
519+
520+
return nil
521+
})
522+
return err
523+
}
524+
456525
func (db *participationDB) registerInner(updated map[ParticipationID]updatingParticipationRecord) error {
457526
var cacheDeletes []ParticipationID
458527
err := db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
@@ -502,12 +571,12 @@ func (db *participationDB) deleteInner(id ParticipationID) error {
502571

503572
// Delete rows
504573
result, err := tx.Exec(deleteKeysets, pk)
505-
if err := verifyExecWithOneRowEffected(err, result, "delete keyset"); err != nil {
574+
if err = verifyExecWithOneRowEffected(err, result, "delete keyset"); err != nil {
506575
return err
507576
}
508577

509578
result, err = tx.Exec(deleteRolling, pk)
510-
if err := verifyExecWithOneRowEffected(err, result, "delete rolling"); err != nil {
579+
if err = verifyExecWithOneRowEffected(err, result, "delete rolling"); err != nil {
511580
return err
512581
}
513582

@@ -578,6 +647,8 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err
578647

579648
id = record.ID()
580649
if _, ok := db.cache[id]; ok {
650+
// PKI TODO: Add a special case to set the StateProof public key if it is in the input
651+
// but not in the cache.
581652
return id, ErrAlreadyInserted
582653
}
583654

@@ -619,6 +690,27 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err
619690
return
620691
}
621692

693+
func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error {
694+
db.mutex.Lock()
695+
defer db.mutex.Unlock()
696+
697+
if _, ok := db.cache[id]; !ok {
698+
return ErrParticipationIDNotFound
699+
}
700+
701+
keyCopy := make(map[uint64]StateProofKey, len(keys))
702+
for k, v := range keys {
703+
keyCopy[k] = v // PKI TODO: Deep copy?
704+
}
705+
706+
// Update the DB asynchronously.
707+
db.writeQueue <- partDBWriteRecord{
708+
insertID: id,
709+
keys: keyCopy,
710+
}
711+
return nil
712+
}
713+
622714
func (db *participationDB) Delete(id ParticipationID) error {
623715
db.mutex.Lock()
624716
defer db.mutex.Unlock()
@@ -629,6 +721,7 @@ func (db *participationDB) Delete(id ParticipationID) error {
629721
}
630722
delete(db.dirty, id)
631723
delete(db.cache, id)
724+
632725
// do the db part async
633726
db.writeQueue <- partDBWriteRecord{
634727
delete: id,
@@ -770,6 +863,34 @@ func (db *participationDB) GetAll() []ParticipationRecord {
770863
return results
771864
}
772865

866+
// GetForRound fetches a record with all secrets for a particular round.
867+
func (db *participationDB) GetForRound(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) {
868+
var result ParticipationRecordForRound
869+
result.ParticipationRecord = db.Get(id)
870+
if result.ParticipationRecord.IsZero() {
871+
return ParticipationRecordForRound{}, ErrParticipationIDNotFound
872+
}
873+
874+
err := db.store.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
875+
row := tx.QueryRow(selectStateProofKeys, round, id[:])
876+
err := row.Scan(&result.StateProof)
877+
if err == sql.ErrNoRows {
878+
return ErrSecretNotFound
879+
}
880+
if err != nil {
881+
return fmt.Errorf("error while querying secrets: %w", err)
882+
}
883+
884+
return nil
885+
})
886+
887+
if err != nil {
888+
return ParticipationRecordForRound{}, fmt.Errorf("unable to lookup secrets: %w", err)
889+
}
890+
891+
return result, nil
892+
}
893+
773894
// updateRollingFields sets all of the rolling fields according to the record object.
774895
func updateRollingFields(ctx context.Context, tx *sql.Tx, record ParticipationRecord) error {
775896
result, err := tx.ExecContext(ctx, updateRollingFieldsSQL,
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (C) 2019-2021 Algorand, Inc.
2+
// This file is part of go-algorand
3+
//
4+
// go-algorand is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as
6+
// published by the Free Software Foundation, either version 3 of the
7+
// License, or (at your option) any later version.
8+
//
9+
// go-algorand is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package account
18+
19+
import (
20+
"fmt"
21+
"testing"
22+
23+
"github.com/algorand/go-algorand/data/basics"
24+
"github.com/algorand/go-algorand/logging"
25+
"github.com/algorand/go-algorand/util/db"
26+
)
27+
28+
func benchmarkKeyRegistration(numKeys int, b *testing.B) {
29+
// setup
30+
rootDB, err := db.OpenPair(b.Name(), true)
31+
if err != nil {
32+
b.Fail()
33+
}
34+
registry, err := makeParticipationRegistry(rootDB, logging.TestingLog(b))
35+
if err != nil {
36+
b.Fail()
37+
}
38+
39+
// Insert records so that we can t
40+
b.Run(fmt.Sprintf("KeyInsert_%d", numKeys), func(b *testing.B) {
41+
for n := 0; n < b.N; n++ {
42+
for key := 0; key < numKeys; key++ {
43+
p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
44+
registry.Insert(p)
45+
}
46+
}
47+
})
48+
49+
// The first call to Register updates the DB.
50+
b.Run(fmt.Sprintf("KeyRegistered_%d", numKeys), func(b *testing.B) {
51+
for n := 0; n < b.N; n++ {
52+
for key := 0; key < numKeys; key++ {
53+
p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
54+
55+
// Unfortunately we need to repeatedly clear out the registration fields to ensure the
56+
// db update runs each time this is called.
57+
record := registry.cache[p.ID()]
58+
record.EffectiveFirst = 0
59+
record.EffectiveLast = 0
60+
registry.cache[p.ID()] = record
61+
registry.Register(p.ID(), 50)
62+
}
63+
}
64+
})
65+
66+
// The keys should now be updated, so Register is a no-op.
67+
b.Run(fmt.Sprintf("NoOp_%d", numKeys), func(b *testing.B) {
68+
for n := 0; n < b.N; n++ {
69+
for key := 0; key < numKeys; key++ {
70+
p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
71+
registry.Register(p.ID(), 50)
72+
}
73+
}
74+
})
75+
}
76+
77+
func BenchmarkKeyRegistration1(b *testing.B) { benchmarkKeyRegistration(1, b) }
78+
func BenchmarkKeyRegistration5(b *testing.B) { benchmarkKeyRegistration(5, b) }
79+
func BenchmarkKeyRegistration10(b *testing.B) { benchmarkKeyRegistration(10, b) }
80+
func BenchmarkKeyRegistration50(b *testing.B) { benchmarkKeyRegistration(50, b) }

0 commit comments

Comments
 (0)