Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/account/participation.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (part Participation) ID() ParticipationID {
copy(idData.VoteID[:], part.Voting.OneTimeSignatureVerifier[:])
}

return ParticipationID(crypto.HashObj(&idData))
return idData.ID()
}

// PersistedParticipation encapsulates the static state of the participation
Expand Down
122 changes: 99 additions & 23 deletions data/account/participationRegistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"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/util/db"
)

Expand All @@ -42,6 +43,7 @@ func (pid ParticipationID) IsZero() bool {
return (crypto.Digest(pid)).IsZero()
}

// String prints a b32 version of this ID.
func (pid ParticipationID) String() string {
return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(pid[:])
}
Expand Down Expand Up @@ -75,8 +77,8 @@ type ParticipationRecord struct {
EffectiveFirst basics.Round
EffectiveLast basics.Round

// VRFSecrets
// OneTimeSignatureSecrets
VRF *crypto.VRFSecrets
Voting *crypto.OneTimeSignatureSecrets
}

var zeroParticipationRecord = ParticipationRecord{}
Expand All @@ -88,6 +90,16 @@ func (r ParticipationRecord) IsZero() bool {

// Duplicate creates a copy of the current object. This is required once secrets are stored.
func (r ParticipationRecord) Duplicate() ParticipationRecord {
var vrf crypto.VRFSecrets
if r.VRF != nil {
copy(vrf.SK[:], r.VRF.SK[:])
copy(vrf.PK[:], r.VRF.PK[:])
}

var voting crypto.OneTimeSignatureSecrets
if r.Voting != nil {
voting = r.Voting.Snapshot()
}
return ParticipationRecord{
ParticipationID: r.ParticipationID,
Account: r.Account,
Expand All @@ -99,6 +111,8 @@ func (r ParticipationRecord) Duplicate() ParticipationRecord {
LastCompactCertificate: r.LastCompactCertificate,
EffectiveFirst: r.EffectiveFirst,
EffectiveLast: r.EffectiveLast,
VRF: &vrf,
Voting: &voting,
}
}

Expand Down Expand Up @@ -221,9 +235,9 @@ var (

firstValidRound INTEGER NOT NULL DEFAULT 0,
lastValidRound INTEGER NOT NULL DEFAULT 0,
keyDilution INTEGER NOT NULL DEFAULT 0
keyDilution INTEGER NOT NULL DEFAULT 0,

-- vrf BLOB, --* msgpack encoding of ParticipationAccount.vrf
vrf BLOB --* msgpack encoding of ParticipationAccount.vrf
)`
createRolling = `CREATE TABLE Rolling (
pk INTEGER PRIMARY KEY NOT NULL,
Expand All @@ -232,23 +246,34 @@ var (
lastBlockProposalRound INTEGER NOT NULL DEFAULT 0,
lastCompactCertificateRound INTEGER NOT NULL DEFAULT 0,
effectiveFirstRound INTEGER NOT NULL DEFAULT 0,
effectiveLastRound INTEGER NOT NULL DEFAULT 0
effectiveLastRound INTEGER NOT NULL DEFAULT 0,

-- voting BLOB, --* msgpack encoding of ParticipationAccount.voting
voting BLOB --* msgpack encoding of ParticipationAccount.voting

-- blockProof BLOB --* msgpack encoding of ParticipationAccount.BlockProof
)`
insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution) VALUES (?, ?, ?, ?, ?)`
insertRollingQuery = `INSERT INTO Rolling (pk) VALUES (?)`

/*
createBlockProof = `CREATE TABLE BlockProofKeys (
id INTEGER PRIMARY KEY,
round INTEGER, --* committed round for this key
key BLOB --* msgpack encoding of ParticipationAccount.BlockProof.SignatureAlgorithm
)`
*/
insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf) VALUES (?, ?, ?, ?, ?, ?)`
insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)`

// SELECT pk FROM Keysets WHERE participationID = ?
selectPK = `SELECT pk FROM Keysets WHERE participationID = ? LIMIT 1`
selectLastPK = `SELECT pk FROM Keysets ORDER BY pk DESC LIMIT 1`
selectRecords = `SELECT
participationID, account, firstValidRound, lastValidRound, keyDilution,
lastVoteRound, lastBlockProposalRound, lastCompactCertificateRound,
effectiveFirstRound, effectiveLastRound
FROM Keysets
INNER JOIN Rolling
ON Keysets.pk = Rolling.pk`
k.participationID, k.account, k.firstValidRound,
k.lastValidRound, k.keyDilution, k.vrf,
r.lastVoteRound, r.lastBlockProposalRound, r.lastCompactCertificateRound,
r.effectiveFirstRound, r.effectiveLastRound, r.voting
FROM Keysets k
INNER JOIN Rolling r
ON k.pk = r.pk`
deleteKeysets = `DELETE FROM Keysets WHERE pk=?`
deleteRolling = `DELETE FROM Rolling WHERE pk=?`
updateRollingFieldsSQL = `UPDATE Rolling
Expand Down Expand Up @@ -381,15 +406,29 @@ func (db *participationDB) writeThread() {
}
}
}

