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
121 changes: 75 additions & 46 deletions cmd/goal/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ var renewParticipationKeyCmd = &cobra.Command{
}

// Make sure we don't already have a partkey valid for (or after) specified roundLastValid
parts, err := client.ListParticipationKeys()
parts, err := client.ListParticipationKeyFiles()
if err != nil {
reportErrorf(errorRequestFail, err)
}
Expand Down Expand Up @@ -991,7 +991,7 @@ func renewPartKeysInDir(dataDir string, lastValidRound uint64, fee uint64, lease
client := ensureAlgodClient(dataDir)

// Build list of accounts to renew from all accounts with part keys present
parts, err := client.ListParticipationKeys()
parts, err := client.ListParticipationKeyFiles()
if err != nil {
return fmt.Errorf(errorRequestFail, err)
}
Expand Down Expand Up @@ -1051,10 +1051,21 @@ func renewPartKeysInDir(dataDir string, lastValidRound uint64, fee uint64, lease
return nil
}

func maxRound(current uint64, next *uint64) uint64 {
if next != nil && *next > current {
return *next
}
return current
}

func uintToStr(number uint64) string {
return fmt.Sprintf("%d", number)
}

var listParticipationKeysCmd = &cobra.Command{
Use: "listpartkeys",
Short: "List participation keys",
Long: `List all participation keys tracked by algod, with additional information such as key validity period.`,
Short: "List participation keys summary",
Long: `List all participation keys tracked by algod along with summary of additional information. For detailed key information use 'partkeyinfo'.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, args []string) {
dataDir := ensureSingleDataDir()
Expand All @@ -1065,37 +1076,53 @@ var listParticipationKeysCmd = &cobra.Command{
reportErrorf(errorRequestFail, err)
}

var filenames []string
for fn := range parts {
filenames = append(filenames, fn)
}
sort.Strings(filenames)

rowFormat := "%-10s\t%-80s\t%-60s\t%12s\t%12s\t%12s\n"
fmt.Printf(rowFormat, "Registered", "Filename", "Parent address", "First round", "Last round", "First key")
for _, fn := range filenames {
// Squeezed this into 77 characters.
rowFormat := "%-10s %-11s %-15s %10s %11s %10s\n"
fmt.Printf(rowFormat, "Registered", "Account", "ParticipationID", "Last Used", "First round", "Last round")
for _, part := range parts {
onlineInfoStr := "unknown"
onlineAccountInfo, err := client.AccountInformation(parts[fn].Address().GetUserAddress())
onlineAccountInfo, err := client.AccountInformation(part.Address)
if err == nil {
votingBytes := parts[fn].Voting.OneTimeSignatureVerifier
vrfBytes := parts[fn].VRF.PK
votingBytes := part.Key.VoteParticipationKey
vrfBytes := part.Key.SelectionParticipationKey
if onlineAccountInfo.Participation != nil &&
(string(onlineAccountInfo.Participation.ParticipationPK) == string(votingBytes[:])) &&
(string(onlineAccountInfo.Participation.VRFPK) == string(vrfBytes[:])) &&
(onlineAccountInfo.Participation.VoteFirst == uint64(parts[fn].FirstValid)) &&
(onlineAccountInfo.Participation.VoteLast == uint64(parts[fn].LastValid)) &&
(onlineAccountInfo.Participation.VoteKeyDilution == parts[fn].KeyDilution) {
(onlineAccountInfo.Participation.VoteFirst == part.Key.VoteFirstValid) &&
(onlineAccountInfo.Participation.VoteLast == part.Key.VoteLastValid) &&
(onlineAccountInfo.Participation.VoteKeyDilution == part.Key.VoteKeyDilution) {
onlineInfoStr = "yes"
} else {
onlineInfoStr = "no"
}

/*
// PKI TODO: We could avoid querying the account with something like this.
// One problem is that it doesn't account for multiple keys on the same
// account, so we'd still need to query the round.
if part.EffectiveFirstValid != nil && part.EffectiveLastValid < currentRound {
onlineInfoStr = "yes"
} else {
onlineInfoStr = "no"
}
*/

// it's okay to proceed without algod info
lastUsed := maxRound(0, part.LastVote)
lastUsed = maxRound(lastUsed, part.LastBlockProposal)
lastUsed = maxRound(lastUsed, part.LastStateProof)
lastUsedString := "N/A"
if lastUsed != 0 {
lastUsedString = uintToStr(lastUsed)
}
fmt.Printf(rowFormat,
onlineInfoStr,
fmt.Sprintf("%s...%s", part.Address[:4], part.Address[len(part.Address)-4:]),
fmt.Sprintf("%s...", part.Id[:8]),
lastUsedString,
uintToStr(part.Key.VoteFirstValid),
uintToStr(part.Key.VoteLastValid))
}
// it's okay to proceed without algod info
first, last := parts[fn].ValidInterval()
fmt.Printf(rowFormat, onlineInfoStr, fn, parts[fn].Address().GetUserAddress(),
fmt.Sprintf("%d", first),
fmt.Sprintf("%d", last),
fmt.Sprintf("%d.%d", parts[fn].Voting.FirstBatch, parts[fn].Voting.FirstOffset))
}
},
}
Expand Down Expand Up @@ -1276,14 +1303,11 @@ var importRootKeysCmd = &cobra.Command{
},
}

type partkeyInfo struct {
_struct struct{} `codec:",omitempty,omitemptyarray"`
Address string `codec:"acct"`
FirstValid basics.Round `codec:"first"`
LastValid basics.Round `codec:"last"`
VoteID crypto.OneTimeSignatureVerifier `codec:"vote"`
SelectionID crypto.VRFVerifier `codec:"sel"`
VoteKeyDilution uint64 `codec:"voteKD"`
func strOrNA(value *uint64) string {
if value == nil {
return "N/A"
}
return uintToStr(*value)
}

var partkeyInfoCmd = &cobra.Command{
Expand All @@ -1295,26 +1319,31 @@ var partkeyInfoCmd = &cobra.Command{

onDataDirs(func(dataDir string) {
fmt.Printf("Dumping participation key info from %s...\n", dataDir)
client := ensureGoalClient(dataDir, libgoal.DynamicClient)
client := ensureAlgodClient(dataDir)

// Make sure we don't already have a partkey valid for (or after) specified roundLastValid
parts, err := client.ListParticipationKeys()
if err != nil {
reportErrorf(errorRequestFail, err)
}

for filename, part := range parts {
fmt.Println("------------------------------------------------------------------")
info := partkeyInfo{
Address: part.Address().String(),
FirstValid: part.FirstValid,
LastValid: part.LastValid,
VoteID: part.VotingSecrets().OneTimeSignatureVerifier,
SelectionID: part.VRFSecrets().PK,
VoteKeyDilution: part.KeyDilution,
}
infoString := protocol.EncodeJSON(&info)
fmt.Printf("File: %s\n%s\n", filename, string(infoString))
for _, part := range parts {
fmt.Println()
fmt.Printf("Participation ID: %s\n", part.Id)
fmt.Printf("Parent address: %s\n", part.Address)
fmt.Printf("Last vote round: %s\n", strOrNA(part.LastVote))
fmt.Printf("Last block proposal round: %s\n", strOrNA(part.LastBlockProposal))
// PKI TODO: enable with state proof support.
//fmt.Printf("Last state proof round: %s\n", strOrNA(part.LastStateProof))
fmt.Printf("Effective first round: %s\n", strOrNA(part.EffectiveFirstValid))
fmt.Printf("Effective last round: %s\n", strOrNA(part.EffectiveLastValid))
fmt.Printf("First round: %d\n", part.Key.VoteFirstValid)
fmt.Printf("Last round: %d\n", part.Key.VoteLastValid)
fmt.Printf("Key dilution: %d\n", part.Key.VoteKeyDilution)
fmt.Printf("Selection key: %s\n", base64.StdEncoding.EncodeToString(part.Key.SelectionParticipationKey))
fmt.Printf("Voting key: %s\n", base64.StdEncoding.EncodeToString(part.Key.VoteParticipationKey))
// PKI TODO: enable with state proof support.
//fmt.Printf("State proof key: %s\n", base64.StdEncoding.EncodeToString(part.StateProofKey))
Copy link
Contributor

Choose a reason for hiding this comment

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

this is the merkle tree root, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the state proof? I'm not sure, this is kinda stubbed in for state-proof integration. I'll add a TODO comment.

}
})
},
Expand Down
44 changes: 43 additions & 1 deletion daemon/algod/api/algod.oas2.json
Original file line number Diff line number Diff line change
Expand Up @@ -1721,7 +1721,49 @@
},
"ParticipationKey": {
"description": "Represents a participation key used by the node.",
"type": "object"
"type": "object",
"required": [
"id",
"key",
"address"
],
"properties": {
"id": {
"description": "The key's ParticipationID.",
"type": "string"
},
"address": {
"description": "Address the key was generated for.",
"type": "string",
"x-algorand-format": "Address"
},
"effective-first-valid": {
"description": "When registered, this is the first round it may be used.",
"type": "integer",
"x-algorand-format": "uint64"
},
"effective-last-valid": {
"description": "When registered, this is the last round it may be used.",
"type": "integer",
"x-algorand-format": "uint64"
},
"last-vote": {
"description": "Round when this key was last used to vote.",
"type": "integer"
},
"last-block-proposal": {
"description": "Round when this key was last used to propose a block.",
"type": "integer"
},
"last-state-proof": {
"description": "Round when this key was last used to generate a state proof.",
"type": "integer"
},
"key": {
"description": "Key information stored on the account.",
"$ref": "#/definitions/AccountParticipation"
}
}
},
"TealKeyValueStore": {
"description": "Represents a key-value store for use in an application.",
Expand Down
41 changes: 41 additions & 0 deletions daemon/algod/api/algod.oas3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,47 @@
},
"ParticipationKey": {
"description": "Represents a participation key used by the node.",
"properties": {
"address": {
"description": "Address the key was generated for.",
"type": "string",
"x-algorand-format": "Address"
},
"effective-first-valid": {
"description": "When registered, this is the first round it may be used.",
"type": "integer",
"x-algorand-format": "uint64"
},
"effective-last-valid": {
"description": "When registered, this is the last round it may be used.",
"type": "integer",
"x-algorand-format": "uint64"
},
"id": {
"description": "The key's ParticipationID.",
"type": "string"
},
"key": {
"$ref": "#/components/schemas/AccountParticipation"
},
"last-block-proposal": {
"description": "Round when this key was last used to propose a block.",
"type": "integer"
},
"last-state-proof": {
"description": "Round when this key was last used to generate a state proof.",
"type": "integer"
},
"last-vote": {
"description": "Round when this key was last used to vote.",
"type": "integer"
}
},
"required": [
"address",
"id",
"key"
],
"type": "object"
},
"PendingTransactionResponse": {
Expand Down
12 changes: 12 additions & 0 deletions daemon/algod/api/client/restClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,15 @@ func (client RestClient) Proof(txid string, round uint64) (response generatedV2.
err = client.get(&response, fmt.Sprintf("/v2/blocks/%d/transactions/%s/proof", round, txid), nil)
return
}

// GetParticipationKeys gets all of the participation keys
func (client RestClient) GetParticipationKeys() (response generatedV2.ParticipationKeysResponse, err error) {
err = client.get(&response, "/v2/participation", nil)
return
}

// GetParticipationKeyByID gets a single participation key
func (client RestClient) GetParticipationKeyByID(participationID string) (response generatedV2.ParticipationKeyResponse, err error) {
err = client.get(&response, fmt.Sprintf("/v2/participation/%s", participationID), nil)
return
}
Loading