From a680ce1c66a3786837582dde48bbc6e0bbeb5ef9 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 17 Oct 2023 11:36:12 -0400 Subject: [PATCH] API: Add participation key generation endpoint to algod API (#5781) --- cmd/algokey/part.go | 3 +- cmd/goal/account.go | 7 +- daemon/algod/api/algod.oas2.json | 80 +++ daemon/algod/api/algod.oas3.yml | 103 ++++ daemon/algod/api/server/router.go | 8 +- .../api/server/v2/generated/data/routes.go | 7 +- .../v2/generated/experimental/routes.go | 18 +- .../api/server/v2/generated/model/types.go | 12 + .../nonparticipating/private/routes.go | 11 +- .../nonparticipating/public/routes.go | 80 +-- .../generated/participating/private/routes.go | 462 ++++++++++-------- .../generated/participating/public/routes.go | 24 +- daemon/algod/api/server/v2/handlers.go | 48 +- .../algod/api/server/v2/test/handlers_test.go | 61 +++ daemon/algod/api/server/v2/test/helpers.go | 2 + data/account/participation.go | 6 + data/account/participationRegistry_test.go | 3 +- go.mod | 2 +- libgoal/participation.go | 84 +--- libgoal/participation/participation.go | 104 ++++ libgoal/participation/participation_test.go | 113 +++++ .../accountParticipationTransitions_test.go | 7 +- .../features/stateproofs/stateproofs_test.go | 7 +- .../transactions/onlineStatusChange_test.go | 7 +- 24 files changed, 889 insertions(+), 370 deletions(-) create mode 100644 libgoal/participation/participation.go create mode 100644 libgoal/participation/participation_test.go diff --git a/cmd/algokey/part.go b/cmd/algokey/part.go index 034b9821a5..76148ae1f6 100644 --- a/cmd/algokey/part.go +++ b/cmd/algokey/part.go @@ -19,7 +19,6 @@ package main import ( "encoding/base64" "fmt" - "math" "os" "github.com/spf13/cobra" @@ -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 diff --git a/cmd/goal/account.go b/cmd/goal/account.go index fcf4fbe09f..60b8ce72d2 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -41,6 +41,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/libgoal/participation" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/db" @@ -912,7 +913,11 @@ var addParticipationKeyCmd = &cobra.Command{ var err error var part algodAcct.Participation participationGen := func() { - part, _, err = client.GenParticipationKeysTo(accountAddress, roundFirstValid, roundLastValid, keyDilution, partKeyOutDir) + installFunc := func(keyPath string) error { + _, installErr := client.AddParticipationKey(keyPath) + return installErr + } + part, _, err = participation.GenParticipationKeysTo(accountAddress, roundFirstValid, roundLastValid, keyDilution, partKeyOutDir, installFunc) } util.RunFuncWithSpinningCursor(participationGen) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index b28e35abaa..b37f2001c4 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -934,6 +934,86 @@ } } }, + "/v2/participation/generate/{address}": { + "post": { + "tags": [ + "private", + "participating" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Generate and install participation keys to the node.", + "operationId": "GenerateParticipationKeys", + "parameters": [ + { + "type": "string", + "description": "An account public key", + "name": "address", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Key dilution for two-level participation keys (defaults to sqrt of validity window).", + "name": "dilution", + "in": "query" + }, + { + "type": "integer", + "description": "First round for participation key.", + "name": "first", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Last round for participation key.", + "name": "last", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "An empty JSON object is returned if the generation process was started. Currently no status is available.", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "503": { + "description": "Service Temporarily Unavailable", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + } + }, "/v2/participation/{participation-id}": { "delete": { "tags": [ diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index ecb205869d..c8c82e0d98 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -5379,6 +5379,109 @@ "x-codegen-request-body-name": "participationkey" } }, + "/v2/participation/generate/{address}": { + "post": { + "operationId": "GenerateParticipationKeys", + "parameters": [ + { + "description": "An account public key", + "in": "path", + "name": "address", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Key dilution for two-level participation keys (defaults to sqrt of validity window).", + "in": "query", + "name": "dilution", + "schema": { + "type": "integer" + } + }, + { + "description": "First round for participation key.", + "in": "query", + "name": "first", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Last round for participation key.", + "in": "query", + "name": "last", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "An empty JSON object is returned if the generation process was started. Currently no status is available." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Service Temporarily Unavailable" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Generate and install participation keys to the node.", + "tags": [ + "private", + "participating" + ] + } + }, "/v2/participation/{participation-id}": { "delete": { "description": "Delete a given participation key by ID", diff --git a/daemon/algod/api/server/router.go b/daemon/algod/api/server/router.go index b599194ab3..e380cb026d 100644 --- a/daemon/algod/api/server/router.go +++ b/daemon/algod/api/server/router.go @@ -19,6 +19,7 @@ package server import ( "fmt" + "golang.org/x/sync/semaphore" "net" "net/http" @@ -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...) diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index a17b5f1ca8..9fd389f273 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -314,9 +314,10 @@ var swaggerSpec = []string{ "4ru9Z2L8LnQF0x21KUbBuSdr2Q4/lKqH++v3vu9Rs1Pdi23Q5F+M4F+M4IiMQNeSJ49ocH9hgSWoXG5d", "TvMl7OIHw9syuOAnlYhlkF/sYBaujUGKV1x0eUUbqTV5/m5csybnfrCW5QIUc63yUaswInMr9MuGI/kz", "j9FPwV7v6kP78f0f4n5/Qbk/z50dtzU+qCwZyIYKKB92lvgXF/hvwwVsixxq93VKNJSlCs++Fnj2rSvG", - "1c3j1kU2kg90yhy2wnTn59MPnT+7CpFa1roQN8G3aFC33qCh7mAe1qr/9+kNZTqbC+lq5mHH7OHHGmh5", - "6hpk9H5ta1IPnmCh7eDHMDst+uspdUpE7Fnle8pHH/YV2dhTp8glXvKhof5xa9QKjUTIPRvz0Lv3hndh", - "K1zHWFubx/PTU8wVWAqlTycfpx969pDw4fuGXHwHt0kl2RpLlL//+P8DAAD//wKdD2Ya9wAA", + "1c3j1kU2kg90yhy2wnTn51NvQIjpkN03P3T+7KpOalnrQtwEs6Dp3fqNhlqGeVir/t+nN5TpbC6kq66H", + "vbWHH2ug5alrpdH7ta1ePXiCJbmDH8M8tuivp9SpG7Fnle8+H33YV3ljT53Kl3jJB5H6x635KzQnIZ9t", + "DEnv3hsuh01zHQturSPPT08xq2AplD6dfJx+6FlOwofvG8Lyvd4mlWRrLGb+/uP/DwAA//+EcjtHRPcA", + "AA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index 77f2fccbea..1888e71ef1 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -285,15 +285,15 @@ var swaggerSpec = []string{ "KLbY83Ds0zXEW3aw9ElvDKx7emxYEawkpzpf1tXxJ/wPUm8AtE3ld6w3/Bjtb8efOmt1nwdr7f7edg9b", "rFeiAA+cmM9tfchdn48/2X+DiWBTgWSGLbTpM5ytsTl0Z8XkZPJ90OjFEvKrCdaUQs8vPE1PHj2K5DkN", "ehF7uOmshMKczGePno3owIUOO7mwnmHHn/kVF9ecYFY8e9PXqxWVW+SgdC25Im9fETYn0J+CKT8D3i50", - "odDCUM9Klk+mkw56Pn52SLNZoI6xitK2xaX/ecvz6I/Dbe5kwEn8fPyp82f3rKhlrQtxHfRFWcsqCobz", - "mY+16v99fE2ZNtyTS6eCxRSHnTXQ8tjlTu792qYrHHzBHIzBj6HjcvTXY+oQOKmEihDje3odKEhPsbFl", - "MUDp7wTe1RNXbqWX6uN4k80YR7r4NGnryLcslv04lNEGb5WRONEi7bVUw1BojMeUgha5kf218GnIJyE/", - "pGUNn6OHCQ/Jox1rcW/QZFw9/G7CyMiKvqMF8WGsGXlDS4MVKMipe8g7S7NH+PGXg+6MW6dKc2QtL/N5", - "OvnmS+LnjBu2m5b+kjHTP/1y05+DXLMcyAWsKiGpZOWW/Mwbv9AbX48/IHFKml8hy9UQrHVikPS662oq", - "42GC3Sz7PmoUiN6QJeVF6QKrRI0FOg1loVZZBNYx86z4KhOVkAiATd8Dhc27oI7I+dKrmjC21Do1Y7Gc", - "NZSiQrUPJqWzk1COaeBxNeH13r3VjQxpDvECeOaukWwmiq2veS3ptd7YGKnBXdUUL49+7PNcsa+O50g0", - "8l5M/nMrf4XyzOTkQyDJfPj4+aP5JtfobvHhU8Cenxwfo1vrUih9PPk8/dRj3cOPHxuE+WJDk0qyNWbT", - "/fj5/wYAAP//423gG8XxAAA=", + "odDCUM9Klk+mkw56Pn52SLNZoI6xitK2xaX/ecvz6I/Dbe5kwEn8fOzfltj10m35qfNn91SpZa0LcR3M", + "glKZVSkMITMfa9X/+/iaMm34LJd4BcsuDjtroOWxy7Lc+7VNbDj4gtkagx9DF+for8fUoXpSCRUh2/f0", + "OlClnmJjy4yA0t8JvNUnrjBLLynI8SabMY4U9GnSVpxvmTH7cSjNDV41I5ui7drrs4ZB0xi5KQUtcqqw", + "3J9LWD4JOScta/gcPXZ4nB7tWIt7rSbjKud3U0tGVvQdLYgPeM3IG1oarEBBTt2T31maPeyPvxx0Z9y6", + "X5rDbbmez9PJN18SP2fcMOi09NeRmf7pl5v+HOSa5UAuYFUJSSUrt+Rn3niQ3vgi/QGJU9L8CpmzhmCt", + "u4Ok112nVBkPKOzm4/fxpUD0hiwpL0oXgiVqLOVpKAv1zyKwo5kHyNejqIREAGyiHyhshgZ1RM6XXimF", + "UajW/RnL6qyhFBUqiDB9nZ2EckwYj6sJH4Lu/W+kTXOIF8Azd41kM1FsfXVsSa/1xkZTDe6qpsx59GOf", + "O4t9ddxJopH3d/KfW0ktlHwmJx8CmefDx88fzTe5RseMD58CRv7k+BgdYJdC6ePJ5+mnHpMffvzYIMyX", + "JZpUkq0x7+7Hz/83AAD//8gL9x7v8QAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index 4be4676f9f..c30caa6c94 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -1485,6 +1485,18 @@ type GetTransactionGroupLedgerStateDeltasForRoundParams struct { // GetTransactionGroupLedgerStateDeltasForRoundParamsFormat defines parameters for GetTransactionGroupLedgerStateDeltasForRound. type GetTransactionGroupLedgerStateDeltasForRoundParamsFormat string +// GenerateParticipationKeysParams defines parameters for GenerateParticipationKeys. +type GenerateParticipationKeysParams struct { + // Dilution Key dilution for two-level participation keys (defaults to sqrt of validity window). + Dilution *uint64 `form:"dilution,omitempty" json:"dilution,omitempty"` + + // First First round for participation key. + First uint64 `form:"first" json:"first"` + + // Last Last round for participation key. + Last uint64 `form:"last" json:"last"` +} + // ShutdownNodeParams defines parameters for ShutdownNode. type ShutdownNodeParams struct { Timeout *uint64 `form:"timeout,omitempty" json:"timeout,omitempty"` diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 04a1236519..3d3c20de36 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -340,11 +340,12 @@ var swaggerSpec = []string{ "wOsIo4ljqhXM69K/O65KNre1ToUo/USqrirDceZUNZTlIluNBGufcTZDk5rngluPOAYR+1yK+BwTH26q", "K1Z1urC5oSpXhZMDuJd6uOm/1iA37a6vmBFF2+0dxGz8nizc4vEALLw70IFZ+JM92eiff8X/tS+tZ4/+", "9ukg8NkKLtgKRK3/rJfmub3B7nRpOhneJtw+1mt+jFFyxx86Gon7PNBIur+33cMW1ytRgFchxHxuq7hv", - "+3z8wf4bTATrCiRbAbflVN2v9uY4xmKem+HPG55Hfxyuo5OIMfHz8YfOn12VTS1rXYgbW1UqKoXgpUhL", - "V0UZ7fONrm9uNzdAm/mR/FA1149LZ0AoVtERtW6NMTbO170TbNxleE+ppfNLLBjHCdDvgbPYcuE0uJYV", - "mBsPTQw9icdB9r0oYCjxxK43B2PnimsIPFKc+87X3ZCdftyP/NE/Y52LQ+IwH2vV//v4hjJt5CKXghEx", - "OuysgZbHrt5K79c2xfngC+ZtD34MHztGfz2mXWrvmj5suf/Ex75dJPbV2QUSjXyksf/c2khDmyOSS2Nt", - "fPfe7DpWVnaU1JrQTo6P8enJUih9jPJl17wWfnzfbLQvCNhs+Mf3H/9/AAAA//9K9iBFafkAAA==", + "+3z8wf4bTATrCiRbAbflVN2v9uY4xmKem+HPG55Hfxyuo5OIMfHzsTdxxLTcbssPnT+7yp1a1roQN7b+", + "VFReweuTlq7eMlryG6uAuQfdAG2OSPJD1VxULvEBoVhvR9S6NdvYiGD3orBxrOGNppbOg7FgHCdADwnO", + "YguL0+ACV2DuRjRG9GQjB9n3ooChbBS7CB2MncuwOQqRMt53vhiHjPfjfgcFPTnWDTkkI/OxVv2/j28o", + "00aCcskaEaPDzhpoeewqs/R+bZOhD75ghvfgx/BZZPTXY9o9F10jidmyVMeBBSX21VkQEo18TLL/3FpT", + "Q+skkktjl3z33uw61mB2lNQa206Oj/GRylIofYySaNcQF35832y0Lx3YbPjH9x//fwAAAP//APnWz5P5", + "AAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 223956bf33..89202c3fb1 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -964,46 +964,46 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 49c6517c19..79a65f5721 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -27,6 +27,9 @@ type ServerInterface interface { // Add a participation key to the node // (POST /v2/participation) AddParticipationKey(ctx echo.Context) error + // Generate and install participation keys to the node. + // (POST /v2/participation/generate/{address}) + GenerateParticipationKeys(ctx echo.Context, address string, params GenerateParticipationKeysParams) error // Delete a given participation key by ID // (DELETE /v2/participation/{participation-id}) DeleteParticipationKeyByID(ctx echo.Context, participationId string) error @@ -65,6 +68,47 @@ func (w *ServerInterfaceWrapper) AddParticipationKey(ctx echo.Context) error { return err } +// GenerateParticipationKeys converts echo context to params. +func (w *ServerInterfaceWrapper) GenerateParticipationKeys(ctx echo.Context) error { + var err error + // ------------- Path parameter "address" ------------- + var address string + + err = runtime.BindStyledParameterWithLocation("simple", false, "address", runtime.ParamLocationPath, ctx.Param("address"), &address) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter address: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Parameter object where we will unmarshal all parameters from the context + var params GenerateParticipationKeysParams + // ------------- Optional query parameter "dilution" ------------- + + err = runtime.BindQueryParameter("form", true, false, "dilution", ctx.QueryParams(), ¶ms.Dilution) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter dilution: %s", err)) + } + + // ------------- Required query parameter "first" ------------- + + err = runtime.BindQueryParameter("form", true, true, "first", ctx.QueryParams(), ¶ms.First) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter first: %s", err)) + } + + // ------------- Required query parameter "last" ------------- + + err = runtime.BindQueryParameter("form", true, true, "last", ctx.QueryParams(), ¶ms.Last) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter last: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GenerateParticipationKeys(ctx, address, params) + return err +} + // DeleteParticipationKeyByID converts echo context to params. func (w *ServerInterfaceWrapper) DeleteParticipationKeyByID(ctx echo.Context) error { var err error @@ -149,6 +193,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/v2/participation", wrapper.GetParticipationKeys, m...) router.POST(baseURL+"/v2/participation", wrapper.AddParticipationKey, m...) + router.POST(baseURL+"/v2/participation/generate/:address", wrapper.GenerateParticipationKeys, m...) router.DELETE(baseURL+"/v2/participation/:participation-id", wrapper.DeleteParticipationKeyByID, m...) router.GET(baseURL+"/v2/participation/:participation-id", wrapper.GetParticipationKeyByID, m...) router.POST(baseURL+"/v2/participation/:participation-id", wrapper.AppendKeys, m...) @@ -158,213 +203,216 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "16auAQEA", + "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", + "CB++b8jjQ3OPODL5+P7j/wsAAP//bjFnIYoHAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 3003f8b27d..67e59f47b8 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -380,18 +380,18 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index bd40224f90..fe7fe97e3e 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -22,9 +22,11 @@ import ( "encoding/base64" "errors" "fmt" + "golang.org/x/sync/semaphore" "io" "math" "net/http" + "os" "strings" "time" @@ -48,6 +50,7 @@ import ( "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/simulation" + "github.com/algorand/go-algorand/libgoal/participation" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" "github.com/algorand/go-algorand/protocol" @@ -71,9 +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{} + Node NodeInterface + Log logging.Logger + Shutdown <-chan struct{} + KeygenLimiter *semaphore.Weighted } // LedgerForAPI describes the Ledger methods used by the v2 API. @@ -240,6 +244,44 @@ func (v2 *Handlers) GetParticipationKeys(ctx echo.Context) error { return ctx.JSON(http.StatusOK, response) } +func (v2 *Handlers) generateKeyHandler(address string, params model.GenerateParticipationKeysParams) error { + installFunc := func(path string) error { + bytes, err := os.ReadFile(path) + if err != nil { + return err + } + partKeyBinary := bytes + + if len(partKeyBinary) == 0 { + return fmt.Errorf("cannot install partkey '%s' is empty", partKeyBinary) + } + + partID, err := v2.Node.InstallParticipationKey(partKeyBinary) + v2.Log.Infof("Installed participation key %s", partID) + return err + } + _, _, err := participation.GenParticipationKeysTo(address, params.First, params.Last, nilToZero(params.Dilution), "", installFunc) + return err +} + +// 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.KeygenLimiter != nil && v2.KeygenLimiter.TryAcquire(1) { + go func() { + defer v2.KeygenLimiter.Release(1) + err := v2.generateKeyHandler(address, params) + if err != nil { + v2.Log.Warnf("Error generating participation keys: %v", err) + } + }() + } else { + err := fmt.Errorf("participation key generation already in progress") + return badRequest(ctx, err, err.Error(), v2.Log) + } + return ctx.String(http.StatusOK, "{}") +} + // AddParticipationKey Add a participation key to the node // (POST /v2/participation) func (v2 *Handlers) AddParticipationKey(ctx echo.Context) error { diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index afe074edab..d8e086ff32 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "golang.org/x/sync/semaphore" "io" "math" "net" @@ -2378,3 +2379,63 @@ func TestRouterRequestBody(t *testing.T) { e.ServeHTTP(rec, req) assert.Equal(t, http.StatusRequestEntityTooLarge, rec.Code) } + +func TestGeneratePartkeys(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + numAccounts := 1 + numTransactions := 1 + offlineAccounts := true + mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) + defer releasefunc() + dummyShutdownChan := make(chan struct{}) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) + handler := v2.Handlers{ + Node: mockNode, + Log: logging.Base(), + Shutdown: dummyShutdownChan, + KeygenLimiter: semaphore.NewWeighted(1), + } + e := echo.New() + + var addr basics.Address + addr[0] = 1 + + { + require.Len(t, mockNode.PartKeyBinary, 0) + req := httptest.NewRequest(http.MethodPost, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := handler.GenerateParticipationKeys(c, addr.String(), model.GenerateParticipationKeysParams{ + First: 1000, + Last: 2000, + }) + + require.NoError(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + + // Wait for keygen to complete + err = handler.KeygenLimiter.Acquire(context.Background(), 1) + require.NoError(t, err) + require.Greater(t, len(mockNode.PartKeyBinary), 0) + 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) + err := handler.KeygenLimiter.Acquire(context.Background(), 1) + require.NoError(t, err) + err = handler.GenerateParticipationKeys(c, addr.String(), model.GenerateParticipationKeysParams{ + First: 1000, + Last: 2000, + }) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, rec.Code) + } + +} diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index aedcede912..2f887d25a1 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -99,9 +99,11 @@ type mockNode struct { status node.StatusReport devmode bool timestampOffset *int64 + PartKeyBinary []byte } func (m *mockNode) InstallParticipationKey(partKeyBinary []byte) (account.ParticipationID, error) { + m.PartKeyBinary = partKeyBinary return account.ParticipationID{}, nil } diff --git a/data/account/participation.go b/data/account/participation.go index 376e9090fc..22130df6c1 100644 --- a/data/account/participation.go +++ b/data/account/participation.go @@ -20,6 +20,7 @@ import ( "context" "database/sql" "fmt" + "math" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -215,6 +216,11 @@ func (part PersistedParticipation) PersistNewParent() error { }) } +// 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))) +} + // FillDBWithParticipationKeys initializes the passed database with participation keys func FillDBWithParticipationKeys(store db.Accessor, address basics.Address, firstValid, lastValid basics.Round, keyDilution uint64) (part PersistedParticipation, err error) { if lastValid < firstValid { diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go index ef77283ce7..e0328bb7f8 100644 --- a/data/account/participationRegistry_test.go +++ b/data/account/participationRegistry_test.go @@ -23,7 +23,6 @@ import ( "encoding/binary" "errors" "fmt" - "math" "os" "path/filepath" "strconv" @@ -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 diff --git a/go.mod b/go.mod index b19e61cb69..87faed7d03 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 diff --git a/libgoal/participation.go b/libgoal/participation.go index f57629a362..302af5df94 100644 --- a/libgoal/participation.go +++ b/libgoal/participation.go @@ -18,15 +18,10 @@ package libgoal import ( "fmt" - "math" - "os" - "path/filepath" - - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/util/db" + "github.com/algorand/go-algorand/libgoal/participation" ) // chooseParticipation chooses which participation keys to use for going online @@ -59,83 +54,14 @@ func (c *Client) chooseParticipation(address basics.Address, round basics.Round) return } -func participationKeysPath(dataDir string, address basics.Address, firstValid, lastValid basics.Round) (string, error) { - // Build ///
...partkey - first := uint64(firstValid) - last := uint64(lastValid) - fileName := config.PartKeyFilename(address.String(), first, last) - return filepath.Join(dataDir, fileName), nil -} - // GenParticipationKeys creates a .partkey database for a given address, fills // it with keys, and installs it in the right place func (c *Client) GenParticipationKeys(address string, firstValid, lastValid, keyDilution uint64) (part account.Participation, filePath string, err error) { - return c.GenParticipationKeysTo(address, firstValid, lastValid, keyDilution, "") -} - -// GenParticipationKeysTo creates a .partkey database for a given address, fills -// it with keys, and saves it in the specified output directory. If the output -// directory is empty, the key will be installed. -func (c *Client) GenParticipationKeysTo(address string, firstValid, lastValid, keyDilution uint64, outDir string) (part account.Participation, filePath string, err error) { - - install := outDir == "" - - // Parse the address - parsedAddr, err := basics.UnmarshalChecksumAddress(address) - if err != nil { - return - } - - firstRound, lastRound := basics.Round(firstValid), basics.Round(lastValid) - - // If we are installing, generate in the temp dir - if install { - outDir = os.TempDir() - } - // Connect to the database - partKeyPath, err := participationKeysPath(outDir, parsedAddr, firstRound, lastRound) - if err != nil { - return - } - _, err = os.Stat(partKeyPath) - if err == nil { - err = fmt.Errorf("ParticipationKeys exist for the range %d to %d", firstRound, lastRound) - return - } else if !os.IsNotExist(err) { - err = fmt.Errorf("participation key file '%s' cannot be accessed : %w", partKeyPath, err) - return - } - - // If the key is being installed, remove it afterwards. - if install { - // Explicitly ignore any errors - defer func(name string) { - _ = os.Remove(name) - }(partKeyPath) - } - - partdb, err := db.MakeErasableAccessor(partKeyPath) - if err != nil { - return - } - - if keyDilution == 0 { - keyDilution = 1 + uint64(math.Sqrt(float64(lastRound-firstRound))) - } - - // Fill the database with new participation keys - newPart, err := account.FillDBWithParticipationKeys(partdb, parsedAddr, firstRound, lastRound, keyDilution) - part = newPart.Participation - partdb.Close() - - if err != nil { - return - } - - if install { - _, err = c.AddParticipationKey(partKeyPath) + installFunc := func(keyPath string) error { + _, err := c.AddParticipationKey(keyPath) + return err } - return part, partKeyPath, err + return participation.GenParticipationKeysTo(address, firstValid, lastValid, keyDilution, "", installFunc) } // ListParticipationKeys returns the available participation keys, diff --git a/libgoal/participation/participation.go b/libgoal/participation/participation.go new file mode 100644 index 0000000000..2d1a30e8c0 --- /dev/null +++ b/libgoal/participation/participation.go @@ -0,0 +1,104 @@ +// Copyright (C) 2019-2023 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 . + +package participation + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/account" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/util/db" +) + +func participationKeysPath(dataDir string, address basics.Address, firstValid, lastValid basics.Round) (string, error) { + // Build ///
...partkey + first := uint64(firstValid) + last := uint64(lastValid) + fileName := config.PartKeyFilename(address.String(), first, last) + return filepath.Join(dataDir, fileName), nil +} + +// GenParticipationKeysTo creates a .partkey database for a given address, fills +// it with keys, and saves it in the specified output directory. If the output +// 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 == "" + 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) + if err != nil { + return + } + + firstRound, lastRound := basics.Round(firstValid), basics.Round(lastValid) + + // If we are installing, generate in the temp dir + if install { + outDir = os.TempDir() + } + // Connect to the database + partKeyPath, err := participationKeysPath(outDir, parsedAddr, firstRound, lastRound) + if err != nil { + return + } + _, err = os.Stat(partKeyPath) + if err == nil { + err = fmt.Errorf("ParticipationKeys exist for the range %d to %d", firstRound, lastRound) + return + } else if !os.IsNotExist(err) { + err = fmt.Errorf("participation key file '%s' cannot be accessed : %w", partKeyPath, err) + return + } + + // If the key is being installed, remove it afterwards. + if install { + // Explicitly ignore any errors + defer func(name string) { + _ = os.Remove(name) + }(partKeyPath) + } + + partdb, err := db.MakeErasableAccessor(partKeyPath) + if err != nil { + return + } + + if keyDilution == 0 { + keyDilution = account.DefaultKeyDilution(firstRound, lastRound) + } + + // Fill the database with new participation keys + newPart, err := account.FillDBWithParticipationKeys(partdb, parsedAddr, firstRound, lastRound, keyDilution) + part = newPart.Participation + partdb.Close() + + if err != nil { + return + } + + if install { + err = installFunc(partKeyPath) + } + return part, partKeyPath, err +} diff --git a/libgoal/participation/participation_test.go b/libgoal/participation/participation_test.go new file mode 100644 index 0000000000..69565a7800 --- /dev/null +++ b/libgoal/participation/participation_test.go @@ -0,0 +1,113 @@ +// Copyright (C) 2019-2023 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 . + +package participation + +import ( + "github.com/algorand/go-algorand/data/account" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/test/partitiontest" + + "github.com/stretchr/testify/require" + + "testing" +) + +func TestGenParticipationKeysTo_Install(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + testcases := []struct { + name string + outDir string + installed bool + }{ + { + name: "install", + installed: true, + }, + { + name: "do not install", + outDir: t.TempDir(), + installed: false, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + var err error + var called bool + installFunc := func(keyPath string) error { + called = true + return nil + } + var addr basics.Address + addr[1] = 1 + + _, _, err = GenParticipationKeysTo(addr.String(), 1000, 2000, 0, tc.outDir, installFunc) + require.NoError(t, err) + require.Equal(t, tc.installed, called, "The install function should only be called when outDir is not set.") + }) + } +} + +func TestGenParticipationKeysTo_DefaultKeyDilution(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var addr basics.Address + addr[1] = 1 + first := uint64(1000) + last := uint64(2000) + + testcases := []struct { + name string + dilution uint64 + expected uint64 + }{ + { + name: "default", + dilution: 0, + expected: account.DefaultKeyDilution(basics.Round(first), basics.Round(last)), + }, { + name: "override", + dilution: 5, + expected: 5, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + part, _, err := GenParticipationKeysTo(addr.String(), first, last, tc.dilution, t.TempDir(), nil) + require.NoError(t, err) + require.Equal(t, tc.expected, part.KeyDilution) + }) + } +} + +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") +} diff --git a/test/e2e-go/features/participation/accountParticipationTransitions_test.go b/test/e2e-go/features/participation/accountParticipationTransitions_test.go index e9a4e5735f..6752af841b 100644 --- a/test/e2e-go/features/participation/accountParticipationTransitions_test.go +++ b/test/e2e-go/features/participation/accountParticipationTransitions_test.go @@ -21,6 +21,7 @@ package participation // deterministic. import ( + "errors" "fmt" "path/filepath" "testing" @@ -32,6 +33,7 @@ import ( "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/libgoal/participation" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -39,7 +41,10 @@ import ( // installParticipationKey generates a new key for a given account and installs it with the client. 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... - part, filePath, err := client.GenParticipationKeysTo(addr, firstValid, lastValid, 100, t.TempDir()) + installFunc := func(keyPath string) error { + 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) require.NotNil(t, filePath) require.Equal(t, addr, part.Parent.String()) diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index 4d4bc9f51b..b58669a6a6 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -18,6 +18,7 @@ package stateproofs import ( "bytes" + "errors" "fmt" "os" "path/filepath" @@ -41,6 +42,7 @@ import ( "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/libgoal/participation" "github.com/algorand/go-algorand/nodecontrol" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" @@ -677,7 +679,10 @@ func installParticipationKey(t *testing.T, client libgoal.Client, addr string, f defer os.RemoveAll(dir) // Install overlapping participation keys... - part, filePath, err := client.GenParticipationKeysTo(addr, firstValid, lastValid, 100, dir) + installFunc := func(keyPath string) error { + 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) require.NotNil(t, filePath) require.Equal(t, addr, part.Parent.String()) diff --git a/test/e2e-go/features/transactions/onlineStatusChange_test.go b/test/e2e-go/features/transactions/onlineStatusChange_test.go index a8a9ceb013..7a9e6b3467 100644 --- a/test/e2e-go/features/transactions/onlineStatusChange_test.go +++ b/test/e2e-go/features/transactions/onlineStatusChange_test.go @@ -17,6 +17,7 @@ package transactions import ( + "errors" "fmt" "path/filepath" "testing" @@ -24,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/libgoal/participation" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -171,7 +173,10 @@ func TestCloseOnError(t *testing.T) { _, curRound := fixture.GetBalanceAndRound(initiallyOnline) var partkeyFile string - _, partkeyFile, err = client.GenParticipationKeysTo(initiallyOffline, 0, curRound+1000, 0, t.TempDir()) + installFunc := func(keyPath string) error { + 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) a.NoError(err) // make a participation key for initiallyOffline