Skip to content

Commit

Permalink
add new table accounts and queries: GetListAccounts, CountAccounts, C…
Browse files Browse the repository at this point in the history
…reateAccount

add new queries in token_transfers: GetTokenTransfersByToAccount, GetTokenTransfersByAccount, CountTokenTransfersByAccount
indexer: update method onSetAccount
integration test: update TestaccountAPI, add new test TestAPIAccountTokentxs
api: update GetTransfers, add countAccounts

swagger documentation
  • Loading branch information
mariajdab committed Oct 25, 2023
1 parent bf96064 commit 60900b3
Show file tree
Hide file tree
Showing 18 changed files with 832 additions and 61 deletions.
133 changes: 129 additions & 4 deletions api/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,30 @@ func (a *API) enableAccountHandlers() error {
); err != nil {
return err
}
if err := a.endpoint.RegisterMethod(
"/accounts/{accountID}/transfers/count",
"GET",
apirest.MethodAccessTypePublic,
a.tokenTransfersCountHandler,
); err != nil {
return err
}
if err := a.endpoint.RegisterMethod(
"/accounts/count",
"GET",
apirest.MethodAccessTypePublic,
a.accountCountHandler,
); err != nil {
return err
}
if err := a.endpoint.RegisterMethod(
"/accounts/page/{page}",
"GET",
apirest.MethodAccessTypePublic,
a.accountsListHandler,
); err != nil {
return err
}

return nil
}
Expand Down Expand Up @@ -125,7 +149,7 @@ func (a *API) accountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er

sik, err := a.vocapp.State.SIKFromAddress(addr)
if err != nil && !errors.Is(err, state.ErrSIKNotFound) {
log.Warnf("uknown error getting SIK: %v", err)
log.Warnf("unknown error getting SIK: %v", err)
return ErrGettingSIK.WithErr(err)
}

Expand Down Expand Up @@ -235,6 +259,33 @@ func (a *API) accountSetHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContex
return ctx.Send(data, apirest.HTTPstatusOK)
}

// accountCountHandler
//
// @Summary Total number of accounts
// @Description Returns the count of total number of existing accounts
// @Tags Accounts
// @Accept json
// @Produce json
// @Success 200 {object} object{count=int}
// @Router /accounts/count [get]
func (a *API) accountCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
count, err := a.indexer.CountTotalAccounts()
if err != nil {
return err
}

data, err := json.Marshal(
struct {
Count uint64 `json:"count"`
}{Count: count},
)
if err != nil {
return ErrMarshalingServerJSONFailed.WithErr(err)
}

return ctx.Send(data, apirest.HTTPstatusOK)
}

// electionListHandler
//
// @Summary List organization elections
Expand Down Expand Up @@ -351,7 +402,7 @@ func (a *API) electionCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPConte

// tokenTransfersHandler
//
// @Summary List account transfers
// @Summary List account received and sent token transfers
// @Description Returns the token transfers for an account. A transfer is a token transference from one account to other (excepting the burn address).
// @Tags Accounts
// @Accept json
Expand Down Expand Up @@ -380,13 +431,13 @@ func (a *API) tokenTransfersHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCont
}
}
page = page * MaxPageSize
transfers, err := a.indexer.GetTokenTransfersByFromAccount(accountID, int32(page), MaxPageSize)
transfers, err := a.indexer.GetTokenTransfersAccount(accountID, int32(page), MaxPageSize)
if err != nil {
return ErrCantFetchTokenTransfers.WithErr(err)
}
data, err := json.Marshal(
struct {
Transfers []*indexertypes.TokenTransferMeta `json:"transfers"`
Transfers map[string][]*indexertypes.TokenTransferMeta `json:"transfers"`
}{Transfers: transfers},
)
if err != nil {
Expand Down Expand Up @@ -441,3 +492,77 @@ func (a *API) tokenFeesHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
}
return ctx.Send(data, apirest.HTTPstatusOK)
}

