Skip to content

Commit

Permalink
Putting ballots in separate memory
Browse files Browse the repository at this point in the history
This PR separates the ballots from the forms.
This allows to run adding a new ballot much faster.
When there are more than 100 ballots, this gets very important:
adding a 1000th ballot can take 1s, a 10'000th ballot 10s.

Using this PR, up to the 10'000th ballot it only takes 100ms.
  • Loading branch information
ineiti committed Feb 23, 2024
1 parent b5ad03e commit 33d78f0
Show file tree
Hide file tree
Showing 18 changed files with 462 additions and 187 deletions.
6 changes: 4 additions & 2 deletions contracts/evoting/controller/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error {
return xerrors.Errorf(getFormErr, err)
}

encryptedBallots := form.Suffragia.Ciphervotes
suff, err := form.Suffragia(serdecontext, service.GetStore())
encryptedBallots := suff.Ciphervotes
dela.Logger.Info().Msg("Length encrypted ballots: " + strconv.Itoa(len(encryptedBallots)))
dela.Logger.Info().Msgf("Ballot of user1: %s", encryptedBallots[0])
dela.Logger.Info().Msgf("Ballot of user2: %s", encryptedBallots[1])
Expand Down Expand Up @@ -485,7 +486,8 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error {

logFormStatus(form)
dela.Logger.Info().Msg("Number of shuffled ballots : " + strconv.Itoa(len(form.ShuffleInstances)))
dela.Logger.Info().Msg("Number of encrypted ballots : " + strconv.Itoa(len(form.Suffragia.Ciphervotes)))
suff, err = form.Suffragia(serdecontext, service.GetStore())
dela.Logger.Info().Msg("Number of encrypted ballots : " + strconv.Itoa(len(suff.Ciphervotes)))

// ###################################### REQUEST PUBLIC SHARES ############

Expand Down
16 changes: 11 additions & 5 deletions contracts/evoting/evoting.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 37 additions & 65 deletions contracts/evoting/json/forms.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package json

import (
"encoding/hex"
"encoding/json"

"github.com/c4dt/d-voting/contracts/evoting/types"
Expand Down Expand Up @@ -35,9 +36,14 @@ func (formFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, erro
}
}

suffragia, err := encodeSuffragia(ctx, m.Suffragia)
if err != nil {
return nil, xerrors.Errorf("failed to encode suffragia: %v", err)
suffragias := make([]string, len(m.SuffragiaIDs))
for i, suf := range m.SuffragiaIDs {
suffragias[i] = hex.EncodeToString(suf)
}

suffragiaHashes := make([]string, len(m.SuffragiaHashes))
for i, sufH := range m.SuffragiaHashes {
suffragiaHashes[i] = hex.EncodeToString(sufH)
}

shuffleInstances, err := encodeShuffleInstances(ctx, m.ShuffleInstances)
Expand All @@ -62,7 +68,9 @@ func (formFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, erro
Status: uint16(m.Status),
Pubkey: pubkey,
BallotSize: m.BallotSize,
Suffragia: suffragia,
Suffragias: suffragias,
SuffragiaHashes: suffragiaHashes,
BallotCount: m.BallotCount,
ShuffleInstances: shuffleInstances,
ShuffleThreshold: m.ShuffleThreshold,
PubsharesUnits: pubsharesUnits,
Expand Down Expand Up @@ -100,9 +108,20 @@ func (formFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error)
}
}

suffragia, err := decodeSuffragia(ctx, formJSON.Suffragia)
if err != nil {
return nil, xerrors.Errorf("failed to decode suffragia: %v", err)
suffragias := make([][]byte, len(formJSON.Suffragias))
for i, suff := range formJSON.Suffragias {
suffragias[i], err = hex.DecodeString(suff)
if err != nil {
return nil, xerrors.Errorf("failed to decode suffragia-address: %v", err)
}
}

suffragiaHashes := make([][]byte, len(formJSON.SuffragiaHashes))
for i, suffH := range formJSON.SuffragiaHashes {
suffragiaHashes[i], err = hex.DecodeString(suffH)
if err != nil {
return nil, xerrors.Errorf("failed to decode suffragia-hash: %v", err)
}
}

shuffleInstances, err := decodeShuffleInstances(ctx, formJSON.ShuffleInstances)
Expand Down Expand Up @@ -132,7 +151,9 @@ func (formFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error)
Status: types.Status(formJSON.Status),
Pubkey: pubKey,
BallotSize: formJSON.BallotSize,
Suffragia: suffragia,
SuffragiaIDs: suffragias,
SuffragiaHashes: suffragiaHashes,
BallotCount: formJSON.BallotCount,
ShuffleInstances: shuffleInstances,
ShuffleThreshold: formJSON.ShuffleThreshold,
PubsharesUnits: pubSharesSubmissions,
Expand All @@ -157,7 +178,14 @@ type FormJSON struct {
// to pad smaller ballots such that all ballots cast have the same size
BallotSize int

Suffragia SuffragiaJSON
// Suffragias are the hex-encoded addresses of the Suffragia storages.
Suffragias []string

// BallotCount represents the total number of ballots cast.
BallotCount uint32

// SuffragiaHashes are the hex-encoded sha256-hashes of the ballots in every Suffragia.
SuffragiaHashes []string

// ShuffleInstances is all the shuffles, along with their proof and identity
// of shuffler.
Expand All @@ -179,62 +207,6 @@ type FormJSON struct {
RosterBuf []byte
}

// SuffragiaJSON defines the JSON representation of a suffragia.
type SuffragiaJSON struct {
UserIDs []string
Ciphervotes []json.RawMessage
}

func encodeSuffragia(ctx serde.Context, suffragia types.Suffragia) (SuffragiaJSON, error) {
ciphervotes := make([]json.RawMessage, len(suffragia.Ciphervotes))

for i, ciphervote := range suffragia.Ciphervotes {
buff, err := ciphervote.Serialize(ctx)
if err != nil {
return SuffragiaJSON{}, xerrors.Errorf("failed to serialize ciphervote: %v", err)
}

ciphervotes[i] = buff
}
return SuffragiaJSON{
UserIDs: suffragia.UserIDs,
Ciphervotes: ciphervotes,
}, nil
}

func decodeSuffragia(ctx serde.Context, suffragiaJSON SuffragiaJSON) (types.Suffragia, error) {
var res types.Suffragia
fac := ctx.GetFactory(types.CiphervoteKey{})

factory, ok := fac.(types.CiphervoteFactory)
if !ok {
return res, xerrors.Errorf("invalid ciphervote factory: '%T'", fac)
}

ciphervotes := make([]types.Ciphervote, len(suffragiaJSON.Ciphervotes))

for i, ciphervoteJSON := range suffragiaJSON.Ciphervotes {
msg, err := factory.Deserialize(ctx, ciphervoteJSON)
if err != nil {
return res, xerrors.Errorf("failed to deserialize ciphervote json: %v", err)
}

ciphervote, ok := msg.(types.Ciphervote)
if !ok {
return res, xerrors.Errorf("wrong type: '%T'", msg)
}

ciphervotes[i] = ciphervote
}

res = types.Suffragia{
UserIDs: suffragiaJSON.UserIDs,
Ciphervotes: ciphervotes,
}

return res, nil
}

// ShuffleInstanceJSON defines the JSON representation of a shuffle instance
type ShuffleInstanceJSON struct {
// ShuffledBallots contains the list of shuffled ciphertext for this round
Expand Down
1 change: 1 addition & 0 deletions contracts/evoting/json/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

func init() {
types.RegisterFormFormat(serde.FormatJSON, formFormat{})
types.RegisterSuffragiaFormat(serde.FormatJSON, suffragiaFormat{})
types.RegisterCiphervoteFormat(serde.FormatJSON, ciphervoteFormat{})
types.RegisterTransactionFormat(serde.FormatJSON, transactionFormat{})
}
97 changes: 97 additions & 0 deletions contracts/evoting/json/suffragia.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package json

import (
"encoding/json"

"github.com/c4dt/d-voting/contracts/evoting/types"
"go.dedis.ch/dela/serde"
"golang.org/x/xerrors"
)

type suffragiaFormat struct{}

func (suffragiaFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) {
switch m := msg.(type) {
case types.Suffragia:
sJson, err := encodeSuffragia(ctx, m)
if err != nil {
return nil, xerrors.Errorf("couldn't encode suffragia: %v", err)
}

buff, err := ctx.Marshal(&sJson)
if err != nil {
return nil, xerrors.Errorf("failed to marshal form: %v", err)
}

return buff, nil
default:
return nil, xerrors.Errorf("Unknown format: %T", msg)
}
}

func (suffragiaFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) {
var sJson SuffragiaJSON

err := ctx.Unmarshal(data, &sJson)
if err != nil {
return nil, xerrors.Errorf("failed to unmarshal form: %v", err)
}

return decodeSuffragia(ctx, sJson)
}

// SuffragiaJSON defines the JSON representation of a suffragia.
type SuffragiaJSON struct {
UserIDs []string
Ciphervotes []json.RawMessage
}

func encodeSuffragia(ctx serde.Context, suffragia types.Suffragia) (SuffragiaJSON, error) {
ciphervotes := make([]json.RawMessage, len(suffragia.Ciphervotes))

for i, ciphervote := range suffragia.Ciphervotes {
buff, err := ciphervote.Serialize(ctx)
if err != nil {
return SuffragiaJSON{}, xerrors.Errorf("failed to serialize ciphervote: %v", err)
}

ciphervotes[i] = buff
}
return SuffragiaJSON{
UserIDs: suffragia.UserIDs,
Ciphervotes: ciphervotes,
}, nil
}

func decodeSuffragia(ctx serde.Context, suffragiaJSON SuffragiaJSON) (types.Suffragia, error) {
var res types.Suffragia
fac := ctx.GetFactory(types.CiphervoteKey{})

factory, ok := fac.(types.CiphervoteFactory)
if !ok {
return res, xerrors.Errorf("invalid ciphervote factory: '%T'", fac)
}

ciphervotes := make([]types.Ciphervote, len(suffragiaJSON.Ciphervotes))

for i, ciphervoteJSON := range suffragiaJSON.Ciphervotes {
msg, err := factory.Deserialize(ctx, ciphervoteJSON)
if err != nil {
return res, xerrors.Errorf("failed to deserialize ciphervote json: %v", err)
}

ciphervote, ok := msg.(types.Ciphervote)
if !ok {
return res, xerrors.Errorf("wrong type: '%T'", msg)
}

ciphervotes[i] = ciphervote
}

res = types.Suffragia{
UserIDs: suffragiaJSON.UserIDs,
Ciphervotes: ciphervotes,
}

return res, nil
}
23 changes: 12 additions & 11 deletions contracts/evoting/mod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,13 @@ func TestCommand_CastVote(t *testing.T) {
form, ok := message.(types.Form)
require.True(t, ok)

require.Len(t, form.Suffragia.Ciphervotes, 1)
require.True(t, castVote.Ballot.Equal(form.Suffragia.Ciphervotes[0]))
require.Len(t, form.BallotCount, 1)
suff, err := form.Suffragia(ctx, snap)
require.NoError(t, err)
require.True(t, castVote.Ballot.Equal(suff.Ciphervotes[0]))

require.Equal(t, castVote.UserID, form.Suffragia.UserIDs[0])
require.Equal(t, float64(len(form.Suffragia.Ciphervotes)), testutil.ToFloat64(PromFormBallots))
require.Equal(t, castVote.UserID, suff.UserIDs[0])
require.Equal(t, float64(form.BallotCount), testutil.ToFloat64(PromFormBallots))
}

func TestCommand_CloseForm(t *testing.T) {
Expand Down Expand Up @@ -364,8 +366,8 @@ func TestCommand_CloseForm(t *testing.T) {
err = cmd.closeForm(snap, makeStep(t, FormArg, string(data)))
require.EqualError(t, err, "at least two ballots are required")

dummyForm.Suffragia.CastVote("dummyUser1", types.Ciphervote{})
dummyForm.Suffragia.CastVote("dummyUser2", types.Ciphervote{})
require.NoError(t, dummyForm.CastVote(ctx, snap, "dummyUser1", types.Ciphervote{}))
require.NoError(t, dummyForm.CastVote(ctx, snap, "dummyUser2", types.Ciphervote{}))

formBuf, err = dummyForm.Serialize(ctx)
require.NoError(t, err)
Expand Down Expand Up @@ -697,7 +699,6 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) {
form.Pubkey = pubKey
shuffleBallots.Round = 0
form.ShuffleInstances = make([]types.ShuffleInstance, 0)
form.Suffragia.Ciphervotes = make([]types.Ciphervote, 0)

data, err = shuffleBallots.Serialize(ctx)
require.NoError(t, err)
Expand All @@ -713,9 +714,9 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) {

// > With only one shuffled ballot the shuffling can't happen

form.Suffragia.CastVote("user1", types.Ciphervote{
require.NoError(t, form.CastVote(ctx, snap, "user1", types.Ciphervote{
types.EGPair{K: suite.Point(), C: suite.Point()},
})
}))

data, err = shuffleBallots.Serialize(ctx)
require.NoError(t, err)
Expand Down Expand Up @@ -1118,7 +1119,6 @@ func initFormAndContract() (types.Form, Contract) {
FormID: fakeFormID,
Status: 0,
Pubkey: nil,
Suffragia: types.Suffragia{},
ShuffleInstances: make([]types.ShuffleInstance, 0),
DecryptedBallots: nil,
ShuffleThreshold: 0,
Expand Down Expand Up @@ -1156,12 +1156,13 @@ func initGoodShuffleBallot(t *testing.T, k int) (types.Form, types.ShuffleBallot
shuffleBallots.Round = 0
form.ShuffleInstances = make([]types.ShuffleInstance, 0)

snap := fake.InMemorySnapshot{}
for i := 0; i < k; i++ {
ballot := types.Ciphervote{types.EGPair{
K: Ks[i],
C: Cs[i],
}}
form.Suffragia.CastVote(fmt.Sprintf("user%d", i), ballot)
form.CastVote(ctx, &snap, fmt.Sprintf("user%d", i), ballot)
}

// Valid Signature of shuffle
Expand Down
Loading

0 comments on commit 33d78f0

Please sign in to comment.