@@ -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+
85101var 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.
153169var 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.
156175type 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 {
332363type 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
415451func (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+
456525func (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+
622714func (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.
774895func updateRollingFields (ctx context.Context , tx * sql.Tx , record ParticipationRecord ) error {
775896 result , err := tx .ExecContext (ctx , updateRollingFieldsSQL ,
0 commit comments