Skip to content

Commit

Permalink
api: support passing delta to /elections/id endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
altergui authored and p4u committed Jun 11, 2024
1 parent 1c49e30 commit a4e9e47
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 20 deletions.
3 changes: 2 additions & 1 deletion api/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ type Validator struct {
Score uint32 `json:"score"`
}

type NextElectionID struct {
type BuildElectionID struct {
Delta int32 `json:"delta"` // 0 means build next ElectionID
OrganizationID types.HexBytes `json:"organizationId"`
CensusOrigin int32 `json:"censusOrigin"`
EnvelopeType struct {
Expand Down
19 changes: 8 additions & 11 deletions api/elections.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (a *API) enableElectionHandlers() error {
"/elections/id",
"POST",
apirest.MethodAccessTypePublic,
a.nextElectionIDHandler,
a.buildElectionIDHandler,
); err != nil {
return err
}
Expand Down Expand Up @@ -684,18 +684,18 @@ func (a *API) electionFilterPaginatedHandler(msg *apirest.APIdata, ctx *httprout
return ctx.Send(data, apirest.HTTPstatusOK)
}

// nextElectionIDHandler
// buildElectionIDHandler
//
// @Summary Get next election ID
// @Description nextElectionIDHandler
// @Summary Build an election ID
// @Description buildElectionIDHandler
// @Tags Elections
// @Accept json
// @Produce json
// @Param transaction body NextElectionID true "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) nextElectionIDHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error {
body := &NextElectionID{}
func (a *API) buildElectionIDHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error {
body := &BuildElectionID{}
if err := json.Unmarshal(msg.Data, body); err != nil {
return err
}
Expand All @@ -710,10 +710,7 @@ func (a *API) nextElectionIDHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCo
CostFromWeight: body.EnvelopeType.CostFromWeight,
},
}
pid, err := processid.BuildProcessID(
process,
a.vocapp.State,
)
pid, err := processid.BuildProcessID(process, a.vocapp.State, body.Delta)
if err != nil {
return ErrCantParseElectionID.WithErr(err)
}
Expand Down
4 changes: 3 additions & 1 deletion apiclient/election.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"go.vocdoni.io/dvote/httprouter/apirest"
"go.vocdoni.io/dvote/log"
"go.vocdoni.io/dvote/types"
"go.vocdoni.io/dvote/vochain/processid"
"go.vocdoni.io/dvote/vochain/state/electionprice"
"go.vocdoni.io/proto/build/go/models"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -454,7 +455,8 @@ func (c *HTTPclient) ElectionPrice(election *api.ElectionDescription) (uint64, e
// NextElectionID gets the next election ID for an organization.
// POST /elections/id
func (c *HTTPclient) NextElectionID(organizationID types.HexBytes, censusOrigin int32, envelopeType *models.EnvelopeType) (string, error) {
body := &api.NextElectionID{
body := &api.BuildElectionID{
Delta: processid.BuildNextProcessID,
OrganizationID: organizationID,
CensusOrigin: censusOrigin,
EnvelopeType: struct {
Expand Down
44 changes: 42 additions & 2 deletions test/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"go.vocdoni.io/dvote/util"
"go.vocdoni.io/dvote/vochain"
"go.vocdoni.io/dvote/vochain/indexer/indexertypes"
"go.vocdoni.io/dvote/vochain/processid"
"go.vocdoni.io/dvote/vochain/state"
"go.vocdoni.io/dvote/vochain/state/electionprice"
"go.vocdoni.io/proto/build/go/models"
Expand Down Expand Up @@ -659,7 +660,7 @@ func sendTokensTx(t testing.TB, c *testutil.TestHTTPclient,
qt.Assert(t, code, qt.Equals, 200, qt.Commentf("response: %s", resp))
}

func TestAPINextElectionID(t *testing.T) {
func TestAPIBuildElectionID(t *testing.T) {
server := testcommon.APIserver{}
server.Start(t,
api.ChainHandler,
Expand Down Expand Up @@ -692,7 +693,8 @@ func TestAPINextElectionID(t *testing.T) {
waitUntilHeight(t, c, 3)

// create request for calling api elections/id
body := api.NextElectionID{
body := api.BuildElectionID{
Delta: processid.BuildNextProcessID,
OrganizationID: signer.Address().Bytes(),
CensusOrigin: int32(models.CensusOrigin_OFF_CHAIN_TREE_WEIGHTED.Number()),
EnvelopeType: struct {
Expand All @@ -718,6 +720,17 @@ func TestAPINextElectionID(t *testing.T) {
err := json.Unmarshal(resp, &nextElectionID)
qt.Assert(t, err, qt.IsNil)

// test building n+1 election ID
body.Delta = processid.BuildNextProcessID + 1
resp, code = c.Request("POST", body, "elections", "id")
qt.Assert(t, code, qt.Equals, 200, qt.Commentf("response: %s", resp))

futureElectionID := struct {
ElectionID string `json:"electionID"`
}{}
err = json.Unmarshal(resp, &futureElectionID)
qt.Assert(t, err, qt.IsNil)

// create a new election
electionParams := electionprice.ElectionParameters{ElectionDuration: 100, MaxCensusSize: 100}
response := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), false)
Expand All @@ -728,6 +741,33 @@ func TestAPINextElectionID(t *testing.T) {

// check next election id is the same as the election id created
qt.Assert(t, nextElectionID.ElectionID, qt.Equals, response.ElectionID.String())

// now build last processID, after election was created.
body.Delta = processid.BuildLastProcessID
resp, code = c.Request("POST", body, "elections", "id")
qt.Assert(t, code, qt.Equals, 200, qt.Commentf("response: %s", resp))

lastElectionID := struct {
ElectionID string `json:"electionID"`
}{}
err = json.Unmarshal(resp, &lastElectionID)
qt.Assert(t, err, qt.IsNil)

qt.Assert(t, lastElectionID, qt.Equals, nextElectionID)

// and finally query again next processID, should match futureProcessID
body.Delta = processid.BuildNextProcessID
resp, code = c.Request("POST", body, "elections", "id")
qt.Assert(t, code, qt.Equals, 200, qt.Commentf("response: %s", resp))

newNextElectionID := struct {
ElectionID string `json:"electionID"`
}{}
err = json.Unmarshal(resp, &newNextElectionID)
qt.Assert(t, err, qt.IsNil)

qt.Assert(t, newNextElectionID, qt.Equals, futureElectionID)

}

func TestAPIEncryptedMetadata(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion vochain/hysteresis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestHysteresis(t *testing.T) {
MaxCensusSize: 10,
CensusOrigin: models.CensusOrigin_OFF_CHAIN_TREE_WEIGHTED,
}
procID, err := processid.BuildProcessID(process, app.State)
procID, err := processid.BuildProcessID(process, app.State, processid.BuildNextProcessID)
c.Assert(err, qt.IsNil)
pid := procID.Marshal()
process.ProcessId = pid
Expand Down
18 changes: 15 additions & 3 deletions vochain/processid/process_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import (
"go.vocdoni.io/proto/build/go/models"
)

// These consts are sugar for calling BuildProcessID
const (
BuildNextProcessID = 0
BuildLastProcessID = -1
)

// ProcessID is a 32 bytes identifier that holds the following information about the voting process:
//
// - chainID: a 6 bytes trunked hash of the blockchain identifier
Expand Down Expand Up @@ -180,8 +186,11 @@ func (p *ProcessID) EnvelopeType() *models.EnvelopeType {
}
}

// BuildProcessID returns a ProcessID constructed in a deterministic way
func BuildProcessID(proc *models.Process, state *state.State) (*ProcessID, error) {
// BuildProcessID returns a ProcessID constructed in a deterministic way.
// - with delta == 0, it will return the next process id
// - with delta != 0, it will return the (next + delta) process id (past or future)
// - for example, with delta == -1 it will return the last process id
func BuildProcessID(proc *models.Process, state *state.State, delta int32) (*ProcessID, error) {
pid := new(ProcessID)
pid.SetChainID(state.ChainID())
if err := pid.SetEnvelopeType(proc.EnvelopeType); err != nil {
Expand All @@ -199,6 +208,9 @@ func BuildProcessID(proc *models.Process, state *state.State) (*ProcessID, error
return nil, fmt.Errorf("account not found %s", addr.Hex())
}
pid.SetAddr(addr)
pid.SetNonce(acc.GetProcessIndex())
if int32(acc.GetProcessIndex())+delta < 0 {
return nil, fmt.Errorf("invalid delta")
}
pid.SetNonce(uint32(int32(acc.GetProcessIndex()) + delta))
return pid, nil
}
2 changes: 1 addition & 1 deletion vochain/transaction/election_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (t *TransactionHandler) NewProcessTxCheck(vtx *vochaintx.Tx) (*models.Proce
}

// build the deterministic process ID
pid, err := processid.BuildProcessID(tx.Process, t.state)
pid, err := processid.BuildProcessID(tx.Process, t.state, processid.BuildNextProcessID)
if err != nil {
return nil, ethereum.Address{}, fmt.Errorf("cannot build processID: %w", err)
}
Expand Down

0 comments on commit a4e9e47

Please sign in to comment.