func (db *participationDB) insertInner(record Participation, id ParticipationID) (err error) {

var rawVRF []byte
var rawVoting []byte

if record.VRF != nil {
rawVRF = protocol.Encode(record.VRF)
}
if record.Voting != nil {
voting := record.Voting.Snapshot()
rawVoting = protocol.Encode(&voting)
}

err = db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
result, err := tx.Exec(
insertKeysetQuery,
id[:],
record.Parent[:],
record.FirstValid,
record.LastValid,
record.KeyDilution)
record.KeyDilution,
rawVRF)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that you're storing nil value directly into the database here. This isn't strictly wrong - but it might require you to work abit harder on the query side ( i.e. sql.NullString ). Please make sure you have at least a single unit test to verify the correctness of this code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out you don't need special handling here because the type is []byte, not string. So nil checking is done in the normal way by looking at len(vrf) and len(voting).

There is a unit test called TestParticipation_EmptyBlobs that is supposed to cover that case. I just used the debugger to double check and can confirm that the query returns nil when scanning results and it is handled as expected.

if err != nil {
return fmt.Errorf("unable to insert keyset: %w", err)
}
Expand All @@ -406,7 +445,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
}

// Create Rolling entry
result, err = tx.Exec(insertRollingQuery, pk)
result, err = tx.Exec(insertRollingQuery, pk, rawVoting)
if err != nil {
return fmt.Errorf("unable insert rolling: %w", err)
}
Expand Down Expand Up @@ -552,8 +591,21 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err
insert: record,
}

// Make some copies.
var vrf *crypto.VRFSecrets
if record.VRF != nil {
vrf = new(crypto.VRFSecrets)
copy(vrf.SK[:], record.VRF.SK[:])
copy(vrf.PK[:], record.VRF.PK[:])
}

var voting *crypto.OneTimeSignatureSecrets
if record.Voting != nil {
voting = new(crypto.OneTimeSignatureSecrets)
*voting = record.Voting.Snapshot()
}

// update cache.
// TODO: simplify to re-initializing with initializeCache()?
db.cache[id] = ParticipationRecord{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks an awful lot like .Duplicate() ?
(and also things should only be assigned if the source was not nil)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I didn't see a good way to reuse it.

ParticipationID: id,
Account: record.Address(),
Expand All @@ -565,6 +617,8 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err
LastCompactCertificate: 0,
EffectiveFirst: 0,
EffectiveLast: 0,
Voting: voting,
VRF: vrf,
}

return
Expand Down Expand Up @@ -605,26 +659,46 @@ func scanRecords(rows *sql.Rows) ([]ParticipationRecord, error) {
results := make([]ParticipationRecord, 0)
for rows.Next() {
var record ParticipationRecord
var participationBlob []byte
var accountBlob []byte
var rawParticipation []byte
var rawAccount []byte
var rawVRF []byte
var rawVoting []byte
err := rows.Scan(
&participationBlob,
&accountBlob,
&rawParticipation,
&rawAccount,
&record.FirstValid,
&record.LastValid,
&record.KeyDilution,
&rawVRF,
&record.LastVote,
&record.LastBlockProposal,
&record.LastCompactCertificate,
&record.EffectiveFirst,
&record.EffectiveLast,
&rawVoting,
)
if err != nil {
return nil, err
}

copy(record.ParticipationID[:], participationBlob)
copy(record.Account[:], accountBlob)
copy(record.ParticipationID[:], rawParticipation)
copy(record.Account[:], rawAccount)

if len(rawVRF) > 0 {
record.VRF = &crypto.VRFSecrets{}
err = protocol.Decode(rawVRF, record.VRF)
if err != nil {
return nil, fmt.Errorf("unable to decode VRF: %w", err)
}
}

if len(rawVoting) > 0 {
record.Voting = &crypto.OneTimeSignatureSecrets{}
err = protocol.Decode(rawVoting, record.Voting)
if err != nil {
return nil, fmt.Errorf("unable to decode Voting: %w", err)
}
}

results = append(results, record)
}
Expand Down Expand Up @@ -763,6 +837,8 @@ func (db *participationDB) Register(id ParticipationID, on basics.Round) error {
db.mutex.Unlock()
}

db.log.Infof("Registered key (%s) for account (%s) first valid (%d) last valid (%d)\n",
id, recordToRegister.Account, recordToRegister.FirstValid, recordToRegister.LastValid)
return nil
}

Expand Down
93 changes: 92 additions & 1 deletion data/account/participationRegistry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import (
"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"
)
Expand Down Expand Up @@ -429,7 +431,8 @@ func TestParticipation_RecordMultipleUpdates_DB(t *testing.T) {
record.Parent[:],
record.FirstValid,
record.LastValid,
record.KeyDilution)
record.KeyDilution,
nil)
if err != nil {
return fmt.Errorf("unable to insert keyset: %w", err)
}
Expand Down Expand Up @@ -532,6 +535,94 @@ func TestParticipation_NoKeyToUpdate(t *testing.T) {
})
}

// 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 := assert.New(t)
registry := getRegistry(t)
defer registry.Close()

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())
a.Equal(id, part.ID())
// check the initial caching
check(id)

// check the re-initialized object
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 registry.Close()

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())
a.Equal(id, part.ID())
// check the initial caching
check(id)

// check the re-initialized object
registry.initializeCache()
check(id)
}

func TestRegisterUpdatedEvent(t *testing.T) {
partitiontest.PartitionTest(t)
a := assert.New(t)
Expand Down
Loading