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
37 changes: 37 additions & 0 deletions .github/workflows/pr-type-category.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Check PR category and type
on:
pull_request:
branches:
- master
types: [opened, synchronize, reopened, labeled, unlabeled, edited]
jobs:
check_label:
runs-on: ubuntu-latest
name: Check PR Category and Type
steps:
- name: "Failed to find proper PR Type label. Please add one of the following: 'New Feature', 'Enhancement', or 'Bug-Fix'"
run: exit 1
if: |
!contains(github.event.pull_request.labels.*.name, 'New Feature') &&
!contains(github.event.pull_request.labels.*.name, 'Enhancement') &&
!contains(github.event.pull_request.labels.*.name, 'Bug-Fix')
- name: "Found more than one PR Type label. Please add only one of the following: 'New Feature', 'Enhancement', or 'Bug-Fix'"
run: exit 1
if: |
(
contains(github.event.pull_request.labels.*.name, 'New Feature') &&
contains(github.event.pull_request.labels.*.name, 'Enhancement')
) || (
contains(github.event.pull_request.labels.*.name, 'New Feature') &&
contains(github.event.pull_request.labels.*.name, 'Bug-Fix')
) || (
contains(github.event.pull_request.labels.*.name, 'Enhancement') &&
contains(github.event.pull_request.labels.*.name, 'Bug-Fix')
)
- name: "PR Category is missing from PR title. Please add it like '<category>: <pr title>'"
run: |
if [[ ! "${{ github.event.pull_request.title }}" =~ ^.{2,}\:.{2,} ]]; then
exit 1
fi
- name: "Found at least one PR Type label and Category in the title. Good job!"
run: exit 0
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ gen/mainnet/genesis.dump: gen/mainnet/genesis.json

mainnetgen: gen/mainnet/genesis.dump

# The mainnet genesis.json file generated by this target does not have timestamp value so the hash is different from the deployed mainnet,
# use a real genesis.json file from installer/genesis/mainnet/genesis.json if needed.
# This target is preserved as part of the history on how mainnet genesis.json was generated from the CSV file.
gen/mainnet/genesis.json: gen/pregen/mainnet/genesis.csv buildsrc
mkdir -p gen/mainnet
cat gen/pregen/mainnet/genesis.csv | $(GOPATH1)/bin/incorporate -m gen/pregen/mainnet/metadata.json > gen/mainnet/genesis.json
Expand Down
4 changes: 2 additions & 2 deletions agreement/asyncVoteVerifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestVerificationAgainstFullExecutionPool(t *testing.T) {
voteVerifier := MakeAsyncVoteVerifier(&expiredExecPool{mainPool})
defer voteVerifier.Quit()
verifyErr := voteVerifier.verifyVote(context.Background(), nil, unauthenticatedVote{}, 0, message{}, make(chan<- asyncVerifyVoteResponse, 1))
require.Error(t, context.Canceled, verifyErr)
require.Equal(t, context.Canceled, verifyErr)
verifyEqVoteErr := voteVerifier.verifyEqVote(context.Background(), nil, unauthenticatedEquivocationVote{}, 0, message{}, make(chan<- asyncVerifyVoteResponse, 1))
require.Error(t, context.Canceled, verifyEqVoteErr)
require.Equal(t, context.Canceled, verifyEqVoteErr)
}
11 changes: 6 additions & 5 deletions agreement/pseudonode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,11 +520,12 @@ func TestPseudonodeFailedEnqueuedTasks(t *testing.T) {
for i := 0; i < pseudonodeVerificationBacklog*2; i++ {
ch, err = pb.MakeProposals(context.Background(), startRound, period(i))
if err != nil {
require.Subset(t, []int{pseudonodeVerificationBacklog, pseudonodeVerificationBacklog + 1}, []int{i})
require.ErrorAs(t, errPseudonodeBacklogFull, &err)
break
}
channels = append(channels, ch)
}
enqueuedProposals := len(channels)
require.Error(t, err, "MakeProposals did not returned an error when being overflowed with requests")
require.True(t, errors.Is(err, errPseudonodeBacklogFull))

Expand All @@ -533,17 +534,17 @@ func TestPseudonodeFailedEnqueuedTasks(t *testing.T) {
for i := 0; i < pseudonodeVerificationBacklog*2; i++ {
ch, err = pb.MakeVotes(context.Background(), startRound, period(i), step(i%5), makeProposalValue(period(i), accounts[0].Address()), persist)
if err != nil {
require.Subset(t, []int{pseudonodeVerificationBacklog, pseudonodeVerificationBacklog + 1}, []int{i})
require.ErrorAs(t, errPseudonodeBacklogFull, &err)
break
}
channels = append(channels, ch)
}
require.Error(t, err, "MakeVotes did not returned an error when being overflowed with requests")

enqueuedVotes := len(channels) - enqueuedProposals
// drain output channels.
for _, ch := range channels {
drainChannel(ch)
}
require.Equal(t, 330, subStrLogger.instancesFound[0])
require.Equal(t, 330, subStrLogger.instancesFound[1])
require.Equal(t, enqueuedVotes*len(accounts), subStrLogger.instancesFound[0])
require.Equal(t, enqueuedProposals*len(accounts), subStrLogger.instancesFound[1])
}
2 changes: 1 addition & 1 deletion buildnumber.dat
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1
2
258 changes: 258 additions & 0 deletions cmd/algokey/keyreg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// Copyright (C) 2019-2022 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"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/spf13/cobra"

"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/account"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/util"
"github.com/algorand/go-algorand/util/db"
)

