diff --git a/.github/dependabot/config.yml b/.github/dependabot/config.yml new file mode 100644 index 000000000..5ace4600a --- /dev/null +++ b/.github/dependabot/config.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dec3fc17e..e8ad79cfd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,9 +38,9 @@ jobs: echo "go mod tidy made these changes, please run 'go mod tidy' and include those changes in a commit" exit 1 fi - - name: Run gofmt - # Run gofmt first, as it's quick and issues are common. - run: diff -u <(echo -n) <(gofmt -s -d .) + - name: Run gofumpt + # Run gofumpt first, as it's quick and issues are common. + run: diff -u <(echo -n) <(go run mvdan.cc/gofumpt@v0.7.0 -d .) - name: Run go vet run: go vet ./... - name: Run go generate @@ -54,7 +54,7 @@ jobs: fi - name: Run staticcheck run: | - go install honnef.co/go/tools/cmd/staticcheck@2023.1.7 + go install honnef.co/go/tools/cmd/staticcheck@2024.1.1 staticcheck -debug.version staticcheck ./... 2> staticcheck-stderr - name: Check staticcheck stderr (this step isn't needed because we are using actions/setup-go@v5 on GitHub hosted runner) diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 33d71cb2c..cfc62f81f 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -57,12 +57,12 @@ jobs: uses: actions/download-artifact@v4 with: name: vocdoni-api.yaml - path: swaggers/vocdoni-api.yaml + path: swaggers - uses: benjlevesque/short-sha@v3.0 # sets env.SHA to the first 7 chars of github.sha - name: Create PR to developer-portal repo id: cpr - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.VOCDONIBOT_PAT }} commit-message: "Update vocdoni-api docs by commit ${{ env.SHA }}" diff --git a/api/accounts.go b/api/accounts.go index e832da44a..ad3322719 100644 --- a/api/accounts.go +++ b/api/accounts.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "strconv" "strings" "time" @@ -16,7 +15,6 @@ import ( "go.vocdoni.io/dvote/log" "go.vocdoni.io/dvote/types" "go.vocdoni.io/dvote/util" - "go.vocdoni.io/dvote/vochain/indexer/indexertypes" "go.vocdoni.io/dvote/vochain/state" "go.vocdoni.io/proto/build/go/models" "google.golang.org/protobuf/proto" @@ -53,31 +51,31 @@ func (a *API) enableAccountHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/accounts/{organizationID}/elections/count", + "/accounts/{organizationId}/elections/count", "GET", apirest.MethodAccessTypePublic, - a.electionCountHandler, + a.accountElectionsCountHandler, ); err != nil { return err } if err := a.Endpoint.RegisterMethod( - "/accounts/{organizationID}/elections/status/{status}/page/{page}", + "/accounts/{organizationId}/elections/status/{status}/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.electionListHandler, + a.accountElectionsListByStatusAndPageHandler, ); err != nil { return err } if err := a.Endpoint.RegisterMethod( - "/accounts/{organizationID}/elections/page/{page}", + "/accounts/{organizationId}/elections/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.electionListHandler, + a.accountElectionsListByPageHandler, ); err != nil { return err } if err := a.Endpoint.RegisterMethod( - "/accounts/{accountID}/transfers/page/{page}", + "/accounts/{accountId}/transfers/page/{page}", "GET", apirest.MethodAccessTypePublic, a.tokenTransfersListHandler, @@ -85,7 +83,7 @@ func (a *API) enableAccountHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/accounts/{accountID}/fees/page/{page}", + "/accounts/{accountId}/fees/page/{page}", "GET", apirest.MethodAccessTypePublic, a.tokenFeesHandler, @@ -93,7 +91,7 @@ func (a *API) enableAccountHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/accounts/{accountID}/transfers/count", + "/accounts/{accountId}/transfers/count", "GET", apirest.MethodAccessTypePublic, a.tokenTransfersCountHandler, @@ -112,6 +110,14 @@ func (a *API) enableAccountHandlers() error { "/accounts/page/{page}", "GET", apirest.MethodAccessTypePublic, + a.accountListByPageHandler, + ); err != nil { + return err + } + if err := a.Endpoint.RegisterMethod( + "/accounts", + "GET", + apirest.MethodAccessTypePublic, a.accountListHandler, ); err != nil { return err @@ -130,9 +136,9 @@ func (a *API) enableAccountHandlers() error { // @Produce json // @Param address path string true "Account address" // @Success 200 {object} Account +// @Success 200 {object} AccountMetadata // @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 @@ -187,15 +193,27 @@ func (a *API) accountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er return ErrGettingSIK.WithErr(err) } + _, transfersCount, err := a.indexer.TokenTransfersList(1, 0, hex.EncodeToString(addr.Bytes()), "", "") + if err != nil { + return ErrCantFetchTokenTransfers.WithErr(err) + } + + _, feesCount, err := a.indexer.TokenFeesList(1, 0, "", "", hex.EncodeToString(addr.Bytes())) + if err != nil { + return ErrCantFetchTokenFees.WithErr(err) + } + var data []byte if data, err = json.Marshal(Account{ - Address: addr.Bytes(), - Nonce: acc.GetNonce(), - Balance: acc.GetBalance(), - ElectionIndex: acc.GetProcessIndex(), - InfoURL: acc.GetInfoURI(), - Metadata: accMetadata, - SIK: types.HexBytes(sik), + Address: addr.Bytes(), + Nonce: acc.GetNonce(), + Balance: acc.GetBalance(), + ElectionIndex: acc.GetProcessIndex(), + TransfersCount: transfersCount, + FeesCount: feesCount, + InfoURL: acc.GetInfoURI(), + Metadata: accMetadata, + SIK: types.HexBytes(sik), }); err != nil { return err } @@ -308,125 +326,121 @@ func (a *API) accountSetHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContex // // @Summary Total number of accounts // @Description Returns the count of total number of existing accounts +// @Deprecated +// @Description (deprecated, in favor of /accounts which reports totalItems) // @Tags Accounts // @Accept json // @Produce json -// @Success 200 {object} object{count=int} +// @Success 200 {object} CountResult // @Router /accounts/count [get] func (a *API) accountCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { count, err := a.indexer.CountTotalAccounts() if err != nil { return err } + return marshalAndSend(ctx, &CountResult{Count: count}) +} - data, err := json.Marshal( - struct { - Count uint64 `json:"count"` - }{Count: count}, +// accountElectionsListByPageHandler +// +// @Summary List organization elections +// @Description List the elections of an organization +// @Deprecated +// @Description (deprecated, in favor of /elections?page=xxx&organizationId=xxx) +// @Tags Accounts +// @Accept json +// @Produce json +// @Param organizationId path string true "Specific organizationId" +// @Param page path number true "Page" +// @Success 200 {object} ElectionsList +// @Router /accounts/{organizationId}/elections/page/{page} [get] +func (a *API) accountElectionsListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := electionParams(ctx.URLParam, + ParamPage, + ParamOrganizationId, ) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + if params.OrganizationID == "" { + return ErrMissingParameter + } + + list, err := a.electionList(params) + if err != nil { + // keep the odd legacy behaviour of sending an empty json "{}"" rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, struct{}{}) + } + return err + } + + return marshalAndSend(ctx, list) } -// electionListHandler +// accountElectionsListByStatusAndPageHandler // -// @Summary List organization elections -// @Description List the elections of an organization +// @Summary List organization elections by status +// @Description List the elections of an organization by status +// @Deprecated +// @Description (deprecated, in favor of /elections?page=xxx&organizationId=xxx&status=xxx) // @Tags Accounts // @Accept json // @Produce json -// @Param organizationID path string true "Specific organizationID" -// @Param page path number true "Define de page number" -// @Success 200 {object} object{elections=[]ElectionSummary} -// @Router /accounts/{organizationID}/elections/page/{page} [get] -// /accounts/{organizationID}/elections/status/{status}/page/{page} [post] Endpoint docs generated on docs/models/model.go -func (a *API) electionListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - organizationID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("organizationID"))) - if err != nil || organizationID == nil { - return ErrCantParseOrgID.Withf("%q", ctx.URLParam("organizationID")) - } - - page := 0 - if ctx.URLParam("page") != "" { - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return ErrCantParsePageNumber - } +// @Param organizationId path string true "Specific organizationId" +// @Param status path string true "Election status" Enums(ready, paused, canceled, ended, results) +// @Param page path number true "Page" +// @Success 200 {object} ElectionsList +// @Router /accounts/{organizationId}/elections/status/{status}/page/{page} [get] +func (a *API) accountElectionsListByStatusAndPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := electionParams(ctx.URLParam, + ParamPage, + ParamStatus, + ParamOrganizationId, + ) + if err != nil { + return err } - page = page * MaxPageSize - var pids [][]byte - switch ctx.URLParam("status") { - case "ready": - pids, err = a.indexer.ProcessList(organizationID, page, MaxPageSize, "", 0, 0, "READY", false) - if err != nil { - return ErrCantFetchElectionList.WithErr(err) - } - case "paused": - pids, err = a.indexer.ProcessList(organizationID, page, MaxPageSize, "", 0, 0, "PAUSED", false) - if err != nil { - return ErrCantFetchElectionList.WithErr(err) - } - case "canceled": - pids, err = a.indexer.ProcessList(organizationID, page, MaxPageSize, "", 0, 0, "CANCELED", false) - if err != nil { - return ErrCantFetchElectionList.WithErr(err) - } - case "ended", "results": - pids, err = a.indexer.ProcessList(organizationID, page, MaxPageSize, "", 0, 0, "RESULTS", false) - if err != nil { - return ErrCantFetchElectionList.WithErr(err) - } - pids2, err := a.indexer.ProcessList(organizationID, page, MaxPageSize, "", 0, 0, "ENDED", false) - if err != nil { - return ErrCantFetchElectionList.WithErr(err) - } - pids = append(pids, pids2...) - case "": - pids, err = a.indexer.ProcessList(organizationID, page, MaxPageSize, "", 0, 0, "", false) - if err != nil { - return ErrCantFetchElectionList.WithErr(err) - } - default: - return ErrParamStatusMissing + if params.OrganizationID == "" || params.Status == "" { + return ErrMissingParameter } - elections := []*ElectionSummary{} - for _, pid := range pids { - procInfo, err := a.indexer.ProcessInfo(pid) - if err != nil { - return ErrCantFetchElection.WithErr(err) - } - summary := a.electionSummary(procInfo) - elections = append(elections, &summary) - } - data, err := json.Marshal(&Organization{ - Elections: elections, - }) + list, err := a.electionList(params) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + // keep the odd legacy behaviour of sending an empty json "{}"" rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, struct{}{}) + } + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + + return marshalAndSend(ctx, list) } -// electionCountHandler +// accountElectionsCountHandler // // @Summary Count organization elections // @Description Returns the number of elections for an organization +// @Deprecated +// @Description (deprecated, in favor of /elections?organizationId=xxx which reports totalItems) // @Tags Accounts // @Accept json // @Produce json -// @Param organizationID path string true "Specific organizationID" -// @Success 200 {object} object{count=number} -// @Router /accounts/{organizationID}/elections/count [get] -func (a *API) electionCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - organizationID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("organizationID"))) - if err != nil || organizationID == nil { - return ErrCantParseOrgID.Withf("%q", ctx.URLParam("organizationID")) +// @Param organizationId path string true "Specific organizationId" +// @Success 200 {object} CountResult +// @Router /accounts/{organizationId}/elections/count [get] +func (a *API) accountElectionsCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + if ctx.URLParam(ParamOrganizationId) == "" { + return ErrMissingParameter } + + organizationID, err := parseHexString(ctx.URLParam(ParamOrganizationId)) + if err != nil { + return err + } + acc, err := a.vocapp.State.GetAccount(common.BytesToAddress(organizationID), true) if acc == nil { return ErrOrgNotFound @@ -434,124 +448,103 @@ func (a *API) electionCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPConte if err != nil { return err } - data, err := json.Marshal( - struct { - Count uint32 `json:"count"` - }{Count: acc.GetProcessIndex()}, - ) - if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) - } - return ctx.Send(data, apirest.HTTPstatusOK) + return marshalAndSend(ctx, &CountResult{Count: uint64(acc.GetProcessIndex())}) } // tokenTransfersListHandler // // @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). +// @Deprecated +// @Description (deprecated, in favor of /chain/transfers?accountId=xxx&page=xxx) // @Tags Accounts // @Accept json // @Produce json -// @Param accountID path string true "Specific accountID" -// @Param page path string true "Paginator page" -// @Success 200 {object} object{transfers=indexertypes.TokenTransfersAccount} -// @Router /accounts/{accountID}/transfers/page/{page} [get] +// @Param accountId path string true "Specific accountId that sent or received the tokens" +// @Param page path number true "Page" +// @Success 200 {object} TransfersList +// @Router /accounts/{accountId}/transfers/page/{page} [get] func (a *API) tokenTransfersListHandler(_ *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 - } + params, err := parseTransfersParams( + ctx.URLParam(ParamPage), + "", + ctx.URLParam(ParamAccountId), + "", + "", + ) if err != nil { return err } - page := 0 - if ctx.URLParam("page") != "" { - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return ErrCantParsePageNumber - } - } - page = page * MaxPageSize - transfers, err := a.indexer.GetTokenTransfersByAccount(accountID, int32(page), MaxPageSize) - if err != nil { - return ErrCantFetchTokenTransfers.WithErr(err) - } - data, err := json.Marshal( - struct { - Transfers indexertypes.TokenTransfersAccount `json:"transfers"` - }{Transfers: transfers}, - ) + + list, err := a.transfersList(params) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyTransfersList()) + } + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + + return marshalAndSend(ctx, list) } // tokenFeesHandler // // @Summary List account token fees // @Description Returns the token fees for an account. A spending is an amount of tokens burnt from one account for executing transactions. +// @Deprecated +// @Description (deprecated, in favor of /chain/transfers?accountId=xxx&page=xxx) // @Tags Accounts // @Accept json // @Produce json -// @Param accountID path string true "Specific accountID" -// @Param page path string true "Paginator page" -// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta} -// @Router /accounts/{accountID}/fees/page/{page} [get] +// @Param accountId path string true "Specific accountId" +// @Param page path number true "Page" +// @Success 200 {object} FeesList +// @Router /accounts/{accountId}/fees/page/{page} [get] func (a *API) tokenFeesHandler(_ *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 - } + params, err := parseFeesParams( + ctx.URLParam(ParamPage), + "", + "", + "", + ctx.URLParam(ParamAccountId), + ) if err != nil { return err } - page := 0 - if ctx.URLParam("page") != "" { - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return ErrCantParsePageNumber - } - } - page = page * MaxPageSize - fees, err := a.indexer.GetTokenFeesByFromAccount(accountID, int32(page), MaxPageSize) - if err != nil { - return ErrCantFetchTokenTransfers.WithErr(err) + if params.AccountID == "" { + return ErrMissingParameter } - data, err := json.Marshal( - struct { - Fees []*indexertypes.TokenFeeMeta `json:"fees"` - }{Fees: fees}, - ) + + list, err := a.feesList(params) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyFeesList()) + } + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + + return marshalAndSend(ctx, list) } // 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 +// @Deprecated +// @Description (deprecated, in favor of /chain/transfers?accountId=xxx which reports totalItems) // @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] +// @Param accountId path string true "Specific accountId" +// @Success 200 {object} CountResult "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"))) + accountID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamAccountId))) if err != nil || accountID == nil { - return ErrCantParseAccountID.Withf("%q", ctx.URLParam("accountID")) + return ErrCantParseAccountID.Withf("%q", ctx.URLParam(ParamAccountId)) } acc, err := a.vocapp.State.GetAccount(common.BytesToAddress(accountID), true) if acc == nil { @@ -565,16 +558,41 @@ func (a *API) tokenTransfersCountHandler(_ *apirest.APIdata, ctx *httprouter.HTT if err != nil { return err } - data, err := json.Marshal( - struct { - Count uint64 `json:"count"` - }{Count: count}, + return marshalAndSend(ctx, &CountResult{Count: count}) +} + +// accountListByPageHandler +// +// @Summary List of the existing accounts +// @Description Returns information (address, balance and nonce) of the existing accounts. +// @Deprecated +// @Description (deprecated, in favor of /accounts?page=xxx) +// @Tags Accounts +// @Accept json +// @Produce json +// @Param page path number true "Page" +// @Success 200 {object} AccountsList +// @Router /accounts/page/{page} [get] +func (a *API) accountListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseAccountParams( + ctx.URLParam(ParamPage), + "", + "", ) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + list, err := a.accountList(params) + if err != nil { + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyAccountsList()) + } + return err + } + + return marshalAndSend(ctx, list) } // accountListHandler @@ -584,30 +602,63 @@ func (a *API) tokenTransfersCountHandler(_ *apirest.APIdata, ctx *httprouter.HTT // @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] +// @Param page query number false "Page" +// @Param limit query number false "Items per page" +// @Param accountId query string false "Filter by partial accountId" +// @Success 200 {object} AccountsList +// @Router /accounts [get] func (a *API) accountListHandler(_ *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 - } + params, err := parseAccountParams( + ctx.QueryParam(ParamPage), + ctx.QueryParam(ParamLimit), + ctx.QueryParam(ParamAccountId), + ) + if err != nil { + return err } - page = page * MaxPageSize - accounts, err := a.indexer.GetListAccounts(int32(page), MaxPageSize) + + list, err := a.accountList(params) if err != nil { - return ErrCantFetchTokenTransfers.WithErr(err) + return err } - data, err := json.Marshal( - struct { - Accounts []indexertypes.Account `json:"accounts"` - }{Accounts: accounts}, + + return marshalAndSend(ctx, list) +} + +// accountList produces a paginated AccountsList. +// +// Errors returned are always of type APIerror. +func (a *API) accountList(params *AccountParams) (*AccountsList, error) { + accounts, total, err := a.indexer.AccountList( + params.Limit, + params.Page*params.Limit, + params.AccountID, ) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + return nil, ErrIndexerQueryFailed.WithErr(err) } - return ctx.Send(data, apirest.HTTPstatusOK) + + pagination, err := calculatePagination(params.Page, params.Limit, total) + if err != nil { + return nil, err + } + + list := &AccountsList{ + Accounts: accounts, + Pagination: pagination, + } + return list, nil +} + +// parseAccountParams returns an AccountParams filled with the passed params +func parseAccountParams(paramPage, paramLimit, paramAccountID string) (*AccountParams, error) { + pagination, err := parsePaginationParams(paramPage, paramLimit) + if err != nil { + return nil, err + } + + return &AccountParams{ + PaginationParams: pagination, + AccountID: util.TrimHex(paramAccountID), + }, nil } diff --git a/api/api.go b/api/api.go index 68cf0879e..6dcacc5f6 100644 --- a/api/api.go +++ b/api/api.go @@ -1,10 +1,7 @@ package api -////// Disabled autoswag due to https://github.com/swaggo/swag/issues/1267 -////// TODO: re-enable when a fixed swaggo/swag is released -////// and remove the workaround done by @selankon on docs/models/models.go -////go:generate go run go.vocdoni.io/dvote/api/autoswag -//go:generate go run github.com/swaggo/swag/cmd/swag@v1.8.10 fmt +//go:generate go run go.vocdoni.io/dvote/api/autoswag +//go:generate go run github.com/swaggo/swag/cmd/swag@v1.16.3 fmt import ( "fmt" @@ -50,8 +47,40 @@ import ( // @securityDefinitions.basic BasicAuth -// MaxPageSize defines the maximum number of results returned by the paginated endpoints -const MaxPageSize = 10 +const ( + // DefaultItemsPerPage defines how many items per page are returned by the paginated endpoints, + // when the client doesn't specify a `limit` param + DefaultItemsPerPage = 10 + // MaxItemsPerPage defines a ceiling for the `limit` param passed by the client + MaxItemsPerPage = 100 +) + +// These consts define the keywords for query (?param=), url (/url/param/) and POST params. +// Note: In JS/TS acronyms like "ID" are camelCased as in "Id". +// +//nolint:revive +const ( + ParamAccountId = "accountId" + ParamCensusId = "censusId" + ParamElectionId = "electionId" + ParamOrganizationId = "organizationId" + ParamVoteId = "voteId" + ParamPage = "page" + ParamLimit = "limit" + ParamStatus = "status" + ParamWithResults = "withResults" + ParamFinalResults = "finalResults" + ParamManuallyEnded = "manuallyEnded" + ParamHeight = "height" + ParamReference = "reference" + ParamType = "type" + ParamAccountIdFrom = "accountIdFrom" + ParamAccountIdTo = "accountIdTo" + ParamStartDateAfter = "startDateAfter" + ParamStartDateBefore = "startDateBefore" + ParamEndDateAfter = "endDateAfter" + ParamEndDateBefore = "endDateBefore" +) var ( ErrMissingModulesForHandler = fmt.Errorf("missing modules attached for enabling handler") @@ -106,7 +135,8 @@ func NewAPI(router *httprouter.HTTProuter, baseRoute, dataDir, dbType string) (* // Attach takes a list of modules which are used by the handlers in order to interact with the system. // Attach must be called before EnableHandlers. func (a *API) Attach(vocdoniAPP *vochain.BaseApplication, vocdoniInfo *vochaininfo.VochainInfo, - indexer *indexer.Indexer, data data.Storage, censusdb *censusdb.CensusDB) { + indexer *indexer.Indexer, data data.Storage, censusdb *censusdb.CensusDB, +) { a.vocapp = vocdoniAPP a.vocinfo = vocdoniInfo a.indexer = indexer diff --git a/api/api_types.go b/api/api_types.go index 65f20aa5f..d948b7207 100644 --- a/api/api_types.go +++ b/api/api_types.go @@ -12,18 +12,97 @@ import ( "google.golang.org/protobuf/encoding/protojson" ) -type Organization struct { - OrganizationID types.HexBytes `json:"organizationID,omitempty" ` - Elections []*ElectionSummary `json:"elections,omitempty"` - Organizations []*OrganizationList `json:"organizations,omitempty"` - Count *uint64 `json:"count,omitempty" example:"1"` +// ### Params accepted ### + +// PaginationParams allows the client to request a specific page, and how many items per page +type PaginationParams struct { + Page int `json:"page,omitempty"` + Limit int `json:"limit,omitempty"` +} + +// ElectionParams allows the client to filter elections +type ElectionParams struct { + PaginationParams + OrganizationID string `json:"organizationId,omitempty"` + ElectionID string `json:"electionId,omitempty"` + Status string `json:"status,omitempty"` + WithResults *bool `json:"withResults,omitempty"` + FinalResults *bool `json:"finalResults,omitempty"` + ManuallyEnded *bool `json:"manuallyEnded,omitempty"` + StartDateAfter *time.Time `json:"startDateAfter,omitempty"` + StartDateBefore *time.Time `json:"startDateBefore,omitempty"` + EndDateAfter *time.Time `json:"endDateAfter,omitempty"` + EndDateBefore *time.Time `json:"endDateBefore,omitempty"` +} + +// OrganizationParams allows the client to filter organizations +type OrganizationParams struct { + PaginationParams + OrganizationID string `json:"organizationId,omitempty"` +} + +// AccountParams allows the client to filter accounts +type AccountParams struct { + PaginationParams + AccountID string `json:"accountId,omitempty"` +} + +// TransactionParams allows the client to filter transactions +type TransactionParams struct { + PaginationParams + Height uint64 `json:"height,omitempty"` + Type string `json:"type,omitempty"` +} + +// FeesParams allows the client to filter fees +type FeesParams struct { + PaginationParams + Reference string `json:"reference,omitempty"` + Type string `json:"type,omitempty"` + AccountID string `json:"accountId,omitempty"` +} + +// TransfersParams allows the client to filter transfers +type TransfersParams struct { + PaginationParams + AccountID string `json:"accountId,omitempty"` + AccountIDFrom string `json:"accountIdFrom,omitempty"` + AccountIDTo string `json:"accountIdTo,omitempty"` +} + +// VoteParams allows the client to filter votes +type VoteParams struct { + PaginationParams + ElectionID string `json:"electionId,omitempty"` +} + +// ### Objects returned ### + +// CountResult wraps a count inside an object +type CountResult struct { + Count uint64 `json:"count" example:"10"` +} + +// Pagination contains all the values needed for the UI to easily organize the returned data +type Pagination struct { + TotalItems uint64 `json:"totalItems"` + PreviousPage *uint64 `json:"previousPage"` + CurrentPage uint64 `json:"currentPage"` + NextPage *uint64 `json:"nextPage"` + LastPage uint64 `json:"lastPage"` } -type OrganizationList struct { +type OrganizationSummary struct { OrganizationID types.HexBytes `json:"organizationID" example:"0x370372b92514d81a0e3efb8eba9d036ae0877653"` ElectionCount uint64 `json:"electionCount" example:"1"` } +// OrganizationsList is used to return a paginated list to the client +type OrganizationsList struct { + Organizations []*OrganizationSummary `json:"organizations"` + Pagination *Pagination `json:"pagination"` +} + type ElectionSummary struct { ElectionID types.HexBytes `json:"electionId" ` OrganizationID types.HexBytes `json:"organizationId" ` @@ -37,6 +116,12 @@ type ElectionSummary struct { ChainID string `json:"chainId"` } +// ElectionsList is used to return a paginated list to the client +type ElectionsList struct { + Elections []*ElectionSummary `json:"elections"` + Pagination *Pagination `json:"pagination"` +} + // ElectionResults is the struct used to wrap the results of an election type ElectionResults struct { // ABIEncoded is the abi encoded election results @@ -100,13 +185,6 @@ type ElectionDescription struct { TempSIKs bool `json:"tempSIKs"` } -type ElectionFilter struct { - OrganizationID types.HexBytes `json:"organizationId,omitempty" ` - ElectionID types.HexBytes `json:"electionId,omitempty" ` - WithResults *bool `json:"withResults,omitempty"` - Status string `json:"status,omitempty"` -} - type Key struct { Index int `json:"index"` Key types.HexBytes `json:"key" ` @@ -115,7 +193,9 @@ type Key struct { type Vote struct { TxPayload []byte `json:"txPayload,omitempty" extensions:"x-omitempty" swaggerignore:"true"` TxHash types.HexBytes `json:"txHash,omitempty" extensions:"x-omitempty" ` - VoteID types.HexBytes `json:"voteID,omitempty" extensions:"x-omitempty" ` + // VoteID here produces a `voteID` over JSON that differs in casing from the rest of params and JSONs + // but is kept for backwards compatibility + VoteID types.HexBytes `json:"voteID,omitempty" extensions:"x-omitempty" ` // Sent only for encrypted elections (no results until the end) EncryptionKeyIndexes []uint32 `json:"encryptionKeys,omitempty" extensions:"x-omitempty"` // For encrypted elections this will be codified @@ -131,6 +211,11 @@ type Vote struct { Date *time.Time `json:"date,omitempty" extensions:"x-omitempty"` } +type VotesList struct { + Votes []*Vote `json:"votes"` + Pagination *Pagination `json:"pagination"` +} + type CensusTypeDescription struct { Type string `json:"type"` Size uint64 `json:"size"` @@ -180,17 +265,22 @@ type TransactionReference struct { Index uint32 `json:"transactionIndex"` } -type TransactionMetadata struct { - Type string `json:"transactionType"` - Number uint32 `json:"transactionNumber"` - Index int32 `json:"transactionIndex"` - Hash types.HexBytes `json:"transactionHash" ` +// TransactionsList is used to return a paginated list to the client +type TransactionsList struct { + Transactions []*indexertypes.Transaction `json:"transactions"` + Pagination *Pagination `json:"pagination"` } -type BlockTransactionsInfo struct { - BlockNumber uint64 `json:"blockNumber"` - TransactionsCount uint32 `json:"transactionCount"` - Transactions []TransactionMetadata `json:"transactions"` +// FeesList is used to return a paginated list to the client +type FeesList struct { + Fees []*indexertypes.TokenFeeMeta `json:"fees"` + Pagination *Pagination `json:"pagination"` +} + +// TransfersList is used to return a paginated list to the client +type TransfersList struct { + Transfers []*indexertypes.TokenTransferMeta `json:"transfers"` + Pagination *Pagination `json:"pagination"` } type GenericTransactionWithInfo struct { @@ -207,6 +297,7 @@ type ChainInfo struct { GenesisTime time.Time `json:"genesisTime" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"` InitialHeight uint32 `json:"initialHeight" example:"5467"` Height uint32 `json:"height" example:"5467"` + BlockStoreBase uint32 `json:"blockStoreBase" example:"5467"` Syncing bool `json:"syncing" example:"true"` Timestamp int64 `json:"blockTimestamp" swaggertype:"string" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"` TransactionCount uint64 `json:"transactionCount" example:"554"` @@ -218,14 +309,21 @@ type ChainInfo struct { } type Account struct { - Address types.HexBytes `json:"address" ` - Nonce uint32 `json:"nonce"` - Balance uint64 `json:"balance"` - ElectionIndex uint32 `json:"electionIndex"` - InfoURL string `json:"infoURL,omitempty"` - Token *uuid.UUID `json:"token,omitempty" swaggerignore:"true"` - Metadata *AccountMetadata `json:"metadata,omitempty"` - SIK types.HexBytes `json:"sik"` + Address types.HexBytes `json:"address" ` + Nonce uint32 `json:"nonce"` + Balance uint64 `json:"balance"` + ElectionIndex uint32 `json:"electionIndex"` + TransfersCount uint64 `json:"transfersCount,omitempty"` + FeesCount uint64 `json:"feesCount,omitempty"` + InfoURL string `json:"infoURL,omitempty"` + Token *uuid.UUID `json:"token,omitempty" swaggerignore:"true"` + Metadata *AccountMetadata `json:"metadata,omitempty"` + SIK types.HexBytes `json:"sik"` +} + +type AccountsList struct { + Accounts []*indexertypes.Account `json:"accounts"` + Pagination *Pagination `json:"pagination"` } type AccountSet struct { @@ -236,6 +334,8 @@ type AccountSet struct { } type Census struct { + // CensusID here produces a `censusID` over JSON that differs in casing from the rest of params and JSONs + // but is kept for backwards compatibility CensusID types.HexBytes `json:"censusID,omitempty"` Type string `json:"type,omitempty"` Weight *types.BigInt `json:"weight,omitempty"` diff --git a/api/census_helpers.go b/api/census_helpers.go index 58e1e441a..fd91ba0f8 100644 --- a/api/census_helpers.go +++ b/api/census_helpers.go @@ -43,7 +43,6 @@ func censusIDparse(censusID string) ([]byte, error) { censusID = util.TrimHex(censusID) if len(censusID) != censusIDsize*2 { return nil, ErrCensusIDLengthInvalid.Withf("(%d != %d)", len(censusID), censusIDsize*2) - } return hex.DecodeString(censusID) } diff --git a/api/censusdb/censusdb.go b/api/censusdb/censusdb.go index 10b0bf4ae..6bd63f1ea 100644 --- a/api/censusdb/censusdb.go +++ b/api/censusdb/censusdb.go @@ -96,12 +96,15 @@ func NewCensusDB(db db.Database) *CensusDB { // New creates a new census and adds it to the database. func (c *CensusDB) New(censusID []byte, censusType models.Census_Type, - uri string, authToken *uuid.UUID, maxLevels int) (*CensusRef, error) { + uri string, authToken *uuid.UUID, maxLevels int, +) (*CensusRef, error) { if c.Exists(censusID) { return nil, ErrCensusAlreadyExists } - tree, err := censustree.New(censustree.Options{Name: censusName(censusID), - ParentDB: c.db, MaxLevels: maxLevels, CensusType: censusType}) + tree, err := censustree.New(censustree.Options{ + Name: censusName(censusID), + ParentDB: c.db, MaxLevels: maxLevels, CensusType: censusType, + }) if err != nil { return nil, err } @@ -274,7 +277,8 @@ func (c *CensusDB) importTreeCommon(censusID []byte, data []byte) error { // addCensusRefToDB adds a censusRef to the database. func (c *CensusDB) addCensusRefToDB(censusID []byte, authToken *uuid.UUID, - t models.Census_Type, uri string, maxLevels int) (*CensusRef, error) { + t models.Census_Type, uri string, maxLevels int, +) (*CensusRef, error) { wtx := c.db.WriteTx() defer wtx.Discard() refData := bytes.Buffer{} diff --git a/api/censuses.go b/api/censuses.go index 7a61c2522..31ec08c7c 100644 --- a/api/censuses.go +++ b/api/censuses.go @@ -49,7 +49,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/participants", + "/censuses/{censusId}/participants", "POST", apirest.MethodAccessTypePublic, a.censusAddHandler, @@ -57,7 +57,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/type", + "/censuses/{censusId}/type", "GET", apirest.MethodAccessTypePublic, a.censusTypeHandler, @@ -65,7 +65,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/root", + "/censuses/{censusId}/root", "GET", apirest.MethodAccessTypePublic, a.censusRootHandler, @@ -73,7 +73,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/export", + "/censuses/{censusId}/export", "GET", apirest.MethodAccessTypePublic, a.censusDumpHandler, @@ -81,7 +81,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/import", + "/censuses/{censusId}/import", "POST", apirest.MethodAccessTypePublic, a.censusImportHandler, @@ -89,7 +89,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/weight", + "/censuses/{censusId}/weight", "GET", apirest.MethodAccessTypePublic, a.censusWeightHandler, @@ -97,7 +97,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/size", + "/censuses/{censusId}/size", "GET", apirest.MethodAccessTypePublic, a.censusSizeHandler, @@ -105,7 +105,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/publish", + "/censuses/{censusId}/publish", "POST", apirest.MethodAccessTypePublic, a.censusPublishHandler, @@ -113,7 +113,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/publish/async", + "/censuses/{censusId}/publish/async", "POST", apirest.MethodAccessTypePublic, a.censusPublishHandler, @@ -121,7 +121,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/check", + "/censuses/{censusId}/check", "GET", apirest.MethodAccessTypePublic, a.censusPublishCheckHandler, @@ -129,7 +129,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/publish/{root}", + "/censuses/{censusId}/publish/{root}", "POST", apirest.MethodAccessTypePublic, a.censusPublishHandler, @@ -137,7 +137,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}", + "/censuses/{censusId}", "DELETE", apirest.MethodAccessTypePublic, a.censusDeleteHandler, @@ -145,7 +145,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/proof/{key}", + "/censuses/{censusId}/proof/{key}", "GET", apirest.MethodAccessTypePublic, a.censusProofHandler, @@ -153,7 +153,7 @@ func (a *API) enableCensusHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/censuses/{censusID}/verify", + "/censuses/{censusId}/verify", "POST", apirest.MethodAccessTypePublic, a.censusVerifyHandler, @@ -222,7 +222,7 @@ func (a *API) censusCreateHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont if err != nil { return err } - censusType := decodeCensusType(ctx.URLParam("type")) + censusType := decodeCensusType(ctx.URLParam(ParamType)) if censusType == models.Census_UNKNOWN { return ErrCensusTypeUnknown } @@ -251,16 +251,16 @@ func (a *API) censusCreateHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont // @Accept json // @Produce json // @Security BasicAuth -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Param transaction body CensusParticipants true "PublicKey - weight array " // @Success 200 "(empty body)" -// @Router /censuses/{censusID}/participants [post] +// @Router /censuses/{censusId}/participants [post] func (a *API) censusAddHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { token, err := uuid.Parse(msg.AuthToken) if err != nil { return err } - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -341,11 +341,11 @@ func (a *API) censusAddHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext // @Tags Censuses // @Accept json // @Produce json -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Success 200 {object} object{census=string} "Census type "weighted", "zkweighted", "csp" -// @Router /censuses/{censusID}/type [get] +// @Router /censuses/{censusId}/type [get] func (a *API) censusTypeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -376,11 +376,11 @@ func (a *API) censusTypeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) // @Tags Censuses // @Accept json // @Produce json -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Success 200 {object} object{root=string} "Merkle root of the census" -// @Router /censuses/{censusID}/root [get] +// @Router /censuses/{censusId}/root [get] func (a *API) censusRootHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -414,15 +414,15 @@ func (a *API) censusRootHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) // @Accept json // @Produce json // @Security BasicAuth -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Success 200 {object} censusdb.CensusDump -// @Router /censuses/{censusID}/export [get] +// @Router /censuses/{censusId}/export [get] func (a *API) censusDumpHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { token, err := uuid.Parse(msg.AuthToken) if err != nil { return err } - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -462,15 +462,15 @@ func (a *API) censusDumpHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContex // @Accept json // @Produce json // @Security BasicAuth -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Success 200 "(empty body)" -// @Router /censuses/{censusID}/import [post] +// @Router /censuses/{censusId}/import [post] func (a *API) censusImportHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { token, err := uuid.Parse(msg.AuthToken) if err != nil { return err } - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -518,11 +518,11 @@ func (a *API) censusImportHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont // @Tags Censuses // @Accept json // @Produce json -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Success 200 {object} object{weight=string} "Sum of weight son a stringfied big int format" -// @Router /censuses/{censusID}/weight [get] +// @Router /censuses/{censusId}/weight [get] func (a *API) censusWeightHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -555,11 +555,11 @@ func (a *API) censusWeightHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContex // @Tags Censuses // @Accept json // @Produce json -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Success 200 {object} object{size=string} "Size as integer" -// @Router /censuses/{censusID}/size [get] +// @Router /censuses/{censusId}/size [get] func (a *API) censusSizeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -595,15 +595,15 @@ func (a *API) censusSizeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) // @Tags Censuses // @Accept json // @Produce json -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Success 200 "(empty body)" -// @Router /censuses/{censusID} [delete] +// @Router /censuses/{censusId} [delete] func (a *API) censusDeleteHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { token, err := uuid.Parse(msg.AuthToken) if err != nil { return err } - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -630,15 +630,17 @@ func (a *API) censusDeleteHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont // @Produce json // @Security BasicAuth // @Success 200 {object} object{census=object{censusID=string,uri=string}} "It return published censusID and the ipfs uri where its uploaded" -// @Param censusID path string true "Census id" -// @Router /censuses/{censusID}/publish [post] -// @Router /censuses/{censusID}/publish/async [post] +// @Param censusId path string true "Census id" +// @Param root path string false "Specific root where to publish the census. Not required" +// @Router /censuses/{censusId}/publish [post] +// @Router /censuses/{censusId}/publish/async [post] +// @Router /censuses/{censusId}/publish/{root} [post] func (a *API) censusPublishHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { token, err := uuid.Parse(msg.AuthToken) if err != nil { return err } - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -780,10 +782,10 @@ func (a *API) censusPublishHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCon // @Tags Censuses // @Produce json // @Success 200 {object} object{census=object{censusID=string,uri=string}} "It return published censusID and the ipfs uri where its uploaded" -// @Param censusID path string true "Census id" -// @Router /censuses/{censusID}/check [get] +// @Param censusId path string true "Census id" +// @Router /censuses/{censusId}/check [get] func (a *API) censusPublishCheckHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -817,12 +819,12 @@ func (a *API) censusPublishCheckHandler(_ *apirest.APIdata, ctx *httprouter.HTTP // @Accept json // @Produce json // @Security BasicAuth -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Param key path string true "Key to proof" // @Success 200 {object} object{weight=number,proof=string,value=string} "where proof is Merkle tree siblings and value is Merkle tree leaf value" -// @Router /censuses/{censusID}/proof/{key} [get] +// @Router /censuses/{censusId}/proof/{key} [get] func (a *API) censusProofHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -892,11 +894,11 @@ func (a *API) censusProofHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext // @Tags Censuses // @Accept json // @Produce json -// @Param censusID path string true "Census id" +// @Param censusId path string true "Census id" // @Success 200 {object} object{valid=bool} -// @Router /censuses/{censusID}/verify [post] +// @Router /censuses/{censusId}/verify [post] func (a *API) censusVerifyHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { - censusID, err := censusIDparse(ctx.URLParam("censusID")) + censusID, err := censusIDparse(ctx.URLParam(ParamCensusId)) if err != nil { return err } @@ -957,7 +959,7 @@ func (a *API) censusVerifyHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont // @Accept json // @Produce json // @Success 200 {object} object{valid=bool} -// @Router /censuses/list/ [get] +// @Router /censuses/list [get] func (a *API) censusListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { list, err := a.censusdb.List() if err != nil { @@ -979,7 +981,8 @@ func (a *API) censusListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) // @Produce json // @Param ipfs path string true "Export to IPFS. Blank to return the JSON file" // @Success 200 {object} object{valid=bool} -// @Router /censuses/export/{ipfs} [get] +// @Router /censuses/export/ipfs [get] +// @Router /censuses/export [get] func (a *API) censusExportDBHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { isIPFSExport := strings.HasSuffix(ctx.Request.URL.Path, "ipfs") buf := bytes.Buffer{} @@ -1012,7 +1015,8 @@ func (a *API) censusExportDBHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCont // @Accept json // @Produce json // @Success 200 {object} object{valid=bool} -// @Router /censuses/import/{ipfscid} [post] +// @Router /censuses/import/{ipfscid} [get] +// @Router /censuses/import [post] func (a *API) censusImportDBHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { ipfscid := ctx.URLParam("ipfscid") if ipfscid == "" { diff --git a/api/chain.go b/api/chain.go index 7300e0f11..13fd80127 100644 --- a/api/chain.go +++ b/api/chain.go @@ -18,9 +18,7 @@ import ( "go.vocdoni.io/dvote/vochain" "go.vocdoni.io/dvote/vochain/genesis" "go.vocdoni.io/dvote/vochain/indexer" - "go.vocdoni.io/dvote/vochain/indexer/indexertypes" - "go.vocdoni.io/proto/build/go/models" - "google.golang.org/protobuf/proto" + "go.vocdoni.io/dvote/vochain/state" ) const ( @@ -29,13 +27,21 @@ const ( func (a *API) enableChainHandlers() error { if err := a.Endpoint.RegisterMethod( - "/chain/organizations/page/{page}", + "/chain/organizations", "GET", apirest.MethodAccessTypePublic, a.organizationListHandler, ); err != nil { return err } + if err := a.Endpoint.RegisterMethod( + "/chain/organizations/page/{page}", + "GET", + apirest.MethodAccessTypePublic, + a.organizationListByPageHandler, + ); err != nil { + return err + } if err := a.Endpoint.RegisterMethod( "/chain/organizations/count", "GET", @@ -96,7 +102,7 @@ func (a *API) enableChainHandlers() error { "/chain/transactions/reference/{hash}", "GET", apirest.MethodAccessTypePublic, - a.chainTxbyHashHandler, + a.chainTxRefByHashHandler, ); err != nil { return err } @@ -104,7 +110,7 @@ func (a *API) enableChainHandlers() error { "/chain/transactions/reference/index/{index}", "GET", apirest.MethodAccessTypePublic, - a.chainTxByIndexHandler, + a.chainTxRefByIndexHandler, ); err != nil { return err } @@ -112,7 +118,7 @@ func (a *API) enableChainHandlers() error { "/chain/blocks/{height}/transactions/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.chainTxByHeightHandler, + a.chainTxListByHeightAndPageHandler, ); err != nil { return err } @@ -132,11 +138,19 @@ func (a *API) enableChainHandlers() error { ); err != nil { return err } + if err := a.Endpoint.RegisterMethod( + "/chain/transactions", + "GET", + apirest.MethodAccessTypePublic, + a.chainTxListHandler, + ); err != nil { + return err + } if err := a.Endpoint.RegisterMethod( "/chain/transactions/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.chainTxListPaginated, + a.chainTxListByPageHandler, ); err != nil { return err } @@ -168,7 +182,7 @@ func (a *API) enableChainHandlers() error { "/chain/organizations/filter/page/{page}", "POST", apirest.MethodAccessTypePublic, - a.chainOrganizationsFilterPaginatedHandler, + a.organizationListByFilterAndPageHandler, ); err != nil { return err } @@ -180,11 +194,19 @@ func (a *API) enableChainHandlers() error { ); err != nil { return err } + if err := a.Endpoint.RegisterMethod( + "/chain/fees", + "GET", + apirest.MethodAccessTypePublic, + a.chainFeesListHandler, + ); err != nil { + return err + } if err := a.Endpoint.RegisterMethod( "/chain/fees/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.chainListFeesHandler, + a.chainFeesListByPageHandler, ); err != nil { return err } @@ -192,7 +214,7 @@ func (a *API) enableChainHandlers() error { "/chain/fees/reference/{reference}/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.chainListFeesByReferenceHandler, + a.chainFeesListByReferenceAndPageHandler, ); err != nil { return err } @@ -200,7 +222,15 @@ func (a *API) enableChainHandlers() error { "/chain/fees/type/{type}/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.chainListFeesByTypeHandler, + a.chainFeesListByTypeAndPageHandler, + ); err != nil { + return err + } + if err := a.Endpoint.RegisterMethod( + "/chain/transfers", + "GET", + apirest.MethodAccessTypePublic, + a.chainTransfersListHandler, ); err != nil { return err } @@ -223,59 +253,151 @@ func (a *API) enableChainHandlers() error { // @Tags Chain // @Accept json // @Produce json -// @Param page path int true "Page number" -// @Success 200 {object} api.organizationListHandler.response -// @Router /chain/organizations/page/{page} [get] +// @Param page query number false "Page" +// @Param limit query number false "Items per page" +// @Param organizationId query string false "Filter by partial organizationId" +// @Success 200 {object} OrganizationsList +// @Router /chain/organizations [get] func (a *API) organizationListHandler(_ *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 + params, err := parseOrganizationParams( + ctx.QueryParam(ParamPage), + ctx.QueryParam(ParamLimit), + ctx.QueryParam(ParamOrganizationId), + ) + if err != nil { + return err + } + + list, err := a.organizationList(params) + if err != nil { + return err + } + + return marshalAndSend(ctx, list) +} + +// organizationListByPageHandler +// +// @Summary List organizations +// @Description List all organizations +// @Deprecated +// @Description (deprecated, in favor of /chain/organizations?page=xxx) +// @Tags Chain +// @Accept json +// @Produce json +// @Param page path number true "Page" +// @Success 200 {object} OrganizationsList +// @Router /chain/organizations/page/{page} [get] +func (a *API) organizationListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseOrganizationParams( + ctx.URLParam(ParamPage), + "", + "", + ) + if err != nil { + return err + } + + list, err := a.organizationList(params) + if err != nil { + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyOrganizationsList()) } + return err } - page = page * MaxPageSize - organizations := []*OrganizationList{} - list := a.indexer.EntityList(MaxPageSize, page, "") - for _, org := range list { - organizations = append(organizations, &OrganizationList{ - OrganizationID: org.EntityID, - ElectionCount: uint64(org.ProcessCount), - }) + return marshalAndSend(ctx, list) +} + +// organizationListByFilterAndPageHandler +// +// @Summary List organizations (filtered) +// @Description Returns a list of organizations filtered by its partial id, paginated by the given page +// @Deprecated +// @Description (deprecated, in favor of /chain/organizations?page=xxx&organizationId=xxx) +// @Tags Chain +// @Accept json +// @Produce json +// @Param body body OrganizationParams true "Partial organizationId to filter by" +// @Param page path number true "Page" +// @Success 200 {object} OrganizationsList +// @Router /chain/organizations/filter/page/{page} [post] +func (a *API) organizationListByFilterAndPageHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { + // get organizationId from the request params + params := &OrganizationParams{} + + // but support legacy URLParam + urlParams, err := parsePaginationParams(ctx.URLParam(ParamPage), "") + if err != nil { + return err } + params.PaginationParams = urlParams - type response struct { - Organizations []*OrganizationList `json:"organizations"` + if err := json.Unmarshal(msg.Data, ¶ms); err != nil { + return ErrCantParseDataAsJSON.WithErr(err) + } + if params == nil { // happens when client POSTs a literal `null` JSON + return ErrMissingParameter } - data, err := json.Marshal(response{organizations}) + list, err := a.organizationList(params) if err != nil { + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyOrganizationsList()) + } return err } - return ctx.Send(data, apirest.HTTPstatusOK) + return marshalAndSend(ctx, list) +} + +// organizationList produces a filtered, paginated OrganizationsList. +// +// Errors returned are always of type APIerror. +func (a *API) organizationList(params *OrganizationParams) (*OrganizationsList, error) { + orgs, total, err := a.indexer.EntityList( + params.Limit, + params.Page*params.Limit, + params.OrganizationID, + ) + if err != nil { + return nil, ErrIndexerQueryFailed.WithErr(err) + } + + pagination, err := calculatePagination(params.Page, params.Limit, total) + if err != nil { + return nil, err + } + + list := &OrganizationsList{ + Organizations: []*OrganizationSummary{}, + Pagination: pagination, + } + for _, org := range orgs { + list.Organizations = append(list.Organizations, &OrganizationSummary{ + OrganizationID: org.EntityID, + ElectionCount: uint64(org.ProcessCount), + }) + } + return list, nil } // organizationCountHandler // // @Summary Count organizations // @Description Return the number of organizations +// @Deprecated +// @Description (deprecated, in favor of /chain/organizations which reports totalItems) // @Tags Chain // @Accept json // @Produce json -// @Success 200 {object} object{count=int} "Number of registered organizations" +// @Success 200 {object} CountResult "Number of registered organizations" // @Router /chain/organizations/count [get] func (a *API) organizationCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { count := a.indexer.CountTotalEntities() - organization := &Organization{Count: &count} - data, err := json.Marshal(organization) - if err != nil { - return err - } - return ctx.Send(data, apirest.HTTPstatusOK) - + return marshalAndSend(ctx, &CountResult{Count: count}) } // chainInfoHandler @@ -318,6 +440,11 @@ func (a *API) chainInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) blockTimesInMs[0] = uint64(a.vocapp.BlockTimeTarget().Milliseconds()) } + blockStoreBase := uint32(0) + if a.vocapp.Node != nil && a.vocapp.Node.BlockStore() != nil { + blockStoreBase = uint32(a.vocapp.Node.BlockStore().Base()) + } + data, err := json.Marshal(&ChainInfo{ ID: a.vocapp.ChainID(), BlockTime: blockTimesInMs, @@ -331,6 +458,7 @@ func (a *API) chainInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) VoteCount: voteCount, GenesisTime: a.vocapp.Genesis().GenesisTime, InitialHeight: uint32(a.vocapp.Genesis().InitialHeight), + BlockStoreBase: blockStoreBase, CircuitVersion: circuit.Version(), MaxCensusSize: maxCensusSize, NetworkCapacity: networkCapacity, @@ -417,7 +545,7 @@ func (a *API) chainEstimateHeightHandler(_ *apirest.APIdata, ctx *httprouter.HTT // @Success 200 {object} object{date=string} // @Router /chain/blockToDate/{height} [get] func (a *API) chainEstimateDateHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - height, err := strconv.ParseUint(ctx.URLParam("height"), 10, 64) + height, err := strconv.ParseUint(ctx.URLParam(ParamHeight), 10, 64) if err != nil { return err } @@ -487,6 +615,10 @@ func (a *API) chainTxCostHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext for k, v := range genesis.TxCostNameToTxTypeMap { txCosts.Costs[k], err = a.vocapp.State.TxBaseCost(v, true) if err != nil { + if errors.Is(err, state.ErrTxCostNotFound) { + txCosts.Costs[k] = 0 + continue + } return err } } @@ -497,49 +629,10 @@ func (a *API) chainTxCostHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext return ctx.Send(data, apirest.HTTPstatusOK) } -// chainTxListPaginated -// -// @Summary List Transactions -// @Description To get full transaction information use [/chain/transaction/{blockHeight}/{txIndex}](transaction-by-block-index).\nWhere transactionIndex is the index of the transaction on the containing block. -// @Tags Chain -// @Accept json -// @Produce json -// @Param page path int true "Page number" -// @Success 200 {object} api.chainTxListPaginated.response "It return a list of transactions references" -// @Router /chain/transactions/page/{page} [get] -func (a *API) chainTxListPaginated(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - page := 0 - if ctx.URLParam("page") != "" { - var err error - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return err - } - } - offset := int32(page * MaxPageSize) - refs, err := a.indexer.GetLastTransactions(MaxPageSize, offset) - if err != nil { - if errors.Is(err, indexer.ErrTransactionNotFound) { - return ErrTransactionNotFound - } - return err - } - // 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"` - } - data, err := json.Marshal(response{refs}) - if err != nil { - return err - } - return ctx.Send(data, apirest.HTTPstatusOK) -} - -// chainTxbyHashHandler +// chainTxRefByHashHandler // // @Summary Transaction by hash -// @Description.markdown chainTxbyHashHandler +// @Description.markdown chainTxRefByHashHandler // @Accept json // @Produce json // @Tags Chain @@ -547,7 +640,7 @@ func (a *API) chainTxListPaginated(_ *apirest.APIdata, ctx *httprouter.HTTPConte // @Success 200 {object} indexertypes.Transaction // @Success 204 "See [errors](vocdoni-api#errors) section" // @Router /chain/transactions/reference/{hash} [get] -func (a *API) chainTxbyHashHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { +func (a *API) chainTxRefByHashHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { hash, err := hex.DecodeString(util.TrimHex(ctx.URLParam("hash"))) if err != nil { return err @@ -580,7 +673,7 @@ func (a *API) chainTxbyHashHandler(_ *apirest.APIdata, ctx *httprouter.HTTPConte // @Success 204 "See [errors](vocdoni-api#errors) section" // @Router /chain/transactions/{height}/{index} [get] func (a *API) chainTxHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - height, err := strconv.ParseInt(ctx.URLParam("height"), 10, 64) + height, err := strconv.ParseInt(ctx.URLParam(ParamHeight), 10, 64) if err != nil { return err } @@ -615,7 +708,7 @@ func (a *API) chainTxHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er return ctx.Send(data, apirest.HTTPstatusOK) } -// chainTxByIndexHandler +// chainTxRefByIndexHandler // // @Summary Transaction by index // @Description Get transaction by its index. This is not transaction reference (hash), and neither the block height and block index. The transaction index is an incremental counter for each transaction. You could use the transaction `block` and `index` to retrieve full info using [transaction by block and index](transaction-by-block-index). @@ -626,7 +719,7 @@ func (a *API) chainTxHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er // @Success 200 {object} indexertypes.Transaction // @Success 204 "See [errors](vocdoni-api#errors) section" // @Router /chain/transactions/reference/index/{index} [get] -func (a *API) chainTxByIndexHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { +func (a *API) chainTxRefByIndexHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { index, err := strconv.ParseUint(ctx.URLParam("index"), 10, 64) if err != nil { return err @@ -645,76 +738,133 @@ func (a *API) chainTxByIndexHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCont return ctx.Send(data, apirest.HTTPstatusOK) } -// chainTxByHeightHandler +// chainTxListHandler +// +// @Summary List transactions +// @Description To get full transaction information use [/chain/transaction/{blockHeight}/{txIndex}](transaction-by-block-index).\nWhere transactionIndex is the index of the transaction on the containing block. +// @Tags Chain +// @Accept json +// @Produce json +// @Param page query number false "Page" +// @Param limit query number false "Items per page" +// @Param height query number false "Block height" +// @Param type query string false "Tx type" +// @Success 200 {object} TransactionsList "List of transactions references" +// @Router /chain/transactions [get] +func (a *API) chainTxListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseTransactionParams( + ctx.QueryParam(ParamPage), + ctx.QueryParam(ParamLimit), + ctx.QueryParam(ParamHeight), + ctx.QueryParam(ParamType), + ) + if err != nil { + return err + } + + list, err := a.transactionList(params) + if err != nil { + return err + } + + return marshalAndSend(ctx, list) +} + +// chainTxListByPageHandler +// +// @Summary List transactions +// @Description To get full transaction information use [/chain/transaction/{blockHeight}/{txIndex}](transaction-by-block-index).\nWhere transactionIndex is the index of the transaction on the containing block. +// @Deprecated +// @Description (deprecated, in favor of /chain/transactions?page=xxx) +// @Tags Chain +// @Accept json +// @Produce json +// @Param page path number true "Page" +// @Success 200 {object} TransactionsList "List of transactions references" +// @Router /chain/transactions/page/{page} [get] +func (a *API) chainTxListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseTransactionParams( + ctx.URLParam(ParamPage), + "", + "", + "", + ) + if err != nil { + return err + } + + list, err := a.transactionList(params) + if err != nil { + // keep the odd legacy behaviour of sending a 204 rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return ErrTransactionNotFound + } + return err + } + + return marshalAndSend(ctx, list) +} + +// chainTxListByHeightAndPageHandler // // @Summary Transactions in a block // @Description Given a block returns the list of transactions for that block +// @Deprecated +// @Description (deprecated, in favor of /chain/transactions?page=xxx&height=xxx) // @Tags Chain // @Accept json // @Produce json // @Param height path number true "Block height" -// @Param page path number true "Page to paginate" -// @Success 200 {object} []TransactionMetadata +// @Param page path number true "Page" +// @Success 200 {object} TransactionsList // @Router /chain/blocks/{height}/transactions/page/{page} [get] -func (a *API) chainTxByHeightHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - height, err := strconv.ParseUint(ctx.URLParam("height"), 10, 64) +func (a *API) chainTxListByHeightAndPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseTransactionParams( + ctx.URLParam(ParamPage), + "", + ctx.URLParam(ParamHeight), + "", + ) if err != nil { return err } - block := a.vocapp.GetBlockByHeight(int64(height)) - if block == nil { - return ErrBlockNotFound - } - blockTxs := &BlockTransactionsInfo{ - BlockNumber: height, - TransactionsCount: uint32(len(block.Txs)), - Transactions: make([]TransactionMetadata, 0), - } - page := 0 - if ctx.URLParam("page") != "" { - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return ErrCantParsePageNumber.WithErr(err) + list, err := a.transactionList(params) + if err != nil { + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyTransactionsList()) } + return err } - page = page * MaxPageSize - count := 0 - for i := page; i < len(block.Txs); i++ { - if count >= MaxPageSize { - break - } - signedTx := new(models.SignedTx) - tx := new(models.Tx) - var err error - if err := proto.Unmarshal(block.Txs[i], signedTx); err != nil { - return ErrUnmarshalingServerProto.WithErr(err) - } - if err := proto.Unmarshal(signedTx.Tx, tx); err != nil { - return ErrUnmarshalingServerProto.WithErr(err) - } - txType := string( - tx.ProtoReflect().WhichOneof( - tx.ProtoReflect().Descriptor().Oneofs().Get(0)).Name()) - // TODO: can we avoid indexer Get calls in a loop? - txRef, err := a.indexer.GetTxHashReference(block.Txs[i].Hash()) - if err != nil { - return ErrTransactionNotFound - } - blockTxs.Transactions = append(blockTxs.Transactions, TransactionMetadata{ - Type: txType, - Index: int32(i), - Number: uint32(txRef.Index), - Hash: block.Txs[i].Hash(), - }) - count++ + return marshalAndSend(ctx, list) +} + +// transactionList produces a filtered, paginated TransactionList. +// +// Errors returned are always of type APIerror. +func (a *API) transactionList(params *TransactionParams) (*TransactionsList, error) { + txs, total, err := a.indexer.SearchTransactions( + params.Limit, + params.Page*params.Limit, + params.Height, + params.Type, + ) + if err != nil { + return nil, ErrIndexerQueryFailed.WithErr(err) } - data, err := json.Marshal(blockTxs) + + pagination, err := calculatePagination(params.Page, params.Limit, total) if err != nil { - return err + return nil, err } - return ctx.Send(data, apirest.HTTPstatusOK) + + list := &TransactionsList{ + Transactions: txs, + Pagination: pagination, + } + return list, nil } // chainValidatorsHandler @@ -763,7 +913,7 @@ func (a *API) chainValidatorsHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCon // @Success 200 {object} api.Block // @Router /chain/blocks/{height} [get] func (a *API) chainBlockHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - height, err := strconv.ParseUint(ctx.URLParam("height"), 10, 64) + height, err := strconv.ParseUint(ctx.URLParam(ParamHeight), 10, 64) if err != nil { return err } @@ -822,204 +972,273 @@ func (a *API) chainBlockByHashHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCo return ctx.Send(convertKeysToCamel(data), apirest.HTTPstatusOK) } -// chainOrganizationsFilterPaginatedHandler +// chainTransactionCountHandler // -// @Summary List organizations (filtered) -// @Description Returns a list of organizations filtered by its partial id, paginated by the given page +// @Summary Transactions count +// @Description Returns the number of transactions +// @Deprecated +// @Description (deprecated, in favor of /chain/transactions which reports totalItems) // @Tags Chain // @Accept json // @Produce json -// @Param organizationId body object{organizationId=string} true "Partial organizationId to filter by" -// @Param page path int true "Current page" -// @Success 200 {object} object{organizations=[]api.OrganizationList} -// @Router /chain/organizations/filter/page/{page} [post] -func (a *API) chainOrganizationsFilterPaginatedHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { - // get organizationId from the request body - requestData := struct { - OrganizationId string `json:"organizationId"` - }{} - if err := json.Unmarshal(msg.Data, &requestData); err != nil { - return ErrCantParseDataAsJSON.WithErr(err) - } - // get page - var err error - page := 0 - if ctx.URLParam("page") != "" { - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return ErrCantParsePageNumber.WithErr(err) - } - } - page = page * MaxPageSize - - organizations := []*OrganizationList{} - // get matching organization ids from the indexer - matchingOrganizationIds := a.indexer.EntityList(MaxPageSize, page, util.TrimHex(requestData.OrganizationId)) - if len(matchingOrganizationIds) == 0 { - return ErrOrgNotFound - } - - for _, org := range matchingOrganizationIds { - organizations = append(organizations, &OrganizationList{ - OrganizationID: org.EntityID, - ElectionCount: uint64(org.ProcessCount), - }) - } - - data, err := json.Marshal(struct { - Organizations []*OrganizationList `json:"organizations"` - }{organizations}) +// @Success 200 {object} CountResult +// @Router /chain/transactions/count [get] +func (a *API) chainTxCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + count, err := a.indexer.CountTotalTransactions() if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + return marshalAndSend(ctx, &CountResult{Count: count}) } -// chainTransactionCountHandler +// chainFeesListHandler // -// @Summary Transactions count -// @Description Returns the number of transactions +// @Summary List all token fees +// @Description Returns the token fees list ordered by date. A spending is an amount of tokens burnt from one account for executing transactions. // @Tags Chain // @Accept json // @Produce json -// @Success 200 {object} uint64 -// @Success 200 {object} object{count=number} -// @Router /chain/transactions/count [get] -func (a *API) chainTxCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - count, err := a.indexer.CountTotalTransactions() +// @Param page query number false "Page" +// @Param limit query number false "Items per page" +// @Param reference query string false "Reference filter" +// @Param type query string false "Type filter" +// @Param accountId query string false "Specific accountId" +// @Success 200 {object} FeesList +// @Router /chain/fees [get] +func (a *API) chainFeesListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseFeesParams( + ctx.QueryParam(ParamPage), + ctx.QueryParam(ParamLimit), + ctx.QueryParam(ParamReference), + ctx.QueryParam(ParamType), + ctx.QueryParam(ParamAccountId), + ) if err != nil { return err } - data, err := json.Marshal( - struct { - Count uint64 `json:"count"` - }{Count: count}, - ) + + list, err := a.feesList(params) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + return marshalAndSend(ctx, list) } -// chainListFeesHandler +// chainFeesListByPageHandler // // @Summary List all token fees // @Description Returns the token fees list ordered by date. A spending is an amount of tokens burnt from one account for executing transactions. -// @Tags Accounts +// @Deprecated +// @Description (deprecated, in favor of /chain/fees?page=xxx) +// @Tags Chain // @Accept json // @Produce json -// @Param page path string true "Paginator page" -// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta} +// @Param page path number true "Page" +// @Success 200 {object} FeesList // @Router /chain/fees/page/{page} [get] -func (a *API) chainListFeesHandler(_ *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 - - fees, err := a.indexer.GetTokenFees(int32(page), MaxPageSize) +func (a *API) chainFeesListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseFeesParams( + ctx.URLParam(ParamPage), + "", + "", + "", + "", + ) if err != nil { - return ErrCantFetchTokenTransfers.WithErr(err) + return err } - data, err := json.Marshal( - struct { - Fees []*indexertypes.TokenFeeMeta `json:"fees"` - }{Fees: fees}, - ) + + list, err := a.feesList(params) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyFeesList()) + } + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + + return marshalAndSend(ctx, list) } -// chainListFeesByReferenceHandler +// chainFeesListByReferenceAndPageHandler // // @Summary List all token fees by reference // @Description Returns the token fees list filtered by reference and ordered by date. A spending is an amount of tokens burnt from one account for executing transactions. -// @Tags Accounts +// @Deprecated +// @Description (deprecated, in favor of /chain/fees?page=xxx) +// @Tags Chain // @Accept json // @Produce json // @Param reference path string true "Reference filter" -// @Param page path string true "Paginator page" -// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta} +// @Param page path number true "Page" +// @Success 200 {object} FeesList // @Router /chain/fees/reference/{reference}/page/{page} [get] -func (a *API) chainListFeesByReferenceHandler(_ *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 - } +func (a *API) chainFeesListByReferenceAndPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseFeesParams( + ctx.URLParam(ParamPage), + "", + ctx.URLParam(ParamReference), + "", + "", + ) + if err != nil { + return err } - page = page * MaxPageSize - reference := ctx.URLParam("reference") - if reference == "" { + if params.Reference == "" { return ErrMissingParameter } - fees, err := a.indexer.GetTokenFeesByReference(reference, int32(page), MaxPageSize) + list, err := a.feesList(params) if err != nil { - return ErrCantFetchTokenTransfers.WithErr(err) - } - data, err := json.Marshal( - struct { - Fees []*indexertypes.TokenFeeMeta `json:"fees"` - }{Fees: fees}, - ) - if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyFeesList()) + } + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + + return marshalAndSend(ctx, list) } -// chainListFeesByTypeHandler +// chainFeesListByTypeAndPageHandler // // @Summary List all token fees by type // @Description Returns the token fees list filtered by type and ordered by date. A spending is an amount of tokens burnt from one account for executing transactions. -// @Tags Accounts +// @Deprecated +// @Description (deprecated, in favor of /chain/fees?page=xxx) +// @Tags Chain // @Accept json // @Produce json // @Param type path string true "Type filter" -// @Param page path string true "Paginator page" -// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta} +// @Param page path number true "Page" +// @Success 200 {object} FeesList // @Router /chain/fees/type/{type}/page/{page} [get] -func (a *API) chainListFeesByTypeHandler(_ *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 - } +func (a *API) chainFeesListByTypeAndPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseFeesParams( + ctx.URLParam(ParamPage), + "", + "", + ctx.URLParam(ParamType), + "", + ) + if err != nil { + return err } - page = page * MaxPageSize - typeFilter := ctx.URLParam("type") - if typeFilter == "" { + if params.Type == "" { return ErrMissingParameter } - fees, err := a.indexer.GetTokenFeesByType(typeFilter, int32(page), MaxPageSize) + list, err := a.feesList(params) if err != nil { - return ErrCantFetchTokenTransfers.WithErr(err) + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyFeesList()) + } + return err + } + + return marshalAndSend(ctx, list) +} + +// feesList produces a filtered, paginated FeesList. +// +// Errors returned are always of type APIerror. +func (a *API) feesList(params *FeesParams) (*FeesList, error) { + if params.AccountID != "" && !a.indexer.AccountExists(params.AccountID) { + return nil, ErrAccountNotFound } - data, err := json.Marshal( - struct { - Fees []*indexertypes.TokenFeeMeta `json:"fees"` - }{Fees: fees}, + + fees, total, err := a.indexer.TokenFeesList( + params.Limit, + params.Page*params.Limit, + params.Type, + params.Reference, + params.AccountID, ) if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + return nil, ErrIndexerQueryFailed.WithErr(err) } - return ctx.Send(data, apirest.HTTPstatusOK) + + pagination, err := calculatePagination(params.Page, params.Limit, total) + if err != nil { + return nil, err + } + + list := &FeesList{ + Fees: fees, + Pagination: pagination, + } + return list, nil +} + +// chainTransfersListHandler +// +// @Summary List all token transfers +// @Description Returns the token transfers list ordered by date. +// @Tags Chain +// @Accept json +// @Produce json +// @Param page query number false "Page" +// @Param limit query number false "Items per page" +// @Param accountId query string false "Specific accountId that sent or received the tokens" +// @Param accountIdFrom query string false "Specific accountId that sent the tokens" +// @Param accountIdTo query string false "Specific accountId that received the tokens" +// @Success 200 {object} TransfersList +// @Router /chain/transfers [get] +func (a *API) chainTransfersListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseTransfersParams( + ctx.QueryParam(ParamPage), + ctx.QueryParam(ParamLimit), + ctx.QueryParam(ParamAccountId), + ctx.QueryParam(ParamAccountIdFrom), + ctx.QueryParam(ParamAccountIdTo), + ) + if err != nil { + return err + } + + list, err := a.transfersList(params) + if err != nil { + return err + } + + return marshalAndSend(ctx, list) +} + +// transfersList produces a filtered, paginated TransfersList. +// +// Errors returned are always of type APIerror. +func (a *API) transfersList(params *TransfersParams) (*TransfersList, error) { + for _, param := range []string{params.AccountID, params.AccountIDFrom, params.AccountIDTo} { + if param != "" && !a.indexer.AccountExists(param) { + return nil, ErrAccountNotFound.With(param) + } + } + + transfers, total, err := a.indexer.TokenTransfersList( + params.Limit, + params.Page*params.Limit, + params.AccountID, + params.AccountIDFrom, + params.AccountIDTo, + ) + if err != nil { + return nil, ErrIndexerQueryFailed.WithErr(err) + } + + pagination, err := calculatePagination(params.Page, params.Limit, total) + if err != nil { + return nil, err + } + + list := &TransfersList{ + Transfers: transfers, + Pagination: pagination, + } + return list, nil } // chainIndexerExportHandler @@ -1039,3 +1258,65 @@ func (a *API) chainIndexerExportHandler(_ *apirest.APIdata, ctx *httprouter.HTTP } return ctx.Send(data, apirest.HTTPstatusOK) } + +// parseOrganizationParams returns an OrganizationParams filled with the passed params +func parseOrganizationParams(paramPage, paramLimit, paramOrganizationID string) (*OrganizationParams, error) { + pagination, err := parsePaginationParams(paramPage, paramLimit) + if err != nil { + return nil, err + } + + return &OrganizationParams{ + PaginationParams: pagination, + OrganizationID: util.TrimHex(paramOrganizationID), + }, nil +} + +// parseFeesParams returns an FeesParams filled with the passed params +func parseFeesParams(paramPage, paramLimit, paramReference, paramType, paramAccountId string) (*FeesParams, error) { + pagination, err := parsePaginationParams(paramPage, paramLimit) + if err != nil { + return nil, err + } + + return &FeesParams{ + PaginationParams: pagination, + Reference: util.TrimHex(paramReference), + Type: paramType, + AccountID: util.TrimHex(paramAccountId), + }, nil +} + +// parseTransfersParams returns an TransfersParams filled with the passed params +func parseTransfersParams(paramPage, paramLimit, paramAccountId, paramAccountIdFrom, paramAccountIdTo string) (*TransfersParams, error) { + pagination, err := parsePaginationParams(paramPage, paramLimit) + if err != nil { + return nil, err + } + + return &TransfersParams{ + PaginationParams: pagination, + AccountID: util.TrimHex(paramAccountId), + AccountIDFrom: util.TrimHex(paramAccountIdFrom), + AccountIDTo: util.TrimHex(paramAccountIdTo), + }, nil +} + +// parseTransactionParams returns an TransactionParams filled with the passed params +func parseTransactionParams(paramPage, paramLimit, paramHeight, paramType string) (*TransactionParams, error) { + pagination, err := parsePaginationParams(paramPage, paramLimit) + if err != nil { + return nil, err + } + + height, err := parseNumber(paramHeight) + if err != nil { + return nil, err + } + + return &TransactionParams{ + PaginationParams: pagination, + Height: uint64(height), + Type: paramType, + }, nil +} diff --git a/api/docs/descriptions/chainTxbyHashHandler.md b/api/docs/descriptions/chainTxRefByHashHandler.md similarity index 100% rename from api/docs/descriptions/chainTxbyHashHandler.md rename to api/docs/descriptions/chainTxRefByHashHandler.md diff --git a/api/docs/descriptions/electionFilterPaginatedHandler.md b/api/docs/descriptions/electionListByFilterHandler.md similarity index 71% rename from api/docs/descriptions/electionFilterPaginatedHandler.md rename to api/docs/descriptions/electionListByFilterHandler.md index d058edd46..63cd19fae 100644 --- a/api/docs/descriptions/electionFilterPaginatedHandler.md +++ b/api/docs/descriptions/electionListByFilterHandler.md @@ -11,4 +11,6 @@ Returns a filtered list of elections. The filters have to be sent on the request `electionId` can be partial. -See [elections list](elections-list) \ No newline at end of file +See [elections list](elections-list) + +(deprecated, in favor of /elections?page=xxx&organizationId=xxx&status=xxx) diff --git a/api/docs/models/models.go b/api/docs/models/models.go index f9455a695..b0e8bf4fe 100644 --- a/api/docs/models/models.go +++ b/api/docs/models/models.go @@ -19,37 +19,3 @@ import ( // @Success 200 {object} models.Tx_SetKeykeeper func ChainTxHandler() { } - -// ElectionListByStatusHandler -// -// Add multiple router on swagger generation has this bug https://github.com/swaggo/swag/issues/1267 -// -// @Summary List organization elections by status -// @Description List the elections of an organization by status -// @Tags Accounts -// @Accept json -// @Produce json -// @Param organizationID path string true "Specific organizationID" -// @Param status path string true "Status of the election" Enums(ready, paused, canceled, ended, results) -// @Param page path number true "Define de page number" -// @Success 200 {object} object{elections=[]api.ElectionSummary} -// @Router /accounts/{organizationID}/elections/status/{status}/page/{page} [get] -func ElectionListByStatusHandler() { -} - -// CensusPublishRootHandler -// -// Add multiple router on swagger generation has this bug https://github.com/swaggo/swag/issues/1267 -// -// @Summary Publish census at root -// @Description.markdown censusPublishHandler -// @Tags Censuses -// @Accept json -// @Produce json -// @Security BasicAuth -// @Success 200 {object} object{census=object{censusID=string,uri=string}} "It return published censusID and the ipfs uri where its uploaded" -// @Param censusID path string true "Census id" -// @Param root path string true "Specific root where to publish the census. Not required" -// @Router /censuses/{censusID}/publish/{root} [post] -func CensusPublishRootHandler() { -} diff --git a/api/elections.go b/api/elections.go index 99ca26f63..9e921f616 100644 --- a/api/elections.go +++ b/api/elections.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "strconv" "strings" "time" @@ -34,12 +33,20 @@ func (a *API) enableElectionHandlers() error { "/elections/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.electionFullListHandler, + a.electionListByPageHandler, ); err != nil { return err } if err := a.Endpoint.RegisterMethod( - "/elections/{electionID}", + "/elections", + "GET", + apirest.MethodAccessTypePublic, + a.electionListHandler, + ); err != nil { + return err + } + if err := a.Endpoint.RegisterMethod( + "/elections/{electionId}", "GET", apirest.MethodAccessTypePublic, a.electionHandler, @@ -47,7 +54,7 @@ func (a *API) enableElectionHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/elections/{electionID}/keys", + "/elections/{electionId}/keys", "GET", apirest.MethodAccessTypePublic, a.electionKeysHandler, @@ -55,7 +62,7 @@ func (a *API) enableElectionHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/elections/{electionID}/votes/count", + "/elections/{electionId}/votes/count", "GET", apirest.MethodAccessTypePublic, a.electionVotesCountHandler, @@ -63,15 +70,15 @@ func (a *API) enableElectionHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/elections/{electionID}/votes/page/{page}", + "/elections/{electionId}/votes/page/{page}", "GET", apirest.MethodAccessTypePublic, - a.electionVotesHandler, + a.electionVotesListByPageHandler, ); err != nil { return err } if err := a.Endpoint.RegisterMethod( - "/elections/{electionID}/scrutiny", + "/elections/{electionId}/scrutiny", "GET", apirest.MethodAccessTypePublic, a.electionScrutinyHandler, @@ -107,7 +114,15 @@ func (a *API) enableElectionHandlers() error { "/elections/filter/page/{page}", "POST", apirest.MethodAccessTypePublic, - a.electionFilterPaginatedHandler, + a.electionListByFilterAndPageHandler, + ); err != nil { + return err + } + if err := a.Endpoint.RegisterMethod( + "/elections/filter", + "POST", + apirest.MethodAccessTypePublic, + a.electionListByFilterHandler, ); err != nil { return err } @@ -124,47 +139,205 @@ func (a *API) enableElectionHandlers() error { return nil } -// electionFullListHandler +// electionListByFilterAndPageHandler +// +// @Summary List elections (filtered) +// @Deprecated +// @Description (deprecated, in favor of /elections?page=xxx&organizationId=xxx&status=xxx) +// @Tags Elections +// @Accept json +// @Produce json +// @Param page path number true "Page" +// @Param body body ElectionParams true "Filtered by exact organizationId, partial electionId, election status, results available or not, etc" +// @Success 200 {object} ElectionsList +// @Router /elections/filter/page/{page} [post] +func (a *API) electionListByFilterAndPageHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { + // get params from the request body + params := &ElectionParams{} + + // but support legacy URLParam + urlParams, err := parsePaginationParams(ctx.URLParam(ParamPage), "") + if err != nil { + return err + } + params.PaginationParams = urlParams + + if err := json.Unmarshal(msg.Data, ¶ms); err != nil { + return ErrCantParseDataAsJSON.WithErr(err) + } + if params == nil { // happens when client POSTs a literal `null` JSON + return ErrMissingParameter + } + + list, err := a.electionList(params) + if err != nil { + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyElectionsList()) + } + return err + } + + return marshalAndSend(ctx, list) +} + +// electionListByFilterHandler +// +// @Summary List elections (filtered) +// @Deprecated +// @Description.markdown electionListByFilterHandler +// @Tags Elections +// @Accept json +// @Produce json +// @Param page query number false "Page" +// @Param body body ElectionParams true "Filtered by partial organizationId, partial electionId, election status and with results available or not" +// @Success 200 {object} ElectionsList +// @Router /elections/filter [post] +func (a *API) electionListByFilterHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { + // get params from the request body + params := &ElectionParams{} + if err := json.Unmarshal(msg.Data, ¶ms); err != nil { + return ErrCantParseDataAsJSON.WithErr(err) + } + + list, err := a.electionList(params) + if err != nil { + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyElectionsList()) + } + return err + } + + return marshalAndSend(ctx, list) +} + +// electionListByPageHandler // // @Summary List elections -// @Description Get a list of elections summaries. +// @Description Get a list of elections summaries +// @Deprecated +// @Description (deprecated, in favor of /elections?page=xxx) // @Tags Elections // @Accept json // @Produce json -// @Param page path number true "Page " -// @Success 200 {object} ElectionSummary +// @Param page path number true "Page" +// @Success 200 {object} ElectionsList // @Router /elections/page/{page} [get] -func (a *API) electionFullListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - page := 0 - if ctx.URLParam("page") != "" { - var err error - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return ErrCantParsePageNumber.With(ctx.URLParam("page")) +func (a *API) electionListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := electionParams(ctx.URLParam, + ParamPage, + ) + if err != nil { + return err + } + + list, err := a.electionList(params) + if err != nil { + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyElectionsList()) } + return err + } + + return marshalAndSend(ctx, list) +} + +// electionListHandler +// +// @Summary List elections +// @Description Get a list of elections summaries. +// @Tags Elections +// @Accept json +// @Produce json +// @Param page query number false "Page" +// @Param limit query number false "Items per page" +// @Param organizationId query string false "Filter by partial organizationId" +// @Param status query string false "Election status" Enums(ready, paused, canceled, ended, results) +// @Param electionId query string false "Filter by partial electionId" +// @Param withResults query boolean false "Filter by (partial or final) results available or not" +// @Param finalResults query boolean false "Filter by final results available or not" +// @Param manuallyEnded query boolean false "Filter by whether the election was manually ended or not" +// @Success 200 {object} ElectionsList +// @Router /elections [get] +func (a *API) electionListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := electionParams(ctx.QueryParam, + ParamPage, + ParamLimit, + ParamStatus, + ParamOrganizationId, + ParamElectionId, + ParamWithResults, + ParamFinalResults, + ParamManuallyEnded, + ParamStartDateAfter, + ParamStartDateBefore, + ParamEndDateAfter, + ParamEndDateBefore, + ) + if err != nil { + return err + } + + list, err := a.electionList(params) + if err != nil { + return err + } + + return marshalAndSend(ctx, list) +} + +// electionList produces a filtered, paginated ElectionsList. +// +// Errors returned are always of type APIerror. +func (a *API) electionList(params *ElectionParams) (*ElectionsList, error) { + if params.OrganizationID != "" && !a.indexer.EntityExists(params.OrganizationID) { + return nil, ErrOrgNotFound + } + + status, err := parseStatus(params.Status) + if err != nil { + return nil, err + } + + eids, total, err := a.indexer.ProcessList( + params.Limit, + params.Page*params.Limit, + params.OrganizationID, + params.ElectionID, + 0, + 0, + status, + params.WithResults, + params.FinalResults, + params.ManuallyEnded, + params.StartDateAfter, + params.StartDateBefore, + params.EndDateAfter, + params.EndDateBefore, + ) + if err != nil { + return nil, ErrIndexerQueryFailed.WithErr(err) } - elections, err := a.indexer.ProcessList(nil, page*MaxPageSize, MaxPageSize, "", 0, 0, "", false) + + pagination, err := calculatePagination(params.Page, params.Limit, total) if err != nil { - return ErrCantFetchElectionList.WithErr(err) + return nil, err } - list := []ElectionSummary{} - for _, eid := range elections { + list := &ElectionsList{ + Elections: []*ElectionSummary{}, + Pagination: pagination, + } + for _, eid := range eids { e, err := a.indexer.ProcessInfo(eid) if err != nil { - return ErrCantFetchElection.Withf("(%x): %v", eid, err) + return nil, ErrCantFetchElection.Withf("(%x): %v", eid, err) } - list = append(list, a.electionSummary(e)) - } - // wrap list in a struct to consistently return list in an object, return empty - // object if the list does not contains any result - data, err := json.Marshal(struct { - Elections []ElectionSummary `json:"elections"` - }{list}) - if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + list.Elections = append(list.Elections, a.electionSummary(e)) } - return ctx.Send(data, apirest.HTTPstatusOK) + return list, nil } // electionHandler @@ -174,13 +347,13 @@ func (a *API) electionFullListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCo // @Tags Elections // @Accept json // @Produce json -// @Param electionID path string true "Election id" +// @Param electionId path string true "Election id" // @Success 200 {object} Election -// @Router /elections/{electionID} [get] +// @Router /elections/{electionId} [get] func (a *API) electionHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("electionID"))) + electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamElectionId))) if err != nil { - return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam("electionID"), err) + return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam(ParamElectionId), err) } proc, err := a.indexer.ProcessInfo(electionID) if err != nil { @@ -191,7 +364,7 @@ func (a *API) electionHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) e } election := Election{ - ElectionSummary: a.electionSummary(proc), + ElectionSummary: *a.electionSummary(proc), MetadataURL: proc.Metadata, CreationTime: proc.CreationTime, VoteMode: VoteMode{EnvelopeType: proc.Envelope}, @@ -242,16 +415,18 @@ func (a *API) electionHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) e // // @Summary Count election votes // @Description Get the number of votes for an election +// @Deprecated +// @Description (deprecated, in favor of /votes?electionId=xxx which reports totalItems) // @Tags Elections // @Accept json // @Produce json -// @Param electionID path string true "Election id" -// @Success 200 {object} object{count=number} -// @Router /elections/{electionID}/votes/count [get] +// @Param electionId path string true "Election id" +// @Success 200 {object} CountResult +// @Router /elections/{electionId}/votes/count [get] func (a *API) electionVotesCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("electionID"))) + electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamElectionId))) if err != nil || electionID == nil { - return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam("electionID"), err) + return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam(ParamElectionId), err) } // check process exists and return 404 if not // TODO: use the indexer to count votes @@ -265,15 +440,7 @@ func (a *API) electionVotesCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTP } else if err != nil { return ErrCantCountVotes.WithErr(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) + return marshalAndSend(ctx, &CountResult{Count: count}) } // electionKeysHandler @@ -283,13 +450,13 @@ func (a *API) electionVotesCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTP // @Tags Elections // @Accept json // @Produce json -// @Param electionID path string true "Election id" +// @Param electionId path string true "Election id" // @Success 200 {object} ElectionKeys -// @Router /elections/{electionID}/keys [get] +// @Router /elections/{electionId}/keys [get] func (a *API) electionKeysHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("electionID"))) + electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamElectionId))) if err != nil || electionID == nil { - return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam("electionID"), err) + return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam(ParamElectionId), err) } // TODO: sqlite also has public and private keys, consider using it instead process, err := getElection(electionID, a.vocapp.State) @@ -327,59 +494,39 @@ func (a *API) electionKeysHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContex return ctx.Send(data, apirest.HTTPstatusOK) } -// electionVotesHandler +// electionVotesListByPageHandler // // @Summary List election votes // @Description Returns the list of voteIDs for an election (paginated) +// @Deprecated +// @Description (deprecated, in favor of /votes?page=xxx&electionId=xxx) // @Tags Elections // @Accept json // @Produce json -// @Param electionID path string true "Election id" -// @Param page path number true "Page " -// @Success 200 {object} Vote -// @Router /elections/{electionID}/votes/page/{page} [get] -func (a *API) electionVotesHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("electionID"))) - if err != nil || electionID == nil { - return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam("electionID"), err) - } - // TODO: remove the getElection call? - if _, err := getElection(electionID, a.vocapp.State); err != nil { +// @Param electionId path string true "Election id" +// @Param page path number true "Page" +// @Success 200 {object} VotesList +// @Router /elections/{electionId}/votes/page/{page} [get] +func (a *API) electionVotesListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseVoteParams( + ctx.URLParam(ParamPage), + "", + ctx.URLParam(ParamElectionId), + ) + if err != nil { return err } - page := 0 - if ctx.URLParam("page") != "" { - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return ErrCantParsePageNumber - } - } - page = page * MaxPageSize - votesRaw, err := a.indexer.GetEnvelopes(electionID, MaxPageSize, page, "") + list, err := a.votesList(params) if err != nil { - if errors.Is(err, indexer.ErrVoteNotFound) { - return ErrVoteNotFound + // keep legacy behaviour of sending an empty list rather than a 404 + if errors.Is(err, ErrPageNotFound) { + return marshalAndSend(ctx, emptyVotesList()) } - return ErrCantFetchEnvelope.WithErr(err) - } - votes := []Vote{} - for _, v := range votesRaw { - votes = append(votes, Vote{ - VoteID: v.Nullifier, - VoterID: v.VoterID, - TxHash: v.TxHash, - BlockHeight: v.Height, - TransactionIndex: &v.TxIndex, - }) - } - data, err := json.Marshal(struct { - Votes []Vote `json:"votes"` - }{votes}) - if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) + return err } - return ctx.Send(data, apirest.HTTPstatusOK) + + return marshalAndSend(ctx, list) } // electionScrutinyHandler @@ -389,13 +536,13 @@ func (a *API) electionVotesHandler(_ *apirest.APIdata, ctx *httprouter.HTTPConte // @Tags Elections // @Accept json // @Produce json -// @Param electionID path string true "Election id" +// @Param electionId path string true "Election id" // @Success 200 {object} ElectionResults -// @Router /elections/{electionID}/scrutiny [get] +// @Router /elections/{electionId}/scrutiny [get] func (a *API) electionScrutinyHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("electionID"))) + electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamElectionId))) if err != nil || electionID == nil { - return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam("electionID"), err) + return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam(ParamElectionId), err) } process, err := getElection(electionID, a.vocapp.State) if err != nil { @@ -612,78 +759,6 @@ func getElection(electionID []byte, vs *state.State) (*models.Process, error) { return process, nil } -// electionFilterPaginatedHandler -// -// @Summary List elections (filtered) -// @Description.markdown electionFilterPaginatedHandler -// @Tags Elections -// @Accept json -// @Produce json -// @Param page path number true "Page to paginate" -// @Param transaction body ElectionFilter true "Filtered by partial organizationID, partial processID, process status and with results available or not" -// @Success 200 {object} ElectionSummary -// @Router /elections/filter/page/{page} [post] -func (a *API) electionFilterPaginatedHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { - // get organizationId from the request body - body := &ElectionFilter{} - if err := json.Unmarshal(msg.Data, &body); err != nil { - return ErrCantParseDataAsJSON.WithErr(err) - } - // check that at least one filter is set - if body.OrganizationID == nil && body.ElectionID == nil && body.Status == "" && body.WithResults == nil { - return ErrMissingParameter - } - // get page - var err error - page := 0 - if ctx.URLParam("page") != "" { - page, err = strconv.Atoi(ctx.URLParam("page")) - if err != nil { - return ErrCantParsePageNumber.WithErr(err) - } - } - page = page * MaxPageSize - if body.WithResults == nil { - withResults := false - body.WithResults = &withResults - } - elections, err := a.indexer.ProcessList( - body.OrganizationID, - page, - MaxPageSize, - body.ElectionID.String(), - 0, - 0, - body.Status, - *body.WithResults, - ) - if err != nil { - return ErrCantFetchElectionList.WithErr(err) - } - if len(elections) == 0 { - return ErrElectionNotFound - } - - var list []ElectionSummary - // get election summary - for _, eid := range elections { - e, err := a.indexer.ProcessInfo(eid) - if err != nil { - return ErrCantFetchElection.WithErr(err) - } - list = append(list, a.electionSummary(e)) - } - data, err := json.Marshal(struct { - Elections []ElectionSummary `json:"elections"` - }{ - Elections: list, - }) - if err != nil { - return ErrMarshalingServerJSONFailed.WithErr(err) - } - return ctx.Send(data, apirest.HTTPstatusOK) -} - // buildElectionIDHandler // // @Summary Build an election ID @@ -691,7 +766,7 @@ func (a *API) electionFilterPaginatedHandler(msg *apirest.APIdata, ctx *httprout // @Tags Elections // @Accept json // @Produce json -// @Param transaction body BuildElectionID true "Delta, OrganizationID, CensusOrigin and EnvelopeType" +// @Param transaction body BuildElectionID true "delta, organizationId, censusOrigin and envelopeType" // @Success 200 {object} object{electionID=string} // @Router /elections/id [post] func (a *API) buildElectionIDHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error { @@ -725,3 +800,44 @@ func (a *API) buildElectionIDHandler(msg *apirest.APIdata, ctx *httprouter.HTTPC } return ctx.Send(data, apirest.HTTPstatusOK) } + +// electionParams produces an ElectionParams, calling the passed func (ctx.QueryParam or ctx.URLParam) +// to retrieve all the values of all keys passed. +func electionParams(f func(key string) string, keys ...string) (*ElectionParams, error) { + strings := paramsFromCtxFunc(f, keys...) + + pagination, err := parsePaginationParams(strings[ParamPage], strings[ParamLimit]) + if err != nil { + return nil, err + } + + bools := make(map[string]*bool) + for _, v := range []string{ParamWithResults, ParamFinalResults, ParamManuallyEnded} { + bools[v], err = parseBool(strings[v]) + if err != nil { + return nil, err + } + } + + dates := make(map[string]*time.Time) + for _, v := range []string{ParamStartDateAfter, ParamStartDateBefore, ParamEndDateAfter, ParamEndDateBefore} { + dates[v], err = parseDate(strings[v]) + if err != nil { + return nil, err + } + } + + return &ElectionParams{ + PaginationParams: pagination, + OrganizationID: util.TrimHex(strings[ParamOrganizationId]), + ElectionID: util.TrimHex(strings[ParamElectionId]), + Status: strings[ParamStatus], + WithResults: bools[ParamWithResults], + FinalResults: bools[ParamFinalResults], + ManuallyEnded: bools[ParamManuallyEnded], + StartDateAfter: dates[ParamStartDateAfter], + StartDateBefore: dates[ParamStartDateBefore], + EndDateAfter: dates[ParamEndDateAfter], + EndDateBefore: dates[ParamEndDateBefore], + }, nil +} diff --git a/api/errors.go b/api/errors.go index 6346b0017..f6ed2af3c 100644 --- a/api/errors.go +++ b/api/errors.go @@ -38,25 +38,25 @@ var ( ErrMetadataURINotMatchContent = apirest.APIerror{Code: 4010, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("metadata URI does not match metadata content")} ErrMarshalingJSONFailed = apirest.APIerror{Code: 4011, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("marshaling JSON failed")} ErrFileSizeTooBig = apirest.APIerror{Code: 4012, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("file size exceeds the maximum allowed")} - ErrCantParseOrgID = apirest.APIerror{Code: 4013, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse organizationID")} - ErrCantParseAccountID = apirest.APIerror{Code: 4014, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse accountID")} + ErrCantParseOrgID = apirest.APIerror{Code: 4013, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse organizationId")} + ErrCantParseAccountID = apirest.APIerror{Code: 4014, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse accountId")} ErrCantParseBearerToken = apirest.APIerror{Code: 4015, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse bearer token")} ErrCantParseDataAsJSON = apirest.APIerror{Code: 4016, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse data as JSON")} - ErrCantParseElectionID = apirest.APIerror{Code: 4017, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse electionID")} + ErrCantParseElectionID = apirest.APIerror{Code: 4017, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse electionId")} ErrCantParseMetadataAsJSON = apirest.APIerror{Code: 4018, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse metadata (invalid format)")} - ErrCantParsePageNumber = apirest.APIerror{Code: 4019, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse page number")} + ErrCantParseNumber = apirest.APIerror{Code: 4019, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse number")} ErrCantParsePayloadAsJSON = apirest.APIerror{Code: 4020, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse payload as JSON")} - ErrCantParseVoteID = apirest.APIerror{Code: 4021, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse voteID")} + ErrCantParseVoteID = apirest.APIerror{Code: 4021, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse voteId")} ErrCantExtractMetadataURI = apirest.APIerror{Code: 4022, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot extract metadata URI")} - ErrVoteIDMalformed = apirest.APIerror{Code: 4023, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("voteID is malformed")} + ErrVoteIDMalformed = apirest.APIerror{Code: 4023, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("voteId is malformed")} ErrVoteNotFound = apirest.APIerror{Code: 4024, HTTPstatus: apirest.HTTPstatusNotFound, Err: fmt.Errorf("vote not found")} - ErrCensusIDLengthInvalid = apirest.APIerror{Code: 4025, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("censusID length is wrong")} + ErrCensusIDLengthInvalid = apirest.APIerror{Code: 4025, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("censusId length is wrong")} ErrCensusRootIsNil = apirest.APIerror{Code: 4026, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("census root is nil")} ErrCensusTypeUnknown = apirest.APIerror{Code: 4027, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("census type is unknown")} ErrCensusTypeMismatch = apirest.APIerror{Code: 4028, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("census type mismatch")} ErrCensusIndexedFlagMismatch = apirest.APIerror{Code: 4029, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("census indexed flag mismatch")} ErrCensusRootHashMismatch = apirest.APIerror{Code: 4030, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("census root hash mismatch after importing dump")} - ErrParamStatusMissing = apirest.APIerror{Code: 4031, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("parameter (status) missing or invalid")} + ErrParamStatusInvalid = apirest.APIerror{Code: 4031, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("parameter (status) invalid")} ErrParamParticipantsMissing = apirest.APIerror{Code: 4032, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("parameter (participants) missing")} ErrParamParticipantsTooBig = apirest.APIerror{Code: 4033, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("parameter (participants) exceeds max length per call")} ErrParamDumpOrRootMissing = apirest.APIerror{Code: 4034, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("parameter (dump or root) missing")} @@ -80,16 +80,18 @@ var ( ErrUnmarshalingServerProto = apirest.APIerror{Code: 4052, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("error unmarshaling protobuf data")} ErrMarshalingServerProto = apirest.APIerror{Code: 4053, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("error marshaling protobuf data")} ErrSIKNotFound = apirest.APIerror{Code: 4054, HTTPstatus: apirest.HTTPstatusNotFound, Err: fmt.Errorf("SIK not found")} + ErrCantParseBoolean = apirest.APIerror{Code: 4055, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse string into boolean")} + ErrCantParseHexString = apirest.APIerror{Code: 4056, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse string into hex bytes")} + ErrPageNotFound = apirest.APIerror{Code: 4057, HTTPstatus: apirest.HTTPstatusNotFound, Err: fmt.Errorf("page not found")} + ErrCantParseDate = apirest.APIerror{Code: 4058, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse date")} ErrVochainEmptyReply = apirest.APIerror{Code: 5000, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain returned an empty reply")} ErrVochainSendTxFailed = apirest.APIerror{Code: 5001, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain SendTx failed")} ErrVochainGetTxFailed = apirest.APIerror{Code: 5002, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain GetTx failed")} ErrVochainReturnedErrorCode = apirest.APIerror{Code: 5003, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain replied with error code")} - ErrVochainReturnedInvalidElectionID = apirest.APIerror{Code: 5004, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain returned an invalid electionID after executing tx")} + ErrVochainReturnedInvalidElectionID = apirest.APIerror{Code: 5004, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain returned an invalid electionId after executing tx")} ErrVochainReturnedWrongMetadataCID = apirest.APIerror{Code: 5005, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain returned an unexpected metadata CID after executing tx")} ErrMarshalingServerJSONFailed = apirest.APIerror{Code: 5006, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("marshaling (server-side) JSON failed")} - ErrCantFetchElectionList = apirest.APIerror{Code: 5007, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("cannot fetch election list")} ErrCantFetchElection = apirest.APIerror{Code: 5008, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("cannot fetch election")} - ErrCantFetchElectionResults = apirest.APIerror{Code: 5009, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("cannot fetch election results")} // unused as of 2023-06-28 ErrCantFetchTokenTransfers = apirest.APIerror{Code: 5010, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("cannot fetch token transfers")} ErrCantFetchEnvelopeHeight = apirest.APIerror{Code: 5011, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("cannot fetch envelope height")} ErrCantFetchEnvelope = apirest.APIerror{Code: 5012, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("cannot fetch vote envelope")} @@ -113,4 +115,6 @@ var ( ErrVochainOverloaded = apirest.APIerror{Code: 5030, HTTPstatus: apirest.HTTPstatusServiceUnavailable, Err: fmt.Errorf("vochain overloaded")} ErrGettingSIK = apirest.APIerror{Code: 5031, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("error getting SIK")} ErrCensusBuild = apirest.APIerror{Code: 5032, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("error building census")} + ErrIndexerQueryFailed = apirest.APIerror{Code: 5033, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("indexer query failed")} + ErrCantFetchTokenFees = apirest.APIerror{Code: 5034, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("cannot fetch token fees")} ) diff --git a/api/faucet/faucet.go b/api/faucet/faucet.go index c26cdf5d4..a68a6d482 100644 --- a/api/faucet/faucet.go +++ b/api/faucet/faucet.go @@ -31,7 +31,8 @@ type FaucetAPI struct { // The networks map defines the amount of tokens to send for each network. Networks not defined are // considered invalid. func AttachFaucetAPI(signingKey *ethereum.SignKeys, amount uint64, - api *apirest.API, pathPrefix string) error { + api *apirest.API, pathPrefix string, +) error { f := &FaucetAPI{ signingKey: signingKey, amount: amount, diff --git a/api/helpers.go b/api/helpers.go index 144e349d6..3c47b4ac2 100644 --- a/api/helpers.go +++ b/api/helpers.go @@ -5,7 +5,11 @@ import ( "encoding/json" "errors" "fmt" + "math" "math/big" + "strconv" + "strings" + "time" cometpool "github.com/cometbft/cometbft/mempool" cometcoretypes "github.com/cometbft/cometbft/rpc/core/types" @@ -13,15 +17,18 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/iancoleman/strcase" "go.vocdoni.io/dvote/crypto/nacl" + "go.vocdoni.io/dvote/httprouter" + "go.vocdoni.io/dvote/httprouter/apirest" "go.vocdoni.io/dvote/types" + "go.vocdoni.io/dvote/util" "go.vocdoni.io/dvote/vochain/indexer/indexertypes" "go.vocdoni.io/proto/build/go/models" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) -func (a *API) electionSummary(pi *indexertypes.Process) ElectionSummary { - return ElectionSummary{ +func (a *API) electionSummary(pi *indexertypes.Process) *ElectionSummary { + return &ElectionSummary{ ElectionID: pi.ID, OrganizationID: pi.EntityID, Status: models.ProcessStatus_name[pi.Status], @@ -118,7 +125,8 @@ func convertKeysToCamelInner(val any) any { // in this case we encode the organizationId the censusRoot and the results that will be translated in the EVM // contract to the corresponding struct{address, bytes32, uint256[][]} func encodeEVMResultsArgs(electionId common.Hash, organizationId common.Address, censusRoot common.Hash, - sourceContractAddress common.Address, results [][]*types.BigInt) (string, error) { + sourceContractAddress common.Address, results [][]*types.BigInt, +) (string, error) { address, _ := abi.NewType("address", "", nil) bytes32, _ := abi.NewType("bytes32", "", nil) uint256SliceNested, _ := abi.NewType("uint256[][]", "", nil) @@ -161,3 +169,187 @@ func decryptVotePackage(vp []byte, privKeys []string, indexes []uint32) ([]byte, } return vp, nil } + +// marshalAndSend marshals any passed struct and sends it over ctx.Send() +func marshalAndSend(ctx *httprouter.HTTPContext, v any) error { + data, err := json.Marshal(v) + if err != nil { + return ErrMarshalingServerJSONFailed.WithErr(err) + } + return ctx.Send(data, apirest.HTTPstatusOK) +} + +// parseNumber parses a string into an int. +// +// If the string is not parseable, returns an APIerror. +// +// The empty string "" is treated specially, returns 0 with no error. +func parseNumber(s string) (int, error) { + if s == "" { + return 0, nil + } + page, err := strconv.Atoi(s) + if err != nil { + return 0, ErrCantParseNumber.With(s) + } + return page, nil +} + +// parsePage parses a string into an int. +// +// If the resulting int is negative, returns ErrNoSuchPage. +// If the string is not parseable, returns an APIerror. +// +// The empty string "" is treated specially, returns 0 with no error. +func parsePage(s string) (int, error) { + page, err := parseNumber(s) + if err != nil { + return 0, err + } + if page < 0 { + return 0, ErrPageNotFound + } + return page, nil +} + +// parseLimit parses a string into an int. +// +// The empty string "" is treated specially, returns DefaultItemsPerPage with no error. +// If the resulting int is higher than MaxItemsPerPage, returns MaxItemsPerPage. +// If the resulting int is 0 or negative, returns DefaultItemsPerPage. +// +// If the string is not parseable, returns an APIerror. +func parseLimit(s string) (int, error) { + limit, err := parseNumber(s) + if err != nil { + return 0, err + } + if limit > MaxItemsPerPage { + limit = MaxItemsPerPage + } + if limit <= 0 { + limit = DefaultItemsPerPage + } + return limit, nil +} + +// parseStatus converts a string ("READY", "ready", "PAUSED", etc) +// to a models.ProcessStatus. +// +// If the string doesn't map to a value, returns an APIerror. +// +// The empty string "" is treated specially, returns 0 with no error. +func parseStatus(s string) (models.ProcessStatus, error) { + if s == "" { + return 0, nil + } + status, found := models.ProcessStatus_value[strings.ToUpper(s)] + if !found { + return 0, ErrParamStatusInvalid.With(s) + } + return models.ProcessStatus(status), nil +} + +// parseHexString converts a string like 0x1234cafe (or 1234cafe) +// to a types.HexBytes. +// +// If the string can't be parsed, returns an APIerror. +func parseHexString(s string) (types.HexBytes, error) { + orgID, err := hex.DecodeString(util.TrimHex(s)) + if err != nil { + return nil, ErrCantParseHexString.Withf("%q", s) + } + return orgID, nil +} + +// parseBool parses a string into a boolean value. +// +// The empty string "" is treated specially, returns a nil pointer with no error. +func parseBool(s string) (*bool, error) { + if s == "" { + return nil, nil + } + b, err := strconv.ParseBool(s) + if err != nil { + return nil, ErrCantParseBoolean.With(s) + } + return &b, nil +} + +// parseDate parses an RFC3339 string into a time.Time value. +// As a convenience, accepts also time.DateOnly format (i.e. 2006-01-02). +// +// The empty string "" is treated specially, returns a nil pointer with no error. +func parseDate(s string) (*time.Time, error) { + if s == "" { + return nil, nil + } + b, err := time.Parse(time.RFC3339, s) + if err != nil { + if b, err := time.Parse(time.DateOnly, s); err == nil { + return &b, nil + } + return nil, ErrCantParseDate.WithErr(err) + } + return &b, nil +} + +// parsePaginationParams returns a PaginationParams filled with the passed params +func parsePaginationParams(paramPage, paramLimit string) (PaginationParams, error) { + page, err := parsePage(paramPage) + if err != nil { + return PaginationParams{}, err + } + + limit, err := parseLimit(paramLimit) + if err != nil { + return PaginationParams{}, err + } + + return PaginationParams{ + Page: page, + Limit: limit, + }, nil +} + +// calculatePagination calculates PreviousPage, NextPage and LastPage. +// +// If page is negative or higher than LastPage, returns an APIerror (ErrPageNotFound) +func calculatePagination(page int, limit int, totalItems uint64) (*Pagination, error) { + // pages start at 0 index, for legacy reasons + lastp := int(math.Ceil(float64(totalItems)/float64(limit)) - 1) + if totalItems == 0 { + lastp = 0 + } + + if page > lastp || page < 0 { + return nil, ErrPageNotFound + } + + var prevp, nextp *uint64 + if page > 0 { + prevPage := uint64(page - 1) + prevp = &prevPage + } + if page < lastp { + nextPage := uint64(page + 1) + nextp = &nextPage + } + + return &Pagination{ + TotalItems: totalItems, + PreviousPage: prevp, + CurrentPage: uint64(page), + NextPage: nextp, + LastPage: uint64(lastp), + }, nil +} + +// paramsFromCtxFunc calls f(key) for each key passed, and the resulting value is saved in map[key] of the returned map +func paramsFromCtxFunc(f func(key string) string, keys ...string) map[string]string { + m := make(map[string]string) + for _, key := range keys { + m[key] = f(key) + } + return m +} diff --git a/api/legacy.go b/api/legacy.go new file mode 100644 index 000000000..436ea1531 --- /dev/null +++ b/api/legacy.go @@ -0,0 +1,61 @@ +package api + +// +// Legacy lists used to return an empty list (instead of null or an error) +// + +func emptyElectionsList() any { + return struct { + List []any `json:"elections"` + }{ + List: []any{}, + } +} + +func emptyOrganizationsList() any { + return struct { + List []any `json:"organizations"` + }{ + List: []any{}, + } +} + +func emptyVotesList() any { + return struct { + List []any `json:"votes"` + }{ + List: []any{}, + } +} + +func emptyTransactionsList() any { + return struct { + List []any `json:"transactions"` + }{ + List: []any{}, + } +} + +func emptyFeesList() any { + return struct { + List []any `json:"fees"` + }{ + List: []any{}, + } +} + +func emptyTransfersList() any { + return struct { + List []any `json:"transfers"` + }{ + List: []any{}, + } +} + +func emptyAccountsList() any { + return struct { + List []any `json:"accounts"` + }{ + List: []any{}, + } +} diff --git a/api/vote.go b/api/vote.go index 9cd774a04..aeda3a862 100644 --- a/api/vote.go +++ b/api/vote.go @@ -26,7 +26,15 @@ func (a *API) enableVoteHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/votes/{voteID}", + "/votes", + "GET", + apirest.MethodAccessTypePublic, + a.votesListHandler, + ); err != nil { + return err + } + if err := a.Endpoint.RegisterMethod( + "/votes/{voteId}", "GET", apirest.MethodAccessTypePublic, a.getVoteHandler, @@ -34,7 +42,7 @@ func (a *API) enableVoteHandlers() error { return err } if err := a.Endpoint.RegisterMethod( - "/votes/verify/{electionID}/{voteID}", + "/votes/verify/{electionId}/{voteId}", "GET", apirest.MethodAccessTypePublic, a.verifyVoteHandler, @@ -90,9 +98,9 @@ func (a *API) submitVoteHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContex // @Produce json // @Param voteID path string true "Nullifier of the vote" // @Success 200 {object} Vote -// @Router /votes/{voteID} [get] +// @Router /votes/{voteId} [get] func (a *API) getVoteHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - voteID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("voteID"))) + voteID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamVoteId))) if err != nil { return ErrCantParseVoteID.WithErr(err) } @@ -162,22 +170,22 @@ func (a *API) getVoteHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er // @Tags Votes // @Accept json // @Produce json -// @Param electionID path string true "Election id" -// @Param voteID path string true "Nullifier of the vote" +// @Param electionId path string true "Election id" +// @Param voteId path string true "Nullifier of the vote" // @Success 200 "(empty body)" // @Failure 404 {object} apirest.APIerror -// @Router /votes/verify/{electionID}/{voteID} [get] +// @Router /votes/verify/{electionId}/{voteId} [get] func (a *API) verifyVoteHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - voteID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("voteID"))) + voteID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamVoteId))) if err != nil { return ErrCantParseVoteID.WithErr(err) } if len(voteID) == 0 { return ErrVoteIDMalformed.Withf("%x", voteID) } - electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("electionID"))) + electionID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamElectionId))) if err != nil { - return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam("electionID"), err) + return ErrCantParseElectionID.Withf("(%s): %v", ctx.URLParam(ParamElectionId), err) } if len(voteID) != types.ProcessIDsize { return ErrVoteIDMalformed.Withf("%x", voteID) @@ -188,3 +196,86 @@ func (a *API) verifyVoteHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) } return ctx.Send(nil, apirest.HTTPstatusOK) } + +// votesListHandler +// +// @Summary List votes +// @Description Returns the list of votes +// @Tags Votes +// @Accept json +// @Produce json +// @Param page query number false "Page" +// @Param limit query number false "Items per page" +// @Param electionId query string false "Election id" +// @Success 200 {object} VotesList +// @Router /votes [get] +func (a *API) votesListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseVoteParams( + ctx.QueryParam(ParamPage), + ctx.QueryParam(ParamLimit), + ctx.QueryParam(ParamElectionId), + ) + if err != nil { + return err + } + + list, err := a.votesList(params) + if err != nil { + return err + } + + return marshalAndSend(ctx, list) +} + +// votesList produces a filtered, paginated VotesList. +// +// Errors returned are always of type APIerror. +func (a *API) votesList(params *VoteParams) (*VotesList, error) { + if params.ElectionID != "" && !a.indexer.ProcessExists(params.ElectionID) { + return nil, ErrElectionNotFound + } + + votes, total, err := a.indexer.VoteList( + params.Limit, + params.Page*params.Limit, + params.ElectionID, + "", + ) + if err != nil { + return nil, ErrIndexerQueryFailed.WithErr(err) + } + + pagination, err := calculatePagination(params.Page, params.Limit, total) + if err != nil { + return nil, err + } + + list := &VotesList{ + Votes: []*Vote{}, + Pagination: pagination, + } + for _, vote := range votes { + list.Votes = append(list.Votes, &Vote{ + ElectionID: vote.ProcessId, + VoteID: vote.Nullifier, + VoterID: vote.VoterID, + TxHash: vote.TxHash, + BlockHeight: vote.Height, + TransactionIndex: &vote.TxIndex, + }) + } + return list, nil +} + +// parseVoteParams returns an VoteParams filled with the passed params +func parseVoteParams(paramPage, paramLimit, paramElectionID string) (*VoteParams, error) { + pagination, err := parsePaginationParams(paramPage, paramLimit) + if err != nil { + return nil, err + } + + return &VoteParams{ + PaginationParams: pagination, + ElectionID: util.TrimHex(paramElectionID), + }, nil +} diff --git a/api/wallet.go b/api/wallet.go index 303e48a0f..9d952e812 100644 --- a/api/wallet.go +++ b/api/wallet.go @@ -223,7 +223,8 @@ func (a *API) walletCreateHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont Account: wallet.Address().Bytes(), FaucetPackage: nil, }, - }}) + }, + }) if err != nil { return err } @@ -285,7 +286,8 @@ func (a *API) walletTransferHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCo To: dst.Bytes(), Value: amount, }, - }}) + }, + }) if err != nil { return err } diff --git a/apiclient/account.go b/apiclient/account.go index b600b2f54..b0e974b45 100644 --- a/apiclient/account.go +++ b/apiclient/account.go @@ -25,10 +25,8 @@ var DefaultFaucetURLs = map[string]string{ "prod": "https://api-faucet.vocdoni.io/v2/open/claim/", } -var ( - // ErrAccountNotConfigured is returned when the client has not been configured with an account. - ErrAccountNotConfigured = fmt.Errorf("account not configured") -) +// ErrAccountNotConfigured is returned when the client has not been configured with an account. +var ErrAccountNotConfigured = fmt.Errorf("account not configured") // Account returns the information about a Vocdoni account. If address is empty, it returns the information // about the account associated with the client. @@ -92,9 +90,7 @@ func (c *HTTPclient) Transfer(to common.Address, amount uint64) (types.HexBytes, // TransferWithNonce sends tokens from the account associated with the client to the given address. // Returns the transaction hash. func (c *HTTPclient) TransferWithNonce(to common.Address, amount uint64, nonce uint32) (types.HexBytes, error) { - var err error - stx := models.SignedTx{} - stx.Tx, err = proto.Marshal(&models.Tx{ + tx, err := proto.Marshal(&models.Tx{ Payload: &models.Tx_SendTokens{ SendTokens: &models.SendTokensTx{ Txtype: models.TxType_SET_ACCOUNT_INFO_URI, @@ -103,11 +99,12 @@ func (c *HTTPclient) TransferWithNonce(to common.Address, amount uint64, nonce u To: to.Bytes(), Value: amount, }, - }}) + }, + }) if err != nil { return nil, err } - txHash, _, err := c.SignAndSendTx(&stx) + txHash, _, err := c.SignAndSendTx(tx) return txHash, err } @@ -143,7 +140,8 @@ func (c *HTTPclient) AccountBootstrap(faucetPkg *models.FaucetPackage, metadata InfoURI: &metadataURI, SIK: sik, }, - }}) + }, + }) if err != nil { return nil, err } @@ -292,21 +290,19 @@ func (c *HTTPclient) AccountSetMetadata(metadata *api.AccountMetadata) (types.He } // ListTokenTransfers returns the list of sent and received token transfers associated with an account -func (c *HTTPclient) ListTokenTransfers(account common.Address, page int) (indexertypes.TokenTransfersAccount, error) { +func (c *HTTPclient) ListTokenTransfers(account common.Address, page int) (*api.TransfersList, error) { resp, code, err := c.Request(HTTPGET, nil, "accounts", account.Hex(), "transfers", "page", strconv.Itoa(page)) if err != nil { - return indexertypes.TokenTransfersAccount{}, err + return nil, err } if code != apirest.HTTPstatusOK { - return indexertypes.TokenTransfersAccount{}, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp) + return nil, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp) } - tokenTxs := new(struct { - Transfers indexertypes.TokenTransfersAccount `json:"transfers"` - }) - if err := json.Unmarshal(resp, &tokenTxs); err != nil { - return indexertypes.TokenTransfersAccount{}, err + tokenTxs := &api.TransfersList{} + if err := json.Unmarshal(resp, tokenTxs); err != nil { + return nil, err } - return tokenTxs.Transfers, nil + return tokenTxs, nil } // SetSIK function allows to update the Secret Identity Key for the current @@ -367,7 +363,8 @@ func (c *HTTPclient) DelSIK() (types.HexBytes, error) { DelSIK: &models.SIKTx{ Txtype: models.TxType_DEL_ACCOUNT_SIK, }, - }}) + }, + }) if err != nil { return nil, err } @@ -421,8 +418,7 @@ func (c *HTTPclient) RegisterSIKForVote(electionId types.HexBytes, proof *Census return nil, fmt.Errorf("error generating SIK: %w", err) } // compose and encode the transaction - stx := &models.SignedTx{} - stx.Tx, err = proto.Marshal(&models.Tx{ + tx, err := proto.Marshal(&models.Tx{ Payload: &models.Tx_RegisterSIK{ RegisterSIK: &models.RegisterSIKTx{ SIK: sik, @@ -444,7 +440,7 @@ func (c *HTTPclient) RegisterSIKForVote(electionId types.HexBytes, proof *Census return nil, fmt.Errorf("error encoding RegisterSIKTx: %w", err) } // sign it and send it - hash, _, err := c.SignAndSendTx(stx) + hash, _, err := c.SignAndSendTx(tx) if err != nil { return nil, fmt.Errorf("error signing or sending the Tx: %w", err) } diff --git a/apiclient/blockchain.go b/apiclient/blockchain.go index 5bdb26f3c..565c3806a 100644 --- a/apiclient/blockchain.go +++ b/apiclient/blockchain.go @@ -10,13 +10,10 @@ import ( "go.vocdoni.io/dvote/vochain/genesis" "go.vocdoni.io/proto/build/go/models" "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" ) -var ( - // ErrTransactionDoesNotExist is returned when the transaction does not exist - ErrTransactionDoesNotExist = fmt.Errorf("transaction does not exist") -) +// ErrTransactionDoesNotExist is returned when the transaction does not exist +var ErrTransactionDoesNotExist = fmt.Errorf("transaction does not exist") // ChainInfo returns some information about the chain, such as block height. func (c *HTTPclient) ChainInfo() (*api.ChainInfo, error) { @@ -123,7 +120,8 @@ func (c *HTTPclient) TransactionByHash(txHash types.HexBytes) (*models.Tx, error // OrganizationsBySearchTermPaginated returns a paginated list of organizations // that match the given search term. func (c *HTTPclient) OrganizationsBySearchTermPaginated( - organizationID types.HexBytes, page int) ([]types.HexBytes, error) { + organizationID types.HexBytes, page int, +) ([]types.HexBytes, error) { // make a post request to /chain/organizations/filter/page/ with the organizationID // as the body and page as the url parameter resp, code, err := c.Request(HTTPPOST, @@ -163,60 +161,3 @@ func (c *HTTPclient) TransactionCount() (uint64, error) { return txsCount.Count, json.Unmarshal(resp, txsCount) } - -func (c *HTTPclient) TransactionSetCensus(electionID types.HexBytes, census api.ElectionCensus) (types.HexBytes, error) { - if c.account == nil { - return nil, fmt.Errorf("no account configured") - } - - if _, ok := models.CensusOrigin_value[census.CensusOrigin]; !ok { - return nil, fmt.Errorf("invalid census origin %s", census.CensusOrigin) - } - - // get the own account details - acc, err := c.Account("") - if err != nil { - return nil, fmt.Errorf("could not fetch account info: %s", acc.Address) - } - - tx := &models.SetProcessTx{ - Txtype: models.TxType_SET_PROCESS_CENSUS, - Nonce: acc.Nonce, - ProcessId: electionID, - CensusRoot: census.CensusRoot, - CensusURI: &census.CensusURL, - } - - txb, err := proto.Marshal(&models.Tx{ - Payload: &models.Tx_SetProcess{SetProcess: tx}, - }) - if err != nil { - return nil, err - } - signedTxb, err := c.account.SignVocdoniTx(txb, c.chainID) - if err != nil { - return nil, err - } - stx, err := proto.Marshal( - &models.SignedTx{ - Tx: txb, - Signature: signedTxb, - }) - if err != nil { - return nil, err - } - - // send the transaction - resp, code, err := c.Request(HTTPPOST, &api.Transaction{Payload: stx}, "chain", "transactions") - if err != nil { - return nil, err - } - if code != apirest.HTTPstatusOK { - return nil, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp) - } - txResp := new(api.Transaction) - if err := json.Unmarshal(resp, txResp); err != nil { - return nil, err - } - return txResp.Hash, nil -} diff --git a/apiclient/election.go b/apiclient/election.go index 8aeaf11e3..c46d1b107 100644 --- a/apiclient/election.go +++ b/apiclient/election.go @@ -296,36 +296,114 @@ func (c *HTTPclient) SetElectionStatus(electionID types.HexBytes, status string) txb, err := proto.Marshal(&models.Tx{ Payload: &models.Tx_SetProcess{ SetProcess: &tx, - }}) + }, + }) if err != nil { return nil, err } - signedTxb, err := c.account.SignVocdoniTx(txb, c.chainID) + hash, _, err := c.SignAndSendTx(txb) + return hash, err +} + +// SetElectionCensusSize sets the new census size of an election. +func (c *HTTPclient) SetElectionCensusSize(electionID types.HexBytes, newSize uint64) (types.HexBytes, error) { + if c.account == nil { + return nil, fmt.Errorf("no account configured") + } + // get the own account details + acc, err := c.Account("") if err != nil { - return nil, err + return nil, fmt.Errorf("could not fetch account info: %s", acc.Address) } - stx, err := proto.Marshal( - &models.SignedTx{ - Tx: txb, - Signature: signedTxb, - }) + + // build the set process transaction + tx := models.SetProcessTx{ + Txtype: models.TxType_SET_PROCESS_CENSUS, + ProcessId: electionID, + CensusSize: &newSize, + Nonce: acc.Nonce, + } + txb, err := proto.Marshal(&models.Tx{ + Payload: &models.Tx_SetProcess{ + SetProcess: &tx, + }, + }) if err != nil { return nil, err } + hash, _, err := c.SignAndSendTx(txb) + return hash, err +} - // send the transaction - resp, code, err := c.Request(HTTPPOST, &api.Transaction{Payload: stx}, "chain", "transactions") +// SetElectionDuration modify the duration of an election (in seconds). +func (c *HTTPclient) SetElectionDuration(electionID types.HexBytes, newDuration uint32) (types.HexBytes, error) { + if c.account == nil { + return nil, fmt.Errorf("no account configured") + } + // get the own account details + acc, err := c.Account("") + if err != nil { + return nil, fmt.Errorf("could not fetch account info: %s", acc.Address) + } + + // build the set process transaction + tx := models.SetProcessTx{ + Txtype: models.TxType_SET_PROCESS_DURATION, + ProcessId: electionID, + Duration: &newDuration, + Nonce: acc.Nonce, + } + txb, err := proto.Marshal(&models.Tx{ + Payload: &models.Tx_SetProcess{ + SetProcess: &tx, + }, + }) if err != nil { return nil, err } - if code != apirest.HTTPstatusOK { - return nil, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp) + hash, _, err := c.SignAndSendTx(txb) + return hash, err +} + +// SetElectionCensus updates the census of an election. Root, URI and size can be updated. +func (c *HTTPclient) SetElectionCensus(electionID types.HexBytes, census api.ElectionCensus) (types.HexBytes, error) { + if c.account == nil { + return nil, fmt.Errorf("no account configured") + } + + if _, ok := models.CensusOrigin_value[census.CensusOrigin]; !ok { + return nil, fmt.Errorf("invalid census origin %s", census.CensusOrigin) + } + + // get the own account details + acc, err := c.Account("") + if err != nil { + return nil, fmt.Errorf("could not fetch account info: %s", acc.Address) + } + + tx := &models.SetProcessTx{ + Txtype: models.TxType_SET_PROCESS_CENSUS, + Nonce: acc.Nonce, + ProcessId: electionID, + CensusRoot: census.CensusRoot, + CensusURI: &census.CensusURL, + CensusSize: func() *uint64 { + if census.MaxCensusSize > 0 { + return &census.MaxCensusSize + } + return nil + }(), } - txResp := new(api.Transaction) - if err := json.Unmarshal(resp, txResp); err != nil { + + txb, err := proto.Marshal(&models.Tx{ + Payload: &models.Tx_SetProcess{SetProcess: tx}, + }) + if err != nil { return nil, err } - return txResp.Hash, nil + + hash, _, err := c.SignAndSendTx(txb) + return hash, err } // ElectionVoteCount returns the number of registered votes for a given election. @@ -367,7 +445,8 @@ func (c *HTTPclient) ElectionResults(electionID types.HexBytes) (*api.ElectionRe // POST /elections/filter/page/ // Returns a list of elections filtered by the given parameters. func (c *HTTPclient) ElectionFilterPaginated(organizationID types.HexBytes, electionID types.HexBytes, - status models.ProcessStatus, withResults bool, page int) (*[]api.ElectionSummary, error) { + status models.ProcessStatus, withResults bool, page int, +) (*[]api.ElectionSummary, error) { body := struct { OrganizationID types.HexBytes `json:"organizationId,omitempty"` ElectionID types.HexBytes `json:"electionId,omitempty"` diff --git a/apiclient/helpers.go b/apiclient/helpers.go index f344aae45..1d036a822 100644 --- a/apiclient/helpers.go +++ b/apiclient/helpers.go @@ -12,6 +12,7 @@ import ( "go.vocdoni.io/dvote/api" "go.vocdoni.io/dvote/api/faucet" + "go.vocdoni.io/dvote/config" "go.vocdoni.io/dvote/httprouter/apirest" "go.vocdoni.io/dvote/log" "go.vocdoni.io/dvote/types" @@ -21,9 +22,8 @@ import ( ) const ( - DefaultBlockInterval = 8 * time.Second - WaitTimeout = 3 * DefaultBlockInterval - PollInterval = DefaultBlockInterval / 2 + WaitTimeout = config.DefaultMinerTargetBlockTime * 3 + PollInterval = config.DefaultMinerTargetBlockTime / 2 ) func (c *HTTPclient) DateToHeight(date time.Time) (uint32, error) { @@ -43,17 +43,26 @@ func (c *HTTPclient) DateToHeight(date time.Time) (uint32, error) { return h.Height, nil } -func (c *HTTPclient) SignAndSendTx(stx *models.SignedTx) (types.HexBytes, []byte, error) { - var err error - if stx.Signature, err = c.account.SignVocdoniTx(stx.Tx, c.ChainID()); err != nil { +// SignAndSendTx signs the given transaction and sends it to the blockchain. +// It returns the transaction hash and the blockchain response (if any). +// Takes a protobuf marshaled transaction as input of type models.Tx +func (c *HTTPclient) SignAndSendTx(marshaledTx []byte) (types.HexBytes, []byte, error) { + // Sign the transaction + sitnature, err := c.account.SignVocdoniTx(marshaledTx, c.ChainID()) + if err != nil { return nil, nil, err } - txData, err := proto.Marshal(stx) + // Build the signed transaction + stx, err := proto.Marshal( + &models.SignedTx{ + Tx: marshaledTx, + Signature: sitnature, + }) if err != nil { return nil, nil, err } - - tx := &api.Transaction{Payload: txData} + // Send the signed transaction and fetch the response + tx := &api.Transaction{Payload: stx} resp, code, err := c.Request(HTTPPOST, tx, "chain", "transactions") if err != nil { return nil, nil, err diff --git a/benchmark/zk_census_benchmark_test.go b/benchmark/zk_census_benchmark_test.go index de5abd89c..b47dc2d63 100644 --- a/benchmark/zk_census_benchmark_test.go +++ b/benchmark/zk_census_benchmark_test.go @@ -121,7 +121,6 @@ func zkCensusBenchmark(b *testing.B, cc, sc *testutil.TestHTTPclient, vapp *voch qt.Assert(b, json.Unmarshal(resp, sikData), qt.IsNil) genProofZk(b, electionID, admin, censusData, sikData) - } func genProofZk(b *testing.B, electionID []byte, acc *ethereum.SignKeys, censusData, sikData *api.Census) { diff --git a/censustree/censustree.go b/censustree/censustree.go index 50ce22d21..515648b73 100644 --- a/censustree/censustree.go +++ b/censustree/censustree.go @@ -74,7 +74,7 @@ func DeleteCensusTreeFromDatabase(kv db.Database, name string) (int, error) { // New returns a new Tree, if there already is a Tree in the // database, it will load it. func New(opts Options) (*Tree, error) { - var maxLevels = opts.MaxLevels + maxLevels := opts.MaxLevels if maxLevels > DefaultMaxLevels { maxLevels = DefaultMaxLevels } diff --git a/censustree/censustree_test.go b/censustree/censustree_test.go index f25f6a239..7064e5965 100644 --- a/censustree/censustree_test.go +++ b/censustree/censustree_test.go @@ -18,8 +18,10 @@ import ( func TestImportWeighted(t *testing.T) { db := metadb.NewTest(t) - censusTree, err := New(Options{Name: "test", ParentDB: db, MaxLevels: DefaultMaxLevels, - CensusType: models.Census_ARBO_BLAKE2B}) + censusTree, err := New(Options{ + Name: "test", ParentDB: db, MaxLevels: DefaultMaxLevels, + CensusType: models.Census_ARBO_BLAKE2B, + }) qt.Assert(t, err, qt.IsNil) rnd := testutil.NewRandom(0) @@ -46,8 +48,10 @@ func TestImportWeighted(t *testing.T) { qt.Assert(t, err, qt.IsNil) // import into a new tree - censusTree2, err := New(Options{Name: "test2", ParentDB: db, MaxLevels: DefaultMaxLevels, - CensusType: models.Census_ARBO_BLAKE2B}) + censusTree2, err := New(Options{ + Name: "test2", ParentDB: db, MaxLevels: DefaultMaxLevels, + CensusType: models.Census_ARBO_BLAKE2B, + }) qt.Assert(t, err, qt.IsNil) err = censusTree2.ImportDump(dump) @@ -74,8 +78,10 @@ func TestImportWeighted(t *testing.T) { func TestWeightedProof(t *testing.T) { db := metadb.NewTest(t) - censusTree, err := New(Options{Name: "test", ParentDB: db, MaxLevels: DefaultMaxLevels, - CensusType: models.Census_ARBO_POSEIDON}) + censusTree, err := New(Options{ + Name: "test", ParentDB: db, MaxLevels: DefaultMaxLevels, + CensusType: models.Census_ARBO_POSEIDON, + }) qt.Assert(t, err, qt.IsNil) rnd := testutil.NewRandom(0) @@ -111,8 +117,10 @@ func TestWeightedProof(t *testing.T) { func TestGetCensusWeight(t *testing.T) { db := metadb.NewTest(t) - tree, err := New(Options{Name: "test", ParentDB: db, MaxLevels: DefaultMaxLevels, - CensusType: models.Census_ARBO_BLAKE2B}) + tree, err := New(Options{ + Name: "test", ParentDB: db, MaxLevels: DefaultMaxLevels, + CensusType: models.Census_ARBO_BLAKE2B, + }) qt.Assert(t, err, qt.IsNil) w, err := tree.GetCensusWeight() @@ -196,8 +204,10 @@ func TestGetCensusWeight(t *testing.T) { // dump the leaves & import them into a new empty tree, and check that // the censusWeight is correctly recomputed db2 := metadb.NewTest(t) - tree2, err := New(Options{Name: "test2", ParentDB: db2, MaxLevels: DefaultMaxLevels, - CensusType: models.Census_ARBO_BLAKE2B}) + tree2, err := New(Options{ + Name: "test2", ParentDB: db2, MaxLevels: DefaultMaxLevels, + CensusType: models.Census_ARBO_BLAKE2B, + }) qt.Assert(t, err, qt.IsNil) dump, err := tree.Dump() diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 9ab5a88fb..c3c8277ba 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -167,7 +167,6 @@ func main() { errorp.Println("unknown option or not yet implemented") } } - } func accountIsSet(c *VocdoniCLI) bool { @@ -626,16 +625,21 @@ func electionHandler(cli *VocdoniCLI) error { VoteType: api.VoteType{MaxVoteOverwrites: 1}, ElectionType: api.ElectionType{Autostart: true, Interruptible: true}, Questions: []api.Question{ - {Title: map[string]string{"default": "question title"}, + { + Title: map[string]string{"default": "question title"}, Description: map[string]string{"default": "question description"}, Choices: []api.ChoiceMetadata{ { Title: map[string]string{"default": "1 choice title"}, - Value: 0}, + Value: 0, + }, { Title: map[string]string{"default": "2 choice title"}, - Value: 1}, - }}}, + Value: 1, + }, + }, + }, + }, Census: api.CensusTypeDescription{ Type: "weighted", RootHash: make(types.HexBytes, 32), diff --git a/cmd/end2endtest/account.go b/cmd/end2endtest/account.go index c2489b265..af0ab6c86 100644 --- a/cmd/end2endtest/account.go +++ b/cmd/end2endtest/account.go @@ -30,9 +30,21 @@ func init() { example: os.Args[0] + " --operation=tokentxs " + "--host http://127.0.0.1:9090/v2", } + + ops["createaccts"] = operation{ + testFunc: func() VochainTest { + return &E2ECreateAccts{} + }, + description: "Creates N accounts", + example: os.Args[0] + " --operation=createaccts --votes=1000 " + + "--host http://127.0.0.1:9090/v2", + } } -var _ VochainTest = (*E2ETokenTxs)(nil) +var ( + _ VochainTest = (*E2ETokenTxs)(nil) + _ VochainTest = (*E2ECreateAccts)(nil) +) type E2ETokenTxs struct { api *apiclient.HTTPclient @@ -42,6 +54,8 @@ type E2ETokenTxs struct { aliceFP *models.FaucetPackage } +type E2ECreateAccts struct{ e2eElection } + func (t *E2ETokenTxs) Setup(api *apiclient.HTTPclient, config *config) error { t.api = api t.config = config @@ -80,7 +94,7 @@ func (*E2ETokenTxs) Teardown() error { func (t *E2ETokenTxs) Run() error { // check send tokens - if err := testSendTokens(t.api, t.alice, t.bob); err != nil { + if err := testSendTokens(t.api, t.alice, t.bob, t.config.timeout); err != nil { return fmt.Errorf("error in testSendTokens: %w", err) } @@ -129,7 +143,7 @@ func testCreateAndSetAccount(api *apiclient.HTTPclient, fp *models.FaucetPackage return nil } -func testSendTokens(api *apiclient.HTTPclient, aliceKeys, bobKeys *ethereum.SignKeys) error { +func testSendTokens(api *apiclient.HTTPclient, aliceKeys, bobKeys *ethereum.SignKeys, timeout time.Duration) error { // if both alice and bob start with 50 tokens each // alice sends 19 to bob // and bob sends 23 to alice @@ -207,7 +221,7 @@ func testSendTokens(api *apiclient.HTTPclient, aliceKeys, bobKeys *ethereum.Sign log.Infof("alice sent %d tokens to bob", amountAtoB) log.Debugf("tx hash is %x", txhasha) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() txrefa, err := api.WaitUntilTxIsMined(ctx, txhasha) if err != nil { @@ -229,7 +243,7 @@ func testSendTokens(api *apiclient.HTTPclient, aliceKeys, bobKeys *ethereum.Sign log.Infof("bob sent %d tokens to alice", amountBtoA) log.Debugf("tx hash is %x", txhashb) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() txrefb, err := api.WaitUntilTxIsMined(ctx, txhashb) if err != nil { @@ -315,14 +329,12 @@ func checkTokenTransfersCount(api *apiclient.HTTPclient, address common.Address) if err != nil { return err } - countTokenTxs := uint64(len(tokenTxs.Received) + len(tokenTxs.Sent)) - count, err := api.CountTokenTransfers(address) if err != nil { return err } - if count != countTokenTxs { - return fmt.Errorf("expected %s to match transfers count %d and %d", address, count, countTokenTxs) + if count != tokenTxs.Pagination.TotalItems { + return fmt.Errorf("expected %s to match transfers count %d and %d", address, count, tokenTxs.Pagination.TotalItems) } log.Infow("current transfers count", "account", address.String(), "count", count) @@ -330,7 +342,8 @@ func checkTokenTransfersCount(api *apiclient.HTTPclient, address common.Address) } func ensureAccountExists(api *apiclient.HTTPclient, - faucetPkg *models.FaucetPackage) (*apipkg.Account, error) { + faucetPkg *models.FaucetPackage, +) (*apipkg.Account, error) { for i := 0; i < retries; i++ { acct, err := api.Account("") if err != nil { @@ -381,3 +394,32 @@ func ensureAccountMetadataEquals(api *apiclient.HTTPclient, metadata *apipkg.Acc } return nil, fmt.Errorf("cannot set account %s metadata after %d retries", api.MyAddress(), retries) } + +func (t *E2ECreateAccts) Setup(api *apiclient.HTTPclient, c *config) error { + t.api = api + t.config = c + + return nil +} + +func (*E2ECreateAccts) Teardown() error { + // nothing to do here + return nil +} + +func (t *E2ECreateAccts) Run() error { + startTime := time.Now() + + voterAccounts := ethereum.NewSignKeysBatch(t.config.nvotes) + if err := t.registerAnonAccts(voterAccounts); err != nil { + return err + } + + log.Infow("accounts created successfully", + "n", t.config.nvotes, "time", time.Since(startTime), + "vps", int(float64(t.config.nvotes)/time.Since(startTime).Seconds())) + + log.Infof("voterAccounts: %d", len(voterAccounts)) + + return nil +} diff --git a/cmd/end2endtest/ballot.go b/cmd/end2endtest/ballot.go index eeaf86b97..d7f6069cf 100644 --- a/cmd/end2endtest/ballot.go +++ b/cmd/end2endtest/ballot.go @@ -51,21 +51,25 @@ func init() { } } -var _ VochainTest = (*E2EBallotRanked)(nil) -var _ VochainTest = (*E2EBallotQuadratic)(nil) -var _ VochainTest = (*E2EBallotRange)(nil) -var _ VochainTest = (*E2EBallotApproval)(nil) +var ( + _ VochainTest = (*E2EBallotRanked)(nil) + _ VochainTest = (*E2EBallotQuadratic)(nil) + _ VochainTest = (*E2EBallotRange)(nil) + _ VochainTest = (*E2EBallotApproval)(nil) +) -type E2EBallotRanked struct{ e2eElection } -type E2EBallotQuadratic struct{ e2eElection } -type E2EBallotRange struct{ e2eElection } -type E2EBallotApproval struct{ e2eElection } +type ( + E2EBallotRanked struct{ e2eElection } + E2EBallotQuadratic struct{ e2eElection } + E2EBallotRange struct{ e2eElection } + E2EBallotApproval struct{ e2eElection } +) func (t *E2EBallotRanked) Setup(api *apiclient.HTTPclient, c *config) error { t.api = api t.config = c - //setup for ranked voting + // setup for ranked voting p := newTestProcess() p.VoteOptions = &models.ProcessVoteOptions{ MaxCount: 4, @@ -275,9 +279,16 @@ func ballotVotes(vop vapi.TallyMode, nvotes int, ballotType string, expectUnique // fill initial 10 votes to be sent by the ballot test case rankedVote: v = [][]int{ - {0, 5, 0, 2}, {3, 1, 0, 2}, {3, 2, 0, 1}, {1, 0, 3, 2}, - {2, 3, 1, 1}, {4, 1, 1, 0}, {0, 0, 2, 1}, {0, 2, 1, 3}, - {1, 1, 1, 4}, {0, 3, 2, 1}, + {0, 5, 0, 2}, + {3, 1, 0, 2}, + {3, 2, 0, 1}, + {1, 0, 3, 2}, + {2, 3, 1, 1}, + {4, 1, 1, 0}, + {0, 0, 2, 1}, + {0, 2, 1, 3}, + {1, 1, 1, 4}, + {0, 3, 2, 1}, } // default results on field 1,2,3,4 for 10 votes resultsFields = append(resultsFields, votesToBigInt(20, 10, 0, 20), @@ -286,9 +297,16 @@ func ballotVotes(vop vapi.TallyMode, nvotes int, ballotType string, expectUnique case quadraticVote: v = [][]int{ - {2, 4, 2, 0}, {3, 3, 2, 2}, {2, 3, 0, 1}, {0, 2, 3, 3}, - {1, 2, 1, 2}, {5, 0, 1, 0}, {0, 2, 2, 2}, {2, 1, 1, 2}, - {1, 3, 0, 1}, {1, 3, 1, 1}, + {2, 4, 2, 0}, + {3, 3, 2, 2}, + {2, 3, 0, 1}, + {0, 2, 3, 3}, + {1, 2, 1, 2}, + {5, 0, 1, 0}, + {0, 2, 2, 2}, + {2, 1, 1, 2}, + {1, 3, 0, 1}, + {1, 3, 1, 1}, } resultsFields = append(resultsFields, votesToBigInt(10, 30, 10, 0), votesToBigInt(0, 10, 20, 20), votesToBigInt(10, 30, 10, 0), @@ -296,9 +314,16 @@ func ballotVotes(vop vapi.TallyMode, nvotes int, ballotType string, expectUnique case rangeVote: v = [][]int{ - {5, 0, 4, 2}, {3, 1, 2, 4}, {6, 0, 1, 3}, {0, 3, 2, 1}, - {2, 1, 3, 0}, {3, 2, 1, 3}, {1, 3, 2, 0}, {2, 1, 1, 2}, - {1, 2, 0, 3}, {4, 3, 1, 1}, + {5, 0, 4, 2}, + {3, 1, 2, 4}, + {6, 0, 1, 3}, + {0, 3, 2, 1}, + {2, 1, 3, 0}, + {3, 2, 1, 3}, + {1, 3, 2, 0}, + {2, 1, 1, 2}, + {1, 2, 0, 3}, + {4, 3, 1, 1}, } resultsFields = append(resultsFields, votesToBigInt(10, 20, 10, 0), votesToBigInt(0, 10, 10, 20), votesToBigInt(10, 0, 20, 10), @@ -306,9 +331,16 @@ func ballotVotes(vop vapi.TallyMode, nvotes int, ballotType string, expectUnique case approvalVote: v = [][]int{ - {1, 0, 0, 0}, {2, 1, 0, 1}, {0, 1, 2, 3}, {0, 0, 0, 1}, - {0, 0, 1, 1}, {2, 2, 1, 0}, {1, 0, 1, 1}, {0, 0, 0, 0}, - {1, 1, 0, 3}, {1, 1, 1, 1}, + {1, 0, 0, 0}, + {2, 1, 0, 1}, + {0, 1, 2, 3}, + {0, 0, 0, 1}, + {0, 0, 1, 1}, + {2, 2, 1, 0}, + {1, 0, 1, 1}, + {0, 0, 0, 0}, + {1, 1, 0, 3}, + {1, 1, 1, 1}, } resultsFields = append(resultsFields, votesToBigInt(30, 30), votesToBigInt(50, 10), votesToBigInt(30, 30), diff --git a/cmd/end2endtest/csp.go b/cmd/end2endtest/csp.go index e62a86b3a..05632ec7e 100644 --- a/cmd/end2endtest/csp.go +++ b/cmd/end2endtest/csp.go @@ -30,7 +30,7 @@ func (t *E2ECSPElection) Setup(api *apiclient.HTTPclient, c *config) error { t.api = api t.config = c - //setup for ranked voting + // setup for ranked voting p := newTestProcess() // update to use csp origin p.CensusOrigin = models.CensusOrigin_OFF_CHAIN_CA diff --git a/cmd/end2endtest/dynamicensus.go b/cmd/end2endtest/dynamicensus.go index 90bcecac7..f6db2226d 100644 --- a/cmd/end2endtest/dynamicensus.go +++ b/cmd/end2endtest/dynamicensus.go @@ -150,19 +150,18 @@ func (t *E2EDynamicensusElection) Run() error { log.Debugf("election details before set a new census: %s %s %x", t.elections[0].election.Census.CensusOrigin, t.elections[0].election.Census.CensusURL, t.elections[0].election.Census.CensusRoot) - hash, err := api.TransactionSetCensus(electionID, vapi.ElectionCensus{ + hash, err := api.SetElectionCensus(electionID, vapi.ElectionCensus{ CensusOrigin: "OFF_CHAIN_TREE_WEIGHTED", CensusRoot: censusRoot2, CensusURL: "http://test/census", }) - if err != nil { errCh <- fmt.Errorf("unexpected error from set process census %s", err) return } log.Debugw("process census set", "tx hash:", hash) - ctx, cancel := context.WithTimeout(context.Background(), apiclient.WaitTimeout*3) + ctx, cancel := context.WithTimeout(context.Background(), t.elections[0].config.timeout) defer cancel() if _, err := api.WaitUntilTxIsMined(ctx, hash); err != nil { errCh <- fmt.Errorf("gave up waiting for tx %x to be mined: %s", hash, err) @@ -212,7 +211,6 @@ func (t *E2EDynamicensusElection) Run() error { log.Infof("election %s status is RESULTS", electionID.String()) log.Infof("election results: %v %x %s", elres.Results, elres.CensusRoot, t.elections[0].election.Census.CensusURL) - }() // election with dynamic census disabled @@ -261,7 +259,7 @@ func (t *E2EDynamicensusElection) Run() error { log.Debugf("election details before: %s %s %x", t.elections[1].election.Census.CensusOrigin, t.elections[1].election.Census.CensusURL, t.elections[1].election.Census.CensusRoot) - if _, err := api.TransactionSetCensus(election.ElectionID, vapi.ElectionCensus{ + if _, err := api.SetElectionCensus(election.ElectionID, vapi.ElectionCensus{ CensusOrigin: "OFF_CHAIN_TREE_WEIGHTED", CensusRoot: censusRoot2, CensusURL: "http://test/census", diff --git a/cmd/end2endtest/helpers.go b/cmd/end2endtest/helpers.go index 896a1152b..d06a6748d 100644 --- a/cmd/end2endtest/helpers.go +++ b/cmd/end2endtest/helpers.go @@ -114,7 +114,7 @@ func (t *e2eElection) createAccount(privateKey string) (*vapi.Account, *apiclien return nil, nil, err } - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) + ctx, cancel := context.WithTimeout(context.Background(), t.config.timeout) defer cancel() if _, err := accountApi.WaitUntilTxIsMined(ctx, hash); err != nil { @@ -132,7 +132,6 @@ func (t *e2eElection) createAccount(privateKey string) (*vapi.Account, *apiclien return nil, nil, err } return acc, accountApi, nil - } func (t *e2eElection) addParticipantsCensus(censusID types.HexBytes, voterAccounts []*ethereum.SignKeys) error { @@ -349,7 +348,7 @@ func (t *e2eElection) setupElection(ed *vapi.ElectionDescription, nvAccts int, w } t.election = election } else { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) + ctx, cancel := context.WithTimeout(context.Background(), t.config.timeout) defer cancel() if _, err := t.api.WaitUntilElectionCreated(ctx, electionID); err != nil { return err @@ -378,7 +377,7 @@ func (t *e2eElection) setupElection(ed *vapi.ElectionDescription, nvAccts int, w errorChan <- err } log.Infow("sik registered for anonymous census uncreated account", "index", i, "address", acc.AddressString()) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) + ctx, cancel := context.WithTimeout(context.Background(), t.config.timeout) defer cancel() if _, err := accountApi.WaitUntilTxIsMined(ctx, hash); err != nil { @@ -395,7 +394,6 @@ func (t *e2eElection) setupElection(ed *vapi.ElectionDescription, nvAccts int, w errorChan <- fmt.Errorf("unexpected invalid SIK for account %x", acc.Address()) } log.Infof("valid SIK for the account %x", acc.Address()) - }(i, acc) } @@ -765,6 +763,8 @@ func (t *e2eElection) registerAnonAccts(voterAccounts []*ethereum.SignKeys) erro errorChan := make(chan error) wg := &sync.WaitGroup{} + sem := make(chan any, t.config.parallelCount) + for i, acc := range voterAccounts { if i%10 == 0 { // Print some information about progress on large censuses @@ -773,7 +773,11 @@ func (t *e2eElection) registerAnonAccts(voterAccounts []*ethereum.SignKeys) erro wg.Add(1) go func(i int, acc *ethereum.SignKeys) { + sem <- nil // acquire a token + defer func() { <-sem }() // release the token + defer wg.Done() + pKey := acc.PrivateKey() if _, _, err := t.createAccount(pKey.String()); err != nil && !strings.Contains(err.Error(), "createAccountTx: account already exists") { @@ -818,5 +822,4 @@ func logElection(e *vapi.Election) { "tallyMaxTotalCost", e.TallyMode.MaxTotalCost, "tallyCostExponent", e.TallyMode.CostExponent, ) - } diff --git a/cmd/end2endtest/main.go b/cmd/end2endtest/main.go index c9fa36936..eed064e35 100644 --- a/cmd/end2endtest/main.go +++ b/cmd/end2endtest/main.go @@ -117,7 +117,7 @@ func parseFlags(c *config) { flag.IntVar(&c.parallelCount, "parallel", 4, "number of parallel requests") flag.StringVar(&c.faucet, "faucet", "dev", "faucet URL for fetching tokens (special keyword 'dev' translates into hardcoded URL for dev faucet)") flag.StringVar(&c.faucetAuthToken, "faucetAuthToken", "", "(optional) token passed as Bearer when fetching faucetURL") - flag.DurationVar(&c.timeout, "timeout", apiclient.WaitTimeout*6, "timeout duration to wait for operations to complete") + flag.DurationVar(&c.timeout, "timeout", apiclient.WaitTimeout*2, "timeout duration to wait for operations to complete") flag.IntVar(&c.parallelTests, "parallelTests", 1, "number of parallel tests to run (of the same type specified in --operation)") flag.IntVar(&c.runs, "runs", 1, "number of tests to run (of the same type specified in --operation)") diff --git a/cmd/end2endtest/zkweighted.go b/cmd/end2endtest/zkweighted.go index ffc0f5d74..dda708ff5 100644 --- a/cmd/end2endtest/zkweighted.go +++ b/cmd/end2endtest/zkweighted.go @@ -38,13 +38,17 @@ func init() { } } -var _ VochainTest = (*E2EAnonElection)(nil) -var _ VochainTest = (*E2EAnonElectionTempSIKs)(nil) -var _ VochainTest = (*E2EAnonElectionEncrypted)(nil) +var ( + _ VochainTest = (*E2EAnonElection)(nil) + _ VochainTest = (*E2EAnonElectionTempSIKs)(nil) + _ VochainTest = (*E2EAnonElectionEncrypted)(nil) +) -type E2EAnonElection struct{ e2eElection } -type E2EAnonElectionTempSIKs struct{ e2eElection } -type E2EAnonElectionEncrypted struct{ e2eElection } +type ( + E2EAnonElection struct{ e2eElection } + E2EAnonElectionTempSIKs struct{ e2eElection } + E2EAnonElectionEncrypted struct{ e2eElection } +) func (t *E2EAnonElection) Setup(api *apiclient.HTTPclient, c *config) error { t.api = api diff --git a/cmd/node/main.go b/cmd/node/main.go index 22f62a527..94ee39023 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -204,7 +204,7 @@ func loadConfig() *config.Config { flag.Bool("vochainStateSyncFetchParamsFromRPC", true, "allow statesync to fetch TrustHash and TrustHeight from the first RPCServer") - flag.Int("vochainMinerTargetBlockTimeSeconds", config.DefaultMinerTargetBlockTimeSeconds, + flag.Int("vochainMinerTargetBlockTimeSeconds", int(config.DefaultMinerTargetBlockTime.Seconds()), "vochain consensus block time target (in seconds)") flag.Bool("vochainSkipPreviousOffchainData", false, "if enabled the census downloader will import all existing census") diff --git a/cmd/tools/censusdump/main.go b/cmd/tools/censusdump/main.go index f172b014b..a7f9b09c0 100644 --- a/cmd/tools/censusdump/main.go +++ b/cmd/tools/censusdump/main.go @@ -140,7 +140,7 @@ func main() { log.Fatalf("could not marshal census data: %s", err) } - if err := os.WriteFile(*output, censusPrint, 0644); err != nil { + if err := os.WriteFile(*output, censusPrint, 0o644); err != nil { log.Fatalf("could not write census data: %s", err) } log.Infow("census data exported", "file", *output) diff --git a/config/defaults.go b/config/defaults.go index bdb052494..166224f9e 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -1,10 +1,12 @@ package config +import "time" + // These consts are defaults used in VochainCfg const ( - DefaultMinerTargetBlockTimeSeconds = 10 - DefaultCometBFTPath = "cometbft" - DefaultGenesisPath = DefaultCometBFTPath + "/config/genesis.json" + DefaultMinerTargetBlockTime = 10 * time.Second + DefaultCometBFTPath = "cometbft" + DefaultGenesisPath = DefaultCometBFTPath + "/config/genesis.json" ) // DefaultSeedNodes is a map indexed by network name diff --git a/crypto/ethereum/vocdoni_test.go b/crypto/ethereum/vocdoni_test.go index f3925c795..c436d49f5 100644 --- a/crypto/ethereum/vocdoni_test.go +++ b/crypto/ethereum/vocdoni_test.go @@ -33,7 +33,8 @@ func TestVocdoniSignature(t *testing.T) { To: toAddr, Value: value, Nonce: 123, - }}} + }, + }} message, err := proto.Marshal(tx) qt.Assert(t, err, qt.IsNil) @@ -59,7 +60,8 @@ func TestVocdoniSignature(t *testing.T) { InfoURI: &infoUri, Account: s.Address().Bytes(), Nonce: &nonce, - }}} + }, + }} message, err = proto.Marshal(tx) qt.Assert(t, err, qt.IsNil) diff --git a/crypto/zk/circuit/inputs_test.go b/crypto/zk/circuit/inputs_test.go index 674d51621..a063f0ba1 100644 --- a/crypto/zk/circuit/inputs_test.go +++ b/crypto/zk/circuit/inputs_test.go @@ -59,20 +59,26 @@ func TestGenerateCircuitInput(t *testing.T) { SIKSiblings: testSiblings, } // Generate correct inputs - rawInputs, err := GenerateCircuitInput(CircuitInputsParameters{acc, nil, + rawInputs, err := GenerateCircuitInput(CircuitInputsParameters{ + acc, nil, electionId, hexTestRoot, hexTestRoot, testVotePackage, testSiblings, testSiblings, nil, - availableWeight}) + availableWeight, + }) c.Assert(err, qt.IsNil) c.Assert(rawInputs, qt.DeepEquals, expected) - rawInputs, err = GenerateCircuitInput(CircuitInputsParameters{acc, nil, + rawInputs, err = GenerateCircuitInput(CircuitInputsParameters{ + acc, nil, electionId, hexTestRoot, hexTestRoot, testVotePackage, testSiblings, testSiblings, - big.NewInt(1), availableWeight}) + big.NewInt(1), availableWeight, + }) c.Assert(err, qt.IsNil) c.Assert(rawInputs, qt.Not(qt.DeepEquals), expected) - _, err = GenerateCircuitInput(CircuitInputsParameters{nil, nil, electionId, + _, err = GenerateCircuitInput(CircuitInputsParameters{ + nil, nil, electionId, hexTestRoot, hexTestRoot, testVotePackage, testSiblings, testSiblings, big.NewInt(1), - availableWeight}) + availableWeight, + }) c.Assert(err, qt.IsNotNil) } diff --git a/crypto/zk/prover/prover_test.go b/crypto/zk/prover/prover_test.go index c64b93094..1b6448d83 100644 --- a/crypto/zk/prover/prover_test.go +++ b/crypto/zk/prover/prover_test.go @@ -62,12 +62,11 @@ func TestBytes(t *testing.T) { expectedPubSignals, err := json.Marshal(expected.PubSignals) qt.Assert(t, err, qt.IsNil) qt.Assert(t, validPubSignals, qt.DeepEquals, expectedPubSignals) - } func Test_calcWitness(t *testing.T) { // Empty and first set of valid parameters - var emptyWasm, emptyInputs = []byte{}, []byte{} + emptyWasm, emptyInputs := []byte{}, []byte{} _, err := calcWitness(emptyWasm, inputs) qt.Assert(t, err, qt.IsNotNil) diff --git a/crypto/zk/utils.go b/crypto/zk/utils.go index 942bfc540..ad52869ce 100644 --- a/crypto/zk/utils.go +++ b/crypto/zk/utils.go @@ -57,7 +57,8 @@ func ProtobufZKProofToProverProof(p *models.ProofZkSNARK) (*prover.Proof, error) // a defined public signals and any of the rest of the parameters is nil, the // resulting struct will not contains any defined PublicInputs value. func ProverProofToProtobufZKProof(p *prover.Proof, electionId, sikRoot, - censusRoot, nullifier types.HexBytes, voteWeight *big.Int) (*models.ProofZkSNARK, error) { + censusRoot, nullifier types.HexBytes, voteWeight *big.Int, +) (*models.ProofZkSNARK, error) { if len(p.Data.A) != proofALen || len(p.Data.B) != proofBEncLen || len(p.Data.C) != proofCLen { return nil, fmt.Errorf("wrong ZkSnark prover proof format") } diff --git a/data/downloader/downloader.go b/data/downloader/downloader.go index 5a1754e8e..3b3eb8e2f 100644 --- a/data/downloader/downloader.go +++ b/data/downloader/downloader.go @@ -146,7 +146,6 @@ func (d *Downloader) handleImport(item *DownloadItem) { if item.Callback != nil { go item.Callback(item.URI, file) } - } // importQueueDaemon fetches and imports remote files added via importQueue. diff --git a/data/ipfs/init.go b/data/ipfs/init.go index 4ea85b9a5..00679093c 100644 --- a/data/ipfs/init.go +++ b/data/ipfs/init.go @@ -61,7 +61,7 @@ func initRepository() error { return fmt.Errorf("ipfs daemon is running. please stop it to run this command") } - if err := os.MkdirAll(ConfigRoot, 0750); err != nil { + if err := os.MkdirAll(ConfigRoot, 0o750); err != nil { return err } @@ -250,7 +250,7 @@ func checkWritable(dir string) error { if os.IsNotExist(err) { // dir doesn't exist, check that we can create it - return os.Mkdir(dir, 0750) + return os.Mkdir(dir, 0o750) } if os.IsPermission(err) { diff --git a/db/internal/dbtest/dbtest.go b/db/internal/dbtest/dbtest.go index 8b7d22e9d..dea3388b3 100644 --- a/db/internal/dbtest/dbtest.go +++ b/db/internal/dbtest/dbtest.go @@ -89,7 +89,7 @@ func TestIterate(t *testing.T, d db.Database) { // TestConcurrentWriteTx validates the behaviour of badgerdb when multiple // write transactions modify the same key. func TestConcurrentWriteTx(t *testing.T, database db.Database) { - var key = []byte{1} + key := []byte{1} wTx := database.WriteTx() qt.Assert(t, wTx.Set(key, []byte{0}), qt.IsNil) qt.Assert(t, wTx.Commit(), qt.IsNil) @@ -97,7 +97,7 @@ func TestConcurrentWriteTx(t *testing.T, database db.Database) { var wgSync sync.WaitGroup wgSync.Add(2) inc := func(t *testing.T, m *sync.Mutex, database db.Database) error { - var key = []byte{1} + key := []byte{1} wTx := database.WriteTx() // Sync here so that both goroutines have created a WriteTx // before operating with it. diff --git a/go.mod b/go.mod index 080adcab2..aa2aa5a14 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module go.vocdoni.io/dvote -go 1.22.0 +go 1.22.2 // For testing purposes // replace go.vocdoni.io/proto => ../dvote-protobuf @@ -10,15 +10,15 @@ require ( github.com/766b/chi-prometheus v0.0.0-20211217152057-87afa9aa2ca8 github.com/VictoriaMetrics/metrics v1.24.0 github.com/arnaucube/go-blindsecp256k1 v0.0.0-20211204171003-644e7408753f - github.com/cockroachdb/pebble v1.1.0 + github.com/cockroachdb/pebble v1.1.2 github.com/cometbft/cometbft v1.0.0-alpha.1 github.com/cometbft/cometbft-db v0.9.1 github.com/cometbft/cometbft/api v1.0.0-alpha.1 - github.com/ethereum/go-ethereum v1.14.0 + github.com/ethereum/go-ethereum v1.14.7 github.com/fatih/color v1.16.0 github.com/frankban/quicktest v1.14.6 github.com/glendc/go-external-ip v0.1.0 - github.com/go-chi/chi/v5 v5.0.12 + github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/cors v1.2.1 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 @@ -38,7 +38,7 @@ require ( github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/kubo v0.28.0 - github.com/klauspost/compress v1.17.8 + github.com/klauspost/compress v1.17.9 github.com/libp2p/go-libp2p v0.33.2 github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/libp2p/go-reuseport v0.4.0 @@ -47,19 +47,19 @@ require ( github.com/multiformats/go-multiaddr v0.12.3 github.com/multiformats/go-multicodec v0.9.0 github.com/multiformats/go-multihash v0.2.3 - github.com/pressly/goose/v3 v3.20.0 - github.com/prometheus/client_golang v1.19.0 + github.com/pressly/goose/v3 v3.21.1 + github.com/prometheus/client_golang v1.19.1 github.com/rs/zerolog v1.31.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.18.2 + github.com/spf13/viper v1.19.0 github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a github.com/vocdoni/storage-proofs-eth-go v0.1.6 go.mongodb.org/mongo-driver v1.12.1 go.vocdoni.io/proto v1.15.8 - golang.org/x/crypto v0.22.0 - golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 - golang.org/x/net v0.24.0 - google.golang.org/protobuf v1.34.0 + golang.org/x/crypto v0.26.0 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/net v0.28.0 + google.golang.org/protobuf v1.34.2 lukechampine.com/blake3 v1.3.0 ) @@ -69,23 +69,23 @@ require ( github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/Jorropo/jsync v1.0.1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/OneOfOne/xxhash v1.2.5 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/ceramicnetwork/go-dag-jose v0.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheggaaa/pb v1.0.29 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cockroachdb/errors v1.11.1 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect @@ -93,14 +93,14 @@ require ( github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cosmos/gogoproto v1.4.11 // indirect + github.com/cosmos/gogoproto v1.5.0 // indirect github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668 // indirect - github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect - github.com/deckarep/golang-set/v2 v2.1.0 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect @@ -111,14 +111,14 @@ require ( github.com/elastic/gosigar v0.14.2 // indirect github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302 // indirect github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 // indirect github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect - github.com/getsentry/sentry-go v0.18.0 // indirect + github.com/getsentry/sentry-go v0.27.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-kit/kit v0.13.0 // indirect github.com/go-kit/log v0.2.1 // indirect @@ -131,7 +131,7 @@ require ( github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.1.2 // indirect + github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect @@ -141,14 +141,14 @@ require ( github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect - github.com/holiman/uint256 v1.2.4 // indirect + github.com/holiman/uint256 v1.3.1 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/ipfs-shipyard/nopfs v0.0.12 // indirect github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c // indirect @@ -209,7 +209,7 @@ require ( github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-yamux/v4 v4.0.1 // indirect github.com/libp2p/zeroconf/v2 v2.2.0 // indirect - github.com/linxGnu/grocksdb v1.8.6 // indirect + github.com/linxGnu/grocksdb v1.8.14 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -234,6 +234,7 @@ require ( github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multistream v0.5.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo/v2 v2.15.0 // indirect @@ -241,7 +242,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/openzipkin/zipkin-go v0.4.2 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect github.com/petermattis/goid v0.0.0-20221018141743-354ef7f2fd21 // indirect github.com/pion/datachannel v1.5.5 // indirect @@ -263,9 +264,9 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.49.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/statsd_exporter v0.22.8 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/quic-go v0.42.0 // indirect @@ -273,8 +274,8 @@ require ( github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/rs/cors v1.10.1 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rs/cors v1.11.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/samber/lo v1.39.0 // indirect @@ -285,7 +286,7 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect @@ -306,9 +307,9 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - go.etcd.io/bbolt v1.3.8 // indirect + go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect @@ -326,18 +327,17 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.20.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - gonum.org/v1/gonum v0.14.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect - google.golang.org/grpc v1.60.1 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect + gonum.org/v1/gonum v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 1e20c5cc5..29c3e9e21 100644 --- a/go.sum +++ b/go.sum @@ -77,21 +77,20 @@ github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwS github.com/Jorropo/jsync v1.0.1 h1:6HgRolFZnsdfzRUj+ImB9og1JYOxQoReSywkHOGSaUU= github.com/Jorropo/jsync v1.0.1/go.mod h1:jCOZj3vrBCri3bSU3ErUYvevKlnbssrXeCivybS5ABQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= -github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= -github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= -github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/VictoriaMetrics/metrics v1.24.0 h1:ILavebReOjYctAGY5QU2F9X0MYvkcrG3aEn2RKa1Zkw= github.com/VictoriaMetrics/metrics v1.24.0/go.mod h1:eFT25kvsTidQFHb6U0oa0rTrDRdz4xTYjpL8+UPohys= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= @@ -160,8 +159,8 @@ github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcug github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= +github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= @@ -193,8 +192,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -215,12 +214,14 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= -github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= -github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= +github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= +github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= @@ -253,8 +254,8 @@ github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= -github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= +github.com/cosmos/gogoproto v1.5.0 h1:SDVwzEqZDDBoslaeZg+dGE55hdzHfgUA40pEanMh52o= +github.com/cosmos/gogoproto v1.5.0/go.mod h1:iUM31aofn3ymidYG6bUR5ZFrk+Om8p5s754eMUcyp8I= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -263,8 +264,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0q github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668 h1:ZFUue+PNxmHlu7pYv+IYMtqlaO/0VwaGEqKepZf9JpA= github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -283,8 +284,8 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6Uh github.com/dchest/blake512 v1.0.0/go.mod h1:FV1x7xPPLWukZlpDpWQ88rF/SFwZ5qbskrzhLMB92JI= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= -github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= -github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= @@ -340,8 +341,10 @@ github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHE github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= github.com/ethereum/go-ethereum v1.10.8/go.mod h1:pJNuIUYfX5+JKzSD/BTdNsvJSZ1TJqmz0dVyXMAbf6M= -github.com/ethereum/go-ethereum v1.14.0 h1:xRWC5NlB6g1x7vNy4HDBLuqVNbtLrc7v8S6+Uxim1LU= -github.com/ethereum/go-ethereum v1.14.0/go.mod h1:1STrq471D0BQbCX9He0hUj4bHxX2k6mt5nOQJhDNOJ8= +github.com/ethereum/go-ethereum v1.14.7 h1:EHpv3dE8evQmpVEQ/Ne2ahB06n2mQptdwqaMNhAT29g= +github.com/ethereum/go-ethereum v1.14.7/go.mod h1:Mq0biU2jbdmKSZoqOj29017ygFrMnB5/Rifwp980W4o= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -380,12 +383,10 @@ github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcP github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glendc/go-external-ip v0.1.0 h1:iX3xQ2Q26atAmLTbd++nUce2P5ht5P4uD4V7caSY/xg= github.com/glendc/go-external-ip v0.1.0/go.mod h1:CNx312s2FLAJoWNdJWZ2Fpf5O4oLsMFwuYviHjS4uJE= @@ -396,8 +397,8 @@ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclK github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -463,8 +464,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -573,8 +574,8 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -627,8 +628,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= -github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= +github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= @@ -831,8 +832,8 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6 github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -922,8 +923,8 @@ github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linxGnu/grocksdb v1.8.6 h1:O7I6SIGPrypf3f/gmrrLUBQDKfO8uOoYdWf4gLS06tc= -github.com/linxGnu/grocksdb v1.8.6/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= +github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ= +github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -1070,6 +1071,8 @@ github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= @@ -1150,8 +1153,8 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhM github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= @@ -1226,8 +1229,8 @@ github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXx github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pressly/goose/v3 v3.20.0 h1:uPJdOxF/Ipj7ABVNOAMJXSxwFXZGwMGHNqjC8e61VA0= -github.com/pressly/goose/v3 v3.20.0/go.mod h1:BRfF2GcG4FTG12QfdBVy3q1yveaf4ckL9vWwEcIO3lA= +github.com/pressly/goose/v3 v3.21.1 h1:5SSAKKWej8LVVzNLuT6KIvP1eFDuPvxa+B6H0w78buQ= +github.com/pressly/goose/v3 v3.21.1/go.mod h1:sqthmzV8PitchEkjecFJII//l43dLOCzfWh8pHEe+vE= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -1238,16 +1241,16 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1259,8 +1262,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI= -github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1270,8 +1273,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= github.com/prometheus/statsd_exporter v0.22.8 h1:Qo2D9ZzaQG+id9i5NYNGmbf1aa/KxKbB9aKfMS+Yib0= github.com/prometheus/statsd_exporter v0.22.8/go.mod h1:/DzwbTEaFTE0Ojz5PqcSk6+PFHOPWGxdXVr6yC8eFOM= @@ -1300,12 +1303,12 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= -github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= @@ -1397,8 +1400,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= @@ -1412,8 +1415,9 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1426,8 +1430,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= @@ -1527,8 +1532,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 h1:qxen9oVGzDdIRP6ejyAJc760RwW4SnVDiTYTzwnXuxo= +go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5/go.mod h1:eW0HG9/oHQhvRCvb1/pIXW4cOvtDqeQK+XSi3TnwaXY= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= @@ -1544,8 +1549,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= @@ -1634,8 +1639,8 @@ golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45 golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1650,8 +1655,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1679,8 +1684,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1747,8 +1752,8 @@ golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1758,8 +1763,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1775,8 +1780,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1876,8 +1881,8 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1906,8 +1911,8 @@ golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1976,20 +1981,20 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= +gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= @@ -2021,8 +2026,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -2059,12 +2062,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= -google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 h1:OPXtXn7fNMaXwO3JvOmF1QyTc00jsSFFz1vXXBOdCDo= -google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2085,8 +2086,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2102,8 +2103,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2117,8 +2118,8 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/httprouter/apirest/apirest_test.go b/httprouter/apirest/apirest_test.go index 6f7a1743f..31eae157f 100644 --- a/httprouter/apirest/apirest_test.go +++ b/httprouter/apirest/apirest_test.go @@ -88,7 +88,6 @@ func TestRouterWithAPI(t *testing.T) { qt.Check(t, resp, qt.DeepEquals, []byte("hello admin!\n")) resp = doRequest(t, url+"/admin/do", "abcde", "POST", []byte("hello")) qt.Check(t, string(resp), qt.Contains, "admin token not valid\n") - } func doRequest(t *testing.T, url, authToken, method string, body []byte) []byte { diff --git a/httprouter/httprouter.go b/httprouter/httprouter.go index aa0fd2c48..96e2228d6 100644 --- a/httprouter/httprouter.go +++ b/httprouter/httprouter.go @@ -218,7 +218,8 @@ func (r *HTTProuter) getNamespace(id string) (RouterNamespace, bool) { // AddAdminHandler adds a handler function for the namespace, pattern and HTTPmethod. // The Admin requests are usually protected by some authorization mechanism. func (r *HTTProuter) AddAdminHandler(namespaceID, - pattern, HTTPmethod string, handler RouterHandlerFn) { + pattern, HTTPmethod string, handler RouterHandlerFn, +) { log.Infof("added admin handler for namespace %s with pattern %s", namespaceID, pattern) r.Mux.MethodFunc(HTTPmethod, pattern, r.routerHandler(namespaceID, AccessTypeAdmin, handler)) } @@ -226,7 +227,8 @@ func (r *HTTProuter) AddAdminHandler(namespaceID, // AddQuotaHandler adds a handler function for the namespace, pattern and HTTPmethod. // The Quota requests are rate-limited per bearer token func (r *HTTProuter) AddQuotaHandler(namespaceID, - pattern, HTTPmethod string, handler RouterHandlerFn) { + pattern, HTTPmethod string, handler RouterHandlerFn, +) { log.Infow("added handler", "type", "quota", "namespace", namespaceID, "pattern", pattern) r.Mux.MethodFunc(HTTPmethod, pattern, r.routerHandler(namespaceID, AccessTypeQuota, handler)) } @@ -235,7 +237,8 @@ func (r *HTTProuter) AddQuotaHandler(namespaceID, // The Private requests are usually protected by some authorization mechanism, such as signature verification, // which must be handled by the namespace implementation. func (r *HTTProuter) AddPrivateHandler(namespaceID, - pattern, HTTPmethod string, handler RouterHandlerFn) { + pattern, HTTPmethod string, handler RouterHandlerFn, +) { log.Infow("added handler", "type", "private", "namespace", namespaceID, "pattern", pattern) r.Mux.MethodFunc(HTTPmethod, pattern, r.routerHandler(namespaceID, AccessTypePrivate, handler)) } @@ -243,7 +246,8 @@ func (r *HTTProuter) AddPrivateHandler(namespaceID, // AddPublicHandler adds a handled function for the namespace, patter and HTTPmethod. // The public requests are not protected so all requests are allowed. func (r *HTTProuter) AddPublicHandler(namespaceID, - pattern, HTTPmethod string, handler RouterHandlerFn) { + pattern, HTTPmethod string, handler RouterHandlerFn, +) { log.Infow("added handler", "type", "public", "namespace", namespaceID, "pattern", pattern) r.Mux.MethodFunc(HTTPmethod, pattern, r.routerHandler(namespaceID, AccessTypePublic, handler)) } @@ -256,7 +260,8 @@ func (r *HTTProuter) AddRawHTTPHandler(pattern, HTTPmethod string, handler http. } func (r *HTTProuter) routerHandler(namespaceID string, accessType AuthAccessType, - handlerFunc RouterHandlerFn) func(w http.ResponseWriter, req *http.Request) { + handlerFunc RouterHandlerFn, +) func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) { defer req.Body.Close() diff --git a/httprouter/message.go b/httprouter/message.go index c17a12fcd..b1723dd29 100644 --- a/httprouter/message.go +++ b/httprouter/message.go @@ -44,6 +44,12 @@ func (h *HTTPContext) URLParam(key string) string { return chi.URLParam(h.Request, key) } +// QueryParam is a wrapper around go-chi to get the value of a query string parameter (like "?key=value"). +// If key is not present, returns the empty string. +func (h *HTTPContext) QueryParam(key string) string { + return h.Request.URL.Query().Get(key) +} + // Send replies the request with the provided message. func (h *HTTPContext) Send(msg []byte, httpStatusCode int) error { defer func() { diff --git a/log/log.go b/log/log.go index 48d6e8f8c..746fd8aae 100644 --- a/log/log.go +++ b/log/log.go @@ -98,7 +98,7 @@ func Init(level, output string, errorOutput io.Writer) { case logTestWriterName: out = logTestWriter default: - f, err := os.OpenFile(output, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + f, err := os.OpenFile(output, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) if err != nil { panic(fmt.Sprintf("cannot create log output: %v", err)) } diff --git a/statedb/statedb_test.go b/statedb/statedb_test.go index 669593810..1a3f9f541 100644 --- a/statedb/statedb_test.go +++ b/statedb/statedb_test.go @@ -139,7 +139,6 @@ func TestStateDB(t *testing.T) { root2, err = sdb.Hash() qt.Assert(t, err, qt.IsNil) qt.Assert(t, root2, qt.DeepEquals, root1) - } // singleCfg is a test configuration for a singleton subTree diff --git a/statedb/treeupdate.go b/statedb/treeupdate.go index 4d86df6d6..cd1e90a37 100644 --- a/statedb/treeupdate.go +++ b/statedb/treeupdate.go @@ -50,7 +50,7 @@ type TreeUpdate struct { // is used in the TreeTx.Commit to traverse all opened subTrees in a // TreeTx in order to propagate the roots upwards to update the // corresponding parent leafs up to the mainTree. - //openSubs map[string]*TreeUpdate + // openSubs map[string]*TreeUpdate openSubs sync.Map // cfg points to this TreeUpdate configuration. cfg TreeConfig diff --git a/subpub/subpub.go b/subpub/subpub.go index cbf7c8bda..cad6ccb6a 100644 --- a/subpub/subpub.go +++ b/subpub/subpub.go @@ -141,7 +141,8 @@ func (s *SubPub) Stats() map[string]any { return map[string]any{ "peers": len(s.node.PeerHost.Network().Peers()), "known": len(s.node.PeerHost.Peerstore().PeersWithAddrs()), - "cluster": len(s.gossip.topic.ListPeers())} + "cluster": len(s.gossip.topic.ListPeers()), + } } // Address returns the node's ID. diff --git a/test/api_test.go b/test/api_test.go index f28e70d1c..80e4f3699 100644 --- a/test/api_test.go +++ b/test/api_test.go @@ -21,6 +21,7 @@ import ( "go.vocdoni.io/dvote/types" "go.vocdoni.io/dvote/util" "go.vocdoni.io/dvote/vochain" + "go.vocdoni.io/dvote/vochain/genesis" "go.vocdoni.io/dvote/vochain/indexer/indexertypes" "go.vocdoni.io/dvote/vochain/processid" "go.vocdoni.io/dvote/vochain/state" @@ -86,7 +87,7 @@ func TestAPIcensusAndVote(t *testing.T) { qt.Assert(t, censusData.Weight.String(), qt.Equals, "1") electionParams := electionprice.ElectionParameters{ElectionDuration: 100, MaxCensusSize: 100} - election := createElection(t, c, server.Account, electionParams, censusData.CensusRoot, 0, server.VochainAPP.ChainID(), false) + election := createElection(t, c, server.Account, electionParams, censusData.CensusRoot, 0, server.VochainAPP.ChainID(), false, 0) // Block 2 server.VochainAPP.AdvanceTestBlock() @@ -189,9 +190,9 @@ func TestAPIaccount(t *testing.T) { err := json.Unmarshal(resp, &countAccts) qt.Assert(t, err, qt.IsNil) - // 2 accounts must exist: the previously new created account and the auxiliary + // 3 accounts must exist: the previously new created account plus burn + faucet // account used to transfer to the new account - qt.Assert(t, countAccts.Count, qt.Equals, uint64(2)) + qt.Assert(t, countAccts.Count, qt.Equals, uint64(3)) // get the accounts info resp, code = c.Request("GET", nil, "accounts", "page", "0") @@ -213,7 +214,80 @@ func TestAPIaccount(t *testing.T) { // compare the balance expected for the new account in the account list qt.Assert(t, gotAcct.Balance, qt.Equals, initBalance) +} +func TestAPIAccountsList(t *testing.T) { + server := testcommon.APIserver{} + server.Start(t, + api.ChainHandler, + api.CensusHandler, + api.VoteHandler, + api.AccountHandler, + api.ElectionHandler, + api.WalletHandler, + ) + token1 := uuid.New() + c := testutil.NewTestHTTPclient(t, server.ListenAddr, &token1) + + // Block 1 + server.VochainAPP.AdvanceTestBlock() + waitUntilHeight(t, c, 1) + + // create new accounts + for nonce := uint32(0); nonce < 20; nonce++ { + createAccount(t, c, server, uint64(80)) + } + + // Block 2 + server.VochainAPP.AdvanceTestBlock() + waitUntilHeight(t, c, 2) + + // Get the list and check it + fetchAL := func(method string, jsonBody any, query string, urlPath ...string) api.AccountsList { + resp, code := c.RequestWithQuery(method, jsonBody, query, urlPath...) + list := api.AccountsList{} + qt.Assert(t, code, qt.Equals, 200) + err := json.Unmarshal(resp, &list) + qt.Assert(t, err, qt.IsNil) + return list + } + + el := make(map[string]api.AccountsList) + el["0"] = fetchAL("GET", nil, "", "accounts") + el["1"] = fetchAL("GET", nil, "page=1", "accounts") + el["p0"] = fetchAL("GET", nil, "", "accounts", "page", "0") + el["p1"] = fetchAL("GET", nil, "", "accounts", "page", "1") + + qt.Assert(t, el["0"], qt.Not(qt.DeepEquals), el["1"]) + qt.Assert(t, el["0"], qt.DeepEquals, el["p0"]) + qt.Assert(t, el["1"], qt.DeepEquals, el["p1"]) + + // 2 accounts pre-exist: the faucet account, and the burn address + qt.Assert(t, el["0"].Pagination.TotalItems, qt.Equals, uint64(2+20)) + qt.Assert(t, el["1"].Pagination.TotalItems, qt.Equals, el["0"].Pagination.TotalItems) + qt.Assert(t, el["p0"].Pagination.TotalItems, qt.Equals, el["0"].Pagination.TotalItems) + qt.Assert(t, el["p1"].Pagination.TotalItems, qt.Equals, el["0"].Pagination.TotalItems) + + for _, item := range el { + qt.Assert(t, len(item.Accounts), qt.Equals, api.DefaultItemsPerPage) + } + + // test different `limit` params + el["p0l2"] = fetchAL("GET", nil, "page=0&limit=2", "accounts") + qt.Assert(t, len(el["p0l2"].Accounts), qt.Equals, 2) + qt.Assert(t, el["p0l2"].Pagination.CurrentPage, qt.Equals, uint64(0)) + qt.Assert(t, el["p0l2"].Pagination.LastPage, qt.Equals, uint64(10)) + qt.Assert(t, *el["p0l2"].Pagination.NextPage, qt.Equals, uint64(1)) + qt.Assert(t, el["p0l2"].Pagination.PreviousPage, qt.IsNil) + qt.Assert(t, el["p0l2"].Pagination.TotalItems, qt.Equals, uint64(2+20)) + + el["p1l3"] = fetchAL("GET", nil, "page=1&limit=3", "accounts") + qt.Assert(t, len(el["p1l3"].Accounts), qt.Equals, 3) + qt.Assert(t, el["p1l3"].Pagination.CurrentPage, qt.Equals, uint64(1)) + qt.Assert(t, el["p1l3"].Pagination.LastPage, qt.Equals, uint64(7)) + qt.Assert(t, *el["p1l3"].Pagination.NextPage, qt.Equals, uint64(2)) + qt.Assert(t, *el["p1l3"].Pagination.PreviousPage, qt.Equals, uint64(0)) + qt.Assert(t, el["p1l3"].Pagination.TotalItems, qt.Equals, uint64(2+20)) } func TestAPIElectionCost(t *testing.T) { @@ -228,7 +302,7 @@ func TestAPIElectionCost(t *testing.T) { }, 10000, 5000, 5, 1000, - 6) + 2) // bigger census size, duration, reduced network capacity, etc runAPIElectionCostWithParams(t, @@ -241,7 +315,7 @@ func TestAPIElectionCost(t *testing.T) { }, 200000, 6000, 10, 100, - 762) + 753) // very expensive election runAPIElectionCostWithParams(t, @@ -254,7 +328,7 @@ func TestAPIElectionCost(t *testing.T) { }, 100000, 700000, 10, 100, - 547026) + 547017) } func TestAPIAccountTokentxs(t *testing.T) { @@ -317,9 +391,7 @@ func TestAPIAccountTokentxs(t *testing.T) { resp, code = c.Request("GET", nil, "accounts", signer.Address().Hex(), "transfers", "page", "0") qt.Assert(t, code, qt.Equals, 200, qt.Commentf("response: %s", resp)) - tokenTxs := new(struct { - Transfers indexertypes.TokenTransfersAccount `json:"transfers"` - }) + tokenTxs := &api.TransfersList{} err := json.Unmarshal(resp, tokenTxs) qt.Assert(t, err, qt.IsNil) @@ -333,18 +405,14 @@ func TestAPIAccountTokentxs(t *testing.T) { err = json.Unmarshal(resp, &countTnsAcc) qt.Assert(t, err, qt.IsNil) - totalTokenTxs := uint64(len(tokenTxs.Transfers.Received) + len(tokenTxs.Transfers.Sent)) - // compare count of total token transfers for the account 1 using the two response - qt.Assert(t, totalTokenTxs, qt.Equals, countTnsAcc.Count) + qt.Assert(t, tokenTxs.Pagination.TotalItems, qt.Equals, countTnsAcc.Count) // get the token transfers received and sent for account 2 resp, code = c.Request("GET", nil, "accounts", signer2.Address().Hex(), "transfers", "page", "0") qt.Assert(t, code, qt.Equals, 200, qt.Commentf("response: %s", resp)) - tokenTxs2 := new(struct { - Transfers indexertypes.TokenTransfersAccount `json:"transfers"` - }) + tokenTxs2 := &api.TransfersList{} err = json.Unmarshal(resp, tokenTxs2) qt.Assert(t, err, qt.IsNil) @@ -358,9 +426,8 @@ func TestAPIAccountTokentxs(t *testing.T) { err = json.Unmarshal(resp, &countTnsAcc2) qt.Assert(t, err, qt.IsNil) - totalTokenTxs2 := uint64(len(tokenTxs2.Transfers.Received) + len(tokenTxs2.Transfers.Sent)) // compare count of total token transfers for the account 2 using the two response - qt.Assert(t, totalTokenTxs2, qt.Equals, countTnsAcc2.Count) + qt.Assert(t, tokenTxs2.Pagination.TotalItems, qt.Equals, countTnsAcc2.Count) resp, code = c.Request("GET", nil, "accounts", "page", "0") qt.Assert(t, code, qt.Equals, 200, qt.Commentf("response: %s", resp)) @@ -382,7 +449,8 @@ func TestAPIAccountTokentxs(t *testing.T) { qt.Assert(t, gotAcct2.Address.String(), qt.Equals, hex.EncodeToString(signer2.Address().Bytes())) // compare the balance expected for the account 2 in the account list - qt.Assert(t, gotAcct2.Balance, qt.Equals, initBalance+amountAcc1toAcct2-amountAcc2toAcct1) + txBasePrice := genesis.DefaultTransactionCosts().SendTokens + qt.Assert(t, gotAcct2.Balance, qt.Equals, initBalance+amountAcc1toAcct2-amountAcc2toAcct1-uint64(txBasePrice)) gotAcct1 := accts.Accounts[2] @@ -390,8 +458,7 @@ func TestAPIAccountTokentxs(t *testing.T) { qt.Assert(t, gotAcct1.Address.String(), qt.Equals, hex.EncodeToString(signer.Address().Bytes())) // compare the balance expected for the account 1 in the account list - qt.Assert(t, gotAcct1.Balance, qt.Equals, initBalance+amountAcc2toAcct1-amountAcc1toAcct2) - + qt.Assert(t, gotAcct1.Balance, qt.Equals, initBalance+amountAcc2toAcct1-amountAcc1toAcct2-uint64(txBasePrice)) } func runAPIElectionCostWithParams(t *testing.T, @@ -418,6 +485,7 @@ func runAPIElectionCostWithParams(t *testing.T, err = server.VochainAPP.State.SetElectionPriceCalc() qt.Assert(t, err, qt.IsNil) server.VochainAPP.State.ElectionPriceCalc.SetCapacity(networkCapacity) + server.VochainAPP.State.ElectionPriceCalc.SetBasePrice(1) // Block 1 server.VochainAPP.AdvanceTestBlock() @@ -441,7 +509,7 @@ func runAPIElectionCostWithParams(t *testing.T, qt.Assert(t, requestAccount(t, c, signer.Address().String()).Balance, qt.Equals, initialBalance) - createElection(t, c, signer, electionParams, censusRoot, startBlock, server.VochainAPP.ChainID(), false) + createElection(t, c, signer, electionParams, censusRoot, startBlock, server.VochainAPP.ChainID(), false, 0) // Block 3 server.VochainAPP.AdvanceTestBlock() @@ -507,6 +575,7 @@ func createElection(t testing.TB, c *testutil.TestHTTPclient, startBlock uint32, chainID string, encryptedMetadata bool, + nonce uint32, ) api.ElectionCreate { metadataBytes, err := json.Marshal( &api.ElectionMetadata{ @@ -529,7 +598,7 @@ func createElection(t testing.TB, c *testutil.TestHTTPclient, tx := models.Tx_NewProcess{ NewProcess: &models.NewProcessTx{ Txtype: models.TxType_NEW_PROCESS, - Nonce: 0, + Nonce: nonce, Process: &models.Process{ StartBlock: startBlock, BlockCount: electionParams.ElectionDuration, @@ -586,7 +655,8 @@ func predictPriceForElection(t testing.TB, c *testutil.TestHTTPclient, electionP } func createAccount(t testing.TB, c *testutil.TestHTTPclient, - server testcommon.APIserver, initialBalance uint64) *ethereum.SignKeys { + server testcommon.APIserver, initialBalance uint64, +) *ethereum.SignKeys { signer := ethereum.SignKeys{} qt.Assert(t, signer.Generate(), qt.IsNil) @@ -633,7 +703,8 @@ func createAccount(t testing.TB, c *testutil.TestHTTPclient, } func sendTokensTx(t testing.TB, c *testutil.TestHTTPclient, - signerFrom, signerTo *ethereum.SignKeys, chainID string, nonce uint32, amount uint64) { + signerFrom, signerTo *ethereum.SignKeys, chainID string, nonce uint32, amount uint64, +) { var err error stx := models.SignedTx{} stx.Tx, err = proto.Marshal(&models.Tx{ @@ -645,7 +716,8 @@ func sendTokensTx(t testing.TB, c *testutil.TestHTTPclient, To: signerTo.Address().Bytes(), Value: amount, }, - }}) + }, + }) qt.Assert(t, err, qt.IsNil) stx.Signature, err = signerFrom.SignVocdoniTx(stx.Tx, chainID) @@ -733,7 +805,7 @@ func TestAPIBuildElectionID(t *testing.T) { // create a new election electionParams := electionprice.ElectionParameters{ElectionDuration: 100, MaxCensusSize: 100} - response := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), false) + response := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), false, 0) // Block 4 server.VochainAPP.AdvanceTestBlock() @@ -767,7 +839,6 @@ func TestAPIBuildElectionID(t *testing.T) { qt.Assert(t, err, qt.IsNil) qt.Assert(t, newNextElectionID, qt.Equals, futureElectionID) - } func TestAPIEncryptedMetadata(t *testing.T) { @@ -804,7 +875,7 @@ func TestAPIEncryptedMetadata(t *testing.T) { // create a new election electionParams := electionprice.ElectionParameters{ElectionDuration: 100, MaxCensusSize: 100} - electionResponse := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), true) + electionResponse := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), true, 0) // Block 4 server.VochainAPP.AdvanceTestBlock() @@ -830,3 +901,98 @@ func TestAPIEncryptedMetadata(t *testing.T) { qt.Assert(t, err, qt.IsNil) qt.Assert(t, metadata.Title["default"], qt.Equals, "test election") } + +func TestAPIElectionsList(t *testing.T) { + server := testcommon.APIserver{} + server.Start(t, + api.ChainHandler, + api.CensusHandler, + api.VoteHandler, + api.AccountHandler, + api.ElectionHandler, + api.WalletHandler, + ) + // Block 1 + server.VochainAPP.AdvanceTestBlock() + + token1 := uuid.New() + c := testutil.NewTestHTTPclient(t, server.ListenAddr, &token1) + + // create a new census + resp, code := c.Request("POST", nil, "censuses", "weighted") + qt.Assert(t, code, qt.Equals, 200) + censusData := &api.Census{} + qt.Assert(t, json.Unmarshal(resp, censusData), qt.IsNil) + + for nonce := uint32(0); nonce < 20; nonce++ { + createElection(t, c, server.Account, electionprice.ElectionParameters{ + ElectionDuration: 100, + MaxCensusSize: 100, + }, censusData.CensusRoot, 0, server.VochainAPP.ChainID(), false, nonce) + } + + createElection(t, c, server.Account, electionprice.ElectionParameters{ + ElectionDuration: 100, + MaxCensusSize: 100, + EncryptedVotes: true, + }, censusData.CensusRoot, 0, server.VochainAPP.ChainID(), false, 20) + + // Block 2 + server.VochainAPP.AdvanceTestBlock() + waitUntilHeight(t, c, 2) + + // Get the list of elections and check it + fetchEL := func(method string, jsonBody any, query string, urlPath ...string) api.ElectionsList { + resp, code := c.RequestWithQuery(method, jsonBody, query, urlPath...) + elections := api.ElectionsList{} + qt.Assert(t, code, qt.Equals, 200, qt.Commentf("resp: %q", resp)) + err := json.Unmarshal(resp, &elections) + qt.Assert(t, err, qt.IsNil) + return elections + } + + el := make(map[string]api.ElectionsList) + el["0"] = fetchEL("GET", nil, "", "elections") + el["1"] = fetchEL("GET", nil, "page=1", "elections") + el["p0"] = fetchEL("GET", nil, "", "elections", "page", "0") + el["p1"] = fetchEL("GET", nil, "", "elections", "page", "1") + + qt.Assert(t, el["0"], qt.Not(qt.DeepEquals), el["1"]) + qt.Assert(t, el["0"], qt.DeepEquals, el["p0"]) + qt.Assert(t, el["1"], qt.DeepEquals, el["p1"]) + + qt.Assert(t, el["0"].Pagination.TotalItems, qt.Equals, uint64(20+1)) + qt.Assert(t, el["1"].Pagination.TotalItems, qt.Equals, el["0"].Pagination.TotalItems) + qt.Assert(t, el["p0"].Pagination.TotalItems, qt.Equals, el["0"].Pagination.TotalItems) + qt.Assert(t, el["p1"].Pagination.TotalItems, qt.Equals, el["0"].Pagination.TotalItems) + + for _, item := range el { + qt.Assert(t, len(item.Elections), qt.Equals, api.DefaultItemsPerPage) + } + + boolPtr := func(b bool) *bool { return &b } + + // this will exclude the 1 ongoing EncryptedVotes election + el["post_wr"] = fetchEL("POST", api.ElectionParams{ + WithResults: boolPtr(true), + PaginationParams: api.PaginationParams{Limit: 100}, + }, "", "elections", "filter") + qt.Assert(t, el["post_wr"].Pagination.TotalItems, qt.Equals, el["0"].Pagination.TotalItems-1) + el["get_wr"] = fetchEL("GET", nil, "withResults=true&limit=100", "elections") + qt.Assert(t, el["post_wr"], qt.DeepEquals, el["get_wr"]) + + // this should return all elections, since no election has FinalResults or was ManuallyEnded + el["no_fr_me"] = fetchEL("GET", nil, "limit=100", "elections") + for _, e := range el["no_fr_me"].Elections { + t.Logf("%+v", e) + } + qt.Assert(t, el["no_fr_me"].Pagination.TotalItems, qt.Equals, el["0"].Pagination.TotalItems) + + // this should return no elections, + el["fr_true"] = fetchEL("GET", nil, "finalResults=true", "elections") + qt.Assert(t, el["fr_true"].Pagination.TotalItems, qt.Equals, uint64(0)) + + // this should return no elections, + el["me_true"] = fetchEL("GET", nil, "manuallyEnded=true", "elections") + qt.Assert(t, el["me_true"].Pagination.TotalItems, qt.Equals, uint64(0)) +} diff --git a/test/apierror_test.go b/test/apierror_test.go index f18c0e45a..659f8f4e7 100644 --- a/test/apierror_test.go +++ b/test/apierror_test.go @@ -56,55 +56,77 @@ func TestAPIerror(t *testing.T) { want: api.ErrOrgNotFound, }, { - args: args{"GET", nil, []string{"chain", "blocks", "1234"}}, - want: api.ErrBlockNotFound, + args: args{"GET", nil, []string{"accounts", "0123456789012345678901234567890123456789", "elections", "page", "0"}}, + want: api.ErrOrgNotFound, }, { - args: args{"POST", hugeFile, []string{"files", "cid"}}, - want: api.ErrFileSizeTooBig, + args: args{"GET", nil, []string{"accounts", "0123456789012345678901234567890123456789", "transfers", "page", "0"}}, + want: api.ErrAccountNotFound, }, { - args: args{"GET", nil, []string{"accounts", "totallyWrong!@#$", "elections", "status", "ready", "page", "0"}}, - want: api.ErrCantParseOrgID, + args: args{"GET", nil, []string{"accounts", "0123456789012345678901234567890123456789", "fees", "page", "0"}}, + want: api.ErrAccountNotFound, + }, + { + args: args{"GET", nil, []string{"chain", "blocks", "1234"}}, + want: api.ErrBlockNotFound, }, { - args: args{"GET", nil, []string{"accounts", "totallyWrong!@#$", "transfers", "page", "0"}}, - want: api.ErrCantParseAccountID, + args: args{"POST", hugeFile, []string{"files", "cid"}}, + want: api.ErrFileSizeTooBig, }, { - args: args{"GET", nil, []string{"votes", "verify", + args: args{"GET", nil, []string{ + "votes", "verify", "0123456789012345678901234567890123456789012345678901234567890123", - "000"}}, + "000", + }}, want: api.ErrCantParseVoteID, }, { - args: args{"GET", nil, []string{"votes", "verify", + args: args{"GET", nil, []string{ + "votes", "verify", "0123456789012345678901234567890123456789012345678901234567890123", - "0000"}}, + "0000", + }}, want: api.ErrVoteIDMalformed, }, { - args: args{"GET", nil, []string{"votes", "verify", + args: args{"GET", nil, []string{ + "votes", "verify", + "0123456789012345678901234567890123456789012345678901234567890123", "0123456789012345678901234567890123456789012345678901234567890123", - "0123456789012345678901234567890123456789012345678901234567890123"}}, + }}, want: api.ErrVoteNotFound, }, { - args: args{"GET", nil, []string{"votes", "verify", + args: args{"GET", nil, []string{ + "votes", "verify", "bbbbb", - "0123456789012345678901234567890123456789012345678901234567890123"}}, + "0123456789012345678901234567890123456789012345678901234567890123", + }}, want: api.ErrCantParseElectionID, }, { - args: args{"GET", nil, []string{"accounts", "0123456789012345678901234567890123456789", + args: args{"GET", nil, []string{ + "accounts", "0123456789012345678901234567890123456789", "elections", "status", "ready", - "page", "-1"}}, - want: api.ErrCantFetchElectionList, + "page", "-1", + }}, + want: api.ErrPageNotFound, }, { args: args{"GET", nil, []string{"elections", "page", "thisIsTotallyNotAnInt"}}, - want: api.ErrCantParsePageNumber, + want: api.ErrCantParseNumber, + }, + { + args: args{"GET", nil, []string{"elections", "page", "-1"}}, + want: api.ErrPageNotFound, + }, + { + args: args{"GET", nil, []string{"elections", "0123456789012345678901234567890123456789", "votes", "page", "0"}}, + want: api.ErrElectionNotFound, }, } for _, tt := range tests { @@ -118,3 +140,111 @@ func TestAPIerror(t *testing.T) { }) } } + +func TestAPIerrorWithQuery(t *testing.T) { + server := testcommon.APIserver{} + server.Start(t, + api.ChainHandler, + api.CensusHandler, + api.VoteHandler, + api.AccountHandler, + api.ElectionHandler, + api.WalletHandler, + ) + // Block 1 + server.VochainAPP.AdvanceTestBlock() + + token1 := uuid.New() + c := testutil.NewTestHTTPclient(t, server.ListenAddr, &token1) + + type args struct { + method string + jsonBody any + urlPath []string + query string + } + tests := []struct { + name string + args args + want apirest.APIerror + }{ + { + args: args{"GET", nil, []string{"accounts"}, "page=1234"}, + want: api.ErrPageNotFound, + }, + { + args: args{"GET", nil, []string{"elections"}, "organizationId=0123456789"}, + want: api.ErrOrgNotFound, + }, + { + args: args{"GET", nil, []string{"elections"}, "organizationId=0123456789&page=1234"}, + want: api.ErrOrgNotFound, + }, + { + args: args{"GET", nil, []string{"elections"}, "status=FOOBAR"}, + want: api.ErrParamStatusInvalid, + }, + { + args: args{"GET", nil, []string{"elections"}, "manuallyEnded=FOOBAR"}, + want: api.ErrCantParseBoolean, + }, + { + args: args{"GET", nil, []string{"chain", "transactions"}, "page=1234"}, + want: api.ErrPageNotFound, + }, + // TODO: not yet implemented + // { + // args: args{"GET", nil, []string{"chain", "transactions"}, "height=1234"}, + // want: api.ErrBlockNotFound, + // }, + { + args: args{"GET", nil, []string{"chain", "transactions"}, "height=FOOBAR"}, + want: api.ErrCantParseNumber, + }, + // TODO: should this endpoint check `type` is a sane value? + // { + // args: args{"GET", nil, []string{"chain", "transactions"}, "type=FOOBAR"}, + // want: api.ErrParamTypeInvalid, + // }, + { + args: args{"GET", nil, []string{"chain", "fees"}, "accountId=0123456789"}, + want: api.ErrAccountNotFound, + }, + // TODO: should this endpoint check `reference` matches something? + // { + // args: args{"GET", nil, []string{"chain", "fees"}, "reference=0123456789"}, + // want: api.ErrTransactionNotFound, + // }, + // TODO: should this endpoint check `type` is a sane value? + // { + // args: args{"GET", nil, []string{"chain", "fees"}, "type=FOOBAR"}, + // want: api.ErrParamTypeInvalid, + // }, + { + args: args{"GET", nil, []string{"votes"}, "electionId=0123456789"}, + want: api.ErrElectionNotFound, + }, + { + args: args{"GET", nil, []string{"chain", "transfers"}, "accountId=0123456789"}, + want: api.ErrAccountNotFound, + }, + { + args: args{"GET", nil, []string{"chain", "transfers"}, "accountIdFrom=0123456789"}, + want: api.ErrAccountNotFound, + }, + { + args: args{"GET", nil, []string{"chain", "transfers"}, "accountIdTo=0123456789"}, + want: api.ErrAccountNotFound, + }, + } + for _, tt := range tests { + t.Run(tt.want.Error(), func(t *testing.T) { + resp, code := c.RequestWithQuery(tt.args.method, tt.args.jsonBody, tt.args.query, tt.args.urlPath...) + t.Logf("httpstatus=%d body=%s", code, resp) + qt.Assert(t, code, qt.Equals, tt.want.HTTPstatus) + apierr := &apirest.APIerror{} + qt.Assert(t, json.Unmarshal(resp, apierr), qt.IsNil) + qt.Assert(t, apierr.Code, qt.Equals, tt.want.Code) + }) + } +} diff --git a/test/createaccounts_test.go b/test/createaccounts_test.go new file mode 100644 index 000000000..1fc94d866 --- /dev/null +++ b/test/createaccounts_test.go @@ -0,0 +1,60 @@ +package test + +import ( + "encoding/json" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/google/uuid" + "go.vocdoni.io/dvote/api" + "go.vocdoni.io/dvote/test/testcommon" + "go.vocdoni.io/dvote/test/testcommon/testutil" +) + +func BenchmarkAPICreateNAccounts(b *testing.B) { + server := testcommon.APIserver{} + server.Start(b, + api.ChainHandler, + api.CensusHandler, + api.VoteHandler, + api.AccountHandler, + api.ElectionHandler, + api.WalletHandler, + ) + token1 := uuid.New() + c := testutil.NewTestHTTPclient(b, server.ListenAddr, &token1) + + // Block 1 + server.VochainAPP.AdvanceTestBlock() + waitUntilHeight(b, c, 1) + + countAccts := func() uint64 { + // get accounts count + resp, code := c.Request("GET", nil, "accounts", "count") + qt.Assert(b, code, qt.Equals, 200, qt.Commentf("response: %s", resp)) + + countAccts := struct { + Count uint64 `json:"count"` + }{} + + err := json.Unmarshal(resp, &countAccts) + qt.Assert(b, err, qt.IsNil) + + return countAccts.Count + } + + // create a new account + initBalance := uint64(80) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = createAccount(b, c, server, initBalance) + } + // Block 2 + server.VochainAPP.AdvanceTestBlock() + waitUntilHeight(b, c, 2) + + if count := countAccts(); count < uint64(b.N) { + qt.Assert(b, count, qt.Equals, b.N) + } +} diff --git a/test/testcommon/api.go b/test/testcommon/api.go index 47ef9ff98..2057add3f 100644 --- a/test/testcommon/api.go +++ b/test/testcommon/api.go @@ -65,7 +65,8 @@ func (d *APIserver) Start(t testing.TB, apis ...string) { // create and add balance for the pre-created Account err = d.VochainAPP.State.CreateAccount(d.Account.Address(), "", nil, 1000000) qt.Assert(t, err, qt.IsNil) - d.VochainAPP.CommitState() + _, err = d.VochainAPP.CommitState() + qt.Assert(t, err, qt.IsNil) // create vochain info (we do not start since it is not required) d.VochainInfo = vochaininfo.NewVochainInfo(d.VochainAPP) diff --git a/test/testcommon/testutil/apiclient.go b/test/testcommon/testutil/apiclient.go index d7ae50a2f..7a560dbf7 100644 --- a/test/testcommon/testutil/apiclient.go +++ b/test/testcommon/testutil/apiclient.go @@ -21,16 +21,27 @@ type TestHTTPclient struct { t testing.TB } -func (c *TestHTTPclient) Request(method string, jsonBody any, urlPath ...string) ([]byte, int) { - body, err := json.Marshal(jsonBody) +func (c *TestHTTPclient) RequestWithQuery(method string, jsonBody any, query string, urlPath ...string) ([]byte, int) { + u, err := url.Parse(c.addr.String()) qt.Assert(c.t, err, qt.IsNil) + u.RawQuery = query + return c.request(method, u, jsonBody, urlPath...) +} + +func (c *TestHTTPclient) Request(method string, jsonBody any, urlPath ...string) ([]byte, int) { u, err := url.Parse(c.addr.String()) qt.Assert(c.t, err, qt.IsNil) + return c.request(method, u, jsonBody, urlPath...) +} + +func (c *TestHTTPclient) request(method string, u *url.URL, jsonBody any, urlPath ...string) ([]byte, int) { u.Path = path.Join(u.Path, path.Join(urlPath...)) headers := http.Header{} if c.token != nil { headers = http.Header{"Authorization": []string{"Bearer " + c.token.String()}} } + body, err := json.Marshal(jsonBody) + qt.Assert(c.t, err, qt.IsNil) c.t.Logf("querying %s", u) resp, err := c.c.Do(&http.Request{ Method: method, diff --git a/test/testcommon/testutil/types.go b/test/testcommon/testutil/types.go index 9e28eb6e0..118ace848 100644 --- a/test/testcommon/testutil/types.go +++ b/test/testcommon/testutil/types.go @@ -39,7 +39,8 @@ func (b *MockBlockStore) NewBlock(height int64, timestamp time.Time) { log.Infow("new block", "height", height, "timestamp", timestamp) b.set(height, &comettypes.Block{ Header: comettypes.Header{Height: height, Time: time.Now(), ChainID: "test"}, - Data: comettypes.Data{Txs: make([]comettypes.Tx, 0)}}, + Data: comettypes.Data{Txs: make([]comettypes.Tx, 0)}, + }, ) } diff --git a/test/testcommon/testvoteproof/voteproof.go b/test/testcommon/testvoteproof/voteproof.go index c8cee499d..875f62ffc 100644 --- a/test/testcommon/testvoteproof/voteproof.go +++ b/test/testcommon/testvoteproof/voteproof.go @@ -16,7 +16,8 @@ import ( ) func GetCSPproofBatch(signers []*ethereum.SignKeys, - csp *ethereum.SignKeys, pid []byte) ([]types.HexBytes, error) { + csp *ethereum.SignKeys, pid []byte, +) ([]types.HexBytes, error) { var proofs []types.HexBytes for _, k := range signers { bundle := &models.CAbundle{ @@ -50,8 +51,10 @@ func GetCSPproofBatch(signers []*ethereum.SignKeys, // It returns the keys, the census root and the proofs for each key. func CreateKeysAndBuildCensus(t *testing.T, size int) ([]*ethereum.SignKeys, []byte, [][]byte) { db := metadb.NewTest(t) - tr, err := censustree.New(censustree.Options{Name: "testcensus", ParentDB: db, - MaxLevels: censustree.DefaultMaxLevels, CensusType: models.Census_ARBO_BLAKE2B}) + tr, err := censustree.New(censustree.Options{ + Name: "testcensus", ParentDB: db, + MaxLevels: censustree.DefaultMaxLevels, CensusType: models.Census_ARBO_BLAKE2B, + }) if err != nil { t.Fatal(err) } @@ -80,7 +83,8 @@ func CreateKeysAndBuildCensus(t *testing.T, size int) ([]*ethereum.SignKeys, []b // BuildSignedVoteForOffChainTree builds a signed vote for an off-chain merkle-tree election. func BuildSignedVoteForOffChainTree(t *testing.T, electionID []byte, key *ethereum.SignKeys, - proof []byte, votePackage []int, chainID string) *models.SignedTx { + proof []byte, votePackage []int, chainID string, +) *models.SignedTx { var stx models.SignedTx var err error vp, err := json.Marshal(votePackage) diff --git a/tree/arbo/addbatch_test.go b/tree/arbo/addbatch_test.go index 90d4007f3..0c4cab15f 100644 --- a/tree/arbo/addbatch_test.go +++ b/tree/arbo/addbatch_test.go @@ -39,13 +39,17 @@ func debugTime(descr string, time1, time2 time.Duration) { func testInit(c *qt.C, n int) (*Tree, *Tree) { database1 := metadb.NewTest(c) - tree1, err := NewTree(Config{Database: database1, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree1, err := NewTree(Config{ + Database: database1, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) database2 := metadb.NewTest(c) - tree2, err := NewTree(Config{Database: database2, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree2, err := NewTree(Config{ + Database: database2, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := HashFunctionPoseidon.Len() @@ -116,8 +120,10 @@ func TestAddBatchTreeEmptyNotPowerOf2(t *testing.T) { nLeafs := 1027 database := metadb.NewTest(t) - tree, err := NewTree(Config{database, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree, err := NewTree(Config{ + database, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -130,8 +136,10 @@ func TestAddBatchTreeEmptyNotPowerOf2(t *testing.T) { } database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree2, err := NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) var keys, values [][]byte @@ -161,13 +169,17 @@ func randomBytes(n int) []byte { func TestAddBatchTestVector1(t *testing.T) { c := qt.New(t) database1 := metadb.NewTest(t) - tree1, err := NewTree(Config{database1, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree1, err := NewTree(Config{ + database1, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree2, err := NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) // leafs in 2nd level subtrees: [ 6, 0, 1, 1] @@ -199,13 +211,17 @@ func TestAddBatchTestVector1(t *testing.T) { // 2nd test vectors database1 = metadb.NewTest(t) - tree1, err = NewTree(Config{database1, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree1, err = NewTree(Config{ + database1, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) database2 = metadb.NewTest(t) - tree2, err = NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree2, err = NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) testvectorKeys = []string{ @@ -245,13 +261,17 @@ func TestAddBatchTestVector2(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree1, err := NewTree(Config{database, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree1, err := NewTree(Config{ + database, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree2, err := NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := tree1.HashFunction().Len() @@ -288,13 +308,17 @@ func TestAddBatchTestVector3(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree1, err := NewTree(Config{database, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree1, err := NewTree(Config{ + database, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree2, err := NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := tree1.HashFunction().Len() @@ -335,13 +359,17 @@ func TestAddBatchTreeEmptyRandomKeys(t *testing.T) { nLeafs := 8 database1 := metadb.NewTest(t) - tree1, err := NewTree(Config{database1, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree1, err := NewTree(Config{ + database1, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree2, err := NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) var keys, values [][]byte @@ -683,8 +711,10 @@ func TestAddBatchNotEmptyUnbalanced(t *testing.T) { time1 := time.Since(start) database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree2, err := NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) tree2.dbgInit() @@ -777,8 +807,10 @@ func benchAddBatch(t *testing.T, ks, vs [][]byte) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{database, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree, err := NewTree(Config{ + database, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) tree.dbgInit() @@ -809,8 +841,10 @@ func TestDbgStats(t *testing.T) { // 1 database1 := metadb.NewTest(t) - tree1, err := NewTree(Config{database1, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree1, err := NewTree(Config{ + database1, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) tree1.dbgInit() @@ -822,8 +856,10 @@ func TestDbgStats(t *testing.T) { // 2 database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree2, err := NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) tree2.dbgInit() @@ -834,8 +870,10 @@ func TestDbgStats(t *testing.T) { // 3 database3 := metadb.NewTest(t) - tree3, err := NewTree(Config{database3, 256, DefaultThresholdNLeafs, - HashFunctionBlake2b}) + tree3, err := NewTree(Config{ + database3, 256, DefaultThresholdNLeafs, + HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) tree3.dbgInit() @@ -868,8 +906,10 @@ func TestLoadVT(t *testing.T) { nLeafs := 1024 database := metadb.NewTest(t) - tree, err := NewTree(Config{database, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree, err := NewTree(Config{ + database, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) var keys, values [][]byte @@ -901,8 +941,10 @@ func TestAddKeysWithEmptyValues(t *testing.T) { nLeafs := 1024 database := metadb.NewTest(t) - tree, err := NewTree(Config{database, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree, err := NewTree(Config{ + database, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -921,8 +963,10 @@ func TestAddKeysWithEmptyValues(t *testing.T) { } database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{database2, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree2, err := NewTree(Config{ + database2, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) tree2.dbgInit() @@ -934,8 +978,10 @@ func TestAddKeysWithEmptyValues(t *testing.T) { // use tree3 to add nil value array database3 := metadb.NewTest(t) - tree3, err := NewTree(Config{database3, 256, DefaultThresholdNLeafs, - HashFunctionPoseidon}) + tree3, err := NewTree(Config{ + database3, 256, DefaultThresholdNLeafs, + HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) invalids, err = tree3.AddBatch(keys, nil) @@ -962,8 +1008,10 @@ func TestAddKeysWithEmptyValues(t *testing.T) { c.Check(verif, qt.IsTrue) // check with array with 32 zeroes - e32 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + e32 := []byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + } c.Assert(len(e32), qt.Equals, 32) verif, err = CheckProof(tree.hashFunction, keys[9], e32, root, siblings) c.Assert(err, qt.IsNil) diff --git a/tree/arbo/circomproofs_test.go b/tree/arbo/circomproofs_test.go index 7c48c2c5b..a75eba83f 100644 --- a/tree/arbo/circomproofs_test.go +++ b/tree/arbo/circomproofs_test.go @@ -12,8 +12,10 @@ import ( func TestCircomVerifierProof(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 4, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 4, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) testVector := [][]int64{ diff --git a/tree/arbo/helpers_test.go b/tree/arbo/helpers_test.go index 7853f8ee4..22053d7c6 100644 --- a/tree/arbo/helpers_test.go +++ b/tree/arbo/helpers_test.go @@ -39,7 +39,7 @@ func checkRoots(c *qt.C, tree1, tree2 *Tree) { func storeTree(c *qt.C, tree *Tree, path string) { dump, err := tree.Dump(nil) c.Assert(err, qt.IsNil) - err = os.WriteFile(path+"-"+time.Now().String()+".debug", dump, 0600) + err = os.WriteFile(path+"-"+time.Now().String()+".debug", dump, 0o600) c.Assert(err, qt.IsNil) } @@ -85,13 +85,17 @@ func TestReadTreeDBG(t *testing.T) { c := qt.New(t) database1 := metadb.NewTest(t) - tree1, err := NewTree(Config{Database: database1, MaxLevels: 100, - HashFunction: HashFunctionBlake2b}) + tree1, err := NewTree(Config{ + Database: database1, MaxLevels: 100, + HashFunction: HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{Database: database2, MaxLevels: 100, - HashFunction: HashFunctionBlake2b}) + tree2, err := NewTree(Config{ + Database: database2, MaxLevels: 100, + HashFunction: HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) // tree1 is generated by a loop of .Add diff --git a/tree/arbo/navigate.go b/tree/arbo/navigate.go index 53e596a6c..a6a7d4d0a 100644 --- a/tree/arbo/navigate.go +++ b/tree/arbo/navigate.go @@ -10,7 +10,8 @@ import ( // down goes down to the leaf recursively func (t *Tree) down(rTx db.Reader, newKey, currKey []byte, siblings [][]byte, intermediates *[][]byte, - path []bool, currLvl int, getLeaf bool) ([]byte, []byte, [][]byte, error) { + path []bool, currLvl int, getLeaf bool, +) ([]byte, []byte, [][]byte, error) { if currLvl > t.maxLevels { return nil, nil, nil, ErrMaxLevel } diff --git a/tree/arbo/testvectors/circom/go-data-generator/generator_test.go b/tree/arbo/testvectors/circom/go-data-generator/generator_test.go index 2e1944051..6b681d9e1 100644 --- a/tree/arbo/testvectors/circom/go-data-generator/generator_test.go +++ b/tree/arbo/testvectors/circom/go-data-generator/generator_test.go @@ -14,8 +14,10 @@ import ( func TestGenerator(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := arbo.NewTree(arbo.Config{Database: database, MaxLevels: 4, - HashFunction: arbo.HashFunctionPoseidon}) + tree, err := arbo.NewTree(arbo.Config{ + Database: database, MaxLevels: 4, + HashFunction: arbo.HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) testVector := [][]int64{ @@ -40,7 +42,7 @@ func TestGenerator(t *testing.T) { jCvp, err := json.Marshal(cvp) c.Assert(err, qt.IsNil) // store the data into a file that will be used at the circom test - err = os.WriteFile("go-smt-verifier-inputs.json", jCvp, 0600) + err = os.WriteFile("go-smt-verifier-inputs.json", jCvp, 0o600) c.Assert(err, qt.IsNil) // proof of non-existence @@ -50,6 +52,6 @@ func TestGenerator(t *testing.T) { jCvp, err = json.Marshal(cvp) c.Assert(err, qt.IsNil) // store the data into a file that will be used at the circom test - err = os.WriteFile("go-smt-verifier-non-existence-inputs.json", jCvp, 0600) + err = os.WriteFile("go-smt-verifier-non-existence-inputs.json", jCvp, 0o600) c.Assert(err, qt.IsNil) } diff --git a/tree/arbo/tree_test.go b/tree/arbo/tree_test.go index d2a9750b9..9ff0e9679 100644 --- a/tree/arbo/tree_test.go +++ b/tree/arbo/tree_test.go @@ -74,8 +74,10 @@ func TestAddTestVectors(t *testing.T) { func testAdd(c *qt.C, hashFunc HashFunction, testVectors []string) { database := metadb.NewTest(c) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: hashFunc}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: hashFunc, + }) c.Assert(err, qt.IsNil) root, err := tree.Root() @@ -331,8 +333,10 @@ func TestRootOnTx(t *testing.T) { func TestAux(t *testing.T) { // TODO split in proper tests c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -369,8 +373,10 @@ func TestAux(t *testing.T) { // TODO split in proper tests func TestGet(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -394,17 +400,21 @@ func TestBitmapBytes(t *testing.T) { b := []byte{15} bits := bytesToBitmap(b) - c.Assert(bits, qt.DeepEquals, []bool{true, true, true, true, - false, false, false, false}) + c.Assert(bits, qt.DeepEquals, []bool{ + true, true, true, true, + false, false, false, false, + }) b2 := bitmapToBytes(bits) c.Assert(b2, qt.DeepEquals, b) b = []byte{0, 15, 50} bits = bytesToBitmap(b) - c.Assert(bits, qt.DeepEquals, []bool{false, false, false, + c.Assert(bits, qt.DeepEquals, []bool{ + false, false, false, false, false, false, false, false, true, true, true, true, false, false, false, false, false, true, false, false, true, - true, false, false}) + true, false, false, + }) b2 = bitmapToBytes(bits) c.Assert(b2, qt.DeepEquals, b) @@ -504,8 +514,10 @@ func TestPackAndUnpackSiblings(t *testing.T) { func TestGenProofAndVerify(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -543,8 +555,10 @@ func TestDumpAndImportDumpInFile(t *testing.T) { func testDumpAndImportDump(t *testing.T, inFile bool) { c := qt.New(t) database1 := metadb.NewTest(t) - tree1, err := NewTree(Config{Database: database1, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree1, err := NewTree(Config{ + Database: database1, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -570,8 +584,10 @@ func testDumpAndImportDump(t *testing.T, inFile bool) { } database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{Database: database2, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree2, err := NewTree(Config{ + Database: database2, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) if inFile { @@ -596,8 +612,10 @@ func testDumpAndImportDump(t *testing.T, inFile bool) { func TestRWMutex(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -627,13 +645,17 @@ func TestAddBatchFullyUsed(t *testing.T) { c := qt.New(t) database1 := metadb.NewTest(t) - tree1, err := NewTree(Config{Database: database1, MaxLevels: 4, - HashFunction: HashFunctionPoseidon}) + tree1, err := NewTree(Config{ + Database: database1, MaxLevels: 4, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) database2 := metadb.NewTest(t) - tree2, err := NewTree(Config{Database: database2, MaxLevels: 4, - HashFunction: HashFunctionPoseidon}) + tree2, err := NewTree(Config{ + Database: database2, MaxLevels: 4, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) var keys, values [][]byte @@ -686,8 +708,10 @@ func TestAddBatchFullyUsed(t *testing.T) { func TestSetRoot(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) expectedRoot := "13742386369878513332697380582061714160370929283209286127733983161245560237407" @@ -744,8 +768,10 @@ func TestSetRoot(t *testing.T) { func TestSnapshot(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) // fill the tree @@ -794,8 +820,10 @@ func TestGetFromSnapshotExpectArboErrKeyNotFound(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -815,8 +843,10 @@ func TestKeyLen(t *testing.T) { database := metadb.NewTest(t) // maxLevels is 100, keyPath length = ceil(maxLevels/8) = 13 maxLevels := 100 - tree, err := NewTree(Config{Database: database, MaxLevels: maxLevels, - HashFunction: HashFunctionBlake2b}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: maxLevels, + HashFunction: HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) // expect no errors when adding a key of only 4 bytes (when the @@ -847,8 +877,10 @@ func TestKeyLen(t *testing.T) { // tree: ceil(maxLevels/8) maxLevels = 32 database = metadb.NewTest(t) - tree, err = NewTree(Config{Database: database, MaxLevels: maxLevels, - HashFunction: HashFunctionBlake2b}) + tree, err = NewTree(Config{ + Database: database, MaxLevels: maxLevels, + HashFunction: HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) maxKeyLen := int(math.Ceil(float64(maxLevels) / float64(8))) @@ -917,8 +949,10 @@ func TestKeyLenBiggerThan32(t *testing.T) { c := qt.New(t) maxLevels := 264 database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: maxLevels, - HashFunction: HashFunctionBlake2b}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: maxLevels, + HashFunction: HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) bLen := 33 @@ -940,8 +974,10 @@ func TestKeyLenBiggerThan32(t *testing.T) { func TestDelete(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -1019,8 +1055,10 @@ func BenchmarkAdd(b *testing.B) { func benchmarkAdd(b *testing.B, hashFunc HashFunction, ks, vs [][]byte) { c := qt.New(b) database := metadb.NewTest(c) - tree, err := NewTree(Config{Database: database, MaxLevels: 140, - HashFunction: hashFunc}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 140, + HashFunction: hashFunc, + }) c.Assert(err, qt.IsNil) for i := 0; i < len(ks); i++ { @@ -1059,7 +1097,7 @@ func TestDiskSizeBench(t *testing.T) { countDBitems := func() int { count := 0 if err := tree.db.Iterate(nil, func(_, _ []byte) bool { - //fmt.Printf("db item: %x\n", k) + // fmt.Printf("db item: %x\n", k) count++ return true }); err != nil { @@ -1090,7 +1128,7 @@ func TestDiskSizeBench(t *testing.T) { for i := 0; i < len(ks)/2; i++ { err = tree.DeleteWithTx(tx, ks[i]) c.Assert(err, qt.IsNil) - //fmt.Printf("deleted %x\n", ks[i]) + // fmt.Printf("deleted %x\n", ks[i]) } c.Assert(tx.Commit(), qt.IsNil) printRes(" Delete loop", time.Since(start)) @@ -1130,7 +1168,7 @@ func TestDiskSizeBench(t *testing.T) { for i := 0; i < len(ks); i++ { err = tree.UpdateWithTx(tx, ks[i], vs[len(ks)-i-1]) c.Assert(err, qt.IsNil, qt.Commentf("k=%x", ks[i])) - //fmt.Printf("updated %x\n", ks[i]) + // fmt.Printf("updated %x\n", ks[i]) } c.Assert(tx.Commit(), qt.IsNil) printRes(" Update loop", time.Since(start)) @@ -1168,8 +1206,10 @@ func dirSize(path string) (int64, error) { func TestGetLeavesFromSubPath(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) bLen := 32 @@ -1248,8 +1288,10 @@ func TestTreeAfterDeleteAndReconstruct(t *testing.T) { database2 := metadb.NewTest(t) // 1. A new tree (tree1) is created and filled with some data - tree1, err := NewTree(Config{Database: database1, MaxLevels: 256, - HashFunction: HashFunctionBlake2b}) + tree1, err := NewTree(Config{ + Database: database1, MaxLevels: 256, + HashFunction: HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) // prepare inputs @@ -1274,8 +1316,10 @@ func TestTreeAfterDeleteAndReconstruct(t *testing.T) { } // 3. Create a second tree (tree2) - tree2, err := NewTree(Config{Database: database2, MaxLevels: 256, - HashFunction: HashFunctionBlake2b}) + tree2, err := NewTree(Config{ + Database: database2, MaxLevels: 256, + HashFunction: HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) // 4. Add the non-deleted keys from tree1 to tree2 @@ -1308,8 +1352,10 @@ func TestTreeWithSingleLeaf(t *testing.T) { c := qt.New(t) database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: 256, - HashFunction: HashFunctionPoseidon}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: 256, + HashFunction: HashFunctionPoseidon, + }) c.Assert(err, qt.IsNil) // check empty root diff --git a/tree/arbo/vt.go b/tree/arbo/vt.go index 1bd8dfe98..40b5a8572 100644 --- a/tree/arbo/vt.go +++ b/tree/arbo/vt.go @@ -436,7 +436,7 @@ func (n *node) add(p *params, currLvl int, leaf *node) error { switch t { case vtMid: if leaf.path[currLvl] { - //right + // right if n.r == nil { // empty sub-node, add the leaf here n.r = leaf @@ -568,7 +568,8 @@ func flp2(n int) int { // computeHashes computes the hashes under the node from which is called the // method. Returns an array of key-values to store in the db func (n *node) computeHashes(currLvl, maxLvl int, p *params, pairs [][2][]byte) ( - [][2][]byte, error) { + [][2][]byte, error, +) { if n == nil || currLvl >= maxLvl { // no need to compute any hash return pairs, nil diff --git a/tree/arbo/vt_test.go b/tree/arbo/vt_test.go index 09d1e10b5..705796b15 100644 --- a/tree/arbo/vt_test.go +++ b/tree/arbo/vt_test.go @@ -17,8 +17,10 @@ func testVirtualTree(c *qt.C, maxLevels int, keys, values [][]byte) { // normal tree, to have an expected root value database := metadb.NewTest(c) - tree, err := NewTree(Config{Database: database, MaxLevels: maxLevels, - HashFunction: HashFunctionSha256}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: maxLevels, + HashFunction: HashFunctionSha256, + }) c.Assert(err, qt.IsNil) for i := 0; i < len(keys); i++ { err := tree.Add(keys[i], values[i]) @@ -122,8 +124,10 @@ func TestVirtualTreeAddBatch(t *testing.T) { // normal tree, to have an expected root value database := metadb.NewTest(t) - tree, err := NewTree(Config{Database: database, MaxLevels: maxLevels, - HashFunction: HashFunctionBlake2b}) + tree, err := NewTree(Config{ + Database: database, MaxLevels: maxLevels, + HashFunction: HashFunctionBlake2b, + }) c.Assert(err, qt.IsNil) for i := 0; i < len(keys); i++ { err := tree.Add(keys[i], values[i]) diff --git a/vochain/account_test.go b/vochain/account_test.go index 927de93b2..a491ea940 100644 --- a/vochain/account_test.go +++ b/vochain/account_test.go @@ -36,7 +36,8 @@ func testCommitState(t *testing.T, app *BaseApplication) { } func setupTestBaseApplicationAndSigners(t *testing.T, - numberSigners int) (*BaseApplication, []*ethereum.SignKeys, error) { + numberSigners int, +) (*BaseApplication, []*ethereum.SignKeys, error) { app := TestBaseApplication(t) signers := make([]*ethereum.SignKeys, numberSigners) for i := 0; i < numberSigners; i++ { @@ -77,7 +78,8 @@ func TestSetAccountTx(t *testing.T) { // set account 0 qt.Assert(t, app.State.SetAccount(signers[0].Address(), &state.Account{ - Account: models.Account{Balance: 10000, InfoURI: infoURI}}), + Account: models.Account{Balance: 10000, InfoURI: infoURI}, + }), qt.IsNil, ) testCommitState(t, app) @@ -507,7 +509,8 @@ func testSetAccountTx(t *testing.T, app *BaseApplication, infoURI string, nonce uint32, - create bool) error { + create bool, +) error { var err error sik, err := signer.AccountSIK(nil) @@ -618,7 +621,6 @@ func TestSendTokensTxToTheSameAccount(t *testing.T) { err = testSendTokensTx(t, &signer, app, signer.Address(), 89, 0) qt.Assert(t, err, qt.IsNotNil) - } func testSendTokensTx(t *testing.T, @@ -626,7 +628,8 @@ func testSendTokensTx(t *testing.T, app *BaseApplication, toAddr common.Address, value uint64, - nonce uint32) error { + nonce uint32, +) error { var err error // tx @@ -730,7 +733,8 @@ func testSetAccountDelegateTx(t *testing.T, app *BaseApplication, delegate common.Address, op bool, // true == add, false == del - nonce uint32) error { + nonce uint32, +) error { var err error // tx @@ -854,7 +858,8 @@ func testCollectFaucetTx(t *testing.T, app *BaseApplication, nonce uint32, amount, - identifier uint64) error { + identifier uint64, +) error { var err error // tx diff --git a/vochain/appsetup.go b/vochain/appsetup.go index 0430e8b4a..fb52f66ed 100644 --- a/vochain/appsetup.go +++ b/vochain/appsetup.go @@ -94,7 +94,6 @@ func (app *BaseApplication) SetDefaultMethods() { } return result, err }) - } func (app *BaseApplication) getTxTendermint(height uint32, txIndex int32) (*models.SignedTx, error) { diff --git a/vochain/apptest.go b/vochain/apptest.go index facb0b321..81a74a702 100644 --- a/vochain/apptest.go +++ b/vochain/apptest.go @@ -49,6 +49,8 @@ func TestBaseApplicationWithChainID(tb testing.TB, chainID string) *BaseApplicat if err != nil { tb.Fatal(err) } + app.State.ElectionPriceCalc.SetBasePrice(1) + app.State.ElectionPriceCalc.SetCapacity(10000) // TODO: should this be a Close on the entire BaseApplication? tb.Cleanup(func() { if err := app.State.Close(); err != nil { diff --git a/vochain/apputils.go b/vochain/apputils.go index 7628fcf01..aedf5ba58 100644 --- a/vochain/apputils.go +++ b/vochain/apputils.go @@ -134,7 +134,7 @@ func NewTemplateGenesisFile(dir string, validators int) (*genesis.Doc, error) { Balance: 100000, }, }, - TxCost: genesis.TransactionCosts{}, + TxCost: genesis.DefaultTransactionCosts(), } appState.MaxElectionSize = 100000 diff --git a/vochain/cometbft.go b/vochain/cometbft.go index da83a08b6..f918e7139 100644 --- a/vochain/cometbft.go +++ b/vochain/cometbft.go @@ -37,7 +37,8 @@ import ( // Tendermint expects LastBlockAppHash and LastBlockHeight to be updated during Commit, // ensuring that Commit is never called twice for the same block height. func (app *BaseApplication) Info(_ context.Context, - req *cometabcitypes.InfoRequest) (*cometabcitypes.InfoResponse, error) { + req *cometabcitypes.InfoRequest, +) (*cometabcitypes.InfoResponse, error) { // TODO: move SetElectionPriceCalc() somewhere else, also deduplicating from InitChain if err := app.State.SetElectionPriceCalc(); err != nil { return nil, fmt.Errorf("cannot set election price calc: %w", err) @@ -81,7 +82,8 @@ func (app *BaseApplication) Info(_ context.Context, // InitChainResponse can return a list of validators. If the list is empty, // Tendermint will use the validators loaded in the genesis file. func (app *BaseApplication) InitChain(_ context.Context, - req *cometabcitypes.InitChainRequest) (*cometabcitypes.InitChainResponse, error) { + req *cometabcitypes.InitChainRequest, +) (*cometabcitypes.InitChainResponse, error) { // if our State is already initialized, but cometbft is calling InitChain // it means the ChainID was bumped and cometbft is starting a chain on top of previous one. // Skip all the init, just pass the current validator set and AppHash to cometbft @@ -270,7 +272,8 @@ func (app *BaseApplication) CheckTx(_ context.Context, req *cometabcitypes.Check // Cryptographic commitments to the block and transaction results, returned via the corresponding // parameters in FinalizeBlockResponse, are included in the header of the next block. func (app *BaseApplication) FinalizeBlock(_ context.Context, - req *cometabcitypes.FinalizeBlockRequest) (*cometabcitypes.FinalizeBlockResponse, error) { + req *cometabcitypes.FinalizeBlockRequest, +) (*cometabcitypes.FinalizeBlockResponse, error) { app.prepareProposalLock.Lock() defer app.prepareProposalLock.Unlock() if req == nil { @@ -381,7 +384,8 @@ func (app *BaseApplication) Commit(_ context.Context, _ *cometabcitypes.CommitRe // order in which they appear, and returns the (potentially) modified proposal, called prepared proposal in // the PrepareProposalResponse call. The logic modifying the raw proposal MAY be non-deterministic. func (app *BaseApplication) PrepareProposal(ctx context.Context, - req *cometabcitypes.PrepareProposalRequest) (*cometabcitypes.PrepareProposalResponse, error) { + req *cometabcitypes.PrepareProposalRequest, +) (*cometabcitypes.PrepareProposalResponse, error) { app.prepareProposalLock.Lock() defer app.prepareProposalLock.Unlock() if req == nil { @@ -490,7 +494,8 @@ func (app *BaseApplication) PrepareProposal(ctx context.Context, // is invalid (e.g., an invalid transaction); the Application can ignore the invalid part of the prepared // proposal at block execution time. The logic in ProcessProposal MUST be deterministic. func (app *BaseApplication) ProcessProposal(_ context.Context, - req *cometabcitypes.ProcessProposalRequest) (*cometabcitypes.ProcessProposalResponse, error) { + req *cometabcitypes.ProcessProposalRequest, +) (*cometabcitypes.ProcessProposalResponse, error) { app.prepareProposalLock.Lock() defer app.prepareProposalLock.Unlock() if req == nil { @@ -772,18 +777,21 @@ func (app *BaseApplication) RestoreStateFromSnapshot(snap *snapshot.Snapshot) er // Query does nothing func (*BaseApplication) Query(_ context.Context, - _ *cometabcitypes.QueryRequest) (*cometabcitypes.QueryResponse, error) { + _ *cometabcitypes.QueryRequest, +) (*cometabcitypes.QueryResponse, error) { return &cometabcitypes.QueryResponse{}, nil } // ExtendVote creates application specific vote extension func (*BaseApplication) ExtendVote(_ context.Context, - req *cometabcitypes.ExtendVoteRequest) (*cometabcitypes.ExtendVoteResponse, error) { + req *cometabcitypes.ExtendVoteRequest, +) (*cometabcitypes.ExtendVoteResponse, error) { return &cometabcitypes.ExtendVoteResponse{}, nil } // VerifyVoteExtension verifies application's vote extension data func (*BaseApplication) VerifyVoteExtension(context.Context, - *cometabcitypes.VerifyVoteExtensionRequest) (*cometabcitypes.VerifyVoteExtensionResponse, error) { + *cometabcitypes.VerifyVoteExtensionRequest, +) (*cometabcitypes.VerifyVoteExtensionResponse, error) { return &cometabcitypes.VerifyVoteExtensionResponse{}, nil } diff --git a/vochain/genesis/genesis.go b/vochain/genesis/genesis.go index 0c52efb92..6eb6e0853 100644 --- a/vochain/genesis/genesis.go +++ b/vochain/genesis/genesis.go @@ -65,8 +65,8 @@ import ( // networks is a map containing the default chainID for each network var networks = map[string]string{ "test": "vocdoni/TEST/1", - "dev": "vocdoni/DEV/35", - "stage": "vocdoni/STAGE/11", + "dev": "vocdoni/DEV/36", + "stage": "vocdoni/STAGE/12", "lts": "vocdoni/LTS/1.2", } @@ -110,6 +110,14 @@ var ( AppState: jsonRawMessage(initialAppStateForDev), }, }, + "vocdoni/DEV/36": { + GenesisDoc: comettypes.GenesisDoc{ + GenesisTime: time.Date(2024, time.June, 21, 8, 0, 0, 0, time.UTC), + InitialHeight: 1, + ConsensusParams: DefaultConsensusParams(), + AppState: jsonRawMessage(initialAppStateForDev), + }, + }, // Staging network "vocdoni/STAGE/11": { @@ -119,6 +127,16 @@ var ( ConsensusParams: DefaultConsensusParams(), AppState: jsonRawMessage(initialAppStateForStage), }, + EndOfChain: 1063400, + }, + "vocdoni/STAGE/12": { + GenesisDoc: comettypes.GenesisDoc{ + GenesisTime: time.Date(2024, time.June, 27, 1, 0, 0, 0, time.UTC), + InitialHeight: 1063401, + ConsensusParams: DefaultConsensusParams(), + AppState: jsonRawMessage(initialAppStateForStage), + AppHash: []byte(types.HexStringToHexBytes("7cb55a508f797d1d7fa7612855c5177726ea459c6c26056fe35ed6919fab5c3c")), + }, }, // LTS production network @@ -179,22 +197,7 @@ var initialAppStateForTest = AppState{ Balance: 1000000000000, }, }, - TxCost: TransactionCosts{ - SetProcessStatus: 1, - SetProcessCensus: 1, - SetProcessQuestionIndex: 1, - RegisterKey: 1, - NewProcess: 10, - SendTokens: 2, - SetAccountInfoURI: 2, - CreateAccount: 2, - AddDelegateForAccount: 2, - DelDelegateForAccount: 2, - CollectFaucet: 1, - SetAccountSIK: 1, - DelAccountSIK: 1, - SetAccountValidator: 100, - }, + TxCost: DefaultTransactionCosts(), } var initialAppStateForDev = AppState{ @@ -244,22 +247,7 @@ var initialAppStateForDev = AppState{ Balance: 100000000, }, }, - TxCost: TransactionCosts{ - SetProcessStatus: 2, - SetProcessCensus: 2, - SetProcessQuestionIndex: 1, - RegisterKey: 1, - NewProcess: 5, - SendTokens: 1, - SetAccountInfoURI: 1, - CreateAccount: 1, - AddDelegateForAccount: 1, - DelDelegateForAccount: 1, - CollectFaucet: 1, - SetAccountSIK: 1, - DelAccountSIK: 1, - SetAccountValidator: 10000, - }, + TxCost: DefaultTransactionCosts(), } var initialAppStateForStage = AppState{ @@ -321,6 +309,7 @@ var initialAppStateForStage = AppState{ TxCost: TransactionCosts{ SetProcessStatus: 2, SetProcessCensus: 1, + SetProcessDuration: 2, SetProcessQuestionIndex: 1, RegisterKey: 1, NewProcess: 5, @@ -412,6 +401,7 @@ var initialAppStateForLTS = AppState{ TxCost: TransactionCosts{ SetProcessStatus: 1, SetProcessCensus: 5, + SetProcessDuration: 5, SetProcessQuestionIndex: 1, RegisterKey: 1, NewProcess: 10, @@ -462,6 +452,27 @@ func DefaultValidatorParams() comettypes.ValidatorParams { } } +// DefaultTransactionCosts returns a default set of transaction costs to use as template. +func DefaultTransactionCosts() TransactionCosts { + return TransactionCosts{ + SetProcessStatus: 2, + SetProcessCensus: 2, + SetProcessDuration: 2, + SetProcessQuestionIndex: 1, + RegisterKey: 1, + NewProcess: 5, + SendTokens: 1, + SetAccountInfoURI: 1, + CreateAccount: 1, + AddDelegateForAccount: 1, + DelDelegateForAccount: 1, + CollectFaucet: 1, + SetAccountSIK: 1, + DelAccountSIK: 1, + SetAccountValidator: 10000, + } +} + // AvailableNetworks returns the list of hardcoded networks func AvailableNetworks() []string { list := []string{} diff --git a/vochain/genesis/txcost.go b/vochain/genesis/txcost.go index 577bae1a9..76c63da13 100644 --- a/vochain/genesis/txcost.go +++ b/vochain/genesis/txcost.go @@ -10,6 +10,7 @@ import ( type TransactionCosts struct { SetProcessStatus uint32 `json:"Tx_SetProcessStatus"` SetProcessCensus uint32 `json:"Tx_SetProcessCensus"` + SetProcessDuration uint32 `json:"Tx_SetProcessDuration"` SetProcessQuestionIndex uint32 `json:"Tx_SetProcessQuestionIndex"` RegisterKey uint32 `json:"Tx_RegisterKey"` NewProcess uint32 `json:"Tx_NewProcess"` @@ -43,6 +44,7 @@ func (t *TransactionCosts) AsMap() map[models.TxType]uint64 { var TxCostNameToTxTypeMap = map[string]models.TxType{ "SetProcessStatus": models.TxType_SET_PROCESS_STATUS, "SetProcessCensus": models.TxType_SET_PROCESS_CENSUS, + "SetProcessDuration": models.TxType_SET_PROCESS_DURATION, "SetProcessQuestionIndex": models.TxType_SET_PROCESS_QUESTION_INDEX, "SendTokens": models.TxType_SEND_TOKENS, "SetAccountInfoURI": models.TxType_SET_ACCOUNT_INFO_URI, @@ -69,6 +71,7 @@ func TxCostNameToTxType(key string) models.TxType { var TxTypeToCostNameMap = map[models.TxType]string{ models.TxType_SET_PROCESS_STATUS: "SetProcessStatus", models.TxType_SET_PROCESS_CENSUS: "SetProcessCensus", + models.TxType_SET_PROCESS_DURATION: "SetProcessDuration", models.TxType_SET_PROCESS_QUESTION_INDEX: "SetProcessQuestionIndex", models.TxType_SEND_TOKENS: "SendTokens", models.TxType_SET_ACCOUNT_INFO_URI: "SetAccountInfoURI", diff --git a/vochain/indexer/block.go b/vochain/indexer/block.go index 9b2bad6bf..e40a5fdfb 100644 --- a/vochain/indexer/block.go +++ b/vochain/indexer/block.go @@ -12,10 +12,8 @@ import ( "go.vocdoni.io/dvote/vochain/state" ) -var ( - // ErrBlockNotFound is returned if the block is not found in the indexer database. - ErrBlockNotFound = fmt.Errorf("block not found") -) +// ErrBlockNotFound is returned if the block is not found in the indexer database. +var ErrBlockNotFound = fmt.Errorf("block not found") func (idx *Indexer) OnBeginBlock(bb state.BeginBlock) { idx.blockMu.Lock() diff --git a/vochain/indexer/db/account.sql.go b/vochain/indexer/db/account.sql.go index c34ebc307..9280ea315 100644 --- a/vochain/indexer/db/account.sql.go +++ b/vochain/indexer/db/account.sql.go @@ -13,8 +13,6 @@ import ( ) const countAccounts = `-- name: CountAccounts :one -; - SELECT COUNT(*) FROM accounts ` @@ -41,30 +39,54 @@ func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (s return q.exec(ctx, q.createAccountStmt, createAccount, arg.Account, arg.Balance, arg.Nonce) } -const getListAccounts = `-- name: GetListAccounts :many -; - -SELECT account, balance, nonce -FROM accounts +const searchAccounts = `-- name: SearchAccounts :many +WITH results AS ( + SELECT account, balance, nonce + FROM accounts + WHERE ( + ( + ?3 = '' + OR (LENGTH(?3) = 40 AND LOWER(HEX(account)) = LOWER(?3)) + OR (LENGTH(?3) < 40 AND INSTR(LOWER(HEX(account)), LOWER(?3)) > 0) + -- TODO: consider keeping an account_hex column for faster searches + ) + ) +) +SELECT account, balance, nonce, COUNT(*) OVER() AS total_count +FROM results ORDER BY balance DESC -LIMIT ? OFFSET ? +LIMIT ?2 +OFFSET ?1 ` -type GetListAccountsParams struct { - Limit int64 - Offset int64 +type SearchAccountsParams struct { + Offset int64 + Limit int64 + AccountIDSubstr interface{} +} + +type SearchAccountsRow struct { + Account []byte + Balance int64 + Nonce int64 + TotalCount int64 } -func (q *Queries) GetListAccounts(ctx context.Context, arg GetListAccountsParams) ([]Account, error) { - rows, err := q.query(ctx, q.getListAccountsStmt, getListAccounts, arg.Limit, arg.Offset) +func (q *Queries) SearchAccounts(ctx context.Context, arg SearchAccountsParams) ([]SearchAccountsRow, error) { + rows, err := q.query(ctx, q.searchAccountsStmt, searchAccounts, arg.Offset, arg.Limit, arg.AccountIDSubstr) if err != nil { return nil, err } defer rows.Close() - var items []Account + var items []SearchAccountsRow for rows.Next() { - var i Account - if err := rows.Scan(&i.Account, &i.Balance, &i.Nonce); err != nil { + var i SearchAccountsRow + if err := rows.Scan( + &i.Account, + &i.Balance, + &i.Nonce, + &i.TotalCount, + ); err != nil { return nil, err } items = append(items, i) diff --git a/vochain/indexer/db/db.go b/vochain/indexer/db/db.go index 9db9e8cdf..3695e8b73 100644 --- a/vochain/indexer/db/db.go +++ b/vochain/indexer/db/db.go @@ -66,12 +66,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getEntityCountStmt, err = db.PrepareContext(ctx, getEntityCount); err != nil { return nil, fmt.Errorf("error preparing query GetEntityCount: %w", err) } - if q.getLastTransactionsStmt, err = db.PrepareContext(ctx, getLastTransactions); err != nil { - return nil, fmt.Errorf("error preparing query GetLastTransactions: %w", err) - } - if q.getListAccountsStmt, err = db.PrepareContext(ctx, getListAccounts); err != nil { - return nil, fmt.Errorf("error preparing query GetListAccounts: %w", err) - } if q.getProcessStmt, err = db.PrepareContext(ctx, getProcess); err != nil { return nil, fmt.Errorf("error preparing query GetProcess: %w", err) } @@ -84,27 +78,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getProcessStatusStmt, err = db.PrepareContext(ctx, getProcessStatus); err != nil { return nil, fmt.Errorf("error preparing query GetProcessStatus: %w", err) } - if q.getTokenFeesStmt, err = db.PrepareContext(ctx, getTokenFees); err != nil { - return nil, fmt.Errorf("error preparing query GetTokenFees: %w", err) - } - if q.getTokenFeesByFromAccountStmt, err = db.PrepareContext(ctx, getTokenFeesByFromAccount); err != nil { - return nil, fmt.Errorf("error preparing query GetTokenFeesByFromAccount: %w", err) - } - if q.getTokenFeesByReferenceStmt, err = db.PrepareContext(ctx, getTokenFeesByReference); err != nil { - return nil, fmt.Errorf("error preparing query GetTokenFeesByReference: %w", err) - } - if q.getTokenFeesByTxTypeStmt, err = db.PrepareContext(ctx, getTokenFeesByTxType); err != nil { - return nil, fmt.Errorf("error preparing query GetTokenFeesByTxType: %w", err) - } if q.getTokenTransferStmt, err = db.PrepareContext(ctx, getTokenTransfer); err != nil { return nil, fmt.Errorf("error preparing query GetTokenTransfer: %w", err) } - if q.getTokenTransfersByFromAccountStmt, err = db.PrepareContext(ctx, getTokenTransfersByFromAccount); err != nil { - return nil, fmt.Errorf("error preparing query GetTokenTransfersByFromAccount: %w", err) - } - if q.getTokenTransfersByToAccountStmt, err = db.PrepareContext(ctx, getTokenTransfersByToAccount); err != nil { - return nil, fmt.Errorf("error preparing query GetTokenTransfersByToAccount: %w", err) - } if q.getTransactionStmt, err = db.PrepareContext(ctx, getTransaction); err != nil { return nil, fmt.Errorf("error preparing query GetTransaction: %w", err) } @@ -117,12 +93,24 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getVoteStmt, err = db.PrepareContext(ctx, getVote); err != nil { return nil, fmt.Errorf("error preparing query GetVote: %w", err) } + if q.searchAccountsStmt, err = db.PrepareContext(ctx, searchAccounts); err != nil { + return nil, fmt.Errorf("error preparing query SearchAccounts: %w", err) + } if q.searchEntitiesStmt, err = db.PrepareContext(ctx, searchEntities); err != nil { return nil, fmt.Errorf("error preparing query SearchEntities: %w", err) } if q.searchProcessesStmt, err = db.PrepareContext(ctx, searchProcesses); err != nil { return nil, fmt.Errorf("error preparing query SearchProcesses: %w", err) } + if q.searchTokenFeesStmt, err = db.PrepareContext(ctx, searchTokenFees); err != nil { + return nil, fmt.Errorf("error preparing query SearchTokenFees: %w", err) + } + if q.searchTokenTransfersStmt, err = db.PrepareContext(ctx, searchTokenTransfers); err != nil { + return nil, fmt.Errorf("error preparing query SearchTokenTransfers: %w", err) + } + if q.searchTransactionsStmt, err = db.PrepareContext(ctx, searchTransactions); err != nil { + return nil, fmt.Errorf("error preparing query SearchTransactions: %w", err) + } if q.searchVotesStmt, err = db.PrepareContext(ctx, searchVotes); err != nil { return nil, fmt.Errorf("error preparing query SearchVotes: %w", err) } @@ -219,16 +207,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getEntityCountStmt: %w", cerr) } } - if q.getLastTransactionsStmt != nil { - if cerr := q.getLastTransactionsStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getLastTransactionsStmt: %w", cerr) - } - } - if q.getListAccountsStmt != nil { - if cerr := q.getListAccountsStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getListAccountsStmt: %w", cerr) - } - } if q.getProcessStmt != nil { if cerr := q.getProcessStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getProcessStmt: %w", cerr) @@ -249,41 +227,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getProcessStatusStmt: %w", cerr) } } - if q.getTokenFeesStmt != nil { - if cerr := q.getTokenFeesStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getTokenFeesStmt: %w", cerr) - } - } - if q.getTokenFeesByFromAccountStmt != nil { - if cerr := q.getTokenFeesByFromAccountStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getTokenFeesByFromAccountStmt: %w", cerr) - } - } - if q.getTokenFeesByReferenceStmt != nil { - if cerr := q.getTokenFeesByReferenceStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getTokenFeesByReferenceStmt: %w", cerr) - } - } - if q.getTokenFeesByTxTypeStmt != nil { - if cerr := q.getTokenFeesByTxTypeStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getTokenFeesByTxTypeStmt: %w", cerr) - } - } if q.getTokenTransferStmt != nil { if cerr := q.getTokenTransferStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getTokenTransferStmt: %w", cerr) } } - if q.getTokenTransfersByFromAccountStmt != nil { - if cerr := q.getTokenTransfersByFromAccountStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getTokenTransfersByFromAccountStmt: %w", cerr) - } - } - if q.getTokenTransfersByToAccountStmt != nil { - if cerr := q.getTokenTransfersByToAccountStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getTokenTransfersByToAccountStmt: %w", cerr) - } - } if q.getTransactionStmt != nil { if cerr := q.getTransactionStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getTransactionStmt: %w", cerr) @@ -304,6 +252,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getVoteStmt: %w", cerr) } } + if q.searchAccountsStmt != nil { + if cerr := q.searchAccountsStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing searchAccountsStmt: %w", cerr) + } + } if q.searchEntitiesStmt != nil { if cerr := q.searchEntitiesStmt.Close(); cerr != nil { err = fmt.Errorf("error closing searchEntitiesStmt: %w", cerr) @@ -314,6 +267,21 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing searchProcessesStmt: %w", cerr) } } + if q.searchTokenFeesStmt != nil { + if cerr := q.searchTokenFeesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing searchTokenFeesStmt: %w", cerr) + } + } + if q.searchTokenTransfersStmt != nil { + if cerr := q.searchTokenTransfersStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing searchTokenTransfersStmt: %w", cerr) + } + } + if q.searchTransactionsStmt != nil { + if cerr := q.searchTransactionsStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing searchTransactionsStmt: %w", cerr) + } + } if q.searchVotesStmt != nil { if cerr := q.searchVotesStmt.Close(); cerr != nil { err = fmt.Errorf("error closing searchVotesStmt: %w", cerr) @@ -402,25 +370,21 @@ type Queries struct { createVoteStmt *sql.Stmt getBlockStmt *sql.Stmt getEntityCountStmt *sql.Stmt - getLastTransactionsStmt *sql.Stmt - getListAccountsStmt *sql.Stmt getProcessStmt *sql.Stmt getProcessCountStmt *sql.Stmt getProcessIDsByFinalResultsStmt *sql.Stmt getProcessStatusStmt *sql.Stmt - getTokenFeesStmt *sql.Stmt - getTokenFeesByFromAccountStmt *sql.Stmt - getTokenFeesByReferenceStmt *sql.Stmt - getTokenFeesByTxTypeStmt *sql.Stmt getTokenTransferStmt *sql.Stmt - getTokenTransfersByFromAccountStmt *sql.Stmt - getTokenTransfersByToAccountStmt *sql.Stmt getTransactionStmt *sql.Stmt getTransactionByHashStmt *sql.Stmt getTxReferenceByBlockHeightAndBlockIndexStmt *sql.Stmt getVoteStmt *sql.Stmt + searchAccountsStmt *sql.Stmt searchEntitiesStmt *sql.Stmt searchProcessesStmt *sql.Stmt + searchTokenFeesStmt *sql.Stmt + searchTokenTransfersStmt *sql.Stmt + searchTransactionsStmt *sql.Stmt searchVotesStmt *sql.Stmt setProcessResultsCancelledStmt *sql.Stmt setProcessResultsReadyStmt *sql.Stmt @@ -432,41 +396,37 @@ type Queries struct { func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, - tx: tx, - computeProcessVoteCountStmt: q.computeProcessVoteCountStmt, - countAccountsStmt: q.countAccountsStmt, - countTokenTransfersByAccountStmt: q.countTokenTransfersByAccountStmt, - countTransactionsStmt: q.countTransactionsStmt, - countVotesStmt: q.countVotesStmt, - createAccountStmt: q.createAccountStmt, - createBlockStmt: q.createBlockStmt, - createProcessStmt: q.createProcessStmt, - createTokenFeeStmt: q.createTokenFeeStmt, - createTokenTransferStmt: q.createTokenTransferStmt, - createTransactionStmt: q.createTransactionStmt, - createVoteStmt: q.createVoteStmt, - getBlockStmt: q.getBlockStmt, - getEntityCountStmt: q.getEntityCountStmt, - getLastTransactionsStmt: q.getLastTransactionsStmt, - getListAccountsStmt: q.getListAccountsStmt, - getProcessStmt: q.getProcessStmt, - getProcessCountStmt: q.getProcessCountStmt, - getProcessIDsByFinalResultsStmt: q.getProcessIDsByFinalResultsStmt, - getProcessStatusStmt: q.getProcessStatusStmt, - getTokenFeesStmt: q.getTokenFeesStmt, - getTokenFeesByFromAccountStmt: q.getTokenFeesByFromAccountStmt, - getTokenFeesByReferenceStmt: q.getTokenFeesByReferenceStmt, - getTokenFeesByTxTypeStmt: q.getTokenFeesByTxTypeStmt, - getTokenTransferStmt: q.getTokenTransferStmt, - getTokenTransfersByFromAccountStmt: q.getTokenTransfersByFromAccountStmt, - getTokenTransfersByToAccountStmt: q.getTokenTransfersByToAccountStmt, - getTransactionStmt: q.getTransactionStmt, - getTransactionByHashStmt: q.getTransactionByHashStmt, + db: tx, + tx: tx, + computeProcessVoteCountStmt: q.computeProcessVoteCountStmt, + countAccountsStmt: q.countAccountsStmt, + countTokenTransfersByAccountStmt: q.countTokenTransfersByAccountStmt, + countTransactionsStmt: q.countTransactionsStmt, + countVotesStmt: q.countVotesStmt, + createAccountStmt: q.createAccountStmt, + createBlockStmt: q.createBlockStmt, + createProcessStmt: q.createProcessStmt, + createTokenFeeStmt: q.createTokenFeeStmt, + createTokenTransferStmt: q.createTokenTransferStmt, + createTransactionStmt: q.createTransactionStmt, + createVoteStmt: q.createVoteStmt, + getBlockStmt: q.getBlockStmt, + getEntityCountStmt: q.getEntityCountStmt, + getProcessStmt: q.getProcessStmt, + getProcessCountStmt: q.getProcessCountStmt, + getProcessIDsByFinalResultsStmt: q.getProcessIDsByFinalResultsStmt, + getProcessStatusStmt: q.getProcessStatusStmt, + getTokenTransferStmt: q.getTokenTransferStmt, + getTransactionStmt: q.getTransactionStmt, + getTransactionByHashStmt: q.getTransactionByHashStmt, getTxReferenceByBlockHeightAndBlockIndexStmt: q.getTxReferenceByBlockHeightAndBlockIndexStmt, getVoteStmt: q.getVoteStmt, + searchAccountsStmt: q.searchAccountsStmt, searchEntitiesStmt: q.searchEntitiesStmt, searchProcessesStmt: q.searchProcessesStmt, + searchTokenFeesStmt: q.searchTokenFeesStmt, + searchTokenTransfersStmt: q.searchTokenTransfersStmt, + searchTransactionsStmt: q.searchTransactionsStmt, searchVotesStmt: q.searchVotesStmt, setProcessResultsCancelledStmt: q.setProcessResultsCancelledStmt, setProcessResultsReadyStmt: q.setProcessResultsReadyStmt, diff --git a/vochain/indexer/db/models.go b/vochain/indexer/db/models.go index a1566a32a..08a6e0e2b 100644 --- a/vochain/indexer/db/models.go +++ b/vochain/indexer/db/models.go @@ -10,12 +10,6 @@ import ( "go.vocdoni.io/dvote/types" ) -type Account struct { - Account types.AccountID - Balance int64 - Nonce int64 -} - type Block struct { Height int64 Time time.Time @@ -53,16 +47,6 @@ type Process struct { ManuallyEnded bool } -type TokenFee struct { - ID int64 - BlockHeight int64 - FromAccount []byte - Reference string - Cost int64 - TxType string - SpendTime time.Time -} - type TokenTransfer struct { TxHash types.Hash BlockHeight int64 diff --git a/vochain/indexer/db/processes.sql.go b/vochain/indexer/db/processes.sql.go index 6b313659c..b274c802c 100644 --- a/vochain/indexer/db/processes.sql.go +++ b/vochain/indexer/db/processes.sql.go @@ -176,8 +176,6 @@ func (q *Queries) GetProcessCount(ctx context.Context) (int64, error) { } const getProcessIDsByFinalResults = `-- name: GetProcessIDsByFinalResults :many -; - SELECT id FROM processes WHERE final_results = ? ` @@ -219,27 +217,35 @@ func (q *Queries) GetProcessStatus(ctx context.Context, id types.ProcessID) (int } const searchEntities = `-- name: SearchEntities :many -SELECT entity_id, COUNT(id) AS process_count FROM processes -WHERE (?1 = '' OR (INSTR(LOWER(HEX(entity_id)), ?1) > 0)) +WITH results AS ( + SELECT id, entity_id, start_date, end_date, vote_count, chain_id, have_results, final_results, results_votes, results_weight, results_block_height, census_root, max_census_size, census_uri, metadata, census_origin, status, namespace, envelope, mode, vote_opts, private_keys, public_keys, question_index, creation_time, source_block_height, source_network_id, manually_ended + FROM processes + WHERE (?3 = '' OR (INSTR(LOWER(HEX(entity_id)), ?3) > 0)) +) +SELECT entity_id, + COUNT(id) AS process_count, + COUNT(entity_id) OVER() AS total_count +FROM results GROUP BY entity_id ORDER BY creation_time DESC, id ASC -LIMIT ?3 -OFFSET ?2 +LIMIT ?2 +OFFSET ?1 ` type SearchEntitiesParams struct { - EntityIDSubstr interface{} Offset int64 Limit int64 + EntityIDSubstr interface{} } type SearchEntitiesRow struct { - EntityID types.EntityID + EntityID []byte ProcessCount int64 + TotalCount int64 } func (q *Queries) SearchEntities(ctx context.Context, arg SearchEntitiesParams) ([]SearchEntitiesRow, error) { - rows, err := q.query(ctx, q.searchEntitiesStmt, searchEntities, arg.EntityIDSubstr, arg.Offset, arg.Limit) + rows, err := q.query(ctx, q.searchEntitiesStmt, searchEntities, arg.Offset, arg.Limit, arg.EntityIDSubstr) if err != nil { return nil, err } @@ -247,7 +253,7 @@ func (q *Queries) SearchEntities(ctx context.Context, arg SearchEntitiesParams) var items []SearchEntitiesRow for rows.Next() { var i SearchEntitiesRow - if err := rows.Scan(&i.EntityID, &i.ProcessCount); err != nil { + if err := rows.Scan(&i.EntityID, &i.ProcessCount, &i.TotalCount); err != nil { return nil, err } items = append(items, i) @@ -262,52 +268,106 @@ func (q *Queries) SearchEntities(ctx context.Context, arg SearchEntitiesParams) } const searchProcesses = `-- name: SearchProcesses :many -SELECT id FROM processes -WHERE (LENGTH(?1) = 0 OR entity_id = ?1) - AND (?2 = 0 OR namespace = ?2) - AND (?3 = 0 OR status = ?3) - AND (?4 = 0 OR source_network_id = ?4) - -- TODO(mvdan): consider keeping an id_hex column for faster searches - AND (?5 = '' OR (INSTR(LOWER(HEX(id)), ?5) > 0)) - AND (?6 = FALSE OR have_results) +WITH results AS ( + SELECT id, entity_id, start_date, end_date, vote_count, chain_id, have_results, final_results, results_votes, results_weight, results_block_height, census_root, max_census_size, census_uri, metadata, census_origin, status, namespace, envelope, mode, vote_opts, private_keys, public_keys, question_index, creation_time, source_block_height, source_network_id, manually_ended, + COUNT(*) OVER() AS total_count + FROM processes + WHERE ( + LENGTH(?3) <= 40 -- if passed arg is longer, then just abort the query + AND ( + ?3 = '' + OR (LENGTH(?3) = 40 AND LOWER(HEX(entity_id)) = LOWER(?3)) + OR (LENGTH(?3) < 40 AND INSTR(LOWER(HEX(entity_id)), LOWER(?3)) > 0) + -- TODO: consider keeping an entity_id_hex column for faster searches + ) + AND (?4 = 0 OR namespace = ?4) + AND (?5 = 0 OR status = ?5) + AND (?6 = 0 OR source_network_id = ?6) + AND LENGTH(?7) <= 64 -- if passed arg is longer, then just abort the query + AND ( + ?7 = '' + OR (LENGTH(?7) = 64 AND LOWER(HEX(id)) = LOWER(?7)) + OR (LENGTH(?7) < 64 AND INSTR(LOWER(HEX(id)), LOWER(?7)) > 0) + -- TODO: consider keeping an id_hex column for faster searches + ) + AND ( + ?8 = -1 + OR (?8 = 1 AND have_results = TRUE) + OR (?8 = 0 AND have_results = FALSE) + ) + AND ( + ?9 = -1 + OR (?9 = 1 AND final_results = TRUE) + OR (?9 = 0 AND final_results = FALSE) + ) + AND ( + ?10 = -1 + OR (?10 = 1 AND manually_ended = TRUE) + OR (?10 = 0 AND manually_ended = FALSE) + ) + AND (?11 IS NULL OR start_date >= ?11) + AND (?12 IS NULL OR start_date <= ?12) + AND (?13 IS NULL OR end_date >= ?13) + AND (?14 IS NULL OR end_date <= ?14) + ) +) +SELECT id, total_count +FROM results ORDER BY creation_time DESC, id ASC -LIMIT ?8 -OFFSET ?7 +LIMIT ?2 +OFFSET ?1 ` type SearchProcessesParams struct { - EntityID interface{} + Offset int64 + Limit int64 + EntityIDSubstr interface{} Namespace interface{} Status interface{} SourceNetworkID interface{} IDSubstr interface{} - WithResults interface{} - Offset int64 - Limit int64 + HaveResults interface{} + FinalResults interface{} + ManuallyEnded interface{} + StartDateAfter interface{} + StartDateBefore interface{} + EndDateAfter interface{} + EndDateBefore interface{} } -func (q *Queries) SearchProcesses(ctx context.Context, arg SearchProcessesParams) ([]types.ProcessID, error) { +type SearchProcessesRow struct { + ID []byte + TotalCount int64 +} + +func (q *Queries) SearchProcesses(ctx context.Context, arg SearchProcessesParams) ([]SearchProcessesRow, error) { rows, err := q.query(ctx, q.searchProcessesStmt, searchProcesses, - arg.EntityID, + arg.Offset, + arg.Limit, + arg.EntityIDSubstr, arg.Namespace, arg.Status, arg.SourceNetworkID, arg.IDSubstr, - arg.WithResults, - arg.Offset, - arg.Limit, + arg.HaveResults, + arg.FinalResults, + arg.ManuallyEnded, + arg.StartDateAfter, + arg.StartDateBefore, + arg.EndDateAfter, + arg.EndDateBefore, ) if err != nil { return nil, err } defer rows.Close() - var items []types.ProcessID + var items []SearchProcessesRow for rows.Next() { - var id types.ProcessID - if err := rows.Scan(&id); err != nil { + var i SearchProcessesRow + if err := rows.Scan(&i.ID, &i.TotalCount); err != nil { return nil, err } - items = append(items, id) + items = append(items, i) } if err := rows.Close(); err != nil { return nil, err @@ -382,8 +442,6 @@ func (q *Queries) UpdateProcessEndDate(ctx context.Context, arg UpdateProcessEnd } const updateProcessFromState = `-- name: UpdateProcessFromState :execresult -; - UPDATE processes SET census_root = ?1, census_uri = ?2, @@ -391,8 +449,9 @@ SET census_root = ?1, public_keys = ?4, metadata = ?5, status = ?6, - max_census_size = ?7 -WHERE id = ?8 + max_census_size = ?7, + end_date = ?8 +WHERE id = ?9 ` type UpdateProcessFromStateParams struct { @@ -403,6 +462,7 @@ type UpdateProcessFromStateParams struct { Metadata string Status int64 MaxCensusSize int64 + EndDate time.Time ID types.ProcessID } @@ -415,6 +475,7 @@ func (q *Queries) UpdateProcessFromState(ctx context.Context, arg UpdateProcessF arg.Metadata, arg.Status, arg.MaxCensusSize, + arg.EndDate, arg.ID, ) } diff --git a/vochain/indexer/db/token_fees.sql.go b/vochain/indexer/db/token_fees.sql.go index a9e2bd6d8..0bbe20281 100644 --- a/vochain/indexer/db/token_fees.sql.go +++ b/vochain/indexer/db/token_fees.sql.go @@ -41,168 +41,57 @@ func (q *Queries) CreateTokenFee(ctx context.Context, arg CreateTokenFeeParams) ) } -const getTokenFees = `-- name: GetTokenFees :many -SELECT id, block_height, from_account, reference, cost, tx_type, spend_time FROM token_fees +const searchTokenFees = `-- name: SearchTokenFees :many +WITH results AS ( + SELECT id, block_height, from_account, reference, cost, tx_type, spend_time + FROM token_fees + WHERE ( + (?3 = '' OR LOWER(HEX(from_account)) = LOWER(?3)) + AND (?4 = '' OR LOWER(tx_type) = LOWER(?4)) + AND (?5 = '' OR LOWER(reference) = LOWER(?5)) + ) +) +SELECT id, block_height, from_account, reference, cost, tx_type, spend_time, COUNT(*) OVER() AS total_count +FROM results ORDER BY spend_time DESC LIMIT ?2 OFFSET ?1 ` -type GetTokenFeesParams struct { - Offset int64 - Limit int64 -} - -func (q *Queries) GetTokenFees(ctx context.Context, arg GetTokenFeesParams) ([]TokenFee, error) { - rows, err := q.query(ctx, q.getTokenFeesStmt, getTokenFees, arg.Offset, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []TokenFee - for rows.Next() { - var i TokenFee - if err := rows.Scan( - &i.ID, - &i.BlockHeight, - &i.FromAccount, - &i.Reference, - &i.Cost, - &i.TxType, - &i.SpendTime, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getTokenFeesByFromAccount = `-- name: GetTokenFeesByFromAccount :many -; - -SELECT id, block_height, from_account, reference, cost, tx_type, spend_time FROM token_fees -WHERE from_account = ?1 -ORDER BY spend_time DESC -LIMIT ?3 -OFFSET ?2 -` - -type GetTokenFeesByFromAccountParams struct { - FromAccount []byte +type SearchTokenFeesParams struct { Offset int64 Limit int64 + FromAccount interface{} + TxType interface{} + Reference interface{} } -func (q *Queries) GetTokenFeesByFromAccount(ctx context.Context, arg GetTokenFeesByFromAccountParams) ([]TokenFee, error) { - rows, err := q.query(ctx, q.getTokenFeesByFromAccountStmt, getTokenFeesByFromAccount, arg.FromAccount, arg.Offset, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []TokenFee - for rows.Next() { - var i TokenFee - if err := rows.Scan( - &i.ID, - &i.BlockHeight, - &i.FromAccount, - &i.Reference, - &i.Cost, - &i.TxType, - &i.SpendTime, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getTokenFeesByReference = `-- name: GetTokenFeesByReference :many -; - -SELECT id, block_height, from_account, reference, cost, tx_type, spend_time FROM token_fees -WHERE reference = ?1 -ORDER BY spend_time DESC -LIMIT ?3 -OFFSET ?2 -` - -type GetTokenFeesByReferenceParams struct { - Reference string - Offset int64 - Limit int64 -} - -func (q *Queries) GetTokenFeesByReference(ctx context.Context, arg GetTokenFeesByReferenceParams) ([]TokenFee, error) { - rows, err := q.query(ctx, q.getTokenFeesByReferenceStmt, getTokenFeesByReference, arg.Reference, arg.Offset, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []TokenFee - for rows.Next() { - var i TokenFee - if err := rows.Scan( - &i.ID, - &i.BlockHeight, - &i.FromAccount, - &i.Reference, - &i.Cost, - &i.TxType, - &i.SpendTime, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getTokenFeesByTxType = `-- name: GetTokenFeesByTxType :many -; - -SELECT id, block_height, from_account, reference, cost, tx_type, spend_time FROM token_fees -WHERE tx_type = ?1 -ORDER BY spend_time DESC -LIMIT ?3 -OFFSET ?2 -` - -type GetTokenFeesByTxTypeParams struct { - TxType string - Offset int64 - Limit int64 +type SearchTokenFeesRow struct { + ID int64 + BlockHeight int64 + FromAccount []byte + Reference string + Cost int64 + TxType string + SpendTime time.Time + TotalCount int64 } -func (q *Queries) GetTokenFeesByTxType(ctx context.Context, arg GetTokenFeesByTxTypeParams) ([]TokenFee, error) { - rows, err := q.query(ctx, q.getTokenFeesByTxTypeStmt, getTokenFeesByTxType, arg.TxType, arg.Offset, arg.Limit) +func (q *Queries) SearchTokenFees(ctx context.Context, arg SearchTokenFeesParams) ([]SearchTokenFeesRow, error) { + rows, err := q.query(ctx, q.searchTokenFeesStmt, searchTokenFees, + arg.Offset, + arg.Limit, + arg.FromAccount, + arg.TxType, + arg.Reference, + ) if err != nil { return nil, err } defer rows.Close() - var items []TokenFee + var items []SearchTokenFeesRow for rows.Next() { - var i TokenFee + var i SearchTokenFeesRow if err := rows.Scan( &i.ID, &i.BlockHeight, @@ -211,6 +100,7 @@ func (q *Queries) GetTokenFeesByTxType(ctx context.Context, arg GetTokenFeesByTx &i.Cost, &i.TxType, &i.SpendTime, + &i.TotalCount, ); err != nil { return nil, err } diff --git a/vochain/indexer/db/token_transfers.sql.go b/vochain/indexer/db/token_transfers.sql.go index 2d00e15e0..7e46f74da 100644 --- a/vochain/indexer/db/token_transfers.sql.go +++ b/vochain/indexer/db/token_transfers.sql.go @@ -14,9 +14,6 @@ import ( ) const countTokenTransfersByAccount = `-- name: CountTokenTransfersByAccount :one -; - - SELECT COUNT(*) FROM token_transfers WHERE to_account = ?1 OR from_account = ?1 @@ -79,75 +76,59 @@ func (q *Queries) GetTokenTransfer(ctx context.Context, txHash types.Hash) (Toke return i, err } -const getTokenTransfersByFromAccount = `-- name: GetTokenTransfersByFromAccount :many -SELECT tx_hash, block_height, from_account, to_account, amount, transfer_time FROM token_transfers -WHERE from_account = ?1 +const searchTokenTransfers = `-- name: SearchTokenTransfers :many +WITH results AS ( + SELECT tx_hash, block_height, from_account, to_account, amount, transfer_time + FROM token_transfers + WHERE ( + (?3 = '' OR ( + LOWER(HEX(from_account)) = LOWER(?3) + OR LOWER(HEX(to_account)) = LOWER(?3) + )) + AND (?4 = '' OR LOWER(HEX(from_account)) = LOWER(?4)) + AND (?5 = '' OR LOWER(HEX(to_account)) = LOWER(?5)) + ) +) +SELECT tx_hash, block_height, from_account, to_account, amount, transfer_time, COUNT(*) OVER() AS total_count +FROM results ORDER BY transfer_time DESC -LIMIT ?3 -OFFSET ?2 +LIMIT ?2 +OFFSET ?1 ` -type GetTokenTransfersByFromAccountParams struct { - FromAccount types.AccountID - Offset int64 - Limit int64 +type SearchTokenTransfersParams struct { + Offset int64 + Limit int64 + FromOrToAccount interface{} + FromAccount interface{} + ToAccount interface{} } -func (q *Queries) GetTokenTransfersByFromAccount(ctx context.Context, arg GetTokenTransfersByFromAccountParams) ([]TokenTransfer, error) { - rows, err := q.query(ctx, q.getTokenTransfersByFromAccountStmt, getTokenTransfersByFromAccount, arg.FromAccount, arg.Offset, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []TokenTransfer - for rows.Next() { - var i TokenTransfer - if err := rows.Scan( - &i.TxHash, - &i.BlockHeight, - &i.FromAccount, - &i.ToAccount, - &i.Amount, - &i.TransferTime, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getTokenTransfersByToAccount = `-- name: GetTokenTransfersByToAccount :many -; - -SELECT tx_hash, block_height, from_account, to_account, amount, transfer_time FROM token_transfers -WHERE to_account = ?1 -ORDER BY transfer_time DESC -LIMIT ?3 -OFFSET ?2 -` - -type GetTokenTransfersByToAccountParams struct { - ToAccount types.AccountID - Offset int64 - Limit int64 +type SearchTokenTransfersRow struct { + TxHash []byte + BlockHeight int64 + FromAccount []byte + ToAccount []byte + Amount int64 + TransferTime time.Time + TotalCount int64 } -func (q *Queries) GetTokenTransfersByToAccount(ctx context.Context, arg GetTokenTransfersByToAccountParams) ([]TokenTransfer, error) { - rows, err := q.query(ctx, q.getTokenTransfersByToAccountStmt, getTokenTransfersByToAccount, arg.ToAccount, arg.Offset, arg.Limit) +func (q *Queries) SearchTokenTransfers(ctx context.Context, arg SearchTokenTransfersParams) ([]SearchTokenTransfersRow, error) { + rows, err := q.query(ctx, q.searchTokenTransfersStmt, searchTokenTransfers, + arg.Offset, + arg.Limit, + arg.FromOrToAccount, + arg.FromAccount, + arg.ToAccount, + ) if err != nil { return nil, err } defer rows.Close() - var items []TokenTransfer + var items []SearchTokenTransfersRow for rows.Next() { - var i TokenTransfer + var i SearchTokenTransfersRow if err := rows.Scan( &i.TxHash, &i.BlockHeight, @@ -155,6 +136,7 @@ func (q *Queries) GetTokenTransfersByToAccount(ctx context.Context, arg GetToken &i.ToAccount, &i.Amount, &i.TransferTime, + &i.TotalCount, ); err != nil { return nil, err } diff --git a/vochain/indexer/db/transactions.sql.go b/vochain/indexer/db/transactions.sql.go index c3d6e0b45..2d06f135a 100644 --- a/vochain/indexer/db/transactions.sql.go +++ b/vochain/indexer/db/transactions.sql.go @@ -13,8 +13,6 @@ import ( ) const countTransactions = `-- name: CountTransactions :one -; - SELECT COUNT(*) FROM transactions ` @@ -49,47 +47,6 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa ) } -const getLastTransactions = `-- name: GetLastTransactions :many -SELECT id, hash, block_height, block_index, type FROM transactions -ORDER BY id DESC -LIMIT ? -OFFSET ? -` - -type GetLastTransactionsParams struct { - Limit int64 - Offset int64 -} - -func (q *Queries) GetLastTransactions(ctx context.Context, arg GetLastTransactionsParams) ([]Transaction, error) { - rows, err := q.query(ctx, q.getLastTransactionsStmt, getLastTransactions, arg.Limit, arg.Offset) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Transaction - for rows.Next() { - var i Transaction - if err := rows.Scan( - &i.ID, - &i.Hash, - &i.BlockHeight, - &i.BlockIndex, - &i.Type, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const getTransaction = `-- name: GetTransaction :one SELECT id, hash, block_height, block_index, type FROM transactions WHERE id = ? @@ -151,3 +108,70 @@ func (q *Queries) GetTxReferenceByBlockHeightAndBlockIndex(ctx context.Context, ) return i, err } + +const searchTransactions = `-- name: SearchTransactions :many +WITH results AS ( + SELECT id, hash, block_height, block_index, type + FROM transactions + WHERE ( + (?3 = 0 OR block_height = ?3) + AND (?4 = '' OR LOWER(type) = LOWER(?4)) + ) +) +SELECT id, hash, block_height, block_index, type, COUNT(*) OVER() AS total_count +FROM results +ORDER BY id DESC +LIMIT ?2 +OFFSET ?1 +` + +type SearchTransactionsParams struct { + Offset int64 + Limit int64 + BlockHeight interface{} + TxType interface{} +} + +type SearchTransactionsRow struct { + ID int64 + Hash []byte + BlockHeight int64 + BlockIndex int64 + Type string + TotalCount int64 +} + +func (q *Queries) SearchTransactions(ctx context.Context, arg SearchTransactionsParams) ([]SearchTransactionsRow, error) { + rows, err := q.query(ctx, q.searchTransactionsStmt, searchTransactions, + arg.Offset, + arg.Limit, + arg.BlockHeight, + arg.TxType, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SearchTransactionsRow + for rows.Next() { + var i SearchTransactionsRow + if err := rows.Scan( + &i.ID, + &i.Hash, + &i.BlockHeight, + &i.BlockIndex, + &i.Type, + &i.TotalCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/vochain/indexer/db/votes.sql.go b/vochain/indexer/db/votes.sql.go index b4925e0c1..3c94ae672 100644 --- a/vochain/indexer/db/votes.sql.go +++ b/vochain/indexer/db/votes.sql.go @@ -107,43 +107,63 @@ func (q *Queries) GetVote(ctx context.Context, nullifier types.Nullifier) (GetVo } const searchVotes = `-- name: SearchVotes :many -SELECT v.nullifier, v.process_id, v.block_height, v.block_index, v.weight, v.voter_id, v.overwrite_count, v.encryption_key_indexes, v.package, t.hash FROM votes AS v -LEFT JOIN transactions AS t - ON v.block_height = t.block_height - AND v.block_index = t.block_index -WHERE (?1 = '' OR process_id = ?1) - AND (?2 = '' OR (INSTR(LOWER(HEX(nullifier)), ?2) > 0)) -ORDER BY v.block_height DESC, v.nullifier ASC -LIMIT ?4 -OFFSET ?3 +WITH results AS ( + SELECT v.nullifier, v.process_id, v.block_height, v.block_index, v.weight, v.voter_id, v.overwrite_count, v.encryption_key_indexes, v.package, t.hash + FROM votes AS v + LEFT JOIN transactions AS t + ON v.block_height = t.block_height + AND v.block_index = t.block_index + WHERE ( + LENGTH(?3) <= 64 -- if passed arg is longer, then just abort the query + AND ( + ?3 = '' + OR (LENGTH(?3) = 64 AND LOWER(HEX(process_id)) = LOWER(?3)) + OR (LENGTH(?3) < 64 AND INSTR(LOWER(HEX(process_id)), LOWER(?3)) > 0) + -- TODO: consider keeping an process_id_hex column for faster searches + ) + AND LENGTH(?4) <= 64 -- if passed arg is longer, then just abort the query + AND ( + ?4 = '' + OR (LENGTH(?4) = 64 AND LOWER(HEX(nullifier)) = LOWER(?4)) + OR (LENGTH(?4) < 64 AND INSTR(LOWER(HEX(nullifier)), LOWER(?4)) > 0) + -- TODO: consider keeping an nullifier_hex column for faster searches + ) + ) +) +SELECT nullifier, process_id, block_height, block_index, weight, voter_id, overwrite_count, encryption_key_indexes, package, hash, COUNT(*) OVER() AS total_count +FROM results +ORDER BY block_height DESC, nullifier ASC +LIMIT ?2 +OFFSET ?1 ` type SearchVotesParams struct { - ProcessID interface{} - NullifierSubstr interface{} Offset int64 Limit int64 + ProcessIDSubstr interface{} + NullifierSubstr interface{} } type SearchVotesRow struct { - Nullifier types.Nullifier - ProcessID types.ProcessID + Nullifier []byte + ProcessID []byte BlockHeight int64 BlockIndex int64 Weight string - VoterID state.VoterID + VoterID []byte OverwriteCount int64 EncryptionKeyIndexes string Package string - Hash types.Hash + Hash []byte + TotalCount int64 } func (q *Queries) SearchVotes(ctx context.Context, arg SearchVotesParams) ([]SearchVotesRow, error) { rows, err := q.query(ctx, q.searchVotesStmt, searchVotes, - arg.ProcessID, - arg.NullifierSubstr, arg.Offset, arg.Limit, + arg.ProcessIDSubstr, + arg.NullifierSubstr, ) if err != nil { return nil, err @@ -163,6 +183,7 @@ func (q *Queries) SearchVotes(ctx context.Context, arg SearchVotesParams) ([]Sea &i.EncryptionKeyIndexes, &i.Package, &i.Hash, + &i.TotalCount, ); err != nil { return nil, err } diff --git a/vochain/indexer/indexer.go b/vochain/indexer/indexer.go index 562609cec..43b59323e 100644 --- a/vochain/indexer/indexer.go +++ b/vochain/indexer/indexer.go @@ -268,7 +268,6 @@ func (idx *Indexer) ExportBackupAsBytes(ctx context.Context) ([]byte, error) { tmpDir, err := os.MkdirTemp("", "indexer") if err != nil { return nil, fmt.Errorf("error creating tmpDir: %w", err) - } tmpFilePath := filepath.Join(tmpDir, "indexer.sqlite3") if err := idx.SaveBackup(ctx, tmpFilePath); err != nil { @@ -631,6 +630,13 @@ func (idx *Indexer) OnProcessStatusChange(pid []byte, _ models.ProcessStatus, _ idx.blockUpdateProcs[string(pid)] = true } +// OnProcessDurationChange adds the process to blockUpdateProcs and, if ended, the resultsPool +func (idx *Indexer) OnProcessDurationChange(pid []byte, _ uint32, _ int32) { + idx.blockMu.Lock() + defer idx.blockMu.Unlock() + idx.blockUpdateProcs[string(pid)] = true +} + // OnRevealKeys checks if all keys have been revealed and in such case add the // process to the results queue func (idx *Indexer) OnRevealKeys(pid []byte, _ string, _ int32) { @@ -703,31 +709,6 @@ func (idx *Indexer) OnCensusUpdate(pid, _ []byte, _ string, _ uint64) { idx.blockUpdateProcs[string(pid)] = true } -// GetTokenTransfersByFromAccount returns all the token transfers made from a given account -// from the database, ordered by timestamp and paginated by maxItems and offset -func (idx *Indexer) GetTokenTransfersByFromAccount(from []byte, offset, maxItems int32) ([]*indexertypes.TokenTransferMeta, error) { - ttFromDB, err := idx.readOnlyQuery.GetTokenTransfersByFromAccount(context.TODO(), indexerdb.GetTokenTransfersByFromAccountParams{ - FromAccount: from, - Limit: int64(maxItems), - Offset: int64(offset), - }) - if err != nil { - return nil, err - } - tt := []*indexertypes.TokenTransferMeta{} - for _, t := range ttFromDB { - tt = append(tt, &indexertypes.TokenTransferMeta{ - Amount: uint64(t.Amount), - From: t.FromAccount, - To: t.ToAccount, - Height: uint64(t.BlockHeight), - TxHash: t.TxHash, - Timestamp: t.TransferTime, - }) - } - return tt, nil -} - // OnSpendTokens indexes a token spending event. func (idx *Indexer) OnSpendTokens(address []byte, txType models.TxType, cost uint64, reference string) { idx.blockMu.Lock() @@ -745,145 +726,80 @@ func (idx *Indexer) OnSpendTokens(address []byte, txType models.TxType, cost uin } } -// GetTokenFeesByFromAccount returns all the token transfers made from a given account -// from the database, ordered by timestamp and paginated by maxItems and offset -func (idx *Indexer) GetTokenFeesByFromAccount(from []byte, offset, maxItems int32) ([]*indexertypes.TokenFeeMeta, error) { - ttFromDB, err := idx.readOnlyQuery.GetTokenFeesByFromAccount(context.TODO(), indexerdb.GetTokenFeesByFromAccountParams{ - FromAccount: from, - Limit: int64(maxItems), - Offset: int64(offset), - }) - if err != nil { - return nil, err +// TokenFeesList returns all the token fees associated with a given transaction type, reference and fromAccount +// (all optional filters), ordered by timestamp and paginated by limit and offset +func (idx *Indexer) TokenFeesList(limit, offset int, txType, reference, fromAccount string) ( + []*indexertypes.TokenFeeMeta, uint64, error, +) { + if offset < 0 { + return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) } - tt := []*indexertypes.TokenFeeMeta{} - for _, t := range ttFromDB { - tt = append(tt, &indexertypes.TokenFeeMeta{ - Cost: uint64(t.Cost), - From: t.FromAccount, - TxType: t.TxType, - Height: uint64(t.BlockHeight), - Reference: t.Reference, - Timestamp: t.SpendTime, - }) + if limit <= 0 { + return nil, 0, fmt.Errorf("invalid value: limit cannot be %d", limit) } - return tt, nil -} - -// GetTokenFees returns all the token transfers from the database, ordered -// by timestamp and paginated by maxItems and offset -func (idx *Indexer) GetTokenFees(offset, maxItems int32) ([]*indexertypes.TokenFeeMeta, error) { - ttFromDB, err := idx.readOnlyQuery.GetTokenFees(context.TODO(), indexerdb.GetTokenFeesParams{ - Limit: int64(maxItems), - Offset: int64(offset), + results, err := idx.readOnlyQuery.SearchTokenFees(context.TODO(), indexerdb.SearchTokenFeesParams{ + Limit: int64(limit), + Offset: int64(offset), + TxType: txType, + Reference: reference, + FromAccount: fromAccount, }) if err != nil { - return nil, err - } - tt := []*indexertypes.TokenFeeMeta{} - for _, t := range ttFromDB { - tt = append(tt, &indexertypes.TokenFeeMeta{ - Cost: uint64(t.Cost), - From: t.FromAccount, - TxType: t.TxType, - Height: uint64(t.BlockHeight), - Reference: t.Reference, - Timestamp: t.SpendTime, + return nil, 0, err + } + list := []*indexertypes.TokenFeeMeta{} + for _, row := range results { + list = append(list, &indexertypes.TokenFeeMeta{ + Cost: uint64(row.Cost), + From: row.FromAccount, + TxType: row.TxType, + Height: uint64(row.BlockHeight), + Reference: row.Reference, + Timestamp: row.SpendTime, }) } - return tt, nil -} - -// GetTokenFeesByReference returns all the token fees associated with a given reference -// from the database, ordered by timestamp and paginated by maxItems and offset -func (idx *Indexer) GetTokenFeesByReference(reference string, offset, maxItems int32) ([]*indexertypes.TokenFeeMeta, error) { - ttFromDB, err := idx.readOnlyQuery.GetTokenFeesByReference(context.TODO(), indexerdb.GetTokenFeesByReferenceParams{ - Reference: reference, - Limit: int64(maxItems), - Offset: int64(offset), - }) - if err != nil { - return nil, err - } - tt := []*indexertypes.TokenFeeMeta{} - for _, t := range ttFromDB { - tt = append(tt, &indexertypes.TokenFeeMeta{ - Cost: uint64(t.Cost), - From: t.FromAccount, - TxType: t.TxType, - Height: uint64(t.BlockHeight), - Reference: t.Reference, - Timestamp: t.SpendTime, - }) + if len(results) == 0 { + return list, 0, nil } - return tt, nil + return list, uint64(results[0].TotalCount), nil } -// GetTokenFeesByType returns all the token fees associated with a given transaction type -// from the database, ordered by timestamp and paginated by maxItems and offset -func (idx *Indexer) GetTokenFeesByType(txType string, offset, maxItems int32) ([]*indexertypes.TokenFeeMeta, error) { - ttFromDB, err := idx.readOnlyQuery.GetTokenFeesByTxType(context.TODO(), indexerdb.GetTokenFeesByTxTypeParams{ - TxType: txType, - Limit: int64(maxItems), - Offset: int64(offset), - }) - if err != nil { - return nil, err +// TokenTransfersList returns all the token transfers, made to and/or from a given account +// (all optional filters), ordered by timestamp and paginated by limit and offset +func (idx *Indexer) TokenTransfersList(limit, offset int, fromOrToAccount, fromAccount, toAccount string) ( + []*indexertypes.TokenTransferMeta, uint64, error, +) { + if offset < 0 { + return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) } - tt := []*indexertypes.TokenFeeMeta{} - for _, t := range ttFromDB { - tt = append(tt, &indexertypes.TokenFeeMeta{ - Cost: uint64(t.Cost), - From: t.FromAccount, - TxType: t.TxType, - Height: uint64(t.BlockHeight), - Reference: t.Reference, - Timestamp: t.SpendTime, - }) + if limit <= 0 { + return nil, 0, fmt.Errorf("invalid value: limit cannot be %d", limit) } - return tt, nil -} - -// GetTokenTransfersByToAccount returns all the token transfers made to a given account -// from the database, ordered by timestamp and paginated by maxItems and offset -func (idx *Indexer) GetTokenTransfersByToAccount(to []byte, offset, maxItems int32) ([]*indexertypes.TokenTransferMeta, error) { - ttFromDB, err := idx.readOnlyQuery.GetTokenTransfersByToAccount(context.TODO(), indexerdb.GetTokenTransfersByToAccountParams{ - ToAccount: to, - Limit: int64(maxItems), - Offset: int64(offset), + results, err := idx.readOnlyQuery.SearchTokenTransfers(context.TODO(), indexerdb.SearchTokenTransfersParams{ + Limit: int64(limit), + Offset: int64(offset), + FromOrToAccount: fromOrToAccount, + FromAccount: fromAccount, + ToAccount: toAccount, }) if err != nil { - return nil, err - } - tt := []*indexertypes.TokenTransferMeta{} - for _, t := range ttFromDB { - tt = append(tt, &indexertypes.TokenTransferMeta{ - Amount: uint64(t.Amount), - From: t.FromAccount, - To: t.ToAccount, - Height: uint64(t.BlockHeight), - TxHash: t.TxHash, - Timestamp: t.TransferTime, + return nil, 0, err + } + list := []*indexertypes.TokenTransferMeta{} + for _, row := range results { + list = append(list, &indexertypes.TokenTransferMeta{ + Amount: uint64(row.Amount), + From: row.FromAccount, + To: row.ToAccount, + Height: uint64(row.BlockHeight), + TxHash: row.TxHash, + Timestamp: row.TransferTime, }) } - return tt, nil -} - -// GetTokenTransfersByAccount returns all the token transfers made to and from a given account -// from the database, ordered by timestamp and paginated by maxItems and offset -func (idx *Indexer) GetTokenTransfersByAccount(acc []byte, offset, maxItems int32) (indexertypes.TokenTransfersAccount, error) { - transfersTo, err := idx.GetTokenTransfersByToAccount(acc, offset, maxItems) - if err != nil { - return indexertypes.TokenTransfersAccount{}, err - } - transfersFrom, err := idx.GetTokenTransfersByFromAccount(acc, offset, maxItems) - if err != nil { - return indexertypes.TokenTransfersAccount{}, err + if len(results) == 0 { + return list, 0, nil } - - return indexertypes.TokenTransfersAccount{ - Received: transfersTo, - Sent: transfersFrom}, nil + return list, uint64(results[0].TotalCount), nil } // CountTokenTransfersByAccount returns the count all the token transfers made from a given account @@ -898,21 +814,46 @@ func (idx *Indexer) CountTotalAccounts() (uint64, error) { return uint64(count), err } -func (idx *Indexer) GetListAccounts(offset, maxItems int32) ([]indexertypes.Account, error) { - accsFromDB, err := idx.readOnlyQuery.GetListAccounts(context.TODO(), indexerdb.GetListAccountsParams{ - Limit: int64(maxItems), - Offset: int64(offset), +// AccountList returns a list of accounts, accountID is a partial or full hex string, +// and is optional (declared as zero-value will be ignored). +func (idx *Indexer) AccountList(limit, offset int, accountID string) ([]*indexertypes.Account, uint64, error) { + if offset < 0 { + return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) + } + if limit <= 0 { + return nil, 0, fmt.Errorf("invalid value: limit cannot be %d", limit) + } + results, err := idx.readOnlyQuery.SearchAccounts(context.TODO(), indexerdb.SearchAccountsParams{ + Limit: int64(limit), + Offset: int64(offset), + AccountIDSubstr: accountID, }) if err != nil { - return nil, err - } - tt := []indexertypes.Account{} - for _, acc := range accsFromDB { - tt = append(tt, indexertypes.Account{ - Address: acc.Account, - Balance: uint64(acc.Balance), - Nonce: uint32(acc.Nonce), + return nil, 0, err + } + list := []*indexertypes.Account{} + for _, row := range results { + list = append(list, &indexertypes.Account{ + Address: row.Account, + Balance: uint64(row.Balance), + Nonce: uint32(row.Nonce), }) } - return tt, nil + if len(results) == 0 { + return list, 0, nil + } + return list, uint64(results[0].TotalCount), nil +} + +// AccountExists returns whether the passed accountID exists in the db. +// If passed arg is not the full hex string, returns false (i.e. no substring matching) +func (idx *Indexer) AccountExists(accountID string) bool { + if len(accountID) != 40 { + return false + } + _, count, err := idx.AccountList(1, 0, accountID) + if err != nil { + log.Errorw(err, "indexer query failed") + } + return count > 0 } diff --git a/vochain/indexer/indexer_test.go b/vochain/indexer/indexer_test.go index 901769c0e..fad19d248 100644 --- a/vochain/indexer/indexer_test.go +++ b/vochain/indexer/indexer_test.go @@ -9,6 +9,7 @@ import ( stdlog "log" "math/big" "path/filepath" + "strings" "testing" qt "github.com/frankban/quicktest" @@ -160,7 +161,8 @@ func testEntityList(t *testing.T, entityCount int) { entitiesByID := make(map[string]bool) last := 0 for len(entitiesByID) <= entityCount { - list := idx.EntityList(10, last, "") + list, _, err := idx.EntityList(10, last, "") + qt.Assert(t, err, qt.IsNil) if len(list) < 1 { t.Log("list is empty") break @@ -254,21 +256,25 @@ func TestEntitySearch(t *testing.T) { } app.AdvanceTestBlock() // Exact entity search - list := idx.EntityList(10, 0, "4011d50537fa164b6fef261141797bbe4014526e") - if len(list) < 1 { - t.Fatalf("expected 1 entity, got %d", len(list)) - } + list, _, err := idx.EntityList(10, 0, "4011d50537fa164b6fef261141797bbe4014526e") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, list, qt.HasLen, 1) // Search for nonexistent entity - list = idx.EntityList(10, 0, "4011d50537fa164b6fef261141797bbe4014526f") - if len(list) > 0 { - t.Fatalf("expected 0 entities, got %d", len(list)) - } + list, _, err = idx.EntityList(10, 0, "4011d50537fa164b6fef261141797bbe4014526f") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, list, qt.HasLen, 0) // Search containing part of all manually-defined entities - list = idx.EntityList(10, 0, "011d50537fa164b6fef261141797bbe4014526e") - log.Info(list) - if len(list) < len(entityIds) { - t.Fatalf("expected %d entities, got %d", len(entityIds), len(list)) - } + list, _, err = idx.EntityList(10, 0, "011d50537fa164b6fef261141797bbe4014526e") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, list, qt.HasLen, len(entityIds)) + // Partial entity search as mixed case hex + list, _, err = idx.EntityList(10, 0, "50537FA164B6Fef261141797BbE401452") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, list, qt.HasLen, len(entityIds)) + // Partial entity search as uppercase hex + list, _, err = idx.EntityList(10, 0, "50537FA164B6FEF261141797BBE401452") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, list, qt.HasLen, len(entityIds)) } func TestProcessList(t *testing.T) { @@ -324,10 +330,13 @@ func testProcessList(t *testing.T, procsCount int) { procs := make(map[string]bool) last := 0 for len(procs) < procsCount { - list, err := idx.ProcessList(eidProcsCount, last, 10, "", 0, 0, "", false) + fmt.Printf("%x\n", eidProcsCount) + fmt.Printf("%s\n", hex.EncodeToString(eidProcsCount)) + list, total, err := idx.ProcessList(10, last, hex.EncodeToString(eidProcsCount), "", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } + qt.Assert(t, total, qt.Equals, uint64(procsCount)) if len(list) < 1 { t.Log("list is empty") break @@ -342,12 +351,14 @@ func testProcessList(t *testing.T, procsCount int) { } qt.Assert(t, procs, qt.HasLen, procsCount) - _, err := idx.ProcessList(nil, 0, 64, "", 0, 0, "", false) + _, total, err := idx.ProcessList(64, 0, "", "", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) qt.Assert(t, err, qt.IsNil) + qt.Assert(t, total, qt.Equals, uint64(10+procsCount)) qt.Assert(t, idx.CountTotalProcesses(), qt.Equals, uint64(10+procsCount)) countEntityProcs := func(eid []byte) int64 { - list := idx.EntityList(1, 0, fmt.Sprintf("%x", eid)) + list, _, err := idx.EntityList(1, 0, fmt.Sprintf("%x", eid)) + qt.Assert(t, err, qt.IsNil) if len(list) == 0 { return -1 } @@ -356,6 +367,11 @@ func testProcessList(t *testing.T, procsCount int) { qt.Assert(t, countEntityProcs(eidOneProcess), qt.Equals, int64(1)) qt.Assert(t, countEntityProcs(eidProcsCount), qt.Equals, int64(procsCount)) qt.Assert(t, countEntityProcs([]byte("not an entity id that exists")), qt.Equals, int64(-1)) + + // Past the end (from=10000) should return an empty list + emptyList, _, err := idx.ProcessList(64, 10000, "", "", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, emptyList, qt.DeepEquals, [][]byte{}) } func TestProcessSearch(t *testing.T) { @@ -443,7 +459,7 @@ func TestProcessSearch(t *testing.T) { app.AdvanceTestBlock() // Exact process search - list, err := idx.ProcessList(eidTest, 0, 10, pidExact, 0, 0, "", false) + list, _, err := idx.ProcessList(10, 0, hex.EncodeToString(eidTest), pidExact, 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -452,7 +468,7 @@ func TestProcessSearch(t *testing.T) { } // Exact process search, with it being encrypted. // This once caused a sqlite bug due to a mistake in the SQL query. - list, err = idx.ProcessList(eidTest, 0, 10, pidExactEncrypted, 0, 0, "", false) + list, _, err = idx.ProcessList(10, 0, hex.EncodeToString(eidTest), pidExactEncrypted, 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -460,8 +476,8 @@ func TestProcessSearch(t *testing.T) { t.Fatalf("expected 1 process, got %d", len(list)) } // Search for nonexistent process - list, err = idx.ProcessList(eidTest, 0, 10, - "4011d50537fa164b6fef261141797bbe4014526f", 0, 0, "", false) + list, _, err = idx.ProcessList(10, 0, hex.EncodeToString(eidTest), + "4011d50537fa164b6fef261141797bbe4014526f", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -469,8 +485,8 @@ func TestProcessSearch(t *testing.T) { t.Fatalf("expected 0 processes, got %d", len(list)) } // Search containing part of all manually-defined processes - list, err = idx.ProcessList(eidTest, 0, 10, - "011d50537fa164b6fef261141797bbe4014526e", 0, 0, "", false) + list, _, err = idx.ProcessList(10, 0, hex.EncodeToString(eidTest), + "011d50537fa164b6fef261141797bbe4014526e", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -478,8 +494,8 @@ func TestProcessSearch(t *testing.T) { t.Fatalf("expected %d processes, got %d", len(processIds), len(list)) } - list, err = idx.ProcessList(eidTest, 0, 100, - "0c6ca22d2c175a1fbdd15d7595ae532bb1094b5", 0, 0, "ENDED", false) + list, _, err = idx.ProcessList(100, 0, hex.EncodeToString(eidTest), + "0c6ca22d2c175a1fbdd15d7595ae532bb1094b5", 0, 0, models.ProcessStatus_ENDED, nil, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -487,9 +503,18 @@ func TestProcessSearch(t *testing.T) { t.Fatalf("expected %d processes, got %d", len(endedPIDs), len(list)) } + // Partial process search as uppercase hex + list, _, err = idx.ProcessList(10, 0, hex.EncodeToString(eidTest), "011D50537FA164B6FEF261141797BBE4014526E", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, list, qt.HasLen, len(processIds)) + // Partial process search as mixed case hex + list, _, err = idx.ProcessList(10, 0, hex.EncodeToString(eidTest), "011D50537fA164B6FeF261141797BbE4014526E", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, list, qt.HasLen, len(processIds)) + // Search with an exact Entity ID, but starting with a null byte. // This can trip up sqlite, as it assumes TEXT strings are NUL-terminated. - list, err = idx.ProcessList([]byte("\x00foobar"), 0, 100, "", 0, 0, "", false) + list, _, err = idx.ProcessList(100, 0, "\x00foobar", "", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -498,12 +523,12 @@ func TestProcessSearch(t *testing.T) { } // list all processes, with a max of 10 - list, err = idx.ProcessList(nil, 0, 10, "", 0, 0, "", false) + list, _, err = idx.ProcessList(10, 0, "", "", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) qt.Assert(t, err, qt.IsNil) qt.Assert(t, list, qt.HasLen, 10) // list all processes, with a max of 1000 - list, err = idx.ProcessList(nil, 0, 1000, "", 0, 0, "", false) + list, _, err = idx.ProcessList(1000, 0, "", "", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) qt.Assert(t, err, qt.IsNil) qt.Assert(t, list, qt.HasLen, 21) } @@ -552,25 +577,25 @@ func TestProcessListWithNamespaceAndStatus(t *testing.T) { app.AdvanceTestBlock() // Get the process list for namespace 123 - list, err := idx.ProcessList(eid20, 0, 100, "", 123, 0, "", false) + list, _, err := idx.ProcessList(100, 0, hex.EncodeToString(eid20), "", 123, 0, 0, nil, nil, nil, nil, nil, nil, nil) qt.Assert(t, err, qt.IsNil) // Check there are exactly 10 qt.Assert(t, len(list), qt.CmpEquals(), 10) // Get the process list for all namespaces - list, err = idx.ProcessList(nil, 0, 100, "", 0, 0, "", false) + list, _, err = idx.ProcessList(100, 0, "", "", 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) qt.Assert(t, err, qt.IsNil) // Check there are exactly 10 + 10 qt.Assert(t, len(list), qt.CmpEquals(), 20) // Get the process list for namespace 10 - list, err = idx.ProcessList(nil, 0, 100, "", 10, 0, "", false) + list, _, err = idx.ProcessList(100, 0, "", "", 10, 0, 0, nil, nil, nil, nil, nil, nil, nil) qt.Assert(t, err, qt.IsNil) // Check there is exactly 1 qt.Assert(t, len(list), qt.CmpEquals(), 1) // Get the process list for namespace 10 - list, err = idx.ProcessList(nil, 0, 100, "", 0, 0, "READY", false) + list, _, err = idx.ProcessList(100, 0, "", "", 0, 0, models.ProcessStatus_READY, nil, nil, nil, nil, nil, nil, nil) qt.Assert(t, err, qt.IsNil) // Check there is exactly 1 qt.Assert(t, len(list), qt.CmpEquals(), 10) @@ -588,7 +613,8 @@ func TestResults(t *testing.T) { Status: models.ProcessStatus_READY, Mode: &models.ProcessMode{ AutoStart: true, - Interruptible: true}, + Interruptible: true, + }, BlockCount: 40, EncryptionPrivateKeys: make([]string, 16), EncryptionPublicKeys: make([]string, 16), @@ -627,7 +653,8 @@ func TestResults(t *testing.T) { Type: models.ProofArbo_BLAKE2B, Siblings: proofs[i], KeyType: models.ProofArbo_ADDRESS, - }}}, + }, + }}, ProcessId: pid, VotePackage: vp, Nullifier: util.RandomBytes(32), @@ -670,8 +697,8 @@ func TestResults(t *testing.T) { // Update the process app.AdvanceTestBlock() - // GetEnvelopes with a limit - envelopes, err := idx.GetEnvelopes(pid, 10, 0, "") + // VoteList with a limit + envelopes, _, err := idx.VoteList(10, 0, hex.EncodeToString(pid), "") qt.Assert(t, err, qt.IsNil) qt.Assert(t, envelopes, qt.HasLen, 10) qt.Assert(t, envelopes[0].Height, qt.Equals, uint32(30)) @@ -681,26 +708,43 @@ func TestResults(t *testing.T) { matchNullifier := fmt.Sprintf("%x", envelopes[9].Nullifier) matchHeight := envelopes[9].Height - // GetEnvelopes with a limit and offset - envelopes, err = idx.GetEnvelopes(pid, 5, 27, "") + // VoteList with a limit and offset + envelopes, _, err = idx.VoteList(5, 27, hex.EncodeToString(pid), "") qt.Assert(t, err, qt.IsNil) qt.Assert(t, envelopes, qt.HasLen, 3) qt.Assert(t, envelopes[0].Height, qt.Equals, uint32(3)) qt.Assert(t, envelopes[2].Height, qt.Equals, uint32(1)) - // GetEnvelopes without a match - envelopes, err = idx.GetEnvelopes(pid, 10, 0, fmt.Sprintf("%x", util.RandomBytes(32))) + // VoteList without a match (due to nullifier) + envelopes, _, err = idx.VoteList(10, 0, hex.EncodeToString(pid), "cafebabecafebabe") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, envelopes, qt.HasLen, 0) + + // VoteList without a match (due to processID) + envelopes, _, err = idx.VoteList(10, 0, "cafebabecafebabe", matchNullifier) qt.Assert(t, err, qt.IsNil) qt.Assert(t, envelopes, qt.HasLen, 0) - // GetEnvelopes with one match by full nullifier - envelopes, err = idx.GetEnvelopes(pid, 10, 0, matchNullifier) + // VoteList with one match by full nullifier + envelopes, _, err = idx.VoteList(10, 0, hex.EncodeToString(pid), matchNullifier) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, envelopes, qt.HasLen, 1) + qt.Assert(t, envelopes[0].Height, qt.Equals, matchHeight) + + // VoteList with one match by partial nullifier + envelopes, _, err = idx.VoteList(10, 0, hex.EncodeToString(pid), matchNullifier[:29]) qt.Assert(t, err, qt.IsNil) qt.Assert(t, envelopes, qt.HasLen, 1) qt.Assert(t, envelopes[0].Height, qt.Equals, matchHeight) - // GetEnvelopes with one match by partial nullifier - envelopes, err = idx.GetEnvelopes(pid, 10, 0, matchNullifier[:29]) + // Partial vote search as uppercase hex + envelopes, _, err = idx.VoteList(10, 0, hex.EncodeToString(pid), strings.ToUpper(matchNullifier[:29])) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, envelopes, qt.HasLen, 1) + qt.Assert(t, envelopes[0].Height, qt.Equals, matchHeight) + + // Partial processID search + envelopes, _, err = idx.VoteList(10, 0, hex.EncodeToString(pid[:29]), matchNullifier) qt.Assert(t, err, qt.IsNil) qt.Assert(t, envelopes, qt.HasLen, 1) qt.Assert(t, envelopes[0].Height, qt.Equals, matchHeight) @@ -1179,7 +1223,8 @@ func TestOverwriteVotes(t *testing.T) { Type: models.ProofArbo_BLAKE2B, Siblings: proofs[0], KeyType: models.ProofArbo_ADDRESS, - }}}, + }, + }}, ProcessId: pid, VotePackage: vp, } @@ -1268,7 +1313,8 @@ func TestOverwriteVotes(t *testing.T) { Type: models.ProofArbo_BLAKE2B, Siblings: proofs[1], KeyType: models.ProofArbo_ADDRESS, - }}}, + }, + }}, ProcessId: pid, VotePackage: vp, } @@ -1373,7 +1419,7 @@ func TestTxIndexer(t *testing.T) { } } - txs, err := idx.GetLastTransactions(15, 0) + txs, _, err := idx.SearchTransactions(15, 0, 0, "") qt.Assert(t, err, qt.IsNil) for i, tx := range txs { // Index is between 1 and totalCount. @@ -1384,7 +1430,7 @@ func TestTxIndexer(t *testing.T) { qt.Assert(t, tx.TxType, qt.Equals, "setAccount") } - txs, err = idx.GetLastTransactions(1, 5) + txs, _, err = idx.SearchTransactions(1, 5, 0, "") qt.Assert(t, err, qt.IsNil) qt.Assert(t, txs, qt.HasLen, 1) qt.Assert(t, txs[0].Index, qt.Equals, uint64(95)) @@ -1518,7 +1564,7 @@ func TestAccountsList(t *testing.T) { last := 0 for i := 0; i < int(totalAccs); i++ { - accts, err := idx.GetListAccounts(int32(last), 10) + accts, _, err := idx.AccountList(10, last, "") qt.Assert(t, err, qt.IsNil) for j, acc := range accts { @@ -1541,7 +1587,7 @@ func TestAccountsList(t *testing.T) { app.AdvanceTestBlock() // verify the updated balance and nonce - accts, err := idx.GetListAccounts(int32(0), 5) + accts, _, err := idx.AccountList(5, 0, "") qt.Assert(t, err, qt.IsNil) // the account in the position 0 must be the updated account balance due it has the major balance // indexer query has order BY balance DESC @@ -1604,22 +1650,29 @@ func TestTokenTransfers(t *testing.T) { app.AdvanceTestBlock() // acct 1 must have only one token transfer received - acc1Tokentx, err := idx.GetTokenTransfersByToAccount(keys[1].Address().Bytes(), 0, 10) + acc1Tokentx, _, err := idx.TokenTransfersList(10, 0, "", "", hex.EncodeToString(keys[1].Address().Bytes())) qt.Assert(t, err, qt.IsNil) qt.Assert(t, len(acc1Tokentx), qt.Equals, 1) qt.Assert(t, acc1Tokentx[0].Amount, qt.Equals, uint64(5)) // acct 2 must two token transfers received - acc2Tokentx, err := idx.GetTokenTransfersByToAccount(keys[2].Address().Bytes(), 0, 10) + acc2Tokentx, _, err := idx.TokenTransfersList(10, 0, "", "", hex.EncodeToString(keys[2].Address().Bytes())) qt.Assert(t, err, qt.IsNil) qt.Assert(t, len(acc2Tokentx), qt.Equals, 2) qt.Assert(t, acc2Tokentx[0].Amount, qt.Equals, uint64(95)) qt.Assert(t, acc2Tokentx[1].Amount, qt.Equals, uint64(18)) // acct 0 must zero token transfers received - acc0Tokentx, err := idx.GetTokenTransfersByToAccount(keys[0].Address().Bytes(), 0, 10) + acc0Tokentx, _, err := idx.TokenTransfersList(10, 0, "", "", hex.EncodeToString(keys[0].Address().Bytes())) qt.Assert(t, err, qt.IsNil) qt.Assert(t, len(acc0Tokentx), qt.Equals, 0) + + // acct 1 must have two token transfer received or sent + acc1TokentxFromOrTo, _, err := idx.TokenTransfersList(10, 0, hex.EncodeToString(keys[1].Address().Bytes()), "", "") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, len(acc1TokentxFromOrTo), qt.Equals, 2) + qt.Assert(t, acc1TokentxFromOrTo[0].Amount, qt.Equals, uint64(5)) + qt.Assert(t, acc1TokentxFromOrTo[1].Amount, qt.Equals, uint64(95)) } // friendlyResults translates votes into a matrix of strings diff --git a/vochain/indexer/indexertypes/types.go b/vochain/indexer/indexertypes/types.go index 31ba97d73..d0d2b29aa 100644 --- a/vochain/indexer/indexertypes/types.go +++ b/vochain/indexer/indexertypes/types.go @@ -209,7 +209,7 @@ type TokenTransferMeta struct { Amount uint64 `json:"amount"` From types.AccountID `json:"from"` Height uint64 `json:"height"` - TxHash types.Hash `json:"txHash"` + TxHash types.HexBytes `json:"txHash"` Timestamp time.Time `json:"timestamp"` To types.AccountID `json:"to"` } @@ -236,3 +236,8 @@ type TokenTransfersAccount struct { Received []*TokenTransferMeta `json:"received"` Sent []*TokenTransferMeta `json:"sent"` } + +type Entity struct { + EntityID types.EntityID + ProcessCount int64 +} diff --git a/vochain/indexer/process.go b/vochain/indexer/process.go index a20b0aa64..3a33fee32 100644 --- a/vochain/indexer/process.go +++ b/vochain/indexer/process.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "strings" "time" "go.vocdoni.io/proto/build/go/models" @@ -44,42 +45,64 @@ func (idx *Indexer) ProcessInfo(pid []byte) (*indexertypes.Process, error) { } // ProcessList returns a list of process identifiers (PIDs) registered in the Vochain. -// EntityID, searchTerm, namespace, status, and withResults are optional filters, if -// declared as zero-values will be ignored. SearchTerm is a partial or full PID. +// all args (entityID, processID, etc) are optional filters, if +// declared as zero-values will be ignored. entityID and processID are partial or full hex strings. // Status is one of READY, CANCELED, ENDED, PAUSED, RESULTS -func (idx *Indexer) ProcessList(entityID []byte, from, max int, searchTerm string, namespace uint32, - srcNetworkId int32, status string, withResults bool) ([][]byte, error) { - if from < 0 { - return nil, fmt.Errorf("processList: invalid value: from is invalid value %d", from) - } - // For filtering on Status we use a badgerhold match function. - // If status is not defined, then the match function will return always true. - statusnum := int32(0) - statusfound := false - if status != "" { - if statusnum, statusfound = models.ProcessStatus_value[status]; !statusfound { - return nil, fmt.Errorf("processList: status %s is unknown", status) - } +func (idx *Indexer) ProcessList(limit, offset int, entityID string, processID string, + namespace uint32, srcNetworkID int32, status models.ProcessStatus, + withResults, finalResults, manuallyEnded *bool, + startDateAfter, startDateBefore, endDateAfter, endDateBefore *time.Time, +) ([][]byte, uint64, error) { + if offset < 0 { + return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) + } + if limit <= 0 { + return nil, 0, fmt.Errorf("invalid value: limit cannot be %d", limit) } // Filter match function for source network Id - if _, ok := models.SourceNetworkId_name[srcNetworkId]; !ok { - return nil, fmt.Errorf("sourceNetworkId is unknown %d", srcNetworkId) + if _, ok := models.SourceNetworkId_name[srcNetworkID]; !ok { + return nil, 0, fmt.Errorf("sourceNetworkId is unknown %d", srcNetworkID) } - - procs, err := idx.readOnlyQuery.SearchProcesses(context.TODO(), indexerdb.SearchProcessesParams{ - EntityID: nonNullBytes(entityID), // so that LENGTH never returns NULL + results, err := idx.readOnlyQuery.SearchProcesses(context.TODO(), indexerdb.SearchProcessesParams{ + EntityIDSubstr: entityID, Namespace: int64(namespace), - Status: int64(statusnum), - SourceNetworkID: int64(srcNetworkId), - IDSubstr: searchTerm, - Offset: int64(from), - Limit: int64(max), - WithResults: withResults, + Status: int64(status), + SourceNetworkID: int64(srcNetworkID), + IDSubstr: strings.ToLower(processID), // we search in lowercase + Offset: int64(offset), + Limit: int64(limit), + HaveResults: boolToInt(withResults), + FinalResults: boolToInt(finalResults), + ManuallyEnded: boolToInt(manuallyEnded), + StartDateAfter: startDateAfter, + StartDateBefore: startDateBefore, + EndDateAfter: endDateAfter, + EndDateBefore: endDateBefore, }) if err != nil { - return nil, err + return nil, 0, err + } + list := [][]byte{} + for _, row := range results { + list = append(list, row.ID) + } + if len(results) == 0 { + return list, 0, nil } - return procs, nil + return list, uint64(results[0].TotalCount), nil +} + +// ProcessExists returns whether the passed processID exists in the db. +// If passed arg is not the full hex string, returns false (i.e. no substring matching) +func (idx *Indexer) ProcessExists(processID string) bool { + if len(processID) != 64 { + return false + } + _, count, err := idx.ProcessList(1, 0, "", processID, 0, 0, 0, nil, nil, nil, nil, nil, nil, nil) + if err != nil { + log.Errorw(err, "indexer query failed") + } + return count > 0 } // CountTotalProcesses returns the total number of processes indexed. @@ -93,19 +116,47 @@ func (idx *Indexer) CountTotalProcesses() uint64 { } // EntityList returns the list of entities indexed by the indexer -// searchTerm is optional, if declared as zero-value -// will be ignored. Searches against the ID field. -func (idx *Indexer) EntityList(max, from int, searchTerm string) []indexerdb.SearchEntitiesRow { - rows, err := idx.readOnlyQuery.SearchEntities(context.TODO(), indexerdb.SearchEntitiesParams{ - EntityIDSubstr: searchTerm, - Offset: int64(from), - Limit: int64(max), +// entityID is optional, if declared as zero-value +// will be ignored. Searches against the entityID field as lowercase hex. +func (idx *Indexer) EntityList(limit, offset int, entityID string) ([]indexertypes.Entity, uint64, error) { + if offset < 0 { + return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) + } + if limit <= 0 { + return nil, 0, fmt.Errorf("invalid value: limit cannot be %d", limit) + } + results, err := idx.readOnlyQuery.SearchEntities(context.TODO(), indexerdb.SearchEntitiesParams{ + EntityIDSubstr: strings.ToLower(entityID), // we search in lowercase + Offset: int64(offset), + Limit: int64(limit), }) if err != nil { - log.Errorf("error listing entities: %v", err) - return nil + return nil, 0, err + } + list := []indexertypes.Entity{} + for _, row := range results { + list = append(list, indexertypes.Entity{ + EntityID: row.EntityID, + ProcessCount: row.ProcessCount, + }) + } + if len(results) == 0 { + return list, 0, nil + } + return list, uint64(results[0].TotalCount), nil +} + +// EntityExists returns whether the passed entityID exists in the db. +// If passed arg is not the full hex string, returns false (i.e. no substring matching) +func (idx *Indexer) EntityExists(entityID string) bool { + if len(entityID) != 40 { + return false + } + _, count, err := idx.EntityList(1, 0, entityID) + if err != nil { + log.Errorw(err, "indexer query failed") } - return rows + return count > 0 } // CountTotalEntities return the total number of entities indexed by the indexer @@ -123,6 +174,17 @@ func isOpenProcess(process *indexertypes.Process) bool { return !process.Envelope.EncryptedVotes } +// boolToInt returns -1 if pointer is nil, or 1 for true and 0 for false +func boolToInt(b *bool) int { + if b == nil { + return -1 + } + if *b { + return 1 + } + return 0 +} + // newEmptyProcess creates a new empty process and stores it into the database. // The process must exist on the Vochain state, else an error is returned. func (idx *Indexer) newEmptyProcess(pid []byte) error { @@ -209,6 +271,7 @@ func (idx *Indexer) updateProcess(ctx context.Context, queries *indexerdb.Querie Metadata: p.GetMetadata(), Status: int64(p.Status), MaxCensusSize: int64(p.GetMaxCensusSize()), + EndDate: time.Unix(int64(p.StartTime+p.Duration), 0), }); err != nil { return err } diff --git a/vochain/indexer/queries/account.sql b/vochain/indexer/queries/account.sql index e459ff375..c4e570cea 100644 --- a/vochain/indexer/queries/account.sql +++ b/vochain/indexer/queries/account.sql @@ -1,15 +1,26 @@ -- name: CreateAccount :execresult REPLACE INTO accounts ( account, balance, nonce -) VALUES (?, ?, ?) -; +) VALUES (?, ?, ?); --- name: GetListAccounts :many -SELECT * -FROM accounts +-- name: SearchAccounts :many +WITH results AS ( + SELECT * + FROM accounts + WHERE ( + ( + sqlc.arg(account_id_substr) = '' + OR (LENGTH(sqlc.arg(account_id_substr)) = 40 AND LOWER(HEX(account)) = LOWER(sqlc.arg(account_id_substr))) + OR (LENGTH(sqlc.arg(account_id_substr)) < 40 AND INSTR(LOWER(HEX(account)), LOWER(sqlc.arg(account_id_substr))) > 0) + -- TODO: consider keeping an account_hex column for faster searches + ) + ) +) +SELECT *, COUNT(*) OVER() AS total_count +FROM results ORDER BY balance DESC -LIMIT ? OFFSET ? -; +LIMIT sqlc.arg(limit) +OFFSET sqlc.arg(offset); -- name: CountAccounts :one SELECT COUNT(*) FROM accounts; \ No newline at end of file diff --git a/vochain/indexer/queries/processes.sql b/vochain/indexer/queries/processes.sql index 8c62c1045..afaca4e40 100644 --- a/vochain/indexer/queries/processes.sql +++ b/vochain/indexer/queries/processes.sql @@ -31,18 +31,54 @@ WHERE id = ? LIMIT 1; -- name: SearchProcesses :many -SELECT id FROM processes -WHERE (LENGTH(sqlc.arg(entity_id)) = 0 OR entity_id = sqlc.arg(entity_id)) - AND (sqlc.arg(namespace) = 0 OR namespace = sqlc.arg(namespace)) - AND (sqlc.arg(status) = 0 OR status = sqlc.arg(status)) - AND (sqlc.arg(source_network_id) = 0 OR source_network_id = sqlc.arg(source_network_id)) - -- TODO(mvdan): consider keeping an id_hex column for faster searches - AND (sqlc.arg(id_substr) = '' OR (INSTR(LOWER(HEX(id)), sqlc.arg(id_substr)) > 0)) - AND (sqlc.arg(with_results) = FALSE OR have_results) +WITH results AS ( + SELECT *, + COUNT(*) OVER() AS total_count + FROM processes + WHERE ( + LENGTH(sqlc.arg(entity_id_substr)) <= 40 -- if passed arg is longer, then just abort the query + AND ( + sqlc.arg(entity_id_substr) = '' + OR (LENGTH(sqlc.arg(entity_id_substr)) = 40 AND LOWER(HEX(entity_id)) = LOWER(sqlc.arg(entity_id_substr))) + OR (LENGTH(sqlc.arg(entity_id_substr)) < 40 AND INSTR(LOWER(HEX(entity_id)), LOWER(sqlc.arg(entity_id_substr))) > 0) + -- TODO: consider keeping an entity_id_hex column for faster searches + ) + AND (sqlc.arg(namespace) = 0 OR namespace = sqlc.arg(namespace)) + AND (sqlc.arg(status) = 0 OR status = sqlc.arg(status)) + AND (sqlc.arg(source_network_id) = 0 OR source_network_id = sqlc.arg(source_network_id)) + AND LENGTH(sqlc.arg(id_substr)) <= 64 -- if passed arg is longer, then just abort the query + AND ( + sqlc.arg(id_substr) = '' + OR (LENGTH(sqlc.arg(id_substr)) = 64 AND LOWER(HEX(id)) = LOWER(sqlc.arg(id_substr))) + OR (LENGTH(sqlc.arg(id_substr)) < 64 AND INSTR(LOWER(HEX(id)), LOWER(sqlc.arg(id_substr))) > 0) + -- TODO: consider keeping an id_hex column for faster searches + ) + AND ( + sqlc.arg(have_results) = -1 + OR (sqlc.arg(have_results) = 1 AND have_results = TRUE) + OR (sqlc.arg(have_results) = 0 AND have_results = FALSE) + ) + AND ( + sqlc.arg(final_results) = -1 + OR (sqlc.arg(final_results) = 1 AND final_results = TRUE) + OR (sqlc.arg(final_results) = 0 AND final_results = FALSE) + ) + AND ( + sqlc.arg(manually_ended) = -1 + OR (sqlc.arg(manually_ended) = 1 AND manually_ended = TRUE) + OR (sqlc.arg(manually_ended) = 0 AND manually_ended = FALSE) + ) + AND (sqlc.arg(start_date_after) IS NULL OR start_date >= sqlc.arg(start_date_after)) + AND (sqlc.arg(start_date_before) IS NULL OR start_date <= sqlc.arg(start_date_before)) + AND (sqlc.arg(end_date_after) IS NULL OR end_date >= sqlc.arg(end_date_after)) + AND (sqlc.arg(end_date_before) IS NULL OR end_date <= sqlc.arg(end_date_before)) + ) +) +SELECT id, total_count +FROM results ORDER BY creation_time DESC, id ASC LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; +OFFSET sqlc.arg(offset); -- name: UpdateProcessFromState :execresult UPDATE processes @@ -52,7 +88,8 @@ SET census_root = sqlc.arg(census_root), public_keys = sqlc.arg(public_keys), metadata = sqlc.arg(metadata), status = sqlc.arg(status), - max_census_size = sqlc.arg(max_census_size) + max_census_size = sqlc.arg(max_census_size), + end_date = sqlc.arg(end_date) WHERE id = sqlc.arg(id); -- name: GetProcessStatus :one @@ -95,13 +132,19 @@ SELECT COUNT(*) FROM processes; SELECT COUNT(DISTINCT entity_id) FROM processes; -- name: SearchEntities :many -SELECT entity_id, COUNT(id) AS process_count FROM processes -WHERE (sqlc.arg(entity_id_substr) = '' OR (INSTR(LOWER(HEX(entity_id)), sqlc.arg(entity_id_substr)) > 0)) +WITH results AS ( + SELECT * + FROM processes + WHERE (sqlc.arg(entity_id_substr) = '' OR (INSTR(LOWER(HEX(entity_id)), sqlc.arg(entity_id_substr)) > 0)) +) +SELECT entity_id, + COUNT(id) AS process_count, + COUNT(entity_id) OVER() AS total_count +FROM results GROUP BY entity_id ORDER BY creation_time DESC, id ASC LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; +OFFSET sqlc.arg(offset); -- name: GetProcessIDsByFinalResults :many SELECT id FROM processes diff --git a/vochain/indexer/queries/token_fees.sql b/vochain/indexer/queries/token_fees.sql index f4876c973..2a46393db 100644 --- a/vochain/indexer/queries/token_fees.sql +++ b/vochain/indexer/queries/token_fees.sql @@ -7,33 +7,18 @@ INSERT INTO token_fees ( ?, ?, ? ); --- name: GetTokenFees :many -SELECT * FROM token_fees +-- name: SearchTokenFees :many +WITH results AS ( + SELECT * + FROM token_fees + WHERE ( + (sqlc.arg(from_account) = '' OR LOWER(HEX(from_account)) = LOWER(sqlc.arg(from_account))) + AND (sqlc.arg(tx_type) = '' OR LOWER(tx_type) = LOWER(sqlc.arg(tx_type))) + AND (sqlc.arg(reference) = '' OR LOWER(reference) = LOWER(sqlc.arg(reference))) + ) +) +SELECT *, COUNT(*) OVER() AS total_count +FROM results ORDER BY spend_time DESC LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; - --- name: GetTokenFeesByFromAccount :many -SELECT * FROM token_fees -WHERE from_account = sqlc.arg(from_account) -ORDER BY spend_time DESC -LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; - --- name: GetTokenFeesByTxType :many -SELECT * FROM token_fees -WHERE tx_type = sqlc.arg(tx_type) -ORDER BY spend_time DESC -LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; - --- name: GetTokenFeesByReference :many -SELECT * FROM token_fees -WHERE reference = sqlc.arg(reference) -ORDER BY spend_time DESC -LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; +OFFSET sqlc.arg(offset); diff --git a/vochain/indexer/queries/token_transfers.sql b/vochain/indexer/queries/token_transfers.sql index 345e82dc7..fdc6cee08 100644 --- a/vochain/indexer/queries/token_transfers.sql +++ b/vochain/indexer/queries/token_transfers.sql @@ -12,22 +12,24 @@ SELECT * FROM token_transfers WHERE tx_hash = ? LIMIT 1; --- name: GetTokenTransfersByFromAccount :many -SELECT * FROM token_transfers -WHERE from_account = sqlc.arg(from_account) -ORDER BY transfer_time DESC -LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; - --- name: GetTokenTransfersByToAccount :many -SELECT * FROM token_transfers -WHERE to_account = sqlc.arg(to_account) +-- name: SearchTokenTransfers :many +WITH results AS ( + SELECT * + FROM token_transfers + WHERE ( + (sqlc.arg(from_or_to_account) = '' OR ( + LOWER(HEX(from_account)) = LOWER(sqlc.arg(from_or_to_account)) + OR LOWER(HEX(to_account)) = LOWER(sqlc.arg(from_or_to_account)) + )) + AND (sqlc.arg(from_account) = '' OR LOWER(HEX(from_account)) = LOWER(sqlc.arg(from_account))) + AND (sqlc.arg(to_account) = '' OR LOWER(HEX(to_account)) = LOWER(sqlc.arg(to_account))) + ) +) +SELECT *, COUNT(*) OVER() AS total_count +FROM results ORDER BY transfer_time DESC LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; - +OFFSET sqlc.arg(offset); -- name: CountTokenTransfersByAccount :one SELECT COUNT(*) FROM token_transfers diff --git a/vochain/indexer/queries/transactions.sql b/vochain/indexer/queries/transactions.sql index 5b5ec4698..0e625a197 100644 --- a/vochain/indexer/queries/transactions.sql +++ b/vochain/indexer/queries/transactions.sql @@ -15,13 +15,6 @@ SELECT * FROM transactions WHERE hash = ? LIMIT 1; --- name: GetLastTransactions :many -SELECT * FROM transactions -ORDER BY id DESC -LIMIT ? -OFFSET ? -; - -- name: CountTransactions :one SELECT COUNT(*) FROM transactions; @@ -30,3 +23,17 @@ SELECT * FROM transactions WHERE block_height = ? AND block_index = ? LIMIT 1; +-- name: SearchTransactions :many +WITH results AS ( + SELECT * + FROM transactions + WHERE ( + (sqlc.arg(block_height) = 0 OR block_height = sqlc.arg(block_height)) + AND (sqlc.arg(tx_type) = '' OR LOWER(type) = LOWER(sqlc.arg(tx_type))) + ) +) +SELECT *, COUNT(*) OVER() AS total_count +FROM results +ORDER BY id DESC +LIMIT sqlc.arg(limit) +OFFSET sqlc.arg(offset); diff --git a/vochain/indexer/queries/votes.sql b/vochain/indexer/queries/votes.sql index a26c64ad1..9e92afc7d 100644 --- a/vochain/indexer/queries/votes.sql +++ b/vochain/indexer/queries/votes.sql @@ -23,13 +23,31 @@ LIMIT 1; SELECT COUNT(*) FROM votes; -- name: SearchVotes :many -SELECT v.*, t.hash FROM votes AS v -LEFT JOIN transactions AS t - ON v.block_height = t.block_height - AND v.block_index = t.block_index -WHERE (sqlc.arg(process_id) = '' OR process_id = sqlc.arg(process_id)) - AND (sqlc.arg(nullifier_substr) = '' OR (INSTR(LOWER(HEX(nullifier)), sqlc.arg(nullifier_substr)) > 0)) -ORDER BY v.block_height DESC, v.nullifier ASC +WITH results AS ( + SELECT v.*, t.hash + FROM votes AS v + LEFT JOIN transactions AS t + ON v.block_height = t.block_height + AND v.block_index = t.block_index + WHERE ( + LENGTH(sqlc.arg(process_id_substr)) <= 64 -- if passed arg is longer, then just abort the query + AND ( + sqlc.arg(process_id_substr) = '' + OR (LENGTH(sqlc.arg(process_id_substr)) = 64 AND LOWER(HEX(process_id)) = LOWER(sqlc.arg(process_id_substr))) + OR (LENGTH(sqlc.arg(process_id_substr)) < 64 AND INSTR(LOWER(HEX(process_id)), LOWER(sqlc.arg(process_id_substr))) > 0) + -- TODO: consider keeping an process_id_hex column for faster searches + ) + AND LENGTH(sqlc.arg(nullifier_substr)) <= 64 -- if passed arg is longer, then just abort the query + AND ( + sqlc.arg(nullifier_substr) = '' + OR (LENGTH(sqlc.arg(nullifier_substr)) = 64 AND LOWER(HEX(nullifier)) = LOWER(sqlc.arg(nullifier_substr))) + OR (LENGTH(sqlc.arg(nullifier_substr)) < 64 AND INSTR(LOWER(HEX(nullifier)), LOWER(sqlc.arg(nullifier_substr))) > 0) + -- TODO: consider keeping an nullifier_hex column for faster searches + ) + ) +) +SELECT *, COUNT(*) OVER() AS total_count +FROM results +ORDER BY block_height DESC, nullifier ASC LIMIT sqlc.arg(limit) -OFFSET sqlc.arg(offset) -; +OFFSET sqlc.arg(offset); diff --git a/vochain/indexer/transaction.go b/vochain/indexer/transaction.go index 5d8097886..4b8c9fe21 100644 --- a/vochain/indexer/transaction.go +++ b/vochain/indexer/transaction.go @@ -61,24 +61,39 @@ func (idx *Indexer) GetTxHashReference(hash types.HexBytes) (*indexertypes.Trans return indexertypes.TransactionFromDB(&sqlTxRef), nil } -// GetLastTransactions fetches a number of the latest indexed transactions. +// SearchTransactions returns the list of transactions indexed. +// height and txType are optional, if declared as zero-value will be ignored. // The first one returned is the newest, so they are in descending order. -func (idx *Indexer) GetLastTransactions(limit, offset int32) ([]*indexertypes.Transaction, error) { - sqlTxRefs, err := idx.readOnlyQuery.GetLastTransactions(context.TODO(), indexerdb.GetLastTransactionsParams{ - Limit: int64(limit), - Offset: int64(offset), +func (idx *Indexer) SearchTransactions(limit, offset int, blockHeight uint64, txType string) ([]*indexertypes.Transaction, uint64, error) { + if offset < 0 { + return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) + } + if limit <= 0 { + return nil, 0, fmt.Errorf("invalid value: limit cannot be %d", limit) + } + results, err := idx.readOnlyQuery.SearchTransactions(context.TODO(), indexerdb.SearchTransactionsParams{ + Limit: int64(limit), + Offset: int64(offset), + BlockHeight: blockHeight, + TxType: txType, }) - if err != nil || len(sqlTxRefs) == 0 { - if errors.Is(err, sql.ErrNoRows) || len(sqlTxRefs) == 0 { - return nil, ErrTransactionNotFound - } - return nil, fmt.Errorf("could not get last %d tx refs: %v", limit, err) + if err != nil { + return nil, 0, err + } + list := []*indexertypes.Transaction{} + for _, row := range results { + list = append(list, &indexertypes.Transaction{ + Index: uint64(row.ID), + Hash: row.Hash, + BlockHeight: uint32(row.BlockHeight), + TxBlockIndex: int32(row.BlockIndex), + TxType: row.Type, + }) } - txRefs := make([]*indexertypes.Transaction, len(sqlTxRefs)) - for i, sqlTxRef := range sqlTxRefs { - txRefs[i] = indexertypes.TransactionFromDB(&sqlTxRef) + if len(results) == 0 { + return list, 0, nil } - return txRefs, nil + return list, uint64(results[0].TotalCount), nil } func (idx *Indexer) OnNewTx(tx *vochaintx.Tx, blockHeight uint32, txIndex int32) { diff --git a/vochain/indexer/vote.go b/vochain/indexer/vote.go index 919c60dc8..ca66e59d3 100644 --- a/vochain/indexer/vote.go +++ b/vochain/indexer/vote.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "math/big" + "strings" "time" "go.vocdoni.io/proto/build/go/models" @@ -58,30 +59,26 @@ func (idx *Indexer) GetEnvelope(nullifier []byte) (*indexertypes.EnvelopePackage return envelopePackage, nil } -// GetEnvelopes retrieves all envelope metadata for a ProcessId. -// Returns ErrVoteNotFound if the envelope reference is not found. -func (idx *Indexer) GetEnvelopes(processId []byte, max, from int, - searchTerm string) ([]*indexertypes.EnvelopeMetadata, error) { - if from < 0 { - return nil, fmt.Errorf("GetEnvelopes: invalid value: from is invalid value %d", from) - } - if max <= 0 { - return nil, fmt.Errorf("GetEnvelopes: invalid value: max is invalid value %d", max) - } - envelopes := []*indexertypes.EnvelopeMetadata{} - txRefs, err := idx.readOnlyQuery.SearchVotes(context.TODO(), indexerdb.SearchVotesParams{ - ProcessID: processId, - NullifierSubstr: searchTerm, - Limit: int64(max), - Offset: int64(from), +// VoteList retrieves all envelope metadata for a processID and nullifier (both args do partial or full string match). +func (idx *Indexer) VoteList(limit, offset int, processID string, nullifier string, +) ([]*indexertypes.EnvelopeMetadata, uint64, error) { + if offset < 0 { + return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) + } + if limit <= 0 { + return nil, 0, fmt.Errorf("invalid value: limit cannot be %d", limit) + } + results, err := idx.readOnlyQuery.SearchVotes(context.TODO(), indexerdb.SearchVotesParams{ + ProcessIDSubstr: processID, + NullifierSubstr: strings.ToLower(nullifier), // we search in lowercase + Limit: int64(limit), + Offset: int64(offset), }) if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, ErrVoteNotFound - } - return nil, err + return nil, 0, err } - for _, txRef := range txRefs { + list := []*indexertypes.EnvelopeMetadata{} + for _, txRef := range results { envelopeMetadata := &indexertypes.EnvelopeMetadata{ ProcessId: txRef.ProcessID, Nullifier: txRef.Nullifier, @@ -90,12 +87,14 @@ func (idx *Indexer) GetEnvelopes(processId []byte, max, from int, TxHash: txRef.Hash, } if len(txRef.VoterID) > 0 { - envelopeMetadata.VoterID = txRef.VoterID.Address() + envelopeMetadata.VoterID = state.VoterID(txRef.VoterID).Address() } - envelopes = append(envelopes, envelopeMetadata) + list = append(list, envelopeMetadata) } - return envelopes, nil - + if len(results) == 0 { + return list, 0, nil + } + return list, uint64(results[0].TotalCount), nil } // CountTotalVotes returns the total number of envelopes. diff --git a/vochain/keykeeper/keykeeper.go b/vochain/keykeeper/keykeeper.go index f9d07eb72..7f46f654d 100644 --- a/vochain/keykeeper/keykeeper.go +++ b/vochain/keykeeper/keykeeper.go @@ -276,3 +276,6 @@ func (*KeyKeeper) OnCensusUpdate(_, _ []byte, _ string, _ uint64) {} // OnCancel does nothing func (k *KeyKeeper) OnCancel(_ []byte, _ int32) {} + +// OnProcessDurationChange does nothing +func (k *KeyKeeper) OnProcessDurationChange(_ []byte, _ uint32, _ int32) {} diff --git a/vochain/offchaindatahandler/offchaindatahandler.go b/vochain/offchaindatahandler/offchaindatahandler.go index c58480aa7..d6e97bd9b 100644 --- a/vochain/offchaindatahandler/offchaindatahandler.go +++ b/vochain/offchaindatahandler/offchaindatahandler.go @@ -53,7 +53,8 @@ type OffChainDataHandler struct { // NewOffChainDataHandler creates a new instance of the off chain data downloader daemon. // It will subscribe to Vochain events and perform data import. func NewOffChainDataHandler(v *vochain.BaseApplication, d *downloader.Downloader, - c *censusdb.CensusDB, importOnlyNew bool) *OffChainDataHandler { + c *censusdb.CensusDB, importOnlyNew bool, +) *OffChainDataHandler { od := OffChainDataHandler{ vochain: v, census: c, @@ -138,7 +139,6 @@ func (d *OffChainDataHandler) OnCensusUpdate(pid, censusRoot []byte, censusURI s itemType: itemTypeExternalCensus, }) } - } // OnProcessesStart is triggered when a process starts. Does nothing. @@ -173,3 +173,4 @@ func (*OffChainDataHandler) OnProcessStatusChange(_ []byte, _ models.ProcessStat func (*OffChainDataHandler) OnTransferTokens(_ *vochaintx.TokenTransfer) {} func (*OffChainDataHandler) OnProcessResults(_ []byte, _ *models.ProcessResult, _ int32) {} func (*OffChainDataHandler) OnSpendTokens(_ []byte, _ models.TxType, _ uint64, _ string) {} +func (*OffChainDataHandler) OnProcessDurationChange(_ []byte, _ uint32, _ int32) {} diff --git a/vochain/process_test.go b/vochain/process_test.go index 357a34692..9370a01b6 100644 --- a/vochain/process_test.go +++ b/vochain/process_test.go @@ -323,7 +323,8 @@ func TestProcessSetCensusCheckTxDeliverTxCommitTransitions(t *testing.T) { } func testSetProcessCensus(t *testing.T, pid []byte, txSender *ethereum.SignKeys, - app *BaseApplication, censusRoot []byte, censusURI *string, censusSize uint64) error { + app *BaseApplication, censusRoot []byte, censusURI *string, censusSize uint64, +) error { var stx models.SignedTx var err error @@ -351,6 +352,34 @@ func testSetProcessCensus(t *testing.T, pid []byte, txSender *ethereum.SignKeys, return err } +func testSetProcessDuration(t *testing.T, pid []byte, txSender *ethereum.SignKeys, + app *BaseApplication, duration uint32, +) error { + var stx models.SignedTx + var err error + + txSenderAcc, err := app.State.GetAccount(txSender.Address(), false) + if err != nil { + return fmt.Errorf("cannot get tx sender account %s with error %w", txSender.Address(), err) + } + + tx := &models.SetProcessTx{ + Txtype: models.TxType_SET_PROCESS_DURATION, + Nonce: txSenderAcc.Nonce, + ProcessId: pid, + Duration: &duration, + } + if stx.Tx, err = proto.Marshal(&models.Tx{Payload: &models.Tx_SetProcess{SetProcess: tx}}); err != nil { + return fmt.Errorf("cannot mashal tx %w", err) + } + if stx.Signature, err = txSender.SignVocdoniTx(stx.Tx, app.chainID); err != nil { + return fmt.Errorf("cannot sign tx %+v with error %w", tx, err) + } + + _, err = testCheckTxDeliverTxCommit(t, app, &stx) + return err +} + func TestCount(t *testing.T) { app := TestBaseApplication(t) count, err := app.State.CountProcesses(false) @@ -367,7 +396,8 @@ func TestCount(t *testing.T) { // the application will have the accounts of the keys already initialized, as well as // the burn account and all tx costs set to txCostNumber func createTestBaseApplicationAndAccounts(t *testing.T, - txCostNumber uint64) (*BaseApplication, []*ethereum.SignKeys) { + txCostNumber uint64, +) (*BaseApplication, []*ethereum.SignKeys) { app := TestBaseApplication(t) keys := make([]*ethereum.SignKeys, 0) for i := 0; i < 4; i++ { @@ -401,9 +431,11 @@ func createTestBaseApplicationAndAccounts(t *testing.T, // set tx costs for _, cost := range genesis.TxCostNameToTxTypeMap { qt.Assert(t, app.State.SetTxBaseCost(cost, txCostNumber), qt.IsNil) - } + app.State.ElectionPriceCalc.SetBasePrice(10) + app.State.ElectionPriceCalc.SetCapacity(2000) testCommitState(t, app) + return app, keys } @@ -472,7 +504,7 @@ func testCheckTxDeliverTxCommit(t *testing.T, app *BaseApplication, stx *models. func TestGlobalMaxProcessSize(t *testing.T) { app, accounts := createTestBaseApplicationAndAccounts(t, 10) - app.State.SetMaxProcessSize(10) + qt.Assert(t, app.State.SetMaxProcessSize(10), qt.IsNil) app.AdvanceTestBlock() // define process @@ -487,22 +519,20 @@ func TestGlobalMaxProcessSize(t *testing.T) { CensusRoot: util.RandomBytes(32), CensusURI: &censusURI, CensusOrigin: models.CensusOrigin_OFF_CHAIN_TREE, - BlockCount: 1024, - MaxCensusSize: 20, + Duration: 60, + MaxCensusSize: 5, } - // create process with entityID (should fail) - qt.Assert(t, testCreateProcess(t, accounts[0], app, process), qt.IsNil) + // create process with maxcensussize < 10 (should work) + qt.Assert(t, testCreateProcessWithErr(t, accounts[0], app, process), qt.IsNil) - // create process with entityID (should work) - process.MaxCensusSize = 5 - qt.Assert(t, testCreateProcess(t, accounts[0], app, process), qt.IsNotNil) + // create process with maxcensussize > 10 (should fail) + process.MaxCensusSize = 20 + qt.Assert(t, testCreateProcessWithErr(t, accounts[0], app, process), qt.IsNotNil) } func TestSetProcessCensusSize(t *testing.T) { app, accounts := createTestBaseApplicationAndAccounts(t, 10) - app.State.ElectionPriceCalc.SetBasePrice(10) - app.State.ElectionPriceCalc.SetCapacity(2000) // define process censusURI := ipfsUrlTest @@ -549,13 +579,32 @@ func TestSetProcessCensusSize(t *testing.T) { qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(10)) qt.Assert(t, proc.CensusRoot, qt.IsNotNil) + // Set census size (with same root and no URI) (should work) + qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, proc.CensusRoot, nil, 12), qt.IsNil) + app.AdvanceTestBlock() + + proc, err = app.State.Process(pid, true) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(12)) + qt.Assert(t, proc.CensusRoot, qt.IsNotNil) + + // Set census size (with same root and different URI) (should fail) + uri := "ipfs://987654321" + qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, proc.CensusRoot, &uri, 13), qt.IsNotNil) + app.AdvanceTestBlock() + + proc, err = app.State.Process(pid, true) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(12)) + qt.Assert(t, proc.CensusRoot, qt.IsNotNil) + // Set smaller census size (should fail) qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, nil, nil, 5), qt.IsNotNil) app.AdvanceTestBlock() proc, err = app.State.Process(pid, true) qt.Assert(t, err, qt.IsNil) - qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(10)) + qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(12)) // Check cost is increased with larger census size (should work) account, err := app.State.GetAccount(accounts[0].Address(), true) @@ -570,4 +619,93 @@ func TestSetProcessCensusSize(t *testing.T) { // check that newBalance is at least 100 tokens less than oldBalance qt.Assert(t, oldBalance-newBalance >= 100, qt.IsTrue) + + // define a new process, this time with dynamicCensus=true + process = &models.Process{ + StartBlock: 0, + EnvelopeType: &models.EnvelopeType{EncryptedVotes: false}, + Mode: &models.ProcessMode{Interruptible: true, DynamicCensus: true}, + VoteOptions: &models.ProcessVoteOptions{MaxCount: 16, MaxValue: 16}, + Status: models.ProcessStatus_READY, + EntityId: accounts[0].Address().Bytes(), + CensusRoot: util.RandomBytes(32), + CensusURI: &censusURI, + CensusOrigin: models.CensusOrigin_OFF_CHAIN_TREE, + Duration: 60 * 60, + MaxCensusSize: 2, + } + + // create the process + pid = testCreateProcess(t, accounts[0], app, process) + app.AdvanceTestBlock() + + proc, err = app.State.Process(pid, true) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(2)) + + // Set census size with root (should work since dynamicCensus=true) + qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, util.RandomBytes(32), nil, 5), qt.IsNil) + app.AdvanceTestBlock() + + // Set census size with root (should work since dynamicCensus=true) + qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, util.RandomBytes(32), &uri, 5), qt.IsNil) + app.AdvanceTestBlock() +} + +func TestSetProcessDuration(t *testing.T) { + app, accounts := createTestBaseApplicationAndAccounts(t, 10) + + // define process + censusURI := ipfsUrlTest + process := &models.Process{ + StartBlock: 1, + EnvelopeType: &models.EnvelopeType{EncryptedVotes: false}, + Mode: &models.ProcessMode{Interruptible: true, DynamicCensus: false}, + VoteOptions: &models.ProcessVoteOptions{MaxCount: 16, MaxValue: 16}, + Status: models.ProcessStatus_READY, + EntityId: accounts[0].Address().Bytes(), + CensusRoot: util.RandomBytes(32), + CensusURI: &censusURI, + CensusOrigin: models.CensusOrigin_OFF_CHAIN_TREE, + Duration: 60, + MaxCensusSize: 2, + } + + // create the process + pid := testCreateProcess(t, accounts[0], app, process) + app.AdvanceTestBlock() + + proc, err := app.State.Process(pid, true) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, proc.Duration, qt.Equals, uint32(60)) + + // Set lower duration (should workd) + qt.Assert(t, testSetProcessDuration(t, pid, accounts[0], app, 50), qt.IsNil) + app.AdvanceTestBlock() + + proc, err = app.State.Process(pid, true) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, proc.Duration, qt.Equals, uint32(50)) + + // Set higher duration (should work) + qt.Assert(t, testSetProcessDuration(t, pid, accounts[0], app, 80), qt.IsNil) + app.AdvanceTestBlock() + + proc, err = app.State.Process(pid, true) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, proc.Duration, qt.Equals, uint32(80)) + + // Check cost is increased with larger duration (should work) + account, err := app.State.GetAccount(accounts[0].Address(), true) + qt.Assert(t, err, qt.IsNil) + oldBalance := account.Balance + + qt.Assert(t, testSetProcessDuration(t, pid, accounts[0], app, 2000000), qt.IsNil) + + account, err = app.State.GetAccount(accounts[0].Address(), true) + qt.Assert(t, err, qt.IsNil) + newBalance := account.Balance + + // check that newBalance is at least 30 tokens less than oldBalance + qt.Assert(t, oldBalance-newBalance >= 30, qt.IsTrue) } diff --git a/vochain/processid/process_id.go b/vochain/processid/process_id.go index 45a881a8c..2b4e1b486 100644 --- a/vochain/processid/process_id.go +++ b/vochain/processid/process_id.go @@ -104,7 +104,6 @@ func (p *ProcessID) Unmarshal(pid []byte) error { func (p *ProcessID) String() string { return fmt.Sprintf("[chID:%s/addr:%s/co:%d/et:%d/nc:%d]", p.chainID, p.organizationAddr.Hex(), p.censusOrigin, p.envType, p.nonce) - } // SetChainID sets the process blockchain identifier diff --git a/vochain/processid/process_id_test.go b/vochain/processid/process_id_test.go index 0372e04ec..810704e61 100644 --- a/vochain/processid/process_id_test.go +++ b/vochain/processid/process_id_test.go @@ -9,8 +9,10 @@ import ( "go.vocdoni.io/proto/build/go/models" ) -const test_vbAddr = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" -const test_chID = "vocdoni-bizono" +const ( + test_vbAddr = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + test_chID = "vocdoni-bizono" +) func TestProcessID(t *testing.T) { pid := ProcessID{} diff --git a/vochain/proof_erc20_test.go b/vochain/proof_erc20_test.go index 72af3d1ed..24fb5f856 100644 --- a/vochain/proof_erc20_test.go +++ b/vochain/proof_erc20_test.go @@ -3,7 +3,6 @@ package vochain import ( "context" "encoding/json" - "fmt" "testing" cometabcitypes "github.com/cometbft/cometbft/abci/types" @@ -64,7 +63,8 @@ func TestEthProof(t *testing.T) { } func testEthSendVotes(t *testing.T, s testStorageProof, - pid []byte, vp []byte, app *BaseApplication, expectedResult bool) { + pid []byte, vp []byte, app *BaseApplication, expectedResult bool, +) { cktx := new(cometabcitypes.CheckTxRequest) var cktxresp *cometabcitypes.CheckTxResponse var stx models.SignedTx @@ -116,7 +116,7 @@ func testEthSendVotes(t *testing.T, s testStorageProof, cktxresp, _ = app.CheckTx(context.Background(), cktx) if cktxresp.Code != 0 { if expectedResult { - t.Fatalf(fmt.Sprintf("checkTx failed: %s", cktxresp.Data)) + t.Fatalf("checkTx failed: %s", cktxresp.Data) } } else { if !expectedResult { @@ -131,7 +131,7 @@ func testEthSendVotes(t *testing.T, s testStorageProof, detxresp := app.deliverTx(txb) if detxresp.Code != 0 { if expectedResult { - t.Fatalf(fmt.Sprintf("deliverTx failed: %s", detxresp.Data)) + t.Fatalf("deliverTx failed: %s", detxresp.Data) } } else { if !expectedResult { diff --git a/vochain/proof_minime_test.go b/vochain/proof_minime_test.go index 7f8198363..079260710 100644 --- a/vochain/proof_minime_test.go +++ b/vochain/proof_minime_test.go @@ -3,7 +3,6 @@ package vochain import ( "context" "encoding/json" - "fmt" "testing" cometabcitypes "github.com/cometbft/cometbft/abci/types" @@ -85,7 +84,8 @@ func storageProofToModel(s *ethstorageproof.StorageResult) *models.ProofEthereum } func testMinimeSendVotes(t *testing.T, s ethstorageproof.StorageProof, addr common.Address, - pid []byte, vp []byte, app *BaseApplication, expectedResult bool) { + pid []byte, vp []byte, app *BaseApplication, expectedResult bool, +) { cktx := new(cometabcitypes.CheckTxRequest) var cktxresp *cometabcitypes.CheckTxResponse var stx models.SignedTx @@ -136,7 +136,7 @@ func testMinimeSendVotes(t *testing.T, s ethstorageproof.StorageProof, addr comm cktxresp, _ = app.CheckTx(context.Background(), cktx) if cktxresp.Code != 0 { if expectedResult { - t.Fatalf(fmt.Sprintf("checkTx failed: %s", cktxresp.Data)) + t.Fatalf("checkTx failed: %s", cktxresp.Data) } } else { if !expectedResult { @@ -150,7 +150,7 @@ func testMinimeSendVotes(t *testing.T, s ethstorageproof.StorageProof, addr comm detxresp := app.deliverTx(txb) if detxresp.Code != 0 { if expectedResult { - t.Fatalf(fmt.Sprintf("deliverTx failed: %s", detxresp.Data)) + t.Fatalf("deliverTx failed: %s", detxresp.Data) } } else { if !expectedResult { diff --git a/vochain/proof_test.go b/vochain/proof_test.go index 7f35fef40..b8163ed46 100644 --- a/vochain/proof_test.go +++ b/vochain/proof_test.go @@ -2,7 +2,6 @@ package vochain import ( "context" - "fmt" "testing" cometabcitypes "github.com/cometbft/cometbft/abci/types" @@ -195,7 +194,8 @@ func TestCSPproof(t *testing.T) { } func testCSPsendVotes(t *testing.T, pid []byte, vp []byte, signer *ethereum.SignKeys, - proof *models.ProofCA, app *BaseApplication, expectedResult bool) { + proof *models.ProofCA, app *BaseApplication, expectedResult bool, +) { cktx := new(cometabcitypes.CheckTxRequest) var cktxresp *cometabcitypes.CheckTxResponse var stx models.SignedTx @@ -224,7 +224,7 @@ func testCSPsendVotes(t *testing.T, pid []byte, vp []byte, signer *ethereum.Sign cktxresp, _ = app.CheckTx(context.Background(), cktx) if cktxresp.Code != 0 { if expectedResult { - t.Fatalf(fmt.Sprintf("checkTx failed: %s", cktxresp.Data)) + t.Fatalf("checkTx failed: %s", cktxresp.Data) } } else { if !expectedResult { @@ -236,7 +236,7 @@ func testCSPsendVotes(t *testing.T, pid []byte, vp []byte, signer *ethereum.Sign detxresp := app.deliverTx(txb) if detxresp.Code != 0 { if expectedResult { - t.Fatalf(fmt.Sprintf("deliverTx failed: %s", detxresp.Data)) + t.Fatalf("deliverTx failed: %s", detxresp.Data) } } else { if !expectedResult { diff --git a/vochain/proposal_test.go b/vochain/proposal_test.go index a17ad9edf..bf23ab04a 100644 --- a/vochain/proposal_test.go +++ b/vochain/proposal_test.go @@ -38,7 +38,8 @@ func TestTransactionsSorted(t *testing.T) { From: key.Address().Bytes(), To: keys[(i+1)%50].Address().Bytes(), Value: 1, - }}} + }}, + } txBytes, err := proto.Marshal(&tx) qt.Assert(err, quicktest.IsNil) @@ -62,7 +63,8 @@ func TestTransactionsSorted(t *testing.T) { From: key.Address().Bytes(), To: keys[(i+1)%50].Address().Bytes(), Value: 1, - }}} + }}, + } txBytes, err := proto.Marshal(&tx) qt.Assert(err, quicktest.IsNil) diff --git a/vochain/start.go b/vochain/start.go index 25f6e186e..cd7f183b6 100644 --- a/vochain/start.go +++ b/vochain/start.go @@ -83,17 +83,17 @@ func newTendermint(app *BaseApplication, localConfig *config.VochainCfg) (*comet } // consensus config - blockTime := config.DefaultMinerTargetBlockTimeSeconds + blockTime := config.DefaultMinerTargetBlockTime if localConfig.MinerTargetBlockTimeSeconds > 0 { - blockTime = localConfig.MinerTargetBlockTimeSeconds + blockTime = time.Duration(localConfig.MinerTargetBlockTimeSeconds) * time.Second } tconfig.Consensus.TimeoutProposeDelta = time.Millisecond * 200 - tconfig.Consensus.TimeoutPropose = time.Second * time.Duration(float32(blockTime)*0.6) + tconfig.Consensus.TimeoutPropose = blockTime * 6 / 10 tconfig.Consensus.TimeoutPrevoteDelta = time.Millisecond * 200 tconfig.Consensus.TimeoutPrevote = time.Second * 1 tconfig.Consensus.TimeoutPrecommitDelta = time.Millisecond * 200 tconfig.Consensus.TimeoutPrecommit = time.Second * 1 - tconfig.Consensus.TimeoutCommit = time.Second * time.Duration(blockTime) + tconfig.Consensus.TimeoutCommit = blockTime // if seed node if localConfig.IsSeedNode { @@ -304,7 +304,6 @@ func newTendermint(app *BaseApplication, localConfig *config.VochainCfg) (*comet cometnode.DefaultMetricsProvider(tconfig.Instrumentation), log.NewCometLogger("comet", tconfig.LogLevel), ) - if err != nil { return nil, fmt.Errorf("failed to create new Tendermint node: %w", err) } diff --git a/vochain/state/account.go b/vochain/state/account.go index 8d61eed6f..8bbe4264a 100644 --- a/vochain/state/account.go +++ b/vochain/state/account.go @@ -299,7 +299,8 @@ func (v *State) BurnTxCostIncrementNonce(accountAddress common.Address, txType m // SetAccountDelegate sets a set of delegates for a given account func (v *State) SetAccountDelegate(accountAddr common.Address, delegateAddrs [][]byte, - txType models.TxType) error { + txType models.TxType, +) error { acc, err := v.GetAccount(accountAddr, false) if err != nil { return err diff --git a/vochain/state/balances.go b/vochain/state/balances.go index 28c0b85b1..fac25df09 100644 --- a/vochain/state/balances.go +++ b/vochain/state/balances.go @@ -17,6 +17,7 @@ import ( var ( TxTypeCostToStateKey = map[models.TxType]string{ models.TxType_SET_PROCESS_STATUS: "c_setProcessStatus", + models.TxType_SET_PROCESS_DURATION: "c_setProcessDuration", models.TxType_SET_PROCESS_CENSUS: "c_setProcessCensus", models.TxType_SET_PROCESS_QUESTION_INDEX: "c_setProcessResults", models.TxType_REGISTER_VOTER_KEY: "c_registerKey", diff --git a/vochain/state/electionprice/electionprice_test.go b/vochain/state/electionprice/electionprice_test.go index b1bccdd4a..3d3659e8b 100644 --- a/vochain/state/electionprice/electionprice_test.go +++ b/vochain/state/electionprice/electionprice_test.go @@ -136,10 +136,14 @@ func TestCalculatorPriceTable(_ *testing.T) { c.SetBasePrice(10) hour := uint32(340) - maxCensusSizes := []uint64{100, 200, 500, 1000, 1500, 2000, 2500, 3000, 5000, 10000, 20000, - 50000, 100000, 200000, 500000, 800000, 1000000} - electionDurations := []uint32{1 * hour, 12 * hour, 24 * hour, 24 * hour * 2, 24 * hour * 5, - 24 * hour * 7, 24 * hour * 15} + maxCensusSizes := []uint64{ + 100, 200, 500, 1000, 1500, 2000, 2500, 3000, 5000, 10000, 20000, + 50000, 100000, 200000, 500000, 800000, 1000000, + } + electionDurations := []uint32{ + 1 * hour, 12 * hour, 24 * hour, 24 * hour * 2, 24 * hour * 5, + 24 * hour * 7, 24 * hour * 15, + } encryptedVotes := true anonymousVotes := true maxVoteOverwrite := uint32(0) diff --git a/vochain/state/eventlistener.go b/vochain/state/eventlistener.go index 90748aa2a..1c2d05281 100644 --- a/vochain/state/eventlistener.go +++ b/vochain/state/eventlistener.go @@ -21,6 +21,7 @@ type EventListener interface { OnNewTx(tx *vochaintx.Tx, blockHeight uint32, txIndex int32) OnProcess(process *models.Process, txIndex int32) OnProcessStatusChange(pid []byte, status models.ProcessStatus, txIndex int32) + OnProcessDurationChange(pid []byte, newDuration uint32, txIndex int32) OnCancel(pid []byte, txIndex int32) OnProcessKeys(pid []byte, encryptionPub string, txIndex int32) OnRevealKeys(pid []byte, encryptionPriv string, txIndex int32) diff --git a/vochain/state/process.go b/vochain/state/process.go index cca99c768..2c1b8113a 100644 --- a/vochain/state/process.go +++ b/vochain/state/process.go @@ -16,9 +16,7 @@ import ( "google.golang.org/protobuf/proto" ) -var ( - emptyVotesRoot = make([]byte, StateChildTreeCfg(ChildTreeVotes).HashFunc().Len()) -) +var emptyVotesRoot = make([]byte, StateChildTreeCfg(ChildTreeVotes).HashFunc().Len()) // AddProcess adds a new process to the vochain. Adding a process with a // ProcessId that already exists will return an error. @@ -94,6 +92,9 @@ func getProcess(mainTreeView statedb.TreeViewer, pid []byte) (*models.Process, e if err != nil { return nil, fmt.Errorf("cannot unmarshal process (%s): %w", pid, err) } + if process.Process == nil { + return nil, fmt.Errorf("process %x is nil", pid) + } return process.Process, nil } @@ -278,6 +279,54 @@ func (v *State) SetProcessStatus(pid []byte, newstatus models.ProcessStatus, com return nil } +// SetProcessDuration sets the duration for a given process. +// If commit is true, the change is committed to the state and the event listeners are called. +// The new duration must be greater than zero and different from the current one. If the process is +// not interruptible, the new duration must be greater than the current one. +// The process must be in READY or PAUSED status. +func (v *State) SetProcessDuration(pid []byte, newDurationSeconds uint32, commit bool) error { + process, err := v.Process(pid, false) + if err != nil { + return err + } + currentTime, err := v.Timestamp(false) + if err != nil { + return fmt.Errorf("setProcessStatus: cannot get current timestamp: %w", err) + } + + if newDurationSeconds == 0 { + return fmt.Errorf("cannot set duration to zero") + } + + if newDurationSeconds == process.Duration { + return fmt.Errorf("cannot set duration to the same value") + } + + if process.Status != models.ProcessStatus_READY && process.Status != models.ProcessStatus_PAUSED { + return fmt.Errorf("cannot set duration, invalid status: %s", process.Status) + } + + if currentTime >= process.StartTime+newDurationSeconds { + return fmt.Errorf("cannot set duration to a value that has already passed") + } + + if !process.Mode.Interruptible && newDurationSeconds < process.Duration { + return fmt.Errorf("cannot shorten duration of non-interruptible process") + } + + // If all checks pass, the transition is valid + if commit { + process.Duration = newDurationSeconds + if err := v.UpdateProcess(process, process.ProcessId); err != nil { + return err + } + for _, l := range v.eventListeners { + l.OnProcessDurationChange(process.ProcessId, newDurationSeconds, v.txCounter.Load()) + } + } + return nil +} + // SetProcessResults sets the results for a given process and calls the event listeners. func (v *State) SetProcessResults(pid []byte, result *models.ProcessResult) error { process, err := v.Process(pid, false) @@ -326,8 +375,8 @@ func (v *State) SetProcessCensus(pid, censusRoot []byte, censusURI string, censu if err != nil { return err } - // check dynamic census only if root is being updated - if censusRoot != nil { + // check dynamic census only if root or uri are being updated + if (censusRoot != nil && !bytes.Equal(process.CensusRoot, censusRoot)) || (censusURI != "" && censusURI != *process.CensusURI) { if !process.Mode.DynamicCensus { return fmt.Errorf( "cannot update census, only processes with dynamic census can update their root") @@ -361,6 +410,8 @@ func (v *State) SetProcessCensus(pid, censusRoot []byte, censusURI string, censu if commit { if censusRoot != nil { process.CensusRoot = censusRoot + } + if censusURI != "" { process.CensusURI = &censusURI } if censusSize > 0 { diff --git a/vochain/state/state_test.go b/vochain/state/state_test.go index f32f26acb..e2b76c685 100644 --- a/vochain/state/state_test.go +++ b/vochain/state/state_test.go @@ -174,7 +174,6 @@ func TestBalanceTransfer(t *testing.T) { b2, err = s.GetAccount(addr2.Address(), true) qt.Assert(t, err, qt.IsNil) qt.Assert(t, b2.Balance, qt.Equals, uint64(5)) - } type Listener struct { @@ -186,6 +185,7 @@ func (*Listener) OnNewTx(_ *vochaintx.Tx, _ uint32, _ int32) func (*Listener) OnBeginBlock(BeginBlock) {} func (*Listener) OnProcess(_ *models.Process, _ int32) {} func (*Listener) OnProcessStatusChange(_ []byte, _ models.ProcessStatus, _ int32) {} +func (*Listener) OnProcessDurationChange(_ []byte, _ uint32, _ int32) {} func (*Listener) OnCancel(_ []byte, _ int32) {} func (*Listener) OnProcessKeys(_ []byte, _ string, _ int32) {} func (*Listener) OnRevealKeys(_ []byte, _ string, _ int32) {} @@ -197,6 +197,7 @@ func (*Listener) OnSpendTokens(_ []byte, _ models.TxType, _ uint64, _ string) func (l *Listener) OnProcessesStart(pids [][]byte) { l.processStart = append(l.processStart, pids) } + func (*Listener) Commit(_ uint32) (err error) { return nil } @@ -362,5 +363,4 @@ func TestNoState(t *testing.T) { // check that the value is not in the nostate _, err = ns.Get([]byte("foo")) qt.Assert(t, err, qt.Equals, db.ErrKeyNotFound) - } diff --git a/vochain/state/trees.go b/vochain/state/trees.go index ce83dee78..a6435e211 100644 --- a/vochain/state/trees.go +++ b/vochain/state/trees.go @@ -33,9 +33,7 @@ const ( ChildTreeVotes = "Votes" ) -var ( - ErrProcessChildLeafRootUnknown = fmt.Errorf("process child leaf root is unknown") -) +var ErrProcessChildLeafRootUnknown = fmt.Errorf("process child leaf root is unknown") // treeTxWithMutex is a wrapper over TreeTx with a mutex for convenient // RWLocking. diff --git a/vochain/state/votes.go b/vochain/state/votes.go index f1cc10f5c..4b64bf634 100644 --- a/vochain/state/votes.go +++ b/vochain/state/votes.go @@ -21,10 +21,8 @@ import ( "google.golang.org/protobuf/proto" ) -var ( - // keys; not constants because of []byte - voteCountKey = []byte("voteCount") -) +// keys; not constants because of []byte +var voteCountKey = []byte("voteCount") // Vote represents a vote in the Vochain state. type Vote struct { @@ -297,7 +295,8 @@ func (s *State) VoteExists(processID, nullifier []byte, committed bool) (bool, e // data from the currently open StateDB transaction. // When committed is true, the operation is executed on the last committed version. func (s *State) iterateVotes(processID []byte, - fn func(vid []byte, sdbVote *models.StateDBVote) bool, committed bool) error { + fn func(vid []byte, sdbVote *models.StateDBVote) bool, committed bool, +) error { if !committed { s.tx.RLock() defer s.tx.RUnlock() @@ -352,7 +351,8 @@ func (s *State) CountVotes(processID []byte, committed bool) (uint64, error) { // data from the currently open StateDB transaction. // When committed is true, the operation is executed on the last committed version. func (s *State) EnvelopeList(processID []byte, from, listSize int, - committed bool) (nullifiers [][]byte) { + committed bool, +) (nullifiers [][]byte) { idx := 0 s.iterateVotes(processID, func(vid []byte, sdbVote *models.StateDBVote) bool { if idx >= from+listSize { diff --git a/vochain/transaction/election_tx.go b/vochain/transaction/election_tx.go index fa1ec472d..a13201d78 100644 --- a/vochain/transaction/election_tx.go +++ b/vochain/transaction/election_tx.go @@ -179,6 +179,9 @@ func (t *TransactionHandler) SetProcessTxCheck(vtx *vochaintx.Tx) (ethereum.Addr if err != nil { return ethereum.Address{}, err } + if addr == nil || acc == nil { + return ethereum.Address{}, fmt.Errorf("cannot get account from signature") + } // get process process, err := t.state.Process(tx.ProcessId, false) if err != nil { @@ -206,7 +209,7 @@ func (t *TransactionHandler) SetProcessTxCheck(vtx *vochaintx.Tx) (ethereum.Addr if err != nil { return ethereum.Address{}, fmt.Errorf("cannot get %s transaction cost: %w", tx.Txtype, err) } - // check balance and nonce + // check base cost if acc.Balance < cost { return ethereum.Address{}, vstate.ErrNotEnoughBalance } @@ -229,8 +232,9 @@ func (t *TransactionHandler) SetProcessTxCheck(vtx *vochaintx.Tx) (ethereum.Addr if err := t.checkMaxCensusSize(process); err != nil { return ethereum.Address{}, err } - // get Tx cost, since it is a new process, we should use the election price calculator - if acc.Balance < t.txCostIncreaseCensusSize(process, tx.GetCensusSize()) { + cost = t.txCostIncreaseCensusSize(process, tx.GetCensusSize()) + // get Tx cost, since it is a new census size, we should use the election price calculator + if acc.Balance < cost { return ethereum.Address{}, fmt.Errorf("%w: required %d, got %d", vstate.ErrNotEnoughBalance, cost, acc.Balance) } } @@ -241,6 +245,14 @@ func (t *TransactionHandler) SetProcessTxCheck(vtx *vochaintx.Tx) (ethereum.Addr tx.GetCensusSize(), false, ) + case models.TxType_SET_PROCESS_DURATION: + // get Tx cost, since it modifies the process duration, we should use the election price calculator + cost = t.txCostIncreaseDuration(process, tx.GetDuration()) + if acc.Balance < cost { + return ethereum.Address{}, fmt.Errorf("%w: required %d, got %d", vstate.ErrNotEnoughBalance, cost, acc.Balance) + } + return ethereum.Address(*addr), t.state.SetProcessDuration(process.ProcessId, tx.GetDuration(), false) + default: return ethereum.Address{}, fmt.Errorf("unknown setProcess tx type: %s", tx.Txtype) } @@ -353,3 +365,22 @@ func (t *TransactionHandler) txCostIncreaseCensusSize(process *models.Process, n } return baseCost + (newCost - oldCost) } + +// txCostIncreaseDuration calculates the cost increase of a process based on the new duration. +func (t *TransactionHandler) txCostIncreaseDuration(process *models.Process, newDuration uint32) uint64 { + oldCost := t.txElectionCostFromProcess(process) + oldDuration := process.GetDuration() + process.Duration = newDuration + newCost := t.txElectionCostFromProcess(process) + process.Duration = oldDuration + + baseCost, err := t.state.TxBaseCost(models.TxType_SET_PROCESS_DURATION, false) + if err != nil { + log.Errorw(err, "txCostIncreaseDuration: cannot get transaction base cost") + return 0 + } + if newCost < oldCost { + return baseCost + } + return baseCost + (newCost - oldCost) +} diff --git a/vochain/transaction/proofs/farcasterproof/farcasterproof.go b/vochain/transaction/proofs/farcasterproof/farcasterproof.go index f88c71cac..9bf00c92f 100644 --- a/vochain/transaction/proofs/farcasterproof/farcasterproof.go +++ b/vochain/transaction/proofs/farcasterproof/farcasterproof.go @@ -23,11 +23,9 @@ const ( frameHashSize = 20 ) -var ( - // DisableElectionIDVerification is a flag to dissable the election ID verification on the poll URL. - // This should be used only for testing purposes. - DisableElectionIDVerification = false -) +// DisableElectionIDVerification is a flag to dissable the election ID verification on the poll URL. +// This should be used only for testing purposes. +var DisableElectionIDVerification = false // FarcasterState represents the data stored in the farcaster state message field. type FarcasterState struct { diff --git a/vochain/transaction/transaction.go b/vochain/transaction/transaction.go index b6f7d9743..474dac6c8 100644 --- a/vochain/transaction/transaction.go +++ b/vochain/transaction/transaction.go @@ -181,10 +181,24 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa } cost = t.txCostIncreaseCensusSize(process, tx.GetCensusSize()) } - // update process census + // update process census on state if err := t.state.SetProcessCensus(tx.ProcessId, tx.CensusRoot, tx.GetCensusURI(), tx.GetCensusSize(), true); err != nil { return nil, fmt.Errorf("setProcessCensus: %s", err) } + case models.TxType_SET_PROCESS_DURATION: + if tx.GetDuration() == 0 { + return nil, fmt.Errorf("setProcessDuration: duration cannot be 0") + } + // if duration is increased, cost must be applied + process, err := t.state.Process(tx.ProcessId, false) + if err != nil { + return nil, fmt.Errorf("setProcessDuration: %s", err) + } + cost = t.txCostIncreaseDuration(process, tx.GetDuration()) + // update process duration on state + if err := t.state.SetProcessDuration(tx.ProcessId, tx.GetDuration(), true); err != nil { + return nil, fmt.Errorf("setProcessCensus: %s", err) + } default: return nil, fmt.Errorf("unknown set process tx type") } diff --git a/vochain/transaction_zk_test.go b/vochain/transaction_zk_test.go index 7b3cb34f6..73d3839ab 100644 --- a/vochain/transaction_zk_test.go +++ b/vochain/transaction_zk_test.go @@ -119,7 +119,8 @@ func TestVoteCheckZkSNARK(t *testing.T) { } func testBuildSignedRegisterSIKTx(t *testing.T, account *ethereum.SignKeys, pid, - proof, availableWeight []byte, chainID string) *vochaintx.Tx { + proof, availableWeight []byte, chainID string, +) *vochaintx.Tx { c := qt.New(t) sik, err := account.AccountSIK(nil) diff --git a/vochain/vote_test.go b/vochain/vote_test.go index 9943ffce2..0af7ba480 100644 --- a/vochain/vote_test.go +++ b/vochain/vote_test.go @@ -23,8 +23,10 @@ import ( // leaf value). It returns the keys, the census root and the proofs for each key. func testCreateKeysAndBuildWeightedZkCensus(t *testing.T, size int, weight *big.Int) ([]*ethereum.SignKeys, []byte, [][]byte) { db := metadb.NewTest(t) - tr, err := censustree.New(censustree.Options{Name: "testcensus", ParentDB: db, - MaxLevels: censustree.DefaultMaxLevels, CensusType: models.Census_ARBO_POSEIDON}) + tr, err := censustree.New(censustree.Options{ + Name: "testcensus", ParentDB: db, + MaxLevels: censustree.DefaultMaxLevels, CensusType: models.Census_ARBO_POSEIDON, + }) if err != nil { t.Fatal(err) } @@ -55,8 +57,10 @@ func testCreateKeysAndBuildWeightedZkCensus(t *testing.T, size int, weight *big. // It returns the keys, the census root and the proofs for each key. func testCreateKeysAndBuildCensus(t *testing.T, size int) ([]*ethereum.SignKeys, []byte, [][]byte) { db := metadb.NewTest(t) - tr, err := censustree.New(censustree.Options{Name: "testcensus", ParentDB: db, - MaxLevels: censustree.DefaultMaxLevels, CensusType: models.Census_ARBO_BLAKE2B}) + tr, err := censustree.New(censustree.Options{ + Name: "testcensus", ParentDB: db, + MaxLevels: censustree.DefaultMaxLevels, CensusType: models.Census_ARBO_BLAKE2B, + }) if err != nil { t.Fatal(err) } @@ -84,7 +88,8 @@ func testCreateKeysAndBuildCensus(t *testing.T, size int) ([]*ethereum.SignKeys, } func testBuildSignedVote(t *testing.T, electionID []byte, key *ethereum.SignKeys, - proof []byte, votePackage []int, chainID string) *models.SignedTx { + proof []byte, votePackage []int, chainID string, +) *models.SignedTx { var stx models.SignedTx var err error vp, err := json.Marshal(votePackage)