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

Implemented New REST interfaces #3099

Merged
merged 4 commits into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
93 changes: 89 additions & 4 deletions daemon/algod/api/server/v2/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated"
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/private"
"github.com/algorand/go-algorand/data"
"github.com/algorand/go-algorand/data/account"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
Expand Down Expand Up @@ -67,33 +68,117 @@ type NodeInterface interface {
StartCatchup(catchpoint string) error
AbortCatchup(catchpoint string) error
Config() config.Local
InstallParticipationKey(partKeyBinary *[]byte) (account.ParticipationID, error)
ListParticipationKeys() ([]account.ParticipationRecord, error)
GetParticipationKey(account.ParticipationID) (account.ParticipationRecord, error)
RemoveParticipationKey(account.ParticipationID) error
}

// GetParticipationKeys Return a list of participation keys
// (GET /v2/participation)
func (v2 *Handlers) GetParticipationKeys(ctx echo.Context) error {
return ctx.String(http.StatusNotImplemented, "Endpoint not implemented.")
partKeys, err := v2.Node.ListParticipationKeys()

if err != nil {
return badRequest(ctx, err, err.Error(), v2.Log)
}

response := []generated.ParticipationKey{}

for _, participationRecord := range partKeys {
info := generated.ParticipationKey{
"ID": participationRecord.ParticipationID.String(),
"Address": participationRecord.Account.String(),
"FirstValid": participationRecord.FirstValid,
"LastValid": participationRecord.LastValid,
// TODO add this
"VoteID": crypto.OneTimeSignatureVerifier{},
// TODO add this
"SelectionID": crypto.VRFVerifier{},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"VoteKeyDilution": participationRecord.KeyDilution,
}

response = append(response, info)
}

return ctx.JSON(http.StatusOK, response)

}

// AddParticipationKey Add a participation key to the node
// (POST /v2/participation)
func (v2 *Handlers) AddParticipationKey(ctx echo.Context) error {

return ctx.String(http.StatusNotImplemented, "Endpoint not implemented.")
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(ctx.Request().Body)
if err != nil {
return badRequest(ctx, err, err.Error(), v2.Log)
}
partKeyBinary := buf.Bytes()

if len(partKeyBinary) == 0 {
err := fmt.Errorf("binary was of length zero")
return badRequest(ctx, err, err.Error(), v2.Log)
}

partID, err := v2.Node.InstallParticipationKey(&partKeyBinary)

if err != nil {
return badRequest(ctx, err, err.Error(), v2.Log)
}

response := generated.PostParticipationResponse{PartId: partID.String()}
return ctx.JSON(http.StatusOK, response)

}

// DeleteParticipationKeyByID Delete a given participation key by id
// (DELETE /v2/participation/{participation-id})
func (v2 *Handlers) DeleteParticipationKeyByID(ctx echo.Context, participationID string) error {

return ctx.String(http.StatusNotImplemented, "Endpoint not implemented.")
decodedParticipationID, err := account.ParticipationIDFromString(participationID)

if err != nil {
return badRequest(ctx, err, err.Error(), v2.Log)
}

err = v2.Node.RemoveParticipationKey(decodedParticipationID)
if err != nil {
return badRequest(ctx, err, err.Error(), v2.Log)
}

return ctx.NoContent(http.StatusOK)
}

// GetParticipationKeyByID Get participation key info by id
// (GET /v2/participation/{participation-id})
func (v2 *Handlers) GetParticipationKeyByID(ctx echo.Context, participationID string) error {

return ctx.String(http.StatusNotImplemented, "Endpoint not implemented.")
decodedParticipationID, err := account.ParticipationIDFromString(participationID)

if err != nil {
return badRequest(ctx, err, err.Error(), v2.Log)
}

participationRecord, err := v2.Node.GetParticipationKey(decodedParticipationID)

if err != nil {
return badRequest(ctx, err, err.Error(), v2.Log)
}

response := generated.ParticipationKey{
"ID": participationRecord.ParticipationID.String(),
"Address": participationRecord.Account.String(),
"FirstValid": participationRecord.FirstValid,
"LastValid": participationRecord.LastValid,
// TODO add this
"VoteID": crypto.OneTimeSignatureVerifier{},
// TODO add this
"SelectionID": crypto.VRFVerifier{},
"VoteKeyDilution": participationRecord.KeyDilution,
}

return ctx.JSON(http.StatusOK, response)
}

// RegisterParticipationKeys registers participation keys.
Expand Down
16 changes: 16 additions & 0 deletions daemon/algod/api/server/v2/test/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ type mockNode struct {
err error
}

func (m mockNode) InstallParticipationKey(partKeyBinary *[]byte) (account.ParticipationID, error) {
panic("implement me")
}

func (m mockNode) ListParticipationKeys() ([]account.ParticipationRecord, error) {
panic("implement me")
}

func (m mockNode) GetParticipationKey(id account.ParticipationID) (account.ParticipationRecord, error) {
panic("implement me")
}

func (m mockNode) RemoveParticipationKey(id account.ParticipationID) error {
panic("implement me")
}

func makeMockNode(ledger *data.Ledger, genesisID string, nodeError error) mockNode {
return mockNode{
ledger: ledger,
Expand Down
19 changes: 19 additions & 0 deletions data/account/participationRegistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package account
import (
"context"
"database/sql"
"encoding/base32"
"errors"
"fmt"
"strings"
Expand All @@ -41,6 +42,24 @@ func (pid ParticipationID) IsZero() bool {
return (crypto.Digest(pid)).IsZero()
}

func (pid ParticipationID) String() string {
return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(pid[:])
}

// ParticipationIDFromString takes a string and returns a ParticipationID object
func ParticipationIDFromString(str string) (d ParticipationID, err error) {
decoded, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(str)
if err != nil {
return d, err
}
if len(decoded) != len(d) {
msg := fmt.Sprintf(`Attempted to decode a string which was not a participation id: "%v"`, str)
return d, errors.New(msg)
}
copy(d[:], decoded[:])
return d, err
}

// ParticipationRecord contains all metadata relating to a set of participation keys.
type ParticipationRecord struct {
ParticipationID ParticipationID
Expand Down
108 changes: 108 additions & 0 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,114 @@ func (node *AlgorandFullNode) checkForParticipationKeys() {
}
}

// ListParticipationKeys returns all participation keys currently installed on the node
func (node *AlgorandFullNode) ListParticipationKeys() (partKeys []account.ParticipationRecord, err error) {
return node.accountManager.Registry().GetAll(), nil
}

// GetParticipationKey retries the information of a participation id from the node
func (node *AlgorandFullNode) GetParticipationKey(partKey account.ParticipationID) (account.ParticipationRecord, error) {
return node.accountManager.Registry().Get(partKey), nil
}

// RemoveParticipationKey given a participation id, remove the records from the node
func (node *AlgorandFullNode) RemoveParticipationKey(partKey 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)

if partRecord.IsZero() {
return fmt.Errorf("could not find participation record in registry")
}

genID := node.GenesisID()

outDir := filepath.Join(node.rootDir, genID)

filename := config.PartKeyFilename(partRecord.ParticipationID.String(), uint64(partRecord.FirstValid), uint64(partRecord.LastValid))
fullyQualifiedFilename := filepath.Join(outDir, filepath.Base(filename))

_ = os.Remove(fullyQualifiedFilename)
_ = node.accountManager.Registry().Delete(partKey)

return nil
}

func createTemporaryParticipationKey(outDir string, partKeyBinary *[]byte) (string, error) {
tempFile := filepath.Join(outDir, filepath.Base("tempPartKeyBinary.bin"))

file, err := os.Create(tempFile)

if err != nil {
return "", err
}

_, err = file.Write(*partKeyBinary)

file.Close()

if err != nil {
os.Remove(tempFile)
return "", err
}

return tempFile, nil
}

// InstallParticipationKey Given a participation key binary stream install the participation key
func (node *AlgorandFullNode) InstallParticipationKey(partKeyBinary *[]byte) (account.ParticipationID, error) {
genID := node.GenesisID()

outDir := filepath.Join(node.rootDir, genID)

fullyQualifiedTempFile, err := createTemporaryParticipationKey(outDir, partKeyBinary)
// We need to make sure no tempfile is created/remains if there is an error
// However, we will eventually rename this file but if we fail in-between
// this point and the rename we want to ensure that we remove the temporary file
// After we rename, this will fail anyway since the file will not exist

// Explicitly ignore the error with a closure
defer func(name string) {
_ = os.Remove(name)
}(fullyQualifiedTempFile)

if err != nil {
return account.ParticipationID{}, err
}

inputdb, err := db.MakeErasableAccessor(fullyQualifiedTempFile)
if err != nil {
return account.ParticipationID{}, err
}
defer inputdb.Close()

partkey, err := account.RestoreParticipation(inputdb)
if err != nil {
return account.ParticipationID{}, err
}
defer partkey.Close()

if partkey.Parent == (basics.Address{}) {
return account.ParticipationID{}, fmt.Errorf("cannot install partkey with missing (zero) parent address")
}

// Tell the AccountManager about the Participation (dupes don't matter) so we ignore the return value
_ = node.accountManager.AddParticipation(partkey)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On closer inspection, AddParticipation returns a bool not an error.:

// The return value indicates if the key has been added (true) or    
// if this is a duplicate key (false).     

The code would currently overwrite the duplicate key, I think that's probably fine. In the future when we stop writing the file to begin with, it would just be a no-op.


newFilename := config.PartKeyFilename(partkey.ID().String(), uint64(partkey.FirstValid), uint64(partkey.LastValid))
newFullyQualifiedFilename := filepath.Join(outDir, filepath.Base(newFilename))

err = os.Rename(fullyQualifiedTempFile, newFullyQualifiedFilename)

if err != nil {
return account.ParticipationID{}, nil
}

return partkey.ID(), nil
}

func (node *AlgorandFullNode) loadParticipationKeys() error {
// Generate a list of all potential participation key files
genesisDir := filepath.Join(node.rootDir, node.genesisID)
Expand Down
5 changes: 5 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,9 @@ To run a specific test:
~$ ./e2e_client_runner.py /full/path/to/e2e_subs/test_script.sh
```

Make sure to install the Algorand Python SDK before running:
```
pip install py-algorand-sdk
```

Tests in the `e2e_subs/serial` directory are executed serially instead of in parallel. This should only be used when absolutely necessary.
49 changes: 49 additions & 0 deletions test/scripts/e2e_subs/rest-participation-key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than a few nits, this test looks really nice. This is going to be a nice template for when the storage changes and we get new error cases to check for.

# TIMEOUT=300

my_dir="$(dirname "$0")"
#"$my_dir/rest.sh" "$@"
source "$my_dir/rest.sh" "$@"

date "+$0 start %Y%m%d_%H%M%S"

# Use admin token for both get and post
export USE_ADMIN=true

pushd "${TEMPDIR}" || exit

FIRST_ROUND=0
# A really large (but arbitrary) last valid round
LAST_ROUND=1200000

NAME_OF_TEMP_PARTKEY="tmp.${FIRST_ROUND}.${LAST_ROUND}.partkey"

algokey part generate --first ${FIRST_ROUND} --last ${LAST_ROUND} --keyfile ${NAME_OF_TEMP_PARTKEY} --parent ${ACCOUNT}

popd || exit

call_and_verify "Get List of Keys" "/v2/participation" 200 'Address'

call_post_and_verify "Install a basic participation key" "/v2/participation" 200 ${NAME_OF_TEMP_PARTKEY} 'partId'

call_and_verify "Get List of Keys" "/v2/participation" 200 'Address'

# Let's get a key from the previous response manually and request it specifically
SAMPLE_ID=$(curl -q -s -H "Authorization: Bearer $ADMIN_TOKEN" "$NET/v2/participation" | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj[0]["ID"])')
NUMBER_OF_IDS=$(curl -q -s -H "Authorization: Bearer $ADMIN_TOKEN" "$NET/v2/participation" | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(len(obj))')

call_and_verify "Get a specific ID" "/v2/participation/${SAMPLE_ID}" 200 "${SAMPLE_ID}"

call_delete_and_verify "Delete the specific ID" "/v2/participation/${SAMPLE_ID}" 200

# Verify that it got called previously and will return an error now since it isn't there
call_delete_and_verify "Delete the specific ID" "/v2/participation/${SAMPLE_ID}" 400

NEW_NUMBER_OF_IDS=$(curl -q -s -H "Authorization: Bearer $ADMIN_TOKEN" "$NET/v2/participation" | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(len(obj))')

if [[ "$NEW_NUMBER_OF_IDS" -ge "$NUMBER_OF_IDS" ]]; then
printf "\n\nFailed test. New number of IDs (%s) is greater than or equal to original IDs (%s)\n\n" "${NEW_NUMBER_OF_IDS}" "${NUMBER_OF_IDS}"
exit 1
fi


Loading