var keyregCmd *cobra.Command

type keyregCmdParams struct {
fee uint64
firstValid uint64
lastValid uint64
network string
offline bool
txFile string
partkeyFile string
addr string
}

// There is no node to query, so we do our best here.
const (
txnLife uint64 = 1000
minFee uint64 = 1000
)

var validNetworks map[string]crypto.Digest
var validNetworkList []string

func init() {
var params keyregCmdParams

keyregCmd = &cobra.Command{
Use: "keyreg",
Short: "Make key registration transaction",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) {
err := run(params)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n\n", err.Error())
os.Exit(1)
}
},
}

keyregCmd.Flags().Uint64Var(&params.fee, "fee", minFee, "transaction fee")
keyregCmd.Flags().Uint64Var(&params.firstValid, "firstvalid", 0, "first round where the transaction may be committed to the ledger")
keyregCmd.MarkFlagRequired("firstvalid") // nolint:errcheck
keyregCmd.Flags().Uint64Var(&params.lastValid, "lastvalid", 0, fmt.Sprintf("last round where the generated transaction may be committed to the ledger, defaults to firstvalid + %d", txnLife))
keyregCmd.Flags().StringVar(&params.network, "network", "mainnet", "the network where the provided keys will be registered, one of mainnet/testnet/betanet")
keyregCmd.MarkFlagRequired("network") // nolint:errcheck
keyregCmd.Flags().BoolVar(&params.offline, "offline", false, "set to bring an account offline")
keyregCmd.Flags().StringVarP(&params.txFile, "outputFile", "o", "", fmt.Sprintf("write signed transaction to this file, or '%s' to write to stdout", stdoutFilenameValue))
keyregCmd.Flags().StringVar(&params.partkeyFile, "keyfile", "", "participation keys to register, file is opened to fetch metadata for the transaction; only specify when bringing an account online to vote in Algorand consensus")
keyregCmd.Flags().StringVar(&params.addr, "account", "", "account address to bring offline; only specify when taking an account offline from voting in Algorand consensus")

// TODO: move 'bundleGenesisInject' into something that can be imported here instead of using constants.
validNetworks = map[string]crypto.Digest{
"mainnet": mustConvertB64ToDigest("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="),
"testnet": mustConvertB64ToDigest("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="),
"betanet": mustConvertB64ToDigest("mFgazF+2uRS1tMiL9dsj01hJGySEmPN28B/TjjvpVW0="),
"devnet": mustConvertB64ToDigest("sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E="),
}
validNetworkList = make([]string, 0, len(validNetworks))
for k := range validNetworks {
validNetworkList = append(validNetworkList, k)
}
}

func mustConvertB64ToDigest(b64 string) (digest crypto.Digest) {
data, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to decode digest '%s': %s\n\n", b64, err)
os.Exit(1)
}
if len(data) != len(digest[:]) {
fmt.Fprintf(os.Stderr, "Unexpected decoded digest length decoding '%s'.\n\n", b64)
os.Exit(1)
}
copy(digest[:], data)
return
}

func getGenesisInformation(network string) (crypto.Digest, error) {
// For testing purposes, there is a secret option to override the genesis information.
hashOverride := os.Getenv("ALGOKEY_GENESIS_HASH")
if hashOverride != "" {
return mustConvertB64ToDigest(hashOverride), nil
}

// Otherwise check that network matches one of the known networks.
gen, ok := validNetworks[strings.ToLower(network)]
if !ok {
return crypto.Digest{}, fmt.Errorf("unknown network '%s' provided. Supported networks: %s",
network,
strings.Join(validNetworkList, ", "))
}

return gen, nil
}

