Skip to content
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

API: Add participation key generation endpoint to algod API #5781

Merged
merged 20 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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
3 changes: 1 addition & 2 deletions cmd/algokey/part.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package main
import (
"encoding/base64"
"fmt"
"math"
"os"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -58,7 +57,7 @@ var partGenerateCmd = &cobra.Command{
}

if partKeyDilution == 0 {
partKeyDilution = 1 + uint64(math.Sqrt(float64(partLastRound-partFirstRound)))
partKeyDilution = account.DefaultKeyDilution(basics.Round(partFirstRound), basics.Round(partLastRound))
}

var err error
Expand Down
8 changes: 1 addition & 7 deletions daemon/algod/api/algod.oas2.json
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@
],
"responses": {
"200": {
"description": "The current swagger spec",
"description": "An empty JSON object is returned if the generation process was started. Currently no status is available.",
"schema": {
"type": "string"
jasonpaulos marked this conversation as resolved.
Show resolved Hide resolved
}
Expand All @@ -996,12 +996,6 @@
"$ref": "#/definitions/ErrorResponse"
}
},
"404": {
"description": "Participation Key Not Found",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
Expand Down
12 changes: 1 addition & 11 deletions daemon/algod/api/algod.oas3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5428,7 +5428,7 @@
}
}
},
"description": "The current swagger spec"
"description": "An empty JSON object is returned if the generation process was started. Currently no status is available."
},
"400": {
"content": {
Expand All @@ -5450,16 +5450,6 @@
},
"description": "Invalid API Token"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "Participation Key Not Found"
},
"500": {
"content": {
"application/json": {
Expand Down
8 changes: 5 additions & 3 deletions daemon/algod/api/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package server

import (
"fmt"
"golang.org/x/sync/semaphore"
"net"
"net/http"

Expand Down Expand Up @@ -119,9 +120,10 @@ func NewRouter(logger logging.Logger, node APINodeInterface, shutdown <-chan str

// Registering v2 routes
v2Handler := v2.Handlers{
Node: node,
Log: logger,
Shutdown: shutdown,
Node: node,
Log: logger,
Shutdown: shutdown,
KeygenLimiter: semaphore.NewWeighted(1),
}
nppublic.RegisterHandlers(e, &v2Handler, publicMiddleware...)
npprivate.RegisterHandlers(e, &v2Handler, adminMiddleware...)
Expand Down
419 changes: 210 additions & 209 deletions daemon/algod/api/server/v2/generated/participating/private/routes.go

Large diffs are not rendered by default.

23 changes: 8 additions & 15 deletions daemon/algod/api/server/v2/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"encoding/base64"
"errors"
"fmt"
"golang.org/x/sync/semaphore"
"io"
"math"
"net/http"
Expand Down Expand Up @@ -73,11 +74,10 @@ var WaitForBlockTimeout = 1 * time.Minute

// Handlers is an implementation to the V2 route handler interface defined by the generated code.
type Handlers struct {
Node NodeInterface
Log logging.Logger
Shutdown <-chan struct{}
// Keygen is used to limit the number of concurrent key generation requests.
Keygen chan struct{}
Node NodeInterface
Log logging.Logger
Shutdown <-chan struct{}
KeygenLimiter *semaphore.Weighted
winder marked this conversation as resolved.
Show resolved Hide resolved
}

// LedgerForAPI describes the Ledger methods used by the v2 API.
Expand Down Expand Up @@ -267,22 +267,15 @@ func (v2 *Handlers) generateKeyHandler(address string, params model.GeneratePart
// GenerateParticipationKeys generates and installs participation keys to the node.
// (POST /v2/participation/generate/{address})
func (v2 *Handlers) GenerateParticipationKeys(ctx echo.Context, address string, params model.GenerateParticipationKeysParams) error {
if v2.Keygen == nil {
v2.Keygen = make(chan struct{}, 1)
}

select {
case v2.Keygen <- struct{}{}:
if v2.KeygenLimiter != nil && v2.KeygenLimiter.TryAcquire(1) {
winder marked this conversation as resolved.
Show resolved Hide resolved
go func() {
defer func() {
<-v2.Keygen
}()
defer v2.KeygenLimiter.Release(1)
err := v2.generateKeyHandler(address, params)
if err != nil {
v2.Log.Warnf("Error generating participation keys: %v", err)
}
}()
default:
} else {
err := fmt.Errorf("participation key generation already in progress")
return badRequest(ctx, err, err.Error(), v2.Log)
}
Expand Down
18 changes: 13 additions & 5 deletions daemon/algod/api/server/v2/test/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"encoding/json"
"errors"
"fmt"
"golang.org/x/sync/semaphore"
"io"
"math"
"net"
Expand Down Expand Up @@ -2390,7 +2391,12 @@ func TestGeneratePartkeys(t *testing.T) {
defer releasefunc()
dummyShutdownChan := make(chan struct{})
mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false)
handler := v2.Handlers{Node: mockNode, Log: logging.Base(), Shutdown: dummyShutdownChan}
handler := v2.Handlers{
Node: mockNode,
Log: logging.Base(),
Shutdown: dummyShutdownChan,
KeygenLimiter: semaphore.NewWeighted(1),
}
e := echo.New()

var addr basics.Address
Expand All @@ -2411,18 +2417,20 @@ func TestGeneratePartkeys(t *testing.T) {
assert.Equal(t, http.StatusOK, rec.Code)

// Wait for keygen to complete
handler.Keygen <- struct{}{}
err = handler.KeygenLimiter.Acquire(context.Background(), 1)
require.NoError(t, err)
require.Greater(t, len(mockNode.PartKeyBinary), 0)
<-handler.Keygen
handler.KeygenLimiter.Release(1)
}

{
req := httptest.NewRequest(http.MethodPost, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
// Simulate a blocked keygen process (and block until the previous keygen is complete)
handler.Keygen <- struct{}{}
err := handler.GenerateParticipationKeys(c, addr.String(), model.GenerateParticipationKeysParams{
err := handler.KeygenLimiter.Acquire(context.Background(), 1)
require.NoError(t, err)
err = handler.GenerateParticipationKeys(c, addr.String(), model.GenerateParticipationKeysParams{
First: 1000,
Last: 2000,
})
Expand Down
2 changes: 1 addition & 1 deletion data/account/participation.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func (part PersistedParticipation) PersistNewParent() error {
})
}

// DefaultKeyDilution computes the default dilution based on first and last rounds.
// DefaultKeyDilution computes the default dilution based on first and last rounds as the sqrt of validity window.
func DefaultKeyDilution(first, last basics.Round) uint64 {
return 1 + uint64(math.Sqrt(float64(last-first)))
jannotti marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
3 changes: 1 addition & 2 deletions data/account/participationRegistry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"encoding/binary"
"errors"
"fmt"
"math"
"os"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -99,7 +98,7 @@ func makeTestParticipationWithLifetime(a *require.Assertions, addrID int, first,

// Generate part keys like in partGenerateCmd and FillDBWithParticipationKeys
if dilution == 0 {
dilution = 1 + uint64(math.Sqrt(float64(last-first)))
dilution = DefaultKeyDilution(first, last)
}

// Compute how many distinct participation keys we should generate
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
golang.org/x/sync v0.3.0
golang.org/x/sys v0.13.0
golang.org/x/text v0.13.0
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009
Expand Down Expand Up @@ -155,7 +156,6 @@ require (
go.uber.org/zap v1.24.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
golang.org/x/tools v0.11.0 // indirect
Expand Down
5 changes: 4 additions & 1 deletion libgoal/participation/participation.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ func participationKeysPath(dataDir string, address basics.Address, firstValid, l
// directory is empty, the key will be installed.
func GenParticipationKeysTo(address string, firstValid, lastValid, keyDilution uint64, outDir string, installFunc func(keyPath string) error) (part account.Participation, filePath string, err error) {

install := outDir == "" && installFunc != nil
install := outDir == ""
if install && installFunc == nil {
return account.Participation{}, "", fmt.Errorf("must provide an install function when installing keys")
}

// Parse the address
parsedAddr, err := basics.UnmarshalChecksumAddress(address)
Expand Down
8 changes: 8 additions & 0 deletions libgoal/participation/participation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,11 @@ func TestGenParticipationKeysTo_DefaultKeyDilution(t *testing.T) {
})
}
}

func TestBadInput(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

_, _, err := GenParticipationKeysTo("", 0, 0, 0, "", nil)
require.ErrorContains(t, err, "must provide an install function when installing keys")
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package participation
// deterministic.

import (
"errors"
"fmt"
"path/filepath"
"testing"
Expand All @@ -41,8 +42,7 @@ import (
func installParticipationKey(t *testing.T, client libgoal.Client, addr string, firstValid, lastValid uint64) (resp model.PostParticipationResponse, part account.Participation, err error) {
// Install overlapping participation keys...
installFunc := func(keyPath string) error {
_, err := client.AddParticipationKey(keyPath)
return err
return errors.New("the install directory is provided, so keys should not be installed")
}
part, filePath, err := participation.GenParticipationKeysTo(addr, firstValid, lastValid, 100, t.TempDir(), installFunc)
require.NoError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions test/e2e-go/features/stateproofs/stateproofs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package stateproofs

import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -679,8 +680,7 @@ func installParticipationKey(t *testing.T, client libgoal.Client, addr string, f

// Install overlapping participation keys...
installFunc := func(keyPath string) error {
_, err := client.AddParticipationKey(keyPath)
return err
return errors.New("the install directory is provided, so keys should not be installed")
}
part, filePath, err := participation.GenParticipationKeysTo(addr, firstValid, lastValid, 100, dir, installFunc)
require.NoError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions test/e2e-go/features/transactions/onlineStatusChange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package transactions

import (
"errors"
"fmt"
"path/filepath"
"testing"
Expand Down Expand Up @@ -173,8 +174,7 @@ func TestCloseOnError(t *testing.T) {

var partkeyFile string
installFunc := func(keyPath string) error {
_, err := client.AddParticipationKey(keyPath)
return err
return errors.New("the install directory is provided, so keys should not be installed")
}
_, partkeyFile, err = participation.GenParticipationKeysTo(initiallyOffline, 0, curRound+1000, 0, t.TempDir(), installFunc)
jasonpaulos marked this conversation as resolved.
Show resolved Hide resolved
a.NoError(err)
Expand Down