diff options
author | Will Winder <wwinder.unh@gmail.com> | 2022-01-06 10:09:30 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-06 10:09:30 -0500 |
commit | cb1650ee321b8571d5b29dac1e35217ab7a11aa1 (patch) | |
tree | f077a9ec1f94cafd2c4c62c86c2d2660713a3e82 | |
parent | 7248b958706a3aa401a7713827eb63324f236ddc (diff) |
PKI State Proof Incremental Key Loading (#3281)
## Summary
Followup to #3261 (contained in diff).
Use the new key loading routine from the REST API.
## Test Plan
New unit tests.
-rw-r--r-- | daemon/algod/api/algod.oas2.json | 100 | ||||
-rw-r--r-- | daemon/algod/api/algod.oas3.yml | 137 | ||||
-rw-r--r-- | daemon/algod/api/server/v2/generated/private/routes.go | 313 | ||||
-rw-r--r-- | daemon/algod/api/server/v2/generated/private/types.go | 2 | ||||
-rw-r--r-- | daemon/algod/api/server/v2/generated/routes.go | 346 | ||||
-rw-r--r-- | daemon/algod/api/server/v2/generated/types.go | 2 | ||||
-rw-r--r-- | daemon/algod/api/server/v2/handlers.go | 29 | ||||
-rw-r--r-- | daemon/algod/api/server/v2/test/handlers_test.go | 111 | ||||
-rw-r--r-- | daemon/algod/api/server/v2/test/helpers.go | 12 | ||||
-rw-r--r-- | data/account/msgp_gen.go | 160 | ||||
-rw-r--r-- | data/account/msgp_gen_test.go | 60 | ||||
-rw-r--r-- | data/account/participationRegistry.go | 36 | ||||
-rw-r--r-- | node/node.go | 25 |
13 files changed, 967 insertions, 366 deletions
diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index c6e2816d8..3e258d36f 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -494,7 +494,7 @@ } }, "404": { - "description": "Application Not Found", + "description": "Participation Key Not Found", "schema": { "$ref": "#/definitions/ErrorResponse" } @@ -553,6 +553,12 @@ "$ref": "#/definitions/ErrorResponse" } }, + "404": { + "description": "Participation Key Not Found", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, "500": { "description": "Internal Error", "schema": { @@ -576,14 +582,14 @@ "tags": [ "private" ], - "description": "Delete a given participation key by id", + "description": "Delete a given participation key by ID", "produces": [ "application/json" ], "schemes": [ "http" ], - "summary": "Delete a given participation key by id", + "summary": "Delete a given participation key by ID", "operationId": "DeleteParticipationKeyByID", "responses": { "200": { @@ -601,6 +607,12 @@ "$ref": "#/definitions/ErrorResponse" } }, + "404": { + "description": "Participation Key Not Found", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, "500": { "description": "Internal Error", "schema": { @@ -616,14 +628,14 @@ "tags": [ "private" ], - "description": "Given a participation id, return information about that participation key", + "description": "Given a participation ID, return information about that participation key", "produces": [ "application/json" ], "schemes": [ "http" ], - "summary": "Get participation key info by id", + "summary": "Get participation key info given a participation ID", "operationId": "GetParticipationKeyByID", "responses": { "200": { @@ -643,7 +655,69 @@ } }, "404": { - "description": "Application Not Found", + "description": "Participation Key Not Found", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + }, + "post": { + "tags": [ + "private" + ], + "description": "Given a participation ID, append state proof keys to a particular set of participation keys", + "consumes": [ + "application/msgpack" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "description": "The state proof keys to add to an existing participation ID", + "name": "keymap", + "in": "body", + "required": true, + "schema": { + "type": "string", + "format": "binary" + } + } + ], + "schemes": [ + "http" + ], + "summary": "Append state proof keys to a participation key", + "operationId": "AppendKeys", + "responses": { + "200": { + "description": "OK", + "$ref": "#/responses/ParticipationKeyResponse" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Participation Key Not Found", "schema": { "$ref": "#/definitions/ErrorResponse" } @@ -934,7 +1008,7 @@ }, "/v2/transactions/pending/{txid}": { "get": { - "description": "Given a transaction id of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round \u003e 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.\n", + "description": "Given a transaction ID of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round \u003e 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.\n", "produces": [ "application/json", "application/msgpack" @@ -948,7 +1022,7 @@ { "pattern": "[A-Z0-9]+", "type": "string", - "description": "A transaction id", + "description": "A transaction ID", "name": "txid", "in": "path", "required": true @@ -959,7 +1033,7 @@ ], "responses": { "200": { - "description": "Given a transaction id of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round \u003e 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\n\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.", + "description": "Given a transaction ID of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round \u003e 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\n\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.", "schema": { "$ref": "#/definitions/PendingTransactionResponse" } @@ -998,7 +1072,7 @@ }, "/v2/applications/{application-id}": { "get": { - "description": "Given a application id, it returns application information including creator, approval and clear programs, global and local schemas, and global state.", + "description": "Given a application ID, it returns application information including creator, approval and clear programs, global and local schemas, and global state.", "produces": [ "application/json" ], @@ -1061,7 +1135,7 @@ }, "/v2/assets/{asset-id}": { "get": { - "description": "Given a asset id, it returns asset information including creator, name, total supply and special addresses.", + "description": "Given a asset ID, it returns asset information including creator, name, total supply and special addresses.", "produces": [ "application/json" ], @@ -2576,7 +2650,7 @@ } }, "ParticipationKeyResponse": { - "description": "A detailed description of a participation id", + "description": "A detailed description of a participation ID", "schema": { "$ref": "#/definitions/ParticipationKey" } @@ -2593,7 +2667,7 @@ ], "properties": { "partId": { - "description": "encoding of the participation id.", + "description": "encoding of the participation ID.", "type": "string" } } diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 86ac76a5d..538136733 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -465,7 +465,7 @@ } } }, - "description": "A detailed description of a participation id" + "description": "A detailed description of a participation ID" }, "ParticipationKeysResponse": { "content": { @@ -516,7 +516,7 @@ "schema": { "properties": { "partId": { - "description": "encoding of the participation id.", + "description": "encoding of the participation ID.", "type": "string" } }, @@ -1898,7 +1898,7 @@ }, "/v2/applications/{application-id}": { "get": { - "description": "Given a application id, it returns application information including creator, approval and clear programs, global and local schemas, and global state.", + "description": "Given a application ID, it returns application information including creator, approval and clear programs, global and local schemas, and global state.", "operationId": "GetApplicationByID", "parameters": [ { @@ -1972,7 +1972,7 @@ }, "/v2/assets/{asset-id}": { "get": { - "description": "Given a asset id, it returns asset information including creator, name, total supply and special addresses.", + "description": "Given a asset ID, it returns asset information including creator, name, total supply and special addresses.", "operationId": "GetAssetByID", "parameters": [ { @@ -2582,7 +2582,7 @@ } } }, - "description": "Application Not Found" + "description": "Participation Key Not Found" }, "500": { "content": { @@ -2625,7 +2625,7 @@ "schema": { "properties": { "partId": { - "description": "encoding of the participation id.", + "description": "encoding of the participation ID.", "type": "string" } }, @@ -2658,6 +2658,16 @@ }, "description": "Invalid API Token" }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Participation Key Not Found" + }, "500": { "content": { "application/json": { @@ -2692,7 +2702,7 @@ }, "/v2/participation/{participation-id}": { "delete": { - "description": "Delete a given participation key by id", + "description": "Delete a given participation key by ID", "operationId": "DeleteParticipationKeyByID", "parameters": [ { @@ -2729,6 +2739,16 @@ }, "description": "Invalid API Token" }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Participation Key Not Found" + }, "500": { "content": { "application/json": { @@ -2744,13 +2764,13 @@ "description": "Unknown Error" } }, - "summary": "Delete a given participation key by id", + "summary": "Delete a given participation key by ID", "tags": [ "private" ] }, "get": { - "description": "Given a participation id, return information about that participation key", + "description": "Given a participation ID, return information about that participation key", "operationId": "GetParticipationKeyByID", "parameters": [ { @@ -2771,7 +2791,7 @@ } } }, - "description": "A detailed description of a participation id" + "description": "A detailed description of a participation ID" }, "400": { "content": { @@ -2801,7 +2821,7 @@ } } }, - "description": "Application Not Found" + "description": "Participation Key Not Found" }, "500": { "content": { @@ -2818,10 +2838,97 @@ "description": "Unknown Error" } }, - "summary": "Get participation key info by id", + "summary": "Get participation key info given a participation ID", "tags": [ "private" ] + }, + "post": { + "description": "Given a participation ID, append state proof keys to a particular set of participation keys", + "operationId": "AppendKeys", + "parameters": [ + { + "in": "path", + "name": "participation-id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/msgpack": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "The state proof keys to add to an existing participation ID", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParticipationKey" + } + } + }, + "description": "A detailed description of a participation ID" + }, + "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" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Participation Key Not Found" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Append state proof keys to a participation key", + "tags": [ + "private" + ], + "x-codegen-request-body-name": "keymap" } }, "/v2/shutdown": { @@ -3604,11 +3711,11 @@ }, "/v2/transactions/pending/{txid}": { "get": { - "description": "Given a transaction id of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round > 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.\n", + "description": "Given a transaction ID of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round > 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.\n", "operationId": "PendingTransactionInformation", "parameters": [ { - "description": "A transaction id", + "description": "A transaction ID", "in": "path", "name": "txid", "required": true, @@ -3644,7 +3751,7 @@ } } }, - "description": "Given a transaction id of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round > 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\n\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error." + "description": "Given a transaction ID of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round > 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\n\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error." }, "400": { "content": { diff --git a/daemon/algod/api/server/v2/generated/private/routes.go b/daemon/algod/api/server/v2/generated/private/routes.go index 56baf1f2b..abfd37f7d 100644 --- a/daemon/algod/api/server/v2/generated/private/routes.go +++ b/daemon/algod/api/server/v2/generated/private/routes.go @@ -29,12 +29,15 @@ type ServerInterface interface { // Add a participation key to the node // (POST /v2/participation) AddParticipationKey(ctx echo.Context) error - // Delete a given participation key by id + // Delete a given participation key by ID // (DELETE /v2/participation/{participation-id}) DeleteParticipationKeyByID(ctx echo.Context, participationId string) error - // Get participation key info by id + // Get participation key info given a participation ID // (GET /v2/participation/{participation-id}) GetParticipationKeyByID(ctx echo.Context, participationId string) error + // Append state proof keys to a participation key + // (POST /v2/participation/{participation-id}) + AppendKeys(ctx echo.Context, participationId string) error // (POST /v2/shutdown) ShutdownNode(ctx echo.Context, params ShutdownNodeParams) error @@ -211,6 +214,36 @@ func (w *ServerInterfaceWrapper) GetParticipationKeyByID(ctx echo.Context) error return err } +// AppendKeys converts echo context to params. +func (w *ServerInterfaceWrapper) AppendKeys(ctx echo.Context) error { + + validQueryParams := map[string]bool{ + "pretty": true, + } + + // Check for unknown query parameters. + for name, _ := range ctx.QueryParams() { + if _, ok := validQueryParams[name]; !ok { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unknown parameter detected: %s", name)) + } + } + + var err error + // ------------- Path parameter "participation-id" ------------- + var participationId string + + err = runtime.BindStyledParameter("simple", false, "participation-id", ctx.Param("participation-id"), &participationId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter participation-id: %s", err)) + } + + ctx.Set("api_key.Scopes", []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.AppendKeys(ctx, participationId) + return err +} + // ShutdownNode converts echo context to params. func (w *ServerInterfaceWrapper) ShutdownNode(ctx echo.Context) error { @@ -270,6 +303,7 @@ func RegisterHandlers(router interface { router.POST("/v2/participation", wrapper.AddParticipationKey, m...) router.DELETE("/v2/participation/:participation-id", wrapper.DeleteParticipationKeyByID, m...) router.GET("/v2/participation/:participation-id", wrapper.GetParticipationKeyByID, m...) + router.POST("/v2/participation/:participation-id", wrapper.AppendKeys, m...) router.POST("/v2/shutdown", wrapper.ShutdownNode, m...) } @@ -277,143 +311,144 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "GUfr6/fX/xsAAP//tpthUSegAAA=", + "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", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/private/types.go b/daemon/algod/api/server/v2/generated/private/types.go index 9e6d5685b..301578ade 100644 --- a/daemon/algod/api/server/v2/generated/private/types.go +++ b/daemon/algod/api/server/v2/generated/private/types.go @@ -640,7 +640,7 @@ type PendingTransactionsResponse struct { // PostParticipationResponse defines model for PostParticipationResponse. type PostParticipationResponse struct { - // encoding of the participation id. + // encoding of the participation ID. PartId string `json:"partId"` } diff --git a/daemon/algod/api/server/v2/generated/routes.go b/daemon/algod/api/server/v2/generated/routes.go index 4d3d7bd11..83946f45b 100644 --- a/daemon/algod/api/server/v2/generated/routes.go +++ b/daemon/algod/api/server/v2/generated/routes.go @@ -617,179 +617,179 @@ func RegisterHandlers(router interface { var swaggerSpec = []string{ "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", - "pZTsHHMnfrj8fwEAAP///pnN5DLyAAA=", + "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", + "XErJzjF34ofL/xcAAP//K/9iFjLyAAA=", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/types.go b/daemon/algod/api/server/v2/generated/types.go index 41d33126d..6e1343a7c 100644 --- a/daemon/algod/api/server/v2/generated/types.go +++ b/daemon/algod/api/server/v2/generated/types.go @@ -640,7 +640,7 @@ type PendingTransactionsResponse struct { // PostParticipationResponse defines model for PostParticipationResponse. type PostParticipationResponse struct { - // encoding of the participation id. + // encoding of the participation ID. PartId string `json:"partId"` } diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 278fe7dbf..da20f66fa 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -73,6 +73,7 @@ type NodeInterface interface { ListParticipationKeys() ([]account.ParticipationRecord, error) GetParticipationKey(account.ParticipationID) (account.ParticipationRecord, error) RemoveParticipationKey(account.ParticipationID) error + AppendParticipationKeys(id account.ParticipationID, keys account.StateProofKeys) error } func roundToPtrOrNil(value basics.Round) *uint64 { @@ -140,7 +141,6 @@ func (v2 *Handlers) GetParticipationKeys(ctx echo.Context) error { // AddParticipationKey Add a participation key to the node // (POST /v2/participation) func (v2 *Handlers) AddParticipationKey(ctx echo.Context) error { - buf := new(bytes.Buffer) _, err := buf.ReadFrom(ctx.Request().Body) if err != nil { @@ -212,6 +212,33 @@ func (v2 *Handlers) GetParticipationKeyByID(ctx echo.Context, participationID st return ctx.JSON(http.StatusOK, response) } +// AppendKeys Append state proof keys to a participation key +// (POST /v2/participation/{participation-id}) +func (v2 *Handlers) AppendKeys(ctx echo.Context, participationID string) error { + decodedParticipationID, err := account.ParseParticipationID(participationID) + if err != nil { + return badRequest(ctx, err, err.Error(), v2.Log) + } + + var keys account.StateProofKeys + dec := protocol.NewDecoder(ctx.Request().Body) + err = dec.Decode(&keys) + if err != nil { + err = fmt.Errorf("unable to parse keys from body: %w", err) + return badRequest(ctx, err, err.Error(), v2.Log) + } + if len(keys) == 0 { + err = errors.New("empty request, please attach keys to request body") + return badRequest(ctx, err, err.Error(), v2.Log) + } + + err = v2.Node.AppendParticipationKeys(decodedParticipationID, keys) + if err != nil { + return internalError(ctx, err, err.Error(), v2.Log) + } + return nil +} + // ShutdownNode shuts down the node. // (POST /v2/shutdown) func (v2 *Handlers) ShutdownNode(ctx echo.Context, params private.ShutdownNodeParams) error { diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index d683d8616..0b416eaa5 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -522,7 +522,7 @@ func tealCompileTest(t *testing.T, bytesToUse []byte, expectedCode int, enableDe mockNode := makeMockNode(mockLedger, t.Name(), nil) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ - Node: &mockNode, + Node: mockNode, Log: logging.Base(), Shutdown: dummyShutdownChan, } @@ -562,7 +562,7 @@ func tealDryrunTest( mockNode := makeMockNode(mockLedger, t.Name(), nil) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ - Node: &mockNode, + Node: mockNode, Log: logging.Base(), Shutdown: dummyShutdownChan, } @@ -672,3 +672,110 @@ func TestTealDryrun(t *testing.T) { tealDryrunTest(t, &gdr, "msgp", 200, "REJECT", true) tealDryrunTest(t, &gdr, "json", 404, "", false) } + +func TestAppendParticipationKeys(t *testing.T) { + partitiontest.PartitionTest(t) + + mockLedger, _, _, _, releasefunc := testingenv(t, 1, 1, true) + defer releasefunc() + mockNode := makeMockNode(mockLedger, t.Name(), nil) + handler := v2.Handlers{ + Node: mockNode, + Log: logging.Base(), + Shutdown: make(chan struct{}), + } + + id := account.ParticipationID{} + id[0] = 10 + + t.Run("Happy path", func(t *testing.T) { + // Create test object to append. + keys := make(account.StateProofKeys) + keys[100] = []byte{100} + keys[101] = []byte{101} + keyBytes := protocol.Encode(keys) + + // Put keys in the body. + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(keyBytes)) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + // Call handler with request. + err := handler.AppendKeys(c, id.String()) + + // Verify that request was properly received and deserialized. + require.NoError(t, err) + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, id, mockNode.id) + require.Len(t, mockNode.keys, 2) + require.Equal(t, mockNode.keys[100], keys[100]) + require.Equal(t, mockNode.keys[101], keys[101]) + }) + + t.Run("Invalid body", func(t *testing.T) { + // Create request with bogus bytes in the body + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader([]byte{0x99, 0x88, 0x77})) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + // Call handler with request. + err := handler.AppendKeys(c, id.String()) + + // Verify that request was properly received and deserialized. + require.NoError(t, err) + require.Equal(t, http.StatusBadRequest, rec.Code) + require.Contains(t, rec.Body.String(), "unable to parse keys from body: msgpack decode error") + }) + + t.Run("Empty body", func(t *testing.T) { + // Create test object with no keys to append. + keys := make(account.StateProofKeys) + keyBytes := protocol.Encode(keys) + + // Put keys in the body. + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(keyBytes)) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + // Call handler with request. + err := handler.AppendKeys(c, id.String()) + + // Verify that request was properly received and deserialized. + require.NoError(t, err) + require.Equal(t, http.StatusBadRequest, rec.Code) + require.Contains(t, rec.Body.String(), "empty request, please attach keys to request body") + }) + + t.Run("Internal error", func(t *testing.T) { + // Create mock node with an error. + expectedErr := errors.New("expected error") + mockNode := makeMockNode(mockLedger, t.Name(), expectedErr) + handler := v2.Handlers{ + Node: mockNode, + Log: logging.Base(), + Shutdown: make(chan struct{}), + } + + keys := make(account.StateProofKeys) + keys[100] = []byte{100} + keys[101] = []byte{101} + keyBytes := protocol.Encode(keys) + + // Put keys in the body. + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(keyBytes)) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + // Call handler with request. + err := handler.AppendKeys(c, id.String()) + + // Verify that request was properly received and deserialized. + require.NoError(t, err) + require.Equal(t, http.StatusInternalServerError, rec.Code) + require.Contains(t, rec.Body.String(), expectedErr.Error()) + }) +} diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index 60de7aaf7..bd5768fe1 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -85,6 +85,8 @@ type mockNode struct { genesisID string config config.Local err error + id account.ParticipationID + keys account.StateProofKeys } func (m mockNode) InstallParticipationKey(partKeyBinary []byte) (account.ParticipationID, error) { @@ -103,8 +105,14 @@ func (m mockNode) RemoveParticipationKey(id account.ParticipationID) error { panic("implement me") } -func makeMockNode(ledger *data.Ledger, genesisID string, nodeError error) mockNode { - return mockNode{ +func (m *mockNode) AppendParticipationKeys(id account.ParticipationID, keys account.StateProofKeys) error { + m.id = id + m.keys = keys + return m.err +} + +func makeMockNode(ledger *data.Ledger, genesisID string, nodeError error) *mockNode { + return &mockNode{ ledger: ledger, genesisID: genesisID, config: config.GetDefaultLocal(), diff --git a/data/account/msgp_gen.go b/data/account/msgp_gen.go index 8f6a96fd7..b253f2122 100644 --- a/data/account/msgp_gen.go +++ b/data/account/msgp_gen.go @@ -3,6 +3,8 @@ package account // Code generated by github.com/algorand/msgp DO NOT EDIT. import ( + "sort" + "github.com/algorand/msgp/msgp" ) @@ -15,6 +17,22 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// StateProofKey +// |-----> MarshalMsg +// |-----> CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> Msgsize +// |-----> MsgIsZero +// +// StateProofKeys +// |-----> MarshalMsg +// |-----> CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> Msgsize +// |-----> MsgIsZero +// // MarshalMsg implements msgp.Marshaler func (z *ParticipationKeyIdentity) MarshalMsg(b []byte) (o []byte) { @@ -236,3 +254,145 @@ func (z *ParticipationKeyIdentity) Msgsize() (s int) { func (z *ParticipationKeyIdentity) MsgIsZero() bool { return ((*z).Parent.MsgIsZero()) && ((*z).VRFSK.MsgIsZero()) && ((*z).VoteID.MsgIsZero()) && ((*z).FirstValid.MsgIsZero()) && ((*z).LastValid.MsgIsZero()) && ((*z).KeyDilution == 0) } + +// MarshalMsg implements msgp.Marshaler +func (z StateProofKey) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + o = msgp.AppendBytes(o, []byte(z)) + return +} + +func (_ StateProofKey) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(StateProofKey) + if !ok { + _, ok = (z).(*StateProofKey) + } + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *StateProofKey) UnmarshalMsg(bts []byte) (o []byte, err error) { + { + var zb0001 []byte + zb0001, bts, err = msgp.ReadBytesBytes(bts, []byte((*z))) + if err != nil { + err = msgp.WrapError(err) + return + } + (*z) = StateProofKey(zb0001) + } + o = bts + return +} + +func (_ *StateProofKey) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofKey) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z StateProofKey) Msgsize() (s int) { + s = msgp.BytesPrefixSize + len([]byte(z)) + return +} + +// MsgIsZero returns whether this is a zero value +func (z StateProofKey) MsgIsZero() bool { + return len(z) == 0 +} + +// MarshalMsg implements msgp.Marshaler +func (z StateProofKeys) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + if z == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len(z))) + } + za0001_keys := make([]uint64, 0, len(z)) + for za0001 := range z { + za0001_keys = append(za0001_keys, za0001) + } + sort.Sort(SortUint64(za0001_keys)) + for _, za0001 := range za0001_keys { + za0002 := z[za0001] + _ = za0002 + o = msgp.AppendUint64(o, za0001) + o = msgp.AppendBytes(o, []byte(za0002)) + } + return +} + +func (_ StateProofKeys) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(StateProofKeys) + if !ok { + _, ok = (z).(*StateProofKeys) + } + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *StateProofKeys) UnmarshalMsg(bts []byte) (o []byte, err error) { + var zb0003 int + var zb0004 bool + zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0003 > 1000 { + err = msgp.ErrOverflow(uint64(zb0003), uint64(1000)) + err = msgp.WrapError(err) + return + } + if zb0004 { + (*z) = nil + } else if (*z) == nil { + (*z) = make(StateProofKeys, zb0003) + } + for zb0003 > 0 { + var zb0001 uint64 + var zb0002 StateProofKey + zb0003-- + zb0001, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + { + var zb0005 []byte + zb0005, bts, err = msgp.ReadBytesBytes(bts, []byte(zb0002)) + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + zb0002 = StateProofKey(zb0005) + } + (*z)[zb0001] = zb0002 + } + o = bts + return +} + +func (_ *StateProofKeys) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofKeys) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z StateProofKeys) Msgsize() (s int) { + s = msgp.MapHeaderSize + if z != nil { + for za0001, za0002 := range z { + _ = za0001 + _ = za0002 + s += 0 + msgp.Uint64Size + msgp.BytesPrefixSize + len([]byte(za0002)) + } + } + return +} + +// MsgIsZero returns whether this is a zero value +func (z StateProofKeys) MsgIsZero() bool { + return len(z) == 0 +} diff --git a/data/account/msgp_gen_test.go b/data/account/msgp_gen_test.go index a8927e790..9bde0e5d8 100644 --- a/data/account/msgp_gen_test.go +++ b/data/account/msgp_gen_test.go @@ -71,3 +71,63 @@ func BenchmarkUnmarshalParticipationKeyIdentity(b *testing.B) { } } } + +func TestMarshalUnmarshalStateProofKeys(t *testing.T) { + partitiontest.PartitionTest(t) + v := StateProofKeys{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingStateProofKeys(t *testing.T) { + protocol.RunEncodingTest(t, &StateProofKeys{}) +} + +func BenchmarkMarshalMsgStateProofKeys(b *testing.B) { + v := StateProofKeys{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgStateProofKeys(b *testing.B) { + v := StateProofKeys{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalStateProofKeys(b *testing.B) { + v := StateProofKeys{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index dba96d965..192abcbdb 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -82,16 +82,26 @@ type ParticipationRecord struct { Voting *crypto.OneTimeSignatureSecrets } -// StateProofKey is a placeholder for the real state proof key type. -// PKI TODO: Replace this with a real object. -type StateProofKey []byte +type ( + // StateProofKey is a placeholder for the real state proof key type. + // PKI TODO: Replace this with a real object. + StateProofKey []byte -// ParticipationRecordForRound adds in the per-round state proof key. -type ParticipationRecordForRound struct { - ParticipationRecord + // StateProofKeys are a map of StateProofKeys. + //msgp:allocbound StateProofKeys 1000 + StateProofKeys map[uint64]StateProofKey - StateProof StateProofKey -} + // SortUint64 implements sorting by uint64 keys for + // canonical encoding of maps in msgpack format. + SortUint64 = basics.SortUint64 + + // ParticipationRecordForRound adds in the per-round state proof key. + ParticipationRecordForRound struct { + ParticipationRecord + + StateProof StateProofKey + } +) // IsZero returns true if the object contains zero values. func (r ParticipationRecordForRound) IsZero() bool { @@ -178,7 +188,7 @@ type ParticipationRegistry interface { // AppendKeys appends state proof keys to an existing Participation record. Keys can only be appended // once, an error will occur when the data is flushed when inserting a duplicate key. - AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error + AppendKeys(id ParticipationID, keys StateProofKeys) error // Delete removes a record from storage. Delete(id ParticipationID) error @@ -363,7 +373,7 @@ type updatingParticipationRecord struct { type partDBWriteRecord struct { insertID ParticipationID insert Participation - keys map[uint64]StateProofKey + keys StateProofKeys registerUpdated map[ParticipationID]updatingParticipationRecord @@ -491,7 +501,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID) return err } -func (db *participationDB) appendKeysInner(id ParticipationID, keys map[uint64]StateProofKey) error { +func (db *participationDB) appendKeysInner(id ParticipationID, keys StateProofKeys) error { err := db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { // Fetch primary key var pk int @@ -690,7 +700,7 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err return } -func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error { +func (db *participationDB) AppendKeys(id ParticipationID, keys StateProofKeys) error { db.mutex.Lock() defer db.mutex.Unlock() @@ -698,7 +708,7 @@ func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64]StateP return ErrParticipationIDNotFound } - keyCopy := make(map[uint64]StateProofKey, len(keys)) + keyCopy := make(StateProofKeys, len(keys)) for k, v := range keys { keyCopy[k] = v // PKI TODO: Deep copy? } diff --git a/node/node.go b/node/node.go index 8d497089c..7132a2bc3 100644 --- a/node/node.go +++ b/node/node.go @@ -786,23 +786,23 @@ func (node *AlgorandFullNode) ListParticipationKeys() (partKeys []account.Partic } // GetParticipationKey retries the information of a participation id from the node -func (node *AlgorandFullNode) GetParticipationKey(partKey account.ParticipationID) (account.ParticipationRecord, error) { - rval := node.accountManager.Registry().Get(partKey) +func (node *AlgorandFullNode) GetParticipationKey(partKeyID account.ParticipationID) (account.ParticipationRecord, error) { + rval := node.accountManager.Registry().Get(partKeyID) if rval.IsZero() { return account.ParticipationRecord{}, account.ErrParticipationIDNotFound } - return node.accountManager.Registry().Get(partKey), nil + return rval, nil } // RemoveParticipationKey given a participation id, remove the records from the node -func (node *AlgorandFullNode) RemoveParticipationKey(partKey account.ParticipationID) error { +func (node *AlgorandFullNode) RemoveParticipationKey(partKeyID account.ParticipationID) error { // Need to remove the file and then remove the entry in the registry // Let's first get the recorded information from the registry so we can lookup the file - partRecord := node.accountManager.Registry().Get(partKey) + partRecord := node.accountManager.Registry().Get(partKeyID) if partRecord.IsZero() { return account.ErrParticipationIDNotFound @@ -815,7 +815,7 @@ func (node *AlgorandFullNode) RemoveParticipationKey(partKey account.Participati filename := config.PartKeyFilename(partRecord.ParticipationID.String(), uint64(partRecord.FirstValid), uint64(partRecord.LastValid)) fullyQualifiedFilename := filepath.Join(outDir, filepath.Base(filename)) - err := node.accountManager.Registry().Delete(partKey) + err := node.accountManager.Registry().Delete(partKeyID) if err != nil { return err } @@ -834,6 +834,19 @@ func (node *AlgorandFullNode) RemoveParticipationKey(partKey account.Participati return nil } +// AppendParticipationKeys given a participation id, remove the records from the node +func (node *AlgorandFullNode) AppendParticipationKeys(partKeyID account.ParticipationID, keys account.StateProofKeys) error { + err := node.accountManager.Registry().AppendKeys(partKeyID, keys) + if err != nil { + return err + } + + // PKI TODO: pick a better timeout, this is just something short. This could also be removed if we change + // POST /v2/participation and DELETE /v2/participation to return "202 OK Accepted" instead of waiting and getting + // the error message. + return node.accountManager.Registry().Flush(500 * time.Millisecond) +} + func createTemporaryParticipationKey(outDir string, partKeyBinary []byte) (string, error) { var sb strings.Builder |