From 7514cb70171e6fe21983b59de4e07a77f4894db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mien=20Kocher?= Date: Fri, 6 May 2022 10:11:06 +0200 Subject: [PATCH] Benchmarks the GET elections proxy With many elections the GET elections call cab be slow, >10s to get 15 elections. I suspect the deserialization to be the responsible for that and implemented a "light" version. However results are not convincing. --- contracts/evoting/json/lightelection.go | 71 ++++++++++++ contracts/evoting/json/mod.go | 1 + contracts/evoting/types/election.go | 4 + proxy/election_test.go | 137 ++++++++++++++++++++++++ 4 files changed, 213 insertions(+) create mode 100644 contracts/evoting/json/lightelection.go create mode 100644 proxy/election_test.go diff --git a/contracts/evoting/json/lightelection.go b/contracts/evoting/json/lightelection.go new file mode 100644 index 000000000..e2e83a15b --- /dev/null +++ b/contracts/evoting/json/lightelection.go @@ -0,0 +1,71 @@ +package json + +import ( + "github.com/dedis/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/serde" + "go.dedis.ch/kyber/v3" + "golang.org/x/xerrors" +) + +// lightElectionFormat defines how the election messages are encoded/decoded +// using the JSON format. +// +// - implements serde.FormatEngine +type lightElectionFormat struct{} + +// Encode implements serde.FormatEngine +func (lightElectionFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, error) { + return nil, xerrors.Errorf("encoding of a light election not supported") +} + +// Decode implements serde.FormatEngine +func (lightElectionFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { + var electionJSON LightElectionJSON + + err := ctx.Unmarshal(data, &electionJSON) + if err != nil { + return nil, xerrors.Errorf("failed to unmarshal election: %v", err) + } + + var pubKey kyber.Point + + if electionJSON.Pubkey != nil { + pubKey = suite.Point() + err = pubKey.UnmarshalBinary(electionJSON.Pubkey) + if err != nil { + return nil, xerrors.Errorf("failed to unmarshal pubkey: %v", err) + } + } + + return types.Election{ + Configuration: types.Configuration{ + MainTitle: electionJSON.Configuration.MainTitle, + }, + ElectionID: electionJSON.ElectionID, + Status: types.Status(electionJSON.Status), + Pubkey: pubKey, + BallotSize: electionJSON.BallotSize, + }, nil +} + +// LightConfiguration represents what is need from the configuration in the +// light election. +type LightConfiguration struct { + MainTitle string +} + +// LightElectionJSON defines the Election in the JSON format +type LightElectionJSON struct { + Configuration LightConfiguration + + // ElectionID is the hex-encoded SHA256 of the transaction ID that creates + // the election + ElectionID string + + Status uint16 + Pubkey []byte `json:"Pubkey,omitempty"` + + // BallotSize represents the total size in bytes of one ballot. It is used + // to pad smaller ballots such that all ballots cast have the same size + BallotSize int +} diff --git a/contracts/evoting/json/mod.go b/contracts/evoting/json/mod.go index b715b330c..8be4896c9 100644 --- a/contracts/evoting/json/mod.go +++ b/contracts/evoting/json/mod.go @@ -9,6 +9,7 @@ import ( func init() { types.RegisterElectionFormat(serde.FormatJSON, electionFormat{}) + types.RegisterElectionFormat(types.LightElectionJSONFormat, lightElectionFormat{}) types.RegisterCiphervoteFormat(serde.FormatJSON, ciphervoteFormat{}) types.RegisterTransactionFormat(serde.FormatJSON, transactionFormat{}) } diff --git a/contracts/evoting/types/election.go b/contracts/evoting/types/election.go index 48a76da2d..ac9c1af1c 100644 --- a/contracts/evoting/types/election.go +++ b/contracts/evoting/types/election.go @@ -15,6 +15,10 @@ import ( var suite = suites.MustFind("Ed25519") +// LightElectionJSONFormat defines a special format to deserialize a light +// election. +var LightElectionJSONFormat serde.Format = "lightElectionJSON" + // ID defines the ID of a ballot question type ID string diff --git a/proxy/election_test.go b/proxy/election_test.go new file mode 100644 index 000000000..2b24f091c --- /dev/null +++ b/proxy/election_test.go @@ -0,0 +1,137 @@ +package proxy + +import ( + "encoding/hex" + "encoding/json" + "math/rand" + "net/http/httptest" + "testing" + + "github.com/dedis/d-voting/contracts/evoting" + "github.com/dedis/d-voting/contracts/evoting/types" + "github.com/dedis/d-voting/internal/testing/fake" + "github.com/stretchr/testify/require" + "go.dedis.ch/dela/core/ordering" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + "go.dedis.ch/dela/serde" + sjson "go.dedis.ch/dela/serde/json" +) + +type LightContextEngine struct { + serde.ContextEngine +} + +func (LightContextEngine) GetFormat() serde.Format { + return types.LightElectionJSONFormat +} + +func BenchmarkElectionsGET(b *testing.B) { + b.StopTimer() + rand.Seed(0) + + ciphervoteFac := types.CiphervoteFactory{} + electionFac := types.NewElectionFactory(ciphervoteFac, fakeAuthorityFactory{}) + ctx := serde.NewContext(LightContextEngine{ContextEngine: sjson.NewContext()}) + + // ctx = sjson.NewContext() + + md := types.ElectionsMetadata{ + ElectionsIDs: types.ElectionIDs{}, + } + + ctx2 := sjson.NewContext() + + data := map[string][]byte{} + + for i := 0; i < 50; i++ { + electionIDBuff := make([]byte, 8) + + _, err := rand.Read(electionIDBuff) + require.NoError(b, err) + + electionID := hex.EncodeToString(electionIDBuff) + + elec := types.Election{ + ElectionID: electionID, + Status: 0, + Pubkey: nil, + Suffragia: types.Suffragia{}, + ShuffleInstances: make([]types.ShuffleInstance, 0), + DecryptedBallots: nil, + ShuffleThreshold: 0, + Roster: fake.Authority{}, + } + + md.ElectionsIDs.Add(electionID) + + electionBuff, err := elec.Serialize(ctx2) + require.NoError(b, err) + + data[string(electionIDBuff)] = electionBuff + } + + mdJSON, err := json.Marshal(md) + require.NoError(b, err) + + data[evoting.ElectionsMetadataKey] = mdJSON + + e := election{ + orderingSvc: fakeService{data: data}, + context: ctx, + electionFac: electionFac, + } + + rr := httptest.NewRecorder() + + b.StartTimer() + + for i := 0; i < b.N; i++ { + e.Elections(rr, nil) + } +} + +// ----------------------------------------------------------------------------- +// Utility functions + +type fakeService struct { + ordering.Service + data map[string][]byte +} + +func (f fakeService) GetProof(key []byte) (ordering.Proof, error) { + proof := fakeProof{ + key: key, + value: f.data[string(key)], + } + + return proof, nil +} + +// fakeProof is a fake Proof +// +// - implements ordering.Proof +type fakeProof struct { + key []byte + value []byte +} + +func (f fakeProof) GetKey() []byte { + return f.key +} + +func (f fakeProof) GetValue() []byte { + return f.value +} + +type fakeAuthorityFactory struct { + serde.Factory +} + +func (f fakeAuthorityFactory) AuthorityOf(ctx serde.Context, rosterBuf []byte) (authority.Authority, error) { + fakeAuthority := fakeAuthority{} + return fakeAuthority, nil +} + +type fakeAuthority struct { + authority.Authority +}