func run(params keyregCmdParams) error {
// Implicit last valid
if params.lastValid == 0 {
params.lastValid = params.firstValid + txnLife
}

if params.fee < minFee {
return fmt.Errorf("the provided transaction fee (%d) is too low, the minimum fee is %d", params.fee, minFee)
}

if !params.offline {
if params.partkeyFile == "" {
return errors.New("must provide --keyfile when registering participation keys")
}
if params.addr != "" {
return errors.New("do not provide --address when registering participation keys")
}
} else {
if params.addr == "" {
return errors.New("must provide --address when bringing an account offline")
}
if params.partkeyFile != "" {
return errors.New("do not provide --keyfile when bringing an account offline")
}
}

var accountAddress basics.Address
if params.addr != "" {
var err error
accountAddress, err = basics.UnmarshalChecksumAddress(params.addr)
if err != nil {
return fmt.Errorf("unable to parse --address: %w", err)
}
}

if params.partkeyFile != "" && !util.FileExists(params.partkeyFile) {
return fmt.Errorf("cannot access keyfile '%s'", params.partkeyFile)
}

if params.txFile == "" {
params.txFile = fmt.Sprintf("%s.tx", params.partkeyFile)
}

if util.FileExists(params.txFile) || params.txFile == stdoutFilenameValue {
return fmt.Errorf("outputFile '%s' already exists", params.partkeyFile)
}

// Lookup information from partkey file
var part *account.Participation
if params.partkeyFile != "" {
partDB, err := db.MakeErasableAccessor(params.partkeyFile)
if err != nil {
return fmt.Errorf("cannot open keyfile %s: %v", params.partkeyFile, err)
}

partkey, err := account.RestoreParticipation(partDB)
if err != nil {
return fmt.Errorf("cannot load keyfile %s: %v", params.partkeyFile, err)
}
defer partkey.Close()

part = &partkey.Participation

if params.firstValid < uint64(part.FirstValid) {
return fmt.Errorf("the transaction's firstvalid round (%d) field should be set greater than or equal to the participation key's first valid round (%d). The network will reject key registration transactions that are set to take effect before the participation key's first valid round", params.firstValid, part.FirstValid)
}
}

validRange := params.lastValid - params.firstValid
if validRange > txnLife {
return fmt.Errorf("the transaction's specified validity range must be less than or equal to 1000 rounds due to security constraints. Please enter a first valid round (%d) and last valid round (%d) whose difference is no more than 1000 rounds", params.firstValid, params.lastValid)
}

var txn transactions.Transaction
if !params.offline {
// Generate go-online transaction
txn = part.GenerateRegistrationTransaction(
basics.MicroAlgos{Raw: params.fee},
basics.Round(params.firstValid),
basics.Round(params.lastValid),
[32]byte{},
part.StateProofSecrets != nil)
} else {
// Generate go-offline transaction
txn = transactions.Transaction{
Type: protocol.KeyRegistrationTx,
Header: transactions.Header{
Sender: accountAddress,
Fee: basics.MicroAlgos{Raw: params.fee},
FirstValid: basics.Round(params.firstValid),
LastValid: basics.Round(params.lastValid),
},
}
}

var err error
txn.GenesisHash, err = getGenesisInformation(params.network)
if err != nil {
return err
}

// Wrap in a transactions.SignedTxn with an empty sig.
// This way protocol.Encode will encode the transaction type
stxn, err := transactions.AssembleSignedTxn(txn, crypto.Signature{}, crypto.MultisigSig{})
if err != nil {
return fmt.Errorf("failed to assemble transaction: %w", err)
}

data := protocol.Encode(&stxn)
if params.txFile == stdoutFilenameValue {
// Write to Stdout
if _, err = os.Stdout.Write(data); err != nil {
return fmt.Errorf("failed to write transaction to stdout: %w", err)
}
} else {
if err = ioutil.WriteFile(params.txFile, data, 0600); err != nil {
return fmt.Errorf("failed to write transaction to '%s': %w", params.txFile, err)
}
}

if params.offline {
fmt.Printf("Account key go offline transaction written to '%s'.\n", params.txFile)
} else {
fmt.Printf("Key registration transaction written to '%s'.\n", params.txFile)
}
return nil
}
1 change: 1 addition & 0 deletions cmd/algokey/part.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ func init() {
partCmd.AddCommand(partGenerateCmd)
partCmd.AddCommand(partInfoCmd)
partCmd.AddCommand(partReparentCmd)
partCmd.AddCommand(keyregCmd)

partGenerateCmd.Flags().StringVar(&partKeyfile, "keyfile", "", "Participation key filename")
partGenerateCmd.Flags().Uint64Var(&partFirstRound, "first", 0, "First round for participation key")
Expand Down
18 changes: 18 additions & 0 deletions cmd/goal/bundle_genesis_json.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash

THISDIR=$(dirname $0)

cat <<EOM | gofmt > $THISDIR/bundledGenesisInject.go
// Code generated by bundle_genesis_json.sh. DO NOT EDIT.
package main
var genesisMainnet []byte
var genesisTestnet []byte
var genesisBetanet []byte
var genesisDevnet []byte
func init() {
genesisMainnet = []byte(\`$(cat $THISDIR/../../installer/genesis/mainnet/genesis.json)\`)
genesisTestnet = []byte(\`$(cat $THISDIR/../../installer/genesis/testnet/genesis.json)\`)
genesisBetanet = []byte(\`$(cat $THISDIR/../../installer/genesis/betanet/genesis.json)\`)
genesisDevnet = []byte(\`$(cat $THISDIR/../../installer/genesis/devnet/genesis.json)\`)
}
EOM
Loading