-
Notifications
You must be signed in to change notification settings - Fork 535
support more flexible manipulation of participation keys #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
69198f1
8f81942
ca98cef
95833a0
e3c64c4
36f90cc
f919210
957abb2
8eaf91d
6c38c4d
c72ec5f
8cf132d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| // Copyright (C) 2019 Algorand, Inc. | ||
| // This file is part of go-algorand | ||
| // | ||
| // go-algorand is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU Affero General Public License as | ||
| // published by the Free Software Foundation, either version 3 of the | ||
| // License, or (at your option) any later version. | ||
| // | ||
| // go-algorand is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU Affero General Public License for more details. | ||
| // | ||
| // You should have received a copy of the GNU Affero General Public License | ||
| // along with go-algorand. If not, see <https://www.gnu.org/licenses/>. | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "encoding/base64" | ||
| "fmt" | ||
| "math" | ||
| "os" | ||
|
|
||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/algorand/go-algorand/data/account" | ||
| "github.com/algorand/go-algorand/data/basics" | ||
| "github.com/algorand/go-algorand/util/db" | ||
| ) | ||
|
|
||
| var partKeyfile string | ||
| var partFirstRound uint64 | ||
| var partLastRound uint64 | ||
| var partKeyDilution uint64 | ||
| var partParent string | ||
|
|
||
| var partCmd = &cobra.Command{ | ||
| Use: "part", | ||
| Short: "Manage participation keys", | ||
| Args: cobra.NoArgs, | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| // If no arguments passed, we should fallback to help | ||
| cmd.HelpFunc()(cmd, args) | ||
| }, | ||
| } | ||
|
|
||
| var partGenerateCmd = &cobra.Command{ | ||
| Use: "generate", | ||
| Short: "Generate participation key", | ||
| Args: cobra.NoArgs, | ||
| Run: func(cmd *cobra.Command, _ []string) { | ||
| if partLastRound < partFirstRound { | ||
| fmt.Fprintf(os.Stderr, "Last round %d < first round %d\n", partLastRound, partFirstRound) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| if partKeyDilution == 0 { | ||
| partKeyDilution = 1 + uint64(math.Sqrt(float64(partLastRound-partFirstRound))) | ||
| } | ||
|
|
||
| var err error | ||
| var parent basics.Address | ||
| if partParent != "" { | ||
| parent, err = basics.UnmarshalChecksumAddress(partParent) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot parse parent address %s: %v\n", partParent, err) | ||
| os.Exit(1) | ||
| } | ||
| } | ||
|
|
||
| partdb, err := db.MakeErasableAccessor(partKeyfile) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot open partkey database %s: %v\n", partKeyfile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| partkey, err := account.FillDBWithParticipationKeys(partdb, parent, basics.Round(partFirstRound), basics.Round(partLastRound), partKeyDilution) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot generate partkey database %s: %v\n", partKeyfile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| printPartkey(partkey) | ||
| }, | ||
| } | ||
|
|
||
| var partInfoCmd = &cobra.Command{ | ||
| Use: "info", | ||
| Short: "Print participation key information", | ||
| Args: cobra.NoArgs, | ||
| Run: func(cmd *cobra.Command, _ []string) { | ||
| partdb, err := db.MakeErasableAccessor(partKeyfile) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot open partkey database %s: %v\n", partKeyfile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| partkey, err := account.RestoreParticipation(partdb) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot load partkey database %s: %v\n", partKeyfile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| printPartkey(partkey) | ||
| }, | ||
| } | ||
|
|
||
| var partReparentCmd = &cobra.Command{ | ||
| Use: "reparent", | ||
| Short: "Change parent address of participation key", | ||
| Args: cobra.NoArgs, | ||
| Run: func(cmd *cobra.Command, _ []string) { | ||
| parent, err := basics.UnmarshalChecksumAddress(partParent) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot parse parent address %s: %v\n", partParent, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| partdb, err := db.MakeErasableAccessor(partKeyfile) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot open partkey database %s: %v\n", partKeyfile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| partkey, err := account.RestoreParticipation(partdb) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot load partkey database %s: %v\n", partKeyfile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| partkey.Parent = parent | ||
| err = partkey.PersistNewParent() | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Cannot persist partkey database %s: %v\n", partKeyfile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| printPartkey(partkey) | ||
| }, | ||
| } | ||
|
|
||
| func printPartkey(partkey account.Participation) { | ||
| fmt.Printf("Parent address: %s\n", partkey.Parent.GetChecksumAddress().String()) | ||
| fmt.Printf("VRF public key: %s\n", base64.StdEncoding.EncodeToString(partkey.VRF.PK[:])) | ||
| fmt.Printf("Voting public key: %s\n", base64.StdEncoding.EncodeToString(partkey.Voting.OneTimeSignatureVerifier[:])) | ||
| fmt.Printf("First round: %d\n", partkey.FirstValid) | ||
| fmt.Printf("Last round: %d\n", partkey.LastValid) | ||
| fmt.Printf("Key dilution: %d\n", partkey.KeyDilution) | ||
| fmt.Printf("First batch: %d\n", partkey.Voting.FirstBatch) | ||
| fmt.Printf("First offset: %d\n", partkey.Voting.FirstOffset) | ||
| } | ||
|
|
||
| func init() { | ||
| partCmd.AddCommand(partGenerateCmd) | ||
| partCmd.AddCommand(partInfoCmd) | ||
| partCmd.AddCommand(partReparentCmd) | ||
|
|
||
| partGenerateCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") | ||
| partGenerateCmd.Flags().Uint64VarP(&partFirstRound, "first", "", 0, "First round for participation key") | ||
| partGenerateCmd.Flags().Uint64VarP(&partLastRound, "last", "", 0, "Last round for participation key") | ||
| partGenerateCmd.Flags().Uint64VarP(&partKeyDilution, "dilution", "", 0, "Key dilution (default to sqrt of validity window)") | ||
| partGenerateCmd.Flags().StringVarP(&partParent, "parent", "", "", "Address of parent account") | ||
| partGenerateCmd.MarkFlagRequired("first") | ||
| partGenerateCmd.MarkFlagRequired("last") | ||
| partGenerateCmd.MarkFlagRequired("keyfile") | ||
|
|
||
| partInfoCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") | ||
| partInfoCmd.MarkFlagRequired("keyfile") | ||
|
|
||
| partReparentCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") | ||
| partReparentCmd.Flags().StringVarP(&partParent, "parent", "", "", "Address of parent account") | ||
| partReparentCmd.MarkFlagRequired("keyfile") | ||
| partReparentCmd.MarkFlagRequired("parent") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,7 @@ var ( | |
| keyDilution uint64 | ||
| threshold uint8 | ||
| partKeyOutDir string | ||
| partKeyFile string | ||
| importDefault bool | ||
| mnemonic string | ||
| ) | ||
|
|
@@ -69,6 +70,7 @@ func init() { | |
| accountCmd.AddCommand(rewardsCmd) | ||
| accountCmd.AddCommand(changeOnlineCmd) | ||
| accountCmd.AddCommand(addParticipationKeyCmd) | ||
| accountCmd.AddCommand(installParticipationKeyCmd) | ||
| accountCmd.AddCommand(listParticipationKeysCmd) | ||
| accountCmd.AddCommand(importCmd) | ||
| accountCmd.AddCommand(exportCmd) | ||
|
|
@@ -118,8 +120,8 @@ func init() { | |
| rewardsCmd.MarkFlagRequired("address") | ||
|
|
||
| // changeOnlineStatus flags | ||
| changeOnlineCmd.Flags().StringVarP(&accountAddress, "address", "a", "", "Account address to change (required)") | ||
| changeOnlineCmd.MarkFlagRequired("address") | ||
| changeOnlineCmd.Flags().StringVarP(&accountAddress, "address", "a", "", "Account address to change (required if no -partkeyfile)") | ||
| changeOnlineCmd.Flags().StringVarP(&partKeyFile, "partkeyfile", "", "", "Participation key file (required if no -account)") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not important, but the StringVar() command exists (likewise for other data types) for commands that don't have the short-form parameter, so you don't have to specify "".
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, good to know -- thanks! |
||
| changeOnlineCmd.Flags().BoolVarP(&online, "online", "o", true, "Set this account to online or offline") | ||
| changeOnlineCmd.MarkFlagRequired("online") | ||
| changeOnlineCmd.Flags().Uint64VarP(&transactionFee, "fee", "f", 0, "The Fee to set on the status change transaction (defaults to suggested fee)") | ||
|
|
@@ -138,6 +140,10 @@ func init() { | |
| addParticipationKeyCmd.Flags().StringVarP(&partKeyOutDir, "outdir", "o", "", "Save participation key file to specified output directory to (for offline creation)") | ||
| addParticipationKeyCmd.Flags().Uint64VarP(&keyDilution, "keyDilution", "", 0, "Key dilution for two-level participation keys") | ||
|
|
||
| // installParticipationKey flags | ||
| installParticipationKeyCmd.Flags().StringVarP(&partKeyFile, "partkey", "", "", "Participation key file to install") | ||
| installParticipationKeyCmd.MarkFlagRequired("partkey") | ||
|
|
||
| // import flags | ||
| importCmd.Flags().BoolVarP(&importDefault, "default", "f", false, "Set this account as the default one") | ||
| importCmd.Flags().StringVarP(&mnemonic, "mnemonic", "m", "", "Mnemonic to import (will prompt otherwise)") | ||
|
|
@@ -471,11 +477,36 @@ var changeOnlineCmd = &cobra.Command{ | |
| Long: `Change online status for the specified account. Set online should be 1 to set online, 0 to set offline. The broadcast transaction will be valid for a limited number of rounds. goal will provide the TXID of the transaction if successful. Going online requires that the given account have a valid participation key.`, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While here, can we fix the help-text ('Going online requires that the given account have...' : have -> has)? Also it might be confusing to people that this command does not 'install' the part file too - we should try to make sure there is no confusion.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, clarified both. |
||
| Args: validateNoPosArgsFn, | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| if accountAddress == "" && partKeyFile == "" { | ||
| fmt.Printf("Must specify one of --address or --partkeyfile\n") | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| // Pull the current round for use in our new transactions | ||
| dataDir := ensureSingleDataDir() | ||
| client := ensureFullClient(dataDir) | ||
|
|
||
| err := changeAccountOnlineStatus(accountAddress, nil, online, onlineTxFile, walletName, onlineFirstRound, onlineValidRounds, transactionFee, dataDir, client) | ||
| var part *algodAcct.Participation | ||
| if partKeyFile != "" { | ||
| partdb, err := db.MakeErasableAccessor(partKeyFile) | ||
| if err != nil { | ||
| fmt.Printf("Cannot open partkey %s: %v\n", partKeyFile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| partkey, err := algodAcct.RestoreParticipation(partdb) | ||
| if err != nil { | ||
| fmt.Printf("Cannot load partkey %s: %v\n", partKeyFile, err) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| part = &partkey | ||
| if accountAddress == "" { | ||
| accountAddress = part.Parent.GetChecksumAddress().String() | ||
| } | ||
| } | ||
|
|
||
| err := changeAccountOnlineStatus(accountAddress, part, online, onlineTxFile, walletName, onlineFirstRound, onlineValidRounds, transactionFee, dataDir, client) | ||
| if err != nil { | ||
| reportErrorf(err.Error()) | ||
| } | ||
|
|
@@ -579,6 +610,23 @@ var addParticipationKeyCmd = &cobra.Command{ | |
| }, | ||
| } | ||
|
|
||
| var installParticipationKeyCmd = &cobra.Command{ | ||
| Use: "installpartkey", | ||
| Short: "Install a participation key", | ||
| Long: `Install a participation key`, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should indicate that this does not change online status.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, thanks. |
||
| Args: validateNoPosArgsFn, | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| dataDir := ensureSingleDataDir() | ||
|
|
||
| client := ensureFullClient(dataDir) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need a full client for this do we (just algod)?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, sloppy copy-pasting of libgoal boilerplate on my part. |
||
| _, _, err := client.InstallParticipationKeys(partKeyFile) | ||
| if err != nil { | ||
| reportErrorf(errorRequestFail, err) | ||
| } | ||
| fmt.Println("Participation key installed successfully") | ||
| }, | ||
| } | ||
|
|
||
| var renewParticipationKeyCmd = &cobra.Command{ | ||
| Use: "renewpartkey", | ||
| Short: "Renew an account's participation key", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than breaking the assignment and persistence, can we change PersistNewParent() to ReplaceParent(parent)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going for that originally, but for historical reasons, all current methods on
account.Participationare by value rather than by pointer. So,ReplaceParent()would not have any apparent effect on the caller's partkey object (in particular, the subsequentprintPartkey()would print the old parent), even though it could write the new parent to the partkey db.. Modifying the parent in-place seemed cleaner than having a mix of by-value and by-pointer methods onaccount.Participation, and changing all of them to by-pointer seemed unnecessarily involved.