Skip to content

Commit febeb1e

Browse files
PKI: Enable full usage of REST API to install participation keys (#4002)
* Migrate goal account commands to REST API (#3916) * PKI: 3924 handle stateproof in rest api goal (#3950) Some additional refactoring: * Remove Node Key Monitoring Function * Removes function which calls loadParticipationKeys() every 60 seconds since MakeFull() for the FullNode does the same thing Co-authored-by: Will Winder <[email protected]>
1 parent 5e6c918 commit febeb1e

13 files changed

+517
-404
lines changed

cmd/goal/account.go

+179-114
Large diffs are not rendered by default.

libgoal/libgoal.go

+26
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ package libgoal
1818

1919
import (
2020
"encoding/json"
21+
"errors"
2122
"fmt"
2223
"io/ioutil"
2324
"os"
2425
"path/filepath"
26+
"time"
2527

2628
algodclient "github.com/algorand/go-algorand/daemon/algod/api/client"
2729
v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2"
@@ -938,6 +940,30 @@ func (c *Client) GetPendingTransactionsByAddress(addr string, maxTxns uint64) (r
938940
return
939941
}
940942

943+
// VerifyParticipationKey checks if a given participationID is installed in a loop until timeout has elapsed.
944+
func (c *Client) VerifyParticipationKey(timeout time.Duration, participationID string) error {
945+
start := time.Now()
946+
947+
for {
948+
keysResp, err := c.GetParticipationKeys()
949+
if err != nil {
950+
return err
951+
}
952+
for _, key := range keysResp {
953+
if key.Id == participationID {
954+
// Installation successful.
955+
return nil
956+
}
957+
}
958+
959+
if time.Since(start) > timeout {
960+
return errors.New("timeout waiting for key to appear")
961+
}
962+
963+
time.Sleep(1 * time.Second)
964+
}
965+
}
966+
941967
// AddParticipationKey takes a participation key file and sends it to the node.
942968
// The key will be loaded into the system when the function returns successfully.
943969
func (c *Client) AddParticipationKey(keyfile string) (resp generated.PostParticipationResponse, err error) {

libgoal/participation.go

+30-178
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package libgoal
1818

1919
import (
2020
"fmt"
21-
"io/ioutil"
2221
"math"
2322
"os"
2423
"path/filepath"
@@ -27,76 +26,34 @@ import (
2726
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated"
2827
"github.com/algorand/go-algorand/data/account"
2928
"github.com/algorand/go-algorand/data/basics"
30-
"github.com/algorand/go-algorand/protocol"
3129
"github.com/algorand/go-algorand/util/db"
3230
)
3331

3432
// chooseParticipation chooses which participation keys to use for going online
3533
// based on the address, round number, and available participation databases
36-
func (c *Client) chooseParticipation(address basics.Address, round basics.Round) (part account.Participation, err error) {
37-
genID, err := c.GenesisID()
34+
func (c *Client) chooseParticipation(address basics.Address, round basics.Round) (part generated.ParticipationKey, err error) {
35+
parts, err := c.ListParticipationKeys()
3836
if err != nil {
3937
return
4038
}
4139

42-
// Get a list of files in the participation keys directory
43-
keyDir := filepath.Join(c.DataDir(), genID)
44-
files, err := ioutil.ReadDir(keyDir)
45-
if err != nil {
46-
return
47-
}
48-
// This lambda will be used for finding the desired file.
49-
checkIfFileIsDesiredKey := func(file os.FileInfo, expiresAfter basics.Round) (part account.Participation, err error) {
50-
var handle db.Accessor
51-
var partCandidate account.PersistedParticipation
52-
53-
// If it can't be a participation key database, skip it
54-
if !config.IsPartKeyFilename(file.Name()) {
55-
return
56-
}
57-
58-
filename := file.Name()
59-
60-
// Fetch a handle to this database
61-
handle, err = db.MakeErasableAccessor(filepath.Join(keyDir, filename))
62-
if err != nil {
63-
// Couldn't open it, skip it
64-
return
65-
}
66-
67-
// Fetch an account.Participation from the database
68-
partCandidate, err = account.RestoreParticipation(handle)
69-
if err != nil {
70-
// Couldn't read it, skip it
71-
handle.Close()
72-
return
73-
}
74-
defer partCandidate.Close()
75-
76-
// Return the Participation valid for this round that relates to the passed address
40+
// Loop through each of the participation keys; pick the one that expires farthest in the future.
41+
var expiry uint64 = 0
42+
for _, info := range parts {
43+
// Choose the Participation valid for this round that relates to the passed address
7744
// that expires farthest in the future.
7845
// Note that algod will sign votes with all possible Participations. so any should work
7946
// in the short-term.
8047
// In the future we should allow the user to specify exactly which partkeys to register.
81-
if partCandidate.FirstValid <= round && round <= partCandidate.LastValid && partCandidate.Parent == address && partCandidate.LastValid > expiresAfter {
82-
part = partCandidate.Participation
48+
if info.Key.VoteFirstValid <= uint64(round) && uint64(round) <= info.Key.VoteLastValid && info.Address == address.String() && info.Key.VoteLastValid > expiry {
49+
part = info
50+
expiry = part.Key.VoteLastValid
8351
}
84-
return
85-
}
8652

87-
// Loop through each of the files; pick the one that expires farthest in the future.
88-
var expiry basics.Round
89-
for _, info := range files {
90-
// Use above lambda so the deferred handle closure happens each loop
91-
partCandidate, err := checkIfFileIsDesiredKey(info, expiry)
92-
if err == nil && (!partCandidate.Parent.IsZero()) {
93-
part = partCandidate
94-
expiry = part.LastValid
95-
}
9653
}
97-
if part.Parent.IsZero() {
54+
if part.Address == "" {
9855
// Couldn't find one
99-
err = fmt.Errorf("Couldn't find a participation key database for address %v valid at round %v in directory %v", address.GetUserAddress(), round, keyDir)
56+
err = fmt.Errorf("couldn't find a participation key database for address %v valid at round %v in participation registry", address.GetUserAddress(), round)
10057
return
10158
}
10259
return
@@ -117,8 +74,12 @@ func (c *Client) GenParticipationKeys(address string, firstValid, lastValid, key
11774
}
11875

11976
// GenParticipationKeysTo creates a .partkey database for a given address, fills
120-
// it with keys, and saves it in the specified output directory.
77+
// it with keys, and saves it in the specified output directory. If the output
78+
// directory is empty, the key will be installed.
12179
func (c *Client) GenParticipationKeysTo(address string, firstValid, lastValid, keyDilution uint64, outDir string) (part account.Participation, filePath string, err error) {
80+
81+
install := outDir == ""
82+
12283
// Parse the address
12384
parsedAddr, err := basics.UnmarshalChecksumAddress(address)
12485
if err != nil {
@@ -127,16 +88,9 @@ func (c *Client) GenParticipationKeysTo(address string, firstValid, lastValid, k
12788

12889
firstRound, lastRound := basics.Round(firstValid), basics.Round(lastValid)
12990

130-
// If output directory wasn't specified, store it in the current ledger directory.
131-
if outDir == "" {
132-
// Get the GenesisID for use in the participation key path
133-
var genID string
134-
genID, err = c.GenesisID()
135-
if err != nil {
136-
return
137-
}
138-
139-
outDir = filepath.Join(c.DataDir(), genID)
91+
// If we are installing, generate in the temp dir
92+
if install {
93+
outDir = os.TempDir()
14094
}
14195
// Connect to the database
14296
partKeyPath, err := participationKeysPath(outDir, parsedAddr, firstRound, lastRound)
@@ -152,6 +106,14 @@ func (c *Client) GenParticipationKeysTo(address string, firstValid, lastValid, k
152106
return
153107
}
154108

109+
// If the key is being installed, remove it afterwards.
110+
if install {
111+
// Explicitly ignore any errors
112+
defer func(name string) {
113+
_ = os.Remove(name)
114+
}(partKeyPath)
115+
}
116+
155117
partdb, err := db.MakeErasableAccessor(partKeyPath)
156118
if err != nil {
157119
return
@@ -165,79 +127,15 @@ func (c *Client) GenParticipationKeysTo(address string, firstValid, lastValid, k
165127
newPart, err := account.FillDBWithParticipationKeys(partdb, parsedAddr, firstRound, lastRound, keyDilution)
166128
part = newPart.Participation
167129
partdb.Close()
168-
return part, partKeyPath, err
169-
}
170-
171-
// InstallParticipationKeys creates a .partkey database for a given address,
172-
// based on an existing database from inputfile. On successful install, it
173-
// deletes the input file.
174-
func (c *Client) InstallParticipationKeys(inputfile string) (part account.Participation, filePath string, err error) {
175-
proto, ok := c.consensus[protocol.ConsensusCurrentVersion]
176-
if !ok {
177-
err = fmt.Errorf("Unknown consensus protocol %s", protocol.ConsensusCurrentVersion)
178-
return
179-
}
180-
181-
// Get the GenesisID for use in the participation key path
182-
var genID string
183-
genID, err = c.GenesisID()
184-
if err != nil {
185-
return
186-
}
187-
188-
outDir := filepath.Join(c.DataDir(), genID)
189-
190-
inputdb, err := db.MakeErasableAccessor(inputfile)
191-
if err != nil {
192-
return
193-
}
194-
defer inputdb.Close()
195-
196-
partkey, err := account.RestoreParticipationWithSecrets(inputdb)
197-
if err != nil {
198-
return
199-
}
200-
201-
if partkey.Parent == (basics.Address{}) {
202-
err = fmt.Errorf("Cannot install partkey with missing (zero) parent address")
203-
return
204-
}
205-
206-
newdbpath, err := participationKeysPath(outDir, partkey.Parent, partkey.FirstValid, partkey.LastValid)
207-
if err != nil {
208-
return
209-
}
210130

211-
newdb, err := db.MakeErasableAccessor(newdbpath)
212131
if err != nil {
213132
return
214133
}
215134

216-
newpartkey := partkey
217-
newpartkey.Store = newdb
218-
err = newpartkey.PersistWithSecrets()
219-
if err != nil {
220-
newpartkey.Close()
221-
return
135+
if install {
136+
_, err = c.AddParticipationKey(partKeyPath)
222137
}
223-
224-
// After successful install, remove the input copy of the
225-
// partkey so that old keys cannot be recovered after they
226-
// are used by algod. We try to delete the data inside
227-
// sqlite first, so the key material is zeroed out from
228-
// disk blocks, but regardless of whether that works, we
229-
// delete the input file. The consensus protocol version
230-
// is irrelevant for the maxuint64 round number we pass in.
231-
errCh := partkey.DeleteOldKeys(basics.Round(math.MaxUint64), proto)
232-
err = <-errCh
233-
if err != nil {
234-
newpartkey.Close()
235-
return
236-
}
237-
os.Remove(inputfile)
238-
part = newpartkey.Participation
239-
newpartkey.Close()
240-
return part, newdbpath, nil
138+
return part, partKeyPath, err
241139
}
242140

243141
// ListParticipationKeys returns the available participation keys,
@@ -249,49 +147,3 @@ func (c *Client) ListParticipationKeys() (partKeyFiles generated.ParticipationKe
249147
}
250148
return
251149
}
252-
253-
// ListParticipationKeyFiles returns the available participation keys,
254-
// as a map from database filename to Participation key object.
255-
func (c *Client) ListParticipationKeyFiles() (partKeyFiles map[string]account.Participation, err error) {
256-
genID, err := c.GenesisID()
257-
if err != nil {
258-
return
259-
}
260-
261-
// Get a list of files in the participation keys directory
262-
keyDir := filepath.Join(c.DataDir(), genID)
263-
files, err := ioutil.ReadDir(keyDir)
264-
if err != nil {
265-
return
266-
}
267-
268-
partKeyFiles = make(map[string]account.Participation)
269-
for _, file := range files {
270-
// If it can't be a participation key database, skip it
271-
if !config.IsPartKeyFilename(file.Name()) {
272-
continue
273-
}
274-
275-
filename := file.Name()
276-
277-
// Fetch a handle to this database
278-
handle, err := db.MakeErasableAccessor(filepath.Join(keyDir, filename))
279-
if err != nil {
280-
// Couldn't open it, skip it
281-
continue
282-
}
283-
284-
// Fetch an account.Participation from the database
285-
part, err := account.RestoreParticipation(handle)
286-
if err != nil {
287-
// Couldn't read it, skip it
288-
handle.Close()
289-
continue
290-
}
291-
292-
partKeyFiles[filename] = part.Participation
293-
part.Close()
294-
}
295-
296-
return
297-
}

0 commit comments

Comments
 (0)