Skip to content

Commit

Permalink
api: new endpoint for account metadata
Browse files Browse the repository at this point in the history
Signed-off-by: p4u <[email protected]>
  • Loading branch information
p4u committed Dec 15, 2023
1 parent f432284 commit 918eee1
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 28 deletions.
81 changes: 53 additions & 28 deletions api/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"strconv"
"strings"
"time"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -35,6 +36,14 @@ func (a *API) enableAccountHandlers() error {
); err != nil {
return err
}
if err := a.Endpoint.RegisterMethod(
"/accounts/{address}/metadata",
"GET",
apirest.MethodAccessTypePublic,
a.accountHandler,
); err != nil {
return err
}
if err := a.Endpoint.RegisterMethod(
"/accounts",
"POST",
Expand Down Expand Up @@ -122,6 +131,8 @@ func (a *API) enableAccountHandlers() error {
// @Param address path string true "Account address"
// @Success 200 {object} Account
// @Router /accounts/{address} [get]
// @Router /accounts/{address}/metadata [get]
// @Success 200 {object} AccountMetadata
func (a *API) accountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
if len(util.TrimHex(ctx.URLParam("address"))) != common.AddressLength*2 {
return ErrAddressMalformed
Expand All @@ -132,43 +143,57 @@ func (a *API) accountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er
return ErrAccountNotFound.With(addr.Hex())
}

// If the account does not exist in state, try to retrieve it from the indexer.
// This is a fallback for the case where the account is not in state but it is in the process archive.
if acc == nil {
indexerEntities := a.indexer.EntityList(1, 0, hex.EncodeToString(addr.Bytes()))
if len(indexerEntities) == 0 {
return ErrAccountNotFound.With(addr.Hex())
getAccountMetadata := func() *AccountMetadata {
// Try to retrieve the account info metadata
accMetadata := &AccountMetadata{}
if a.storage != nil {
stgCtx, cancel := context.WithTimeout(context.Background(), AccountFetchMetadataTimeoutSeconds*time.Second)
defer cancel()
metadataBytes, err := a.storage.Retrieve(stgCtx, acc.InfoURI, MaxOffchainFileSize)
if err != nil {
log.Warnf("cannot get account metadata from %s: %v", acc.InfoURI, err)
} else {
if err := json.Unmarshal(metadataBytes, &accMetadata); err != nil {
log.Warnf("cannot unmarshal metadata from %s: %v", acc.InfoURI, err)
}
}
}
return accMetadata
}
// take the last word of the URL path to determine the type of request
// if the last word is "metadata" then return only the account metadata
// otherwise return the full account information
if strings.HasSuffix(ctx.Request.URL.Path, "/metadata") {
// If the account does not exist in state, try to retrieve it from the indexer.
// This is a fallback for the case where the account is not in state but it is in the process archive.
// We only return this information if the query is for the "metadata" endpoint.
var data []byte
if data, err = json.Marshal(Account{
Address: addr.Bytes(),
Nonce: 0,
Balance: 0,
ElectionIndex: uint32(indexerEntities[0].ProcessCount),
InfoURL: "",
Metadata: &AccountMetadata{
if acc == nil {
indexerEntities := a.indexer.EntityList(1, 0, hex.EncodeToString(addr.Bytes()))
if len(indexerEntities) == 0 {
return ErrAccountNotFound.With(addr.Hex())
}
if data, err = json.Marshal(AccountMetadata{
Name: LanguageString{"default": addr.Hex()},
},
}); err != nil {
}); err != nil {
return err
}
return ctx.Send(data, apirest.HTTPstatusOK)
}
accMetadata := getAccountMetadata()
if accMetadata == nil {
return ErrAccountNotFound.With(addr.Hex())
}
if data, err = json.Marshal(accMetadata); err != nil {
return err
}
return ctx.Send(data, apirest.HTTPstatusOK)
}

// Try to retrieve the account info metadata
accMetadata := &AccountMetadata{}
if a.storage != nil {
stgCtx, cancel := context.WithTimeout(context.Background(), AccountFetchMetadataTimeoutSeconds*time.Second)
defer cancel()
metadataBytes, err := a.storage.Retrieve(stgCtx, acc.InfoURI, MaxOffchainFileSize)
if err != nil {
log.Warnf("cannot get account metadata from %s: %v", acc.InfoURI, err)
} else {
if err := json.Unmarshal(metadataBytes, &accMetadata); err != nil {
log.Warnf("cannot unmarshal metadata from %s: %v", acc.InfoURI, err)
}
}
if acc == nil {
return ErrAccountNotFound.With(addr.Hex())
}
accMetadata := getAccountMetadata()

sik, err := a.vocapp.State.SIKFromAddress(addr)
if err != nil && !errors.Is(err, state.ErrSIKNotFound) {
Expand Down
24 changes: 24 additions & 0 deletions apiclient/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ func (c *HTTPclient) Account(address string) (*api.Account, error) {
return acc, nil
}

// AccountMetadata returns the metadata associated with a Vocdoni account. If address is empty, it returns the information
// about the account associated with the client.
func (c *HTTPclient) AccountMetadata(address string) (*api.AccountMetadata, error) {
if address == "" {
if c.account == nil {
return nil, ErrAccountNotConfigured
}
address = c.account.AddressString()
}
resp, code, err := c.Request(HTTPGET, nil, "accounts", address, "metadata")
if err != nil {
return nil, err
}
if code != apirest.HTTPstatusOK {
return nil, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp)
}
acc := &api.AccountMetadata{}
err = json.Unmarshal(resp, acc)
if err != nil {
return nil, err
}
return acc, nil
}

// Transfer sends tokens from the account associated with the client to the given address.
// The nonce is automatically calculated from the account information.
// Returns the transaction hash.
Expand Down

0 comments on commit 918eee1

Please sign in to comment.