// tokenTransfersCountHandler
//
// @Summary Total number of sent and received transactions
// @Description Returns the count of total number of sent and received transactions for an account. A transaction is a token transfer from one account to another existing account
// @Tags Accounts
// @Accept json
// @Produce json
// @Param accountID path string true "Specific accountID"
// @Success 200 {object} object{count=int} "Number of transaction sent and received for the account"
// @Router /accounts/{accountID}/transfers/count [get]
func (a *API) tokenTransfersCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
accountID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("accountID")))
if err != nil || accountID == nil {
return ErrCantParseAccountID.Withf("%q", ctx.URLParam("accountID"))
}
acc, err := a.vocapp.State.GetAccount(common.BytesToAddress(accountID), true)
if acc == nil {
return ErrAccountNotFound
}
if err != nil {
return err
}

count, err := a.indexer.CountTokenTransfersAccount(accountID)
if err != nil {
return err
}
data, err := json.Marshal(
struct {
Count uint64 `json:"count"`
}{Count: count},
)
if err != nil {
return ErrMarshalingServerJSONFailed.WithErr(err)
}

return ctx.Send(data, apirest.HTTPstatusOK)
}

// accountsListHandler
//
// @Summary List of the existing accounts
// @Description Returns information (address, balance and nonce) of the existing accounts
// @Tags Accounts
// @Accept json
// @Produce json
// @Param page path string true "Paginator page"
// @Success 200 {object} object{accounts=[]indexertypes.Account}
// @Router /accounts/page/{page} [get]
func (a *API) accountsListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
var err error
page := 0
if ctx.URLParam("page") != "" {
page, err = strconv.Atoi(ctx.URLParam("page"))
if err != nil {
return ErrCantParsePageNumber
}
}
page = page * MaxPageSize
accounts, err := a.indexer.GetListAccounts(int32(page), MaxPageSize)
if err != nil {
return ErrCantFetchTokenTransfers.WithErr(err)
}
data, err := json.Marshal(
struct {
Accounts []indexertypes.Account `json:"accounts"`
}{Accounts: accounts},
)
if err != nil {
return ErrMarshalingServerJSONFailed.WithErr(err)
}
return ctx.Send(data, apirest.HTTPstatusOK)
}
2 changes: 1 addition & 1 deletion api/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ func (a *API) chainTxListPaginated(_ *apirest.APIdata, ctx *httprouter.HTTPConte
}
return err
}
// wrap list in a struct to consistently return list in a object, return empty
// wrap list in a struct to consistently return list in an object, return empty
// object if the list does not contains any result
type response struct {
Txs []*indexertypes.Transaction `json:"transactions"`
Expand Down
72 changes: 66 additions & 6 deletions apiclient/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"go.vocdoni.io/dvote/data/ipfs"
"go.vocdoni.io/dvote/httprouter/apirest"
"go.vocdoni.io/dvote/types"
indexertypes "go.vocdoni.io/dvote/vochain/indexer/indexertypes"
"go.vocdoni.io/dvote/vochain/indexer/indexertypes"
"go.vocdoni.io/proto/build/go/models"
"google.golang.org/protobuf/proto"
)
Expand Down Expand Up @@ -260,20 +260,22 @@ func (c *HTTPclient) AccountSetMetadata(metadata *api.AccountMetadata) (types.He
return accv.TxHash, nil
}

// GetTransfers returns the list of token transfers associated with an account
func (c *HTTPclient) GetTransfers(from common.Address, page int) ([]*indexertypes.TokenTransferMeta, error) {
// GetTransfers returns the list of sent and received token transfers associated with an account
func (c *HTTPclient) GetTransfers(from common.Address, page int) (map[string][]*indexertypes.TokenTransferMeta, error) {
resp, code, err := c.Request(HTTPGET, nil, "accounts", from.Hex(), "transfers", "page", strconv.Itoa(page))
if err != nil {
return nil, err
}
if code != apirest.HTTPstatusOK {
return nil, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp)
}
var transfers []*indexertypes.TokenTransferMeta
if err := json.Unmarshal(resp, &transfers); err != nil {
trnsfs := new(struct {
Transfers map[string][]*indexertypes.TokenTransferMeta `json:"transfers"`
})
if err := json.Unmarshal(resp, &trnsfs); err != nil {
return nil, err
}
return transfers, nil
return trnsfs.Transfers, nil
}

// SetSIK function allows to update the Secret Identity Key for the current
Expand Down Expand Up @@ -417,3 +419,61 @@ func (c *HTTPclient) RegisterSIKForVote(electionId types.HexBytes, proof *Census
}
return hash, nil
}

// ListAccounts return the account list information (address, balance and nonce)
func (c *HTTPclient) ListAccounts(page int) ([]indexertypes.Account, error) {
resp, code, err := c.Request(HTTPGET, nil, "accounts", "page", strconv.Itoa(page))
if err != nil {
return nil, err
}
if code != apirest.HTTPstatusOK {
return nil, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp)
}
accts := new(struct {
Accounts []indexertypes.Account `json:"accounts"`
})

if err := json.Unmarshal(resp, &accts); err != nil {
return nil, err
}

return accts.Accounts, nil
}

// AccountCount returns the total count of exiting accounts
func (c *HTTPclient) AccountCount() (uint64, error) {
resp, code, err := c.Request(HTTPGET, nil, "accounts", "count")
if err != nil {
return 0, err
}
if code != apirest.HTTPstatusOK {
return 0, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp)
}
accts := new(struct {
Count uint64 `json:"count"`
})

if err := json.Unmarshal(resp, accts); err != nil {
return 0, err
}
return accts.Count, nil
}

// CountTokenTransfers returns the total count of transfers sent and received for an account
func (c *HTTPclient) CountTokenTransfers(accountID common.Address) (uint64, error) {
resp, code, err := c.Request(HTTPGET, nil, "accounts", accountID.Hex(), "transfers", "count")
if err != nil {
return 0, err
}
if code != apirest.HTTPstatusOK {
return 0, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp)
}
tokentxs := new(struct {
Count uint64 `json:"count"`
})

if err := json.Unmarshal(resp, tokentxs); err != nil {
return 0, err
}
return tokentxs.Count, nil
}
22 changes: 22 additions & 0 deletions cmd/end2endtest/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sync"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/google/go-cmp/cmp"
apipkg "go.vocdoni.io/dvote/api"
"go.vocdoni.io/dvote/apiclient"
Expand Down Expand Up @@ -230,6 +231,13 @@ func testSendTokens(api *apiclient.HTTPclient, aliceKeys, bobKeys *ethereum.Sign
return err
}

if err := checkTokenTransfersCount(alice, aliceKeys.Address()); err != nil {
return err
}
if err := checkTokenTransfersCount(bob, bobKeys.Address()); err != nil {
return err
}

return nil
}

Expand All @@ -249,6 +257,20 @@ func checkAccountNonceAndBalance(api *apiclient.HTTPclient, expNonce uint32, exp
return nil
}

func checkTokenTransfersCount(api *apiclient.HTTPclient, address common.Address) error {
tokenTxs, err := api.GetTransfers(address, 0)
if err != nil {
return err
}
countTokenTxs := uint64(len(tokenTxs["Received"]) + len(tokenTxs["Sent"]))

count, err := api.CountTokenTransfers(address)

Check failure on line 267 in cmd/end2endtest/account.go

View workflow job for this annotation

GitHub Actions / job_go_checks

this value of err is never used (SA4006)
if count != countTokenTxs {
return fmt.Errorf("expected %s to match transfers count %d and %d", address, count, countTokenTxs)
}
return nil
}

func ensureAccountExists(api *apiclient.HTTPclient,
faucetPkg *models.FaucetPackage) (*apipkg.Account, error) {
for i := 0; i < retries; i++ {
Expand Down
Loading

0 comments on commit 60900b3

Please sign in to comment.