diff --git a/.github/workflows/go_integration_tests.yml b/.github/workflows/go_integration_tests.yml index dac6070f5..791619e25 100644 --- a/.github/workflows/go_integration_tests.yml +++ b/.github/workflows/go_integration_tests.yml @@ -16,31 +16,9 @@ jobs: uses: actions/setup-go@v2 with: go-version: ^1.17 - - - name: Install crypto util from Dela - run: | - git clone https://github.com/dedis/dela.git - cd dela - go install ./cli/crypto - name: Check out code into the Go module directory uses: actions/checkout@v2 - - - name: Create a private key - run: crypto bls signer new --save private.key - - - name: Install memcoin - run: make build - - - name: Start 3 nodes - run: | - ./memcoin --config /tmp/node1 start --postinstall --promaddr :9100 --proxyaddr :9080 --proxykey adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3 --listen tcp://0.0.0.0:2001 --public //localhost:2001 & - ./memcoin --config /tmp/node2 start --postinstall --promaddr :9101 --proxyaddr :9081 --proxykey adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3 --listen tcp://0.0.0.0:2002 --public //localhost:2002 & - ./memcoin --config /tmp/node3 start --postinstall --promaddr :9102 --proxyaddr :9082 --proxykey adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3 --listen tcp://0.0.0.0:2003 --public //localhost:2003 & - - - name: Run the setup - run: ./setupnNode.sh -n 3 -d false - - - name: Test integration & benchmark with coverage - run: go test -bench -cover -tags=integration ./integration/... + - name: Run the integration test + run: go test -timeout 7m -run TestIntegration ./integration/... \ No newline at end of file diff --git a/.github/workflows/go_scenario_test.yml b/.github/workflows/go_scenario_test.yml new file mode 100644 index 000000000..5d763271a --- /dev/null +++ b/.github/workflows/go_scenario_test.yml @@ -0,0 +1,45 @@ +name: Go Scenario Test (with proxy) + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + + test: + name: Tests + runs-on: ubuntu-latest + steps: + - name: Set up Go ^1.17 + uses: actions/setup-go@v2 + with: + go-version: ^1.17 + + - name: Install crypto util from Dela + run: | + git clone https://github.com/dedis/dela.git + cd dela + go install ./cli/crypto + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Create a private key + run: crypto bls signer new --save private.key + + - name: Install memcoin + run: make build + + - name: Start 3 nodes + run: | + ./memcoin --config /tmp/node1 start --postinstall --promaddr :9100 --proxyaddr :9080 --proxykey adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3 --listen tcp://0.0.0.0:2001 --public //localhost:2001 --routing tree & + ./memcoin --config /tmp/node2 start --postinstall --promaddr :9101 --proxyaddr :9081 --proxykey adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3 --listen tcp://0.0.0.0:2002 --public //localhost:2002 --routing tree & + ./memcoin --config /tmp/node3 start --postinstall --promaddr :9102 --proxyaddr :9082 --proxykey adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3 --listen tcp://0.0.0.0:2003 --public //localhost:2003 --routing tree & + + - name: Run the setup + run: ./setupnNode.sh -n 3 -d false + + - name: Run the scenario Test + run: go test -timeout 7m -run TestScenario ./integration/... diff --git a/.github/workflows/go_test.yml b/.github/workflows/go_test.yml index 934a2c51a..bd7ff1429 100644 --- a/.github/workflows/go_test.yml +++ b/.github/workflows/go_test.yml @@ -1,4 +1,4 @@ -name: Go Test +name: Go Unit Tests on: push: @@ -10,14 +10,14 @@ jobs: name: Tests runs-on: ubuntu-latest steps: - - name: Set up Go ^1.17 - uses: actions/setup-go@v2 + - name: Use Go >= 1.19 + uses: actions/setup-go@v3 with: - go-version: ^1.17 + go-version: '>=1.19' id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Run lint run: make lint diff --git a/README.md b/README.md index 88dad8ce3..e6e6c2617 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ + + +
GitHub contributors @@ -29,6 +32,9 @@ + + +
@@ -354,7 +360,7 @@ results. # ⚙️ Setup -First be sure to have Go installed (at least 1.17). +First be sure to have Go installed (at least 1.19). Be sure to have the `crypto` utility from Dela: diff --git a/cli/cosipbftcontroller/action_test.go b/cli/cosipbftcontroller/action_test.go index 479dd0352..322b89863 100644 --- a/cli/cosipbftcontroller/action_test.go +++ b/cli/cosipbftcontroller/action_test.go @@ -3,7 +3,7 @@ package controller import ( "bytes" "context" - "io/ioutil" + "io" "testing" "time" @@ -172,7 +172,7 @@ func prepContext(calls *fake.Call) node.Context { ctx := node.Context{ Injector: node.NewInjector(), Flags: make(node.FlagSet), - Out: ioutil.Discard, + Out: io.Discard, } events := []ordering.Event{ diff --git a/cli/cosipbftcontroller/mod_test.go b/cli/cosipbftcontroller/mod_test.go index 0a9237641..ac9e3525e 100644 --- a/cli/cosipbftcontroller/mod_test.go +++ b/cli/cosipbftcontroller/mod_test.go @@ -2,7 +2,6 @@ package controller import ( "encoding" - "io/ioutil" "os" "path/filepath" "testing" @@ -105,7 +104,7 @@ func TestMinimal_MalformedKey_OnStart(t *testing.T) { } func TestMinimal_OnStop(t *testing.T) { - dir, err := ioutil.TempDir(os.TempDir(), "dela-test-") + dir, err := os.MkdirTemp(os.TempDir(), "dela-test-") require.NoError(t, err) db, err := kv.New(filepath.Join(dir, "test.db")) @@ -152,7 +151,7 @@ func TestMinimal_OnStop(t *testing.T) { // Utility functions func makeFlags(t *testing.T) (cli.Flags, string, func()) { - dir, err := ioutil.TempDir(os.TempDir(), "dela-") + dir, err := os.MkdirTemp(os.TempDir(), "dela-") require.NoError(t, err) fset := make(node.FlagSet) diff --git a/cli/memcoin/mod_test.go b/cli/memcoin/mod_test.go index df4ec489a..876bbed15 100644 --- a/cli/memcoin/mod_test.go +++ b/cli/memcoin/mod_test.go @@ -3,7 +3,7 @@ package main import ( "bytes" "fmt" - "io/ioutil" + "io" "net" "os" "path/filepath" @@ -25,7 +25,7 @@ func TestMemcoin_Main(t *testing.T) { // be able to communicate, but the chain should proceed because of the // threshold. func TestMemcoin_Scenario_SetupAndTransactions(t *testing.T) { - dir, err := ioutil.TempDir(os.TempDir(), "memcoin1") + dir, err := os.MkdirTemp(os.TempDir(), "memcoin1") require.NoError(t, err) defer os.RemoveAll(dir) @@ -40,7 +40,7 @@ func TestMemcoin_Scenario_SetupAndTransactions(t *testing.T) { node4 := filepath.Join(dir, "node4") node5 := filepath.Join(dir, "node5") - cfg := config{Channel: sigs, Writer: ioutil.Discard} + cfg := config{Channel: sigs, Writer: io.Discard} runNode(t, node1, cfg, 2111, &wg) runNode(t, node2, cfg, 2112, &wg) @@ -117,7 +117,7 @@ func TestMemcoin_Scenario_SetupAndTransactions(t *testing.T) { // restart. It basically tests if the components are correctly loaded from the // persisten storage. func TestMemcoin_Scenario_RestartNode(t *testing.T) { - dir, err := ioutil.TempDir(os.TempDir(), "memcoin2") + dir, err := os.MkdirTemp(os.TempDir(), "memcoin2") require.NoError(t, err) defer os.RemoveAll(dir) @@ -132,7 +132,7 @@ func TestMemcoin_Scenario_RestartNode(t *testing.T) { wg := sync.WaitGroup{} wg.Add(2) - cfg := config{Channel: sigs, Writer: ioutil.Discard} + cfg := config{Channel: sigs, Writer: io.Discard} // Now the node are restarted. It should correctly follow the existing chain // and then participate to new blocks. @@ -177,7 +177,7 @@ func setupChain(t *testing.T, nodes []string, ports []uint16) { wg := sync.WaitGroup{} wg.Add(len(nodes)) - cfg := config{Channel: sigs, Writer: ioutil.Discard} + cfg := config{Channel: sigs, Writer: io.Discard} for i, node := range nodes { runNode(t, node, cfg, ports[i], &wg) diff --git a/contracts/evoting/controller/action.go b/contracts/evoting/controller/action.go index a030613be..b6134f6a8 100644 --- a/contracts/evoting/controller/action.go +++ b/contracts/evoting/controller/action.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "strconv" "strings" @@ -267,7 +266,7 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { } if resp.StatusCode != http.StatusOK { - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) return xerrors.Errorf("unexpected status: %s - %s", resp.Status, buf) } @@ -525,7 +524,7 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { } if resp.StatusCode != http.StatusOK { - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) return xerrors.Errorf("unexpected shuffle status: %s - %s", resp.Status, buf) } @@ -686,7 +685,7 @@ func castVote(electionID string, signed []byte, proxyAddr string) (string, error } if resp.StatusCode != http.StatusOK { - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) return "", xerrors.Errorf("unexpected status: %s - %s", resp.Status, buf) } @@ -721,7 +720,7 @@ func updateElection(secret kyber.Scalar, proxyAddr, electionIDHex, action string } if resp.StatusCode != http.StatusOK { - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) return resp.StatusCode, xerrors.Errorf("unexpected status: %s - %s", resp.Status, buf) } @@ -744,7 +743,7 @@ func initDKG(secret kyber.Scalar, proxyAddr, electionIDHex string) error { } if resp.StatusCode != http.StatusOK { - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) return xerrors.Errorf("unexpected status: %s - %s", resp.Status, buf) } @@ -772,7 +771,7 @@ func updateDKG(secret kyber.Scalar, proxyAddr, electionIDHex, action string) (in } if resp.StatusCode != http.StatusOK { - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) return resp.StatusCode, xerrors.Errorf("unexpected status: %s - %s", resp.Status, buf) } diff --git a/contracts/evoting/types/ballots.go b/contracts/evoting/types/ballots.go index bb95a160e..9cdeaea8c 100644 --- a/contracts/evoting/types/ballots.go +++ b/contracts/evoting/types/ballots.go @@ -171,8 +171,6 @@ func (b *Ballot) invalidate() { // Equal performs a loose comparison of a ballot. func (b *Ballot) Equal(other Ballot) bool { - fmt.Printf("b: %v\n", b) - fmt.Printf("other: %v\n", other) if len(b.SelectResultIDs) != len(other.SelectResultIDs) { return false } diff --git a/contracts/evoting/types/ballots_test.go b/contracts/evoting/types/ballots_test.go index 0dec0b364..c4e28ed73 100644 --- a/contracts/evoting/types/ballots_test.go +++ b/contracts/evoting/types/ballots_test.go @@ -158,7 +158,7 @@ func TestBallot_Unmarshal(t *testing.T) { err = b.Unmarshal(ballotWrongSelect, election) require.EqualError(t, err, "could not unmarshal select answers: "+ - "question Q1 has too many selected answers") + "failed to check number of answers: question Q1 has too many selected answers") // with not enough selected answers in select question ballotWrongSelect = string("select:" + encodedQuestionID(1) + ":1,0,0\n" + @@ -170,7 +170,7 @@ func TestBallot_Unmarshal(t *testing.T) { err = b.Unmarshal(ballotWrongSelect, election) require.EqualError(t, err, "could not unmarshal select answers: "+ - "question Q1 has not enough selected answers") + "failed to check number of answers: question Q1 has not enough selected answers") // with not enough answers in rank question ballotWrongRank := string("select:" + encodedQuestionID(1) + ":1,0,1\n" + @@ -192,7 +192,7 @@ func TestBallot_Unmarshal(t *testing.T) { err = b.Unmarshal(ballotWrongRank, election) require.EqualError(t, err, "could not unmarshal rank answers: "+ - "could not parse rank value for Q.Q2 : strconv.ParseInt: parsing \"x\": invalid syntax") + "could not parse rank value for Q.Q2: strconv.ParseInt: parsing \"x\": invalid syntax") // with too many selected answers in rank question ballotWrongRank = string("select:" + encodedQuestionID(1) + ":1,0,1\n" + @@ -204,7 +204,7 @@ func TestBallot_Unmarshal(t *testing.T) { err = b.Unmarshal(ballotWrongRank, election) require.EqualError(t, err, "could not unmarshal rank answers: "+ - "invalid rank not in range [0, MaxN[") + "invalid rank not in range [0, MaxN[: 3") // with valid ranks but one is selected twice ballotWrongRank = string("select:" + encodedQuestionID(1) + ":1,0,1\n" + @@ -216,7 +216,7 @@ func TestBallot_Unmarshal(t *testing.T) { err = b.Unmarshal(ballotWrongRank, election) require.EqualError(t, err, "could not unmarshal rank answers: "+ - "question Q2 has too many selected answers") + "failed to check number of answers: question Q2 has too many selected answers") // with not enough selected answers in rank question ballotWrongRank = string("select:" + encodedQuestionID(1) + ":1,0,1\n" + @@ -227,7 +227,8 @@ func TestBallot_Unmarshal(t *testing.T) { election.BallotSize = len(ballotWrongRank) err = b.Unmarshal(ballotWrongRank, election) - require.EqualError(t, err, "could not unmarshal rank answers: question"+ + require.EqualError(t, err, "could not unmarshal rank answers: "+ + "failed to check number of answers: question"+ " Q2 has not enough selected answers") // with not enough answers in text question @@ -252,7 +253,7 @@ func TestBallot_Unmarshal(t *testing.T) { err = b.Unmarshal(ballotWrongText, election) require.EqualError(t, err, "could not unmarshal text answers: "+ - "could not decode text for Q. Q4: illegal base64 data at input byte 12") + "could not decode text for Q.Q4: illegal base64 data at input byte 12") // with too many selected answers in text question election.Configuration.Scaffold[0].Texts[0].MaxN = 1 @@ -266,7 +267,7 @@ func TestBallot_Unmarshal(t *testing.T) { err = b.Unmarshal(ballotWrongText, election) require.EqualError(t, err, "could not unmarshal text answers: "+ - "question Q4 has too many selected answers") + "failed to check number of answers: question Q4 has too many selected answers") election.Configuration.Scaffold[0].Texts[0].MaxN = 2 @@ -280,7 +281,7 @@ func TestBallot_Unmarshal(t *testing.T) { err = b.Unmarshal(ballotWrongText, election) require.EqualError(t, err, "could not unmarshal text answers: "+ - "question Q4 has not enough selected answers") + "failed to check number of answers: question Q4 has not enough selected answers") // with unknown question type ballotWrongType := string("wrong:" + encodedQuestionID(1) + ":") @@ -382,14 +383,6 @@ func TestSubject_IsValid(t *testing.T) { valid := configuration.IsValid() require.True(t, valid) - // with wrongly ID not in base64 - mainSubject.ID = "zzz" - - configuration.Scaffold = []Subject{*mainSubject} - - valid = configuration.IsValid() - require.False(t, valid) - // with double IDs mainSubject.ID = ID(base64.StdEncoding.EncodeToString([]byte("S1"))) diff --git a/go.mod b/go.mod index d4523fb84..708aa7ec3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/dedis/d-voting -go 1.16 +go 1.19 require ( github.com/gorilla/mux v1.8.0 @@ -9,10 +9,42 @@ require ( github.com/rs/zerolog v1.19.0 github.com/stretchr/testify v1.7.0 github.com/uber/jaeger-client-go v2.25.0+incompatible - go.dedis.ch/dela v0.0.0-20220705073643-0b58fb2e471f + go.dedis.ch/dela v0.0.0-20220727144418-4a9466c2c294 go.dedis.ch/dela-apps v0.0.0-20211019120455-a0db752a0ba0 go.dedis.ch/kyber/v3 v3.1.0-alpha golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/opentracing-contrib/go-grpc v0.0.0-20200813121455-4a6760c71486 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/rs/xid v1.2.1 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/uber/jaeger-lib v2.4.0+incompatible // indirect + github.com/urfave/cli/v2 v2.2.0 // indirect + go.dedis.ch/fixbuf v1.0.3 // indirect + go.dedis.ch/protobuf v1.0.11 // indirect + go.etcd.io/bbolt v1.3.5 // indirect + go.uber.org/atomic v1.7.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect + google.golang.org/grpc v1.49.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/go.sum b/go.sum index 5984bf2bf..6539bccd6 100644 --- a/go.sum +++ b/go.sum @@ -124,8 +124,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -244,10 +244,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.dedis.ch/dela v0.0.0-20211018150429-1fdbe35cd189/go.mod h1:GVQ2MumgCcAkor2MmfRCoqTBsFjaaqt7HfJpQLhMGok= -go.dedis.ch/dela v0.0.0-20220428080424-2348afb6228a h1:Ahci1dtYYj9MIVOUjsA2kavD6oAEmnLZrwBctzHOYYA= -go.dedis.ch/dela v0.0.0-20220428080424-2348afb6228a/go.mod h1:IIIV0aR0f1c9z4jRB2HVYYeLK7XbQ6pfqv6RufaXmUg= -go.dedis.ch/dela v0.0.0-20220705073643-0b58fb2e471f h1:BbrlaRV3rFMTlKUQ+o2PJJQUlmyINIqB4FPmMlsSLjU= -go.dedis.ch/dela v0.0.0-20220705073643-0b58fb2e471f/go.mod h1:IIIV0aR0f1c9z4jRB2HVYYeLK7XbQ6pfqv6RufaXmUg= +go.dedis.ch/dela v0.0.0-20220727144418-4a9466c2c294 h1:q+kgJ4Cso4daxyR1owhz5jmFNxTG/O0xI4O/ppHaPdQ= +go.dedis.ch/dela v0.0.0-20220727144418-4a9466c2c294/go.mod h1:IIIV0aR0f1c9z4jRB2HVYYeLK7XbQ6pfqv6RufaXmUg= go.dedis.ch/dela-apps v0.0.0-20211019120455-a0db752a0ba0 h1:gPrJd+7QUuADpfToMKr80maGnjGPeB7ft7iNrkAtwGY= go.dedis.ch/dela-apps v0.0.0-20211019120455-a0db752a0ba0/go.mod h1:MoJSdm3LXkNtiKEK3eiBKgqFhory4v8sBr7ldFP/vFc= go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= @@ -536,8 +534,9 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= 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= @@ -549,8 +548,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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= diff --git a/integration/dvotingdela.go b/integration/dvotingdela.go index 8abb41b93..02ec67771 100644 --- a/integration/dvotingdela.go +++ b/integration/dvotingdela.go @@ -315,7 +315,7 @@ func (c dVotingNode) Setup(nodes ...dela) { token := joinable.GenerateToken(time.Hour) - certHash, err := joinable.GetCertificateStore().Hash(joinable.GetCertificate()) + certHash, err := joinable.GetCertificateStore().Hash(joinable.GetCertificateChain()) require.NoError(c.t, err) for _, n := range nodes { diff --git a/integration/integration_test.go b/integration/integration_test.go index 852819270..60985c858 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -8,7 +8,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "math/rand" "os" "strconv" @@ -60,7 +59,7 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { delaPkg.Logger = delaPkg.Logger.Level(zerolog.WarnLevel) - dirPath, err := ioutil.TempDir(os.TempDir(), "d-voting-three-votes") + dirPath, err := os.MkdirTemp(os.TempDir(), "d-voting-three-votes") require.NoError(t, err) defer os.RemoveAll(dirPath) @@ -72,7 +71,7 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { signer := createDVotingAccess(t, nodes, dirPath) - m := newTxManager(signer, nodes[0], time.Second*time.Duration(numNodes/2+1), numNodes*2) + m := newTxManager(signer, nodes[0], time.Second*time.Duration(numNodes/2+1), numNodes*4) err = grantAccess(m, signer) require.NoError(t, err) @@ -111,8 +110,9 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { err = closeElection(m, electionID, adminID) require.NoError(t, err) - waitForStatus(t, types.Closed, electionFac, electionID, nodes, numNodes, + err = waitForStatus(types.Closed, electionFac, electionID, nodes, numNodes, 5*time.Second) + require.NoError(t, err) // ##### SHUFFLE BALLOTS ##### t.Logf("initializing shuffle") @@ -125,8 +125,9 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { err = sActor.Shuffle(electionID) require.NoError(t, err) - waitForStatus(t, types.ShuffledBallots, electionFac, electionID, nodes, + err = waitForStatus(types.ShuffledBallots, electionFac, electionID, nodes, numNodes, 2*time.Second*time.Duration(numNodes)) + require.NoError(t, err) // ##### SUBMIT PUBLIC SHARES ##### t.Logf("submitting public shares") @@ -136,8 +137,9 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { err = actor.ComputePubshares() require.NoError(t, err) - waitForStatus(t, types.PubSharesSubmitted, electionFac, electionID, nodes, + err = waitForStatus(types.PubSharesSubmitted, electionFac, electionID, nodes, numNodes, 6*time.Second*time.Duration(numNodes)) + require.NoError(t, err) // ##### DECRYPT BALLOTS ##### t.Logf("decrypting") @@ -148,8 +150,9 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { err = decryptBallots(m, actor, election) require.NoError(t, err) - waitForStatus(t, types.ResultAvailable, electionFac, electionID, nodes, + err = waitForStatus(types.ResultAvailable, electionFac, electionID, nodes, numNodes, 1500*time.Millisecond*time.Duration(numVotes)) + require.NoError(t, err) t.Logf("get vote proof") election, err = getElection(electionFac, electionID, nodes[0].GetOrdering()) @@ -173,7 +176,12 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { require.True(t, ok) } - closeNodes(t, nodes) + fmt.Println("closing nodes") + + err = closeNodes(nodes) + require.NoError(t, err) + + fmt.Println("test done") } } @@ -544,7 +552,7 @@ func decryptBallots(m txManager, actor dkg.Actor, election types.Election) error return nil } -func closeNodes(t *testing.T, nodes []dVotingCosiDela) { +func closeNodes(nodes []dVotingCosiDela) error { wait := sync.WaitGroup{} wait.Add(len(nodes)) @@ -555,20 +563,19 @@ func closeNodes(t *testing.T, nodes []dVotingCosiDela) { }(n.(dVotingNode)) } - t.Log("stopping nodes...") - done := make(chan struct{}) go func() { - select { - case <-done: - case <-time.After(time.Second * 30): - t.Error("timeout while closing nodes") - } + wait.Wait() + close(done) }() - wait.Wait() - close(done) + select { + case <-done: + return nil + case <-time.After(time.Second * 30): + return xerrors.New("failed to close: timeout") + } } func encodeID(ID string) types.ID { @@ -577,31 +584,40 @@ func encodeID(ID string) types.ID { // waitForStatus polls the nodes until they all updated to the expected status // for the given election. An error is raised if the timeout expires. -func waitForStatus(t *testing.T, status types.Status, electionFac types.ElectionFactory, - electionID []byte, nodes []dVotingCosiDela, numNodes int, timeOut time.Duration) { - // set up a timer to fail the test in case we never reach the status - timer := time.NewTimer(timeOut) - go func() { - <-timer.C - t.Errorf("timed out while waiting for status %d", status) - }() +func waitForStatus(status types.Status, electionFac types.ElectionFactory, + electionID []byte, nodes []dVotingCosiDela, numNodes int, timeOut time.Duration) error { - isOK := func() bool { + expiration := time.Now().Add(timeOut) + + isOK := func() (bool, error) { for _, node := range nodes { election, err := getElection(electionFac, electionID, node.GetOrdering()) - require.NoError(t, err) + if err != nil { + return false, xerrors.Errorf("failed to get election: %v", err) + } if election.Status != status { - return false + return false, nil } } - return true + return true, nil } - for !isOK() { + for { + if time.Now().After(expiration) { + return xerrors.New("status check expired") + } + + ok, err := isOK() + if err != nil { + return xerrors.Errorf("failed to check status: %v", err) + } + + if ok { + return nil + } + time.Sleep(time.Millisecond * 100) } - - timer.Stop() } diff --git a/integration/performance_test.go b/integration/performance_test.go index e23e4321f..3e442a542 100644 --- a/integration/performance_test.go +++ b/integration/performance_test.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "encoding/base64" "fmt" - "io/ioutil" "math/rand" "os" "strconv" @@ -39,7 +38,7 @@ func BenchmarkIntegration_CustomVotesScenario(b *testing.B) { delaPkg.Logger = delaPkg.Logger.Level(zerolog.WarnLevel) - dirPath, err := ioutil.TempDir(os.TempDir(), "d-voting-three-votes") + dirPath, err := os.MkdirTemp(os.TempDir(), "d-voting-three-votes") require.NoError(b, err) defer os.RemoveAll(dirPath) diff --git a/integration/scenario_test.go b/integration/scenario_test.go index fe9a6e691..d2a9e258f 100644 --- a/integration/scenario_test.go +++ b/integration/scenario_test.go @@ -150,7 +150,10 @@ func startElectionProcess(wg *sync.WaitGroup, numNodes int, numVotes int, proxyA // Wait for DKG setup timeTable := make([]float64, 5) oldTime := time.Now() - waitForDKG(proxyArray[0], electionID, time.Second*100, t) + + err = waitForDKG(proxyArray[0], electionID, time.Second*100, t) + require.NoError(t, err) + currentTime := time.Now() diff := currentTime.Sub(oldTime) timeTable[0] = diff.Seconds() @@ -312,7 +315,9 @@ func startElectionProcess(wg *sync.WaitGroup, numNodes int, numVotes int, proxyA getElectionResponse = getElectionInfo(proxyAddr1, electionID, t) electionStatus = getElectionResponse.Status - waitForElectionStatus(proxyAddr1, electionID, uint16(3), time.Second*100, t) + err = waitForElectionStatus(proxyAddr1, electionID, uint16(3), time.Second*100, t) + require.NoError(t, err) + t.Logf("Status of the election : %v", electionStatus) require.Equal(t, uint16(3), electionStatus) @@ -339,7 +344,9 @@ func startElectionProcess(wg *sync.WaitGroup, numNodes int, numVotes int, proxyA electionStatus = getElectionResponse.Status oldTime = time.Now() - waitForElectionStatus(proxyAddr1, electionID, uint16(4), time.Second*300, t) + err = waitForElectionStatus(proxyAddr1, electionID, uint16(4), time.Second*300, t) + require.NoError(t, err) + currentTime = time.Now() diff = currentTime.Sub(oldTime) timeTable[4] = diff.Seconds() @@ -370,7 +377,9 @@ func startElectionProcess(wg *sync.WaitGroup, numNodes int, numVotes int, proxyA getElectionResponse = getElectionInfo(proxyAddr1, electionID, t) electionStatus = getElectionResponse.Status - waitForElectionStatus(proxyAddr1, electionID, uint16(5), time.Second*100, t) + err = waitForElectionStatus(proxyAddr1, electionID, uint16(5), time.Second*100, t) + require.NoError(t, err) + t.Logf("Status of the election : %v", electionStatus) require.Equal(t, uint16(5), electionStatus) @@ -631,52 +640,40 @@ func getDKGInfo(proxyAddr, electionID string, t *testing.T) ptypes.GetActorInfo } -func waitForDKG(proxyAddr, electionID string, timeOut time.Duration, t *testing.T) { - // set up a timer to fail the test in case we never reach the status - timer := time.NewTimer(timeOut) - go func() { - <-timer.C - t.Error("timed out while waiting for DKG setup") - }() +func waitForDKG(proxyAddr, electionID string, timeOut time.Duration, t *testing.T) error { + expired := time.Now().Add(timeOut) isOK := func() bool { infoDKG := getDKGInfo(proxyAddr, electionID, t) - // t.Logf("status is %v", infoDKG.Status) - if infoDKG.Status != 1 { - t.Logf("Node %v status is %v", proxyAddr, infoDKG.Status) - return false - } - return true + return infoDKG.Status == 1 } for !isOK() { - time.Sleep(time.Millisecond * 100) + if time.Now().After(expired) { + return xerrors.New("expired") + } + + time.Sleep(time.Millisecond * 1000) } - timer.Stop() + return nil } -func waitForElectionStatus(proxyAddr, electionID string, status uint16, timeOut time.Duration, t *testing.T) { - // set up a timer to fail the test in case we never reach the status - timer := time.NewTimer(timeOut) - go func() { - <-timer.C - t.Errorf("timed out while waiting for enter status %v", status) - }() +func waitForElectionStatus(proxyAddr, electionID string, status uint16, timeOut time.Duration, t *testing.T) error { + expired := time.Now().Add(timeOut) isOK := func() bool { infoElection := getElectionInfo(proxyAddr, electionID, t) - // t.Logf("status is %v", infoDKG.Status) - if infoElection.Status != status { - t.Logf("Node %v status is %v", proxyAddr, infoElection.Status) - return false - } - return true + return infoElection.Status == status } for !isOK() { - time.Sleep(time.Millisecond * 100) + if time.Now().After(expired) { + return xerrors.New("expired") + } + + time.Sleep(time.Millisecond * 1000) } - timer.Stop() + return nil } diff --git a/internal/testing/fake/ordering.go b/internal/testing/fake/ordering.go index f3c6cabd7..1c9e8da2e 100644 --- a/internal/testing/fake/ordering.go +++ b/internal/testing/fake/ordering.go @@ -3,7 +3,6 @@ package fake import ( "context" "encoding/hex" - "fmt" electionTypes "github.com/dedis/d-voting/contracts/evoting/types" "go.dedis.ch/dela/core/ordering" @@ -112,7 +111,6 @@ func (f *Service) AddTx(tx Transaction) { f.Status = true - fmt.Println("watch", results[0]) f.Channel <- ordering.Event{ Index: 0, Transactions: results, diff --git a/services/dkg/pedersen/controller/action_test.go b/services/dkg/pedersen/controller/action_test.go index ab3156dbd..8634ef6a3 100644 --- a/services/dkg/pedersen/controller/action_test.go +++ b/services/dkg/pedersen/controller/action_test.go @@ -2,10 +2,11 @@ package controller import ( "encoding/hex" - "golang.org/x/xerrors" - "io/ioutil" + "io" "testing" + "golang.org/x/xerrors" + "github.com/dedis/d-voting/internal/testing/fake" "github.com/dedis/d-voting/services/dkg" "github.com/stretchr/testify/require" @@ -21,7 +22,7 @@ func TestInitAction_Execute(t *testing.T) { ctx := node.Context{ Injector: node.NewInjector(), Flags: flags, - Out: ioutil.Discard, + Out: io.Discard, } action := initAction{} @@ -89,7 +90,7 @@ func TestSetupAction_Execute(t *testing.T) { ctx := node.Context{ Injector: inj, - Out: ioutil.Discard, + Out: io.Discard, } electionID := "deadbeef" @@ -151,7 +152,7 @@ func TestExportInfoAction_Execute(t *testing.T) { ctx := node.Context{ Injector: node.NewInjector(), Flags: make(node.FlagSet), - Out: ioutil.Discard, + Out: io.Discard, } action := exportInfoAction{} diff --git a/services/dkg/pedersen/controller/mod_test.go b/services/dkg/pedersen/controller/mod_test.go index 1f1af6f4d..adc148f72 100644 --- a/services/dkg/pedersen/controller/mod_test.go +++ b/services/dkg/pedersen/controller/mod_test.go @@ -1,14 +1,15 @@ package controller import ( - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/crypto/bls" - "io/ioutil" + "io" "os" "path/filepath" "testing" + "go.dedis.ch/dela/core/txn/signed" + "go.dedis.ch/dela/core/validation/simple" + "go.dedis.ch/dela/crypto/bls" + "github.com/dedis/d-voting/internal/testing/fake" "github.com/stretchr/testify/require" "go.dedis.ch/dela/cli/node" @@ -26,7 +27,7 @@ func TestMinimal_OnStart(t *testing.T) { ctx := node.Context{ Injector: node.NewInjector(), Flags: flags, - Out: ioutil.Discard, + Out: io.Discard, } // Should miss mino.Mino @@ -82,7 +83,7 @@ func TestMinimal_OnStart(t *testing.T) { err = c.OnStart(nil, ctx.Injector) require.EqualError(t, err, "no flags") - dir, err := ioutil.TempDir(os.TempDir(), "memcoin1") + dir, err := os.MkdirTemp(os.TempDir(), "memcoin1") require.NoError(t, err) flags.strings["config"] = dir diff --git a/services/dkg/pedersen/handler.go b/services/dkg/pedersen/handler.go index 33a9f004e..70135bf72 100644 --- a/services/dkg/pedersen/handler.go +++ b/services/dkg/pedersen/handler.go @@ -2,15 +2,19 @@ package pedersen import ( "bytes" + "container/list" "context" "crypto/sha256" "encoding/hex" "encoding/json" + "errors" + "io" "math/rand" "sync" "time" "github.com/dedis/d-voting/contracts/evoting" + "github.com/rs/zerolog" "go.dedis.ch/dela/core/execution/native" "go.dedis.ch/dela/core/txn" "go.dedis.ch/dela/core/txn/pool" @@ -33,8 +37,13 @@ import ( "golang.org/x/xerrors" ) -// recvResponseTimeout is the maximum time a node will wait for a response -const recvResponseTimeout = time.Second * 10 +// the receiving time out, after which we check if the DKG setup is done or not. +// Allows to exit the loop. +const recvTimeout = time.Second * 2 + +// the time after which we expect new messages (deals or responses) to be +// received. +const retryTimeout = time.Second * 1 // Handler represents the RPC executed on each node // @@ -58,6 +67,9 @@ type Handler struct { context serde.Context electionFac serde.Factory + + log zerolog.Logger + running bool } // NewHandler creates a new handler @@ -70,6 +82,8 @@ func NewHandler(me mino.Address, service ordering.Service, pool pool.Pool, startRes := handlerData.StartRes privShare := handlerData.PrivShare + log := dela.Logger.With().Str("role", "DKG").Str("address", me.String()).Logger() + return &Handler{ me: me, service: service, @@ -84,6 +98,9 @@ func NewHandler(me mino.Address, service ordering.Service, pool pool.Pool, context: context, electionFac: electionFac, + + log: log, + running: false, } } @@ -97,86 +114,118 @@ func (h *Handler) Stream(out mino.Sender, in mino.Receiver) error { // messages to the other nodes, and then we might get their messages before // the start message. - deals := []types.Deal{} - responses := []*pedersen.Response{} - -mainSwitch: - from, msg, err := in.Recv(context.Background()) - if err != nil { - return xerrors.Errorf("failed to receive: %v", err) + // We make sure not additional request is accepted if a setup is in + // progress. + h.Lock() + if !h.startRes.Done() && h.running { + h.Unlock() + return xerrors.Errorf("DKG is running") } + if !h.startRes.Done() { + // This is the first setup + h.running = true + } + h.Unlock() - // We expect a Start message or a decrypt request at first, but we might - // receive other messages in the meantime, like a Deal. - switch msg := msg.(type) { - - case types.Start: - err := h.start(msg, deals, responses, from, out, in) - if err != nil { - return xerrors.Errorf("failed to start: %v", err) - } + deals := list.New() + responses := list.New() - case types.Deal: - // This is a special case where a DKG started, some nodes received the - // start signal and started sending their deals but we have not yet - // received our start signal. In this case we collect the Deals while - // waiting for the start signal. - deals = append(deals, msg) - goto mainSwitch - - case types.Response: - // This is a special case where a DKG started, some nodes received the - // start signal and started sending their deals but we have not yet - // received our start signal. In this case we collect the Response while - // waiting for the start signal. - response := &pedersen.Response{ - Index: msg.GetIndex(), - Response: &vss.Response{ - SessionID: msg.GetResponse().GetSessionID(), - Index: msg.GetResponse().GetIndex(), - Status: msg.GetResponse().GetStatus(), - Signature: msg.GetResponse().GetSignature(), - }, - } - responses = append(responses, response) - goto mainSwitch + for { + ctx, cancel := context.WithTimeout(context.Background(), recvTimeout) + from, msg, err := in.Recv(ctx) + cancel() - case types.DecryptRequest: + if errors.Is(err, context.DeadlineExceeded) { + if h.startRes.Done() { + return nil + } - if !h.startRes.Done() { - return xerrors.Errorf("you must first initialize DKG. Did you " + - "call setup() first?") + continue } - err = h.handleDecryptRequest(msg.GetElectionId()) - if err != nil { - return xerrors.Errorf("could not send pubShares: %v", err) + if errors.Is(err, io.EOF) { + return nil } - case types.GetPeerPubKey: - response := types.NewGetPeerPubKeyResp(h.pubKey) - errs := out.Send(response, from) - err = <-errs if err != nil { - return xerrors.Errorf("got an error while sending the get peer pubkey resp "+ - "reply: %v", err) + return xerrors.Errorf("failed to receive: %v", err) } - goto mainSwitch - default: - return xerrors.Errorf("expected Start message, decrypt request or "+ - "Deal as first message, got: %T", msg) - } + h.log.Info().Msgf("received message from %s: %T\n", from, msg) - return nil + // We expect a Start message or a decrypt request at first, but we might + // receive other messages in the meantime, like a Deal. + switch msg := msg.(type) { + + case types.Start: + err := h.start(msg, deals, responses, from, out) + if err != nil { + return xerrors.Errorf("failed to start: %v", err) + } + + case types.Deal: + // This is a special case where a DKG started, some nodes received the + // start signal and started sending their deals but we have not yet + // received our start signal. In this case we collect the Deals while + // waiting for the start signal. + h.Lock() + deals.PushBack(msg) + h.Unlock() + + case types.Response: + // This is a special case where a DKG started, some nodes received the + // start signal and started sending their deals but we have not yet + // received our start signal. In this case we collect the Response while + // waiting for the start signal. + response := &pedersen.Response{ + Index: msg.GetIndex(), + Response: &vss.Response{ + SessionID: msg.GetResponse().GetSessionID(), + Index: msg.GetResponse().GetIndex(), + Status: msg.GetResponse().GetStatus(), + Signature: msg.GetResponse().GetSignature(), + }, + } + + h.Lock() + responses.PushBack(response) + h.Unlock() + + case types.DecryptRequest: + + if !h.startRes.Done() { + return xerrors.Errorf("you must first initialize DKG. Did you " + + "call setup() first?") + } + + err = h.handleDecryptRequest(msg.GetElectionId()) + if err != nil { + return xerrors.Errorf("could not send pubShares: %v", err) + } + + return nil + + case types.GetPeerPubKey: + response := types.NewGetPeerPubKeyResp(h.pubKey) + errs := out.Send(response, from) + err = <-errs + if err != nil { + return xerrors.Errorf("got an error while sending the get peer pubkey resp "+ + "reply: %v", err) + } + + default: + return xerrors.Errorf("expected Start message, decrypt request or "+ + "Deal as first message, got: %T", msg) + } + } } // start is called when the node has received its start message. Note that we // might have already received some deals from other nodes in the meantime. The // function handles the DKG creation protocol. -func (h *Handler) start(start types.Start, receivedDeals []types.Deal, - receivedResps []*pedersen.Response, from mino.Address, out mino.Sender, - in mino.Receiver) error { +func (h *Handler) start(start types.Start, deals, resps *list.List, from mino.Address, + out mino.Sender) error { if len(start.GetAddresses()) != len(start.GetPublicKeys()) { return xerrors.Errorf("there should be as many players as "+ @@ -184,23 +233,73 @@ func (h *Handler) start(start types.Start, receivedDeals []types.Deal, } // create the DKG - thrshold := threshold.ByzantineThreshold(len(start.GetPublicKeys())) - d, err := pedersen.NewDistKeyGenerator(suite, h.privKey, start.GetPublicKeys(), thrshold) + t := threshold.ByzantineThreshold(len(start.GetPublicKeys())) + d, err := pedersen.NewDistKeyGenerator(suite, h.privKey, start.GetPublicKeys(), t) if err != nil { return xerrors.Errorf("failed to create new DKG: %v", err) } + h.dkg = d + h.startRes.SetParticipants(start.GetAddresses()) + + // asynchronously start the procedure. This allows for receiving messages + // in the main for loop in the meantime. + go h.doDKG(deals, resps, out, from) + + return nil +} + +// doDKG calls the subsequent DKG steps +func (h *Handler) doDKG(deals, resps *list.List, out mino.Sender, from mino.Address) { + h.log.Info().Str("action", "deal").Msg("new state") + h.deal(out) + + h.log.Info().Str("action", "respond").Msg("new state") + h.respond(deals, out) + + h.log.Info().Str("action", "certify").Msg("new state") + err := h.certify(resps, out) + if err != nil { + dela.Logger.Error().Msgf("failed to certify: %v", err) + return + } + + h.log.Info().Str("action", "finalize").Msg("new state") + + // Send back the public DKG key + distKey, err := h.dkg.DistKeyShare() + if err != nil { + dela.Logger.Error().Msgf("failed to get distr key: %v", err) + return + } + + // Update the state before sending to acknowledgement to the + // orchestrator, so that it can process decrypt requests right away. + h.startRes.SetDistKey(distKey.Public()) + + h.Lock() + h.privShare = distKey.PriShare() + h.Unlock() + + done := types.NewStartDone(distKey.Public()) + err = <-out.Send(done, from) + if err != nil { + dela.Logger.Error().Msgf("got an error while sending pub key: %v", err) + return + } +} + +func (h *Handler) deal(out mino.Sender) error { + // Send my Deals to the other nodes. Note that we take an optimistic + // approach and don't check if the deals are correctly sent to the node. The + // DKG setup needs a full connectivity anyway, and for the moment everything + // fails if this assumption breaks. - // send my Deals to the other nodes deals, err := h.dkg.Deals() if err != nil { return xerrors.Errorf("failed to compute the deals: %v", err) } - // use a waitgroup to send all the deals asynchronously and wait - var wg sync.WaitGroup - wg.Add(len(deals)) - for i, deal := range deals { dealMsg := types.NewDeal( deal.Index, @@ -213,158 +312,67 @@ func (h *Handler) start(start types.Start, receivedDeals []types.Deal, ), ) - errs := out.Send(dealMsg, start.GetAddresses()[i]) - go func(errs <-chan error) { - err, more := <-errs - if more { - dela.Logger.Warn().Msgf("got an error while sending deal: %v", err) - } - wg.Done() - }(errs) - } + to := h.startRes.participants[i] - wg.Wait() + h.log.Info().Str("to", to.String()).Msg("send deal") - dela.Logger.Trace().Msgf("%s sent all its deals", h.me) + out.Send(dealMsg, to) + } + return nil +} + +func (h *Handler) respond(deals *list.List, out mino.Sender) { numReceivedDeals := 0 - // Process the deals we received before the start message - for _, deal := range receivedDeals { - err = h.handleDeal(deal, from, start.GetAddresses(), out) - if err != nil { - dela.Logger.Warn().Msgf("%s failed to handle received deal "+ - "from %s: %v", h.me, from, err) + for numReceivedDeals < len(h.startRes.participants)-1 { + h.Lock() + deal := deals.Front() + if deal != nil { + deals.Remove(deal) } - numReceivedDeals++ - } + h.Unlock() - // If there are N nodes, then N nodes first send (N-1) Deals. Then each node - // send a response to every other nodes. So the number of responses a node - // get is (N-1) * (N-1), where (N-1) should equal len(deals). - for numReceivedDeals < len(deals) { - from, msg, err := in.Recv(context.Background()) - if err != nil { - return xerrors.Errorf("failed to receive after sending deals: %v", err) + if deal == nil { + time.Sleep(retryTimeout) + continue } - switch msg := msg.(type) { - - case types.Deal: - // Process the Deal and Send the response to all the other nodes - err = h.handleDeal(msg, from, start.GetAddresses(), out) - if err != nil { - dela.Logger.Warn().Msgf("%s failed to handle received deal "+ - "from %s: %v", h.me, from, err) - return xerrors.Errorf("failed to handle deal from '%s': %v", from, err) - } - numReceivedDeals++ - - case types.Response: - // Process responses - dela.Logger.Trace().Msgf("%s received response from %s", h.me, from) - response := &pedersen.Response{ - Index: msg.GetIndex(), - Response: &vss.Response{ - SessionID: msg.GetResponse().GetSessionID(), - Index: msg.GetResponse().GetIndex(), - Status: msg.GetResponse().GetStatus(), - Signature: msg.GetResponse().GetSignature(), - }, - } - receivedResps = append(receivedResps, response) - - default: - return xerrors.Errorf("unexpected message: %T", msg) + err := h.handleDeal(deal.Value.(types.Deal), out) + if err != nil { + h.log.Warn().Msgf("failed to handle received deal: %v", err) } - } - - h.startRes.SetParticipants(start.GetAddresses()) - err = h.certify(receivedResps, out, in, from) - if err != nil { - return xerrors.Errorf("failed to certify: %v", err) + numReceivedDeals++ } - - return nil } -func (h *Handler) certify(resps []*pedersen.Response, out mino.Sender, - in mino.Receiver, from mino.Address) error { - - for _, response := range resps { - _, err := h.dkg.ProcessResponse(response) - if err != nil { - dela.Logger.Warn().Msgf("%s failed to process response: %v", h.me, err) - } - } +func (h *Handler) certify(resps *list.List, out mino.Sender) error { for !h.dkg.Certified() { - ctx, cancel := context.WithTimeout(context.Background(), - recvResponseTimeout) - defer cancel() - - from, msg, err := in.Recv(ctx) - if err != nil { - return xerrors.Errorf("failed to receive after sending deals: %v", err) + h.Lock() + resp := resps.Front() + if resp != nil { + resps.Remove(resp) } + h.Unlock() - switch msg := msg.(type) { - - case types.Response: - // Processing responses - dela.Logger.Trace().Msgf("%s received response from %s", h.me, from) - response := &pedersen.Response{ - Index: msg.GetIndex(), - Response: &vss.Response{ - SessionID: msg.GetResponse().GetSessionID(), - Index: msg.GetResponse().GetIndex(), - Status: msg.GetResponse().GetStatus(), - Signature: msg.GetResponse().GetSignature(), - }, - } - - _, err = h.dkg.ProcessResponse(response) - if err != nil { - dela.Logger.Warn().Msgf("%s, failed to process response "+ - "from '%s': %v", h.me, from, err) - } - - default: - return xerrors.Errorf("expected a response, got: %T", msg) + if resp == nil { + time.Sleep(retryTimeout) + continue } - } - - dela.Logger.Trace().Msgf("%s is certified", h.me) - // Send back the public DKG key - distKey, err := h.dkg.DistKeyShare() - if err != nil { - return xerrors.Errorf("failed to get distr key: %v", err) - } - - // Update the state before sending to acknowledgement to the - // orchestrator, so that it can process decrypt requests right away. - h.startRes.SetDistKey(distKey.Public()) - - h.Lock() - h.privShare = distKey.PriShare() - h.Unlock() - - done := types.NewStartDone(distKey.Public()) - err = <-out.Send(done, from) - if err != nil { - return xerrors.Errorf("got an error while sending pub key: %v", err) + _, err := h.dkg.ProcessResponse(resp.Value.(*pedersen.Response)) + if err != nil { + h.log.Warn().Msgf("%s failed to process response: %v", h.me, err) + } } return nil } // handleDeal process the Deal and send the responses to the other nodes. -func (h *Handler) handleDeal(msg types.Deal, from mino.Address, addrs []mino.Address, - out mino.Sender) error { - - dela.Logger.Trace().Msgf("%s received deal from %s", h.me, from) +func (h *Handler) handleDeal(msg types.Deal, out mino.Sender) error { deal := &pedersen.Deal{ Index: msg.GetIndex(), @@ -379,8 +387,7 @@ func (h *Handler) handleDeal(msg types.Deal, from mino.Address, addrs []mino.Add response, err := h.dkg.ProcessDeal(deal) if err != nil { - return xerrors.Errorf("failed to process deal from %s: %v", - h.me, err) + return xerrors.Errorf("failed to process deal: %v", err) } resp := types.NewResponse( @@ -393,19 +400,17 @@ func (h *Handler) handleDeal(msg types.Deal, from mino.Address, addrs []mino.Add ), ) - for _, addr := range addrs { + for _, addr := range h.startRes.participants { if addr.Equal(h.me) { continue } errs := out.Send(resp, addr) + err = <-errs if err != nil { - dela.Logger.Warn().Msgf("got an error while sending "+ - "response: %v", err) return xerrors.Errorf("failed to send response to '%s': %v", addr, err) } - } return nil @@ -471,7 +476,7 @@ func (h *Handler) handleDecryptRequest(electionID string) error { return xerrors.Errorf("failed to make tx: %v", err) } - //TODO: Define in term of size of election ? (same in shuffle) + // TODO: Define in term of size of election ? (same in shuffle) watchTimeout := 4 + rand.Intn(election.ShuffleThreshold) watchCtx, cancel := context.WithTimeout(context.Background(), time.Duration(watchTimeout)*time.Second) defer cancel() diff --git a/services/dkg/pedersen/handler_test.go b/services/dkg/pedersen/handler_test.go index ccf21f9bf..a60d789d0 100644 --- a/services/dkg/pedersen/handler_test.go +++ b/services/dkg/pedersen/handler_test.go @@ -1,8 +1,10 @@ package pedersen import ( + "container/list" "encoding/hex" "strconv" + "strings" "testing" electionTypes "github.com/dedis/d-voting/contracts/evoting/types" @@ -17,7 +19,6 @@ import ( "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/share" pedersen "go.dedis.ch/kyber/v3/share/dkg/pedersen" - vss "go.dedis.ch/kyber/v3/share/vss/pedersen" ) func TestHandler_Stream(t *testing.T) { @@ -111,30 +112,16 @@ func TestHandler_Start(t *testing.T) { []mino.Address{fake.NewAddress(0)}, []kyber.Point{}, ) - err := h.start(start, []types.Deal{}, []*pedersen.Response{}, nil, nil, nil) + err := h.start(start, list.New(), list.New(), nil, nil) require.EqualError(t, err, "there should be as many players as pubKey: 1 := 0") start = types.NewStart( []mino.Address{fake.NewAddress(0), fake.NewAddress(1)}, []kyber.Point{pubKey, suite.Point()}, ) - receiver := fake.NewBadReceiver() - err = h.start(start, []types.Deal{}, []*pedersen.Response{}, nil, fake.Sender{}, receiver) - require.EqualError(t, err, fake.Err("failed to receive after sending deals")) - - receiver = fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.Deal{}), - fake.NewRecvMsg(fake.NewAddress(0), nil), - ) - err = h.start(start, []types.Deal{}, []*pedersen.Response{}, nil, fake.Sender{}, receiver) - require.EqualError(t, err, "failed to handle deal from 'fake.Address[0]': failed to process deal from %!s(): schnorr: signature of invalid length 0 instead of 64") - - err = h.start(start, []types.Deal{}, []*pedersen.Response{}, nil, fake.Sender{}, &fake.Receiver{}) - require.EqualError(t, err, "unexpected message: ") - // We check when there is already something in the slice if Deals - err = h.start(start, []types.Deal{{}}, []*pedersen.Response{}, nil, fake.NewBadSender(), &fake.Receiver{}) - require.EqualError(t, err, "failed to certify: expected a response, got: ") + err = h.start(start, list.New(), list.New(), nil, fake.Sender{}) + require.NoError(t, err) } func TestHandler_Certify(t *testing.T) { @@ -148,19 +135,16 @@ func TestHandler_Certify(t *testing.T) { startRes: &state{}, dkg: dkg, } - receiver := fake.NewBadReceiver() - responses := []*pedersen.Response{{Response: &vss.Response{}}} - err = h.certify(responses, fake.Sender{}, receiver, nil) - require.EqualError(t, err, fake.Err("failed to receive after sending deals")) + responses := list.New() dkg = getCertified(t) h.dkg = dkg - err = h.certify(responses, fake.NewBadSender(), &fake.Receiver{}, nil) - require.EqualError(t, err, fake.Err("got an error while sending pub key")) + err = h.certify(responses, fake.NewBadSender()) + require.NoError(t, err) } -func TestHandler_HandleDeal(t *testing.T) { +func TestHandler_HandleDeal_Fail(t *testing.T) { privKey1 := suite.Scalar().Pick(suite.RandomStream()) pubKey1 := suite.Point().Mul(privKey1, nil) privKey2 := suite.Scalar().Pick(suite.RandomStream()) @@ -194,9 +178,58 @@ func TestHandler_HandleDeal(t *testing.T) { h := Handler{ dkg: dkg1, + startRes: &state{ + participants: []mino.Address{fake.NewAddress(0)}, + }, } - err = h.handleDeal(dealMsg, nil, []mino.Address{fake.NewAddress(0)}, fake.NewBadSender()) + err = h.handleDeal(dealMsg, fake.NewBadSender()) require.EqualError(t, err, fake.Err("failed to send response to 'fake.Address[0]'")) + + err = h.handleDeal(dealMsg, fake.Sender{}) + require.True(t, strings.Contains(err.Error(), "failed to process deal")) +} + +func TestHandler_HandleDeal_Ok(t *testing.T) { + privKey1 := suite.Scalar().Pick(suite.RandomStream()) + pubKey1 := suite.Point().Mul(privKey1, nil) + privKey2 := suite.Scalar().Pick(suite.RandomStream()) + pubKey2 := suite.Point().Mul(privKey2, nil) + + dkg1, err := pedersen.NewDistKeyGenerator(suite, privKey1, []kyber.Point{pubKey1, pubKey2}, 2) + require.NoError(t, err) + + dkg2, err := pedersen.NewDistKeyGenerator(suite, privKey2, []kyber.Point{pubKey1, pubKey2}, 2) + require.NoError(t, err) + + deals, err := dkg2.Deals() + require.Len(t, deals, 1) + require.NoError(t, err) + + var deal *pedersen.Deal + for _, d := range deals { + deal = d + } + + dealMsg := types.NewDeal( + deal.Index, + deal.Signature, + types.NewEncryptedDeal( + deal.Deal.DHKey, + deal.Deal.Signature, + deal.Deal.Nonce, + deal.Deal.Cipher, + ), + ) + + h := Handler{ + dkg: dkg1, + startRes: &state{ + participants: []mino.Address{fake.NewAddress(0)}, + }, + } + + err = h.handleDeal(dealMsg, fake.Sender{}) + require.NoError(t, err) } func TestHandlerData_MarshalJSON(t *testing.T) { @@ -340,6 +373,7 @@ func TestHandler_HandlerDecryptRequest(t *testing.T) { } +// ----------------------------------------------------------------------------- // Utility functions func getCertified(t *testing.T) *pedersen.DistKeyGenerator { diff --git a/services/dkg/pedersen/mod.go b/services/dkg/pedersen/mod.go index 3e9645135..a69366d4e 100644 --- a/services/dkg/pedersen/mod.go +++ b/services/dkg/pedersen/mod.go @@ -14,6 +14,7 @@ import ( "github.com/dedis/d-voting/contracts/evoting" etypes "github.com/dedis/d-voting/contracts/evoting/types" + "github.com/rs/zerolog" "github.com/dedis/d-voting/internal/tracing" "github.com/dedis/d-voting/services/dkg" @@ -124,6 +125,8 @@ func (s *Pedersen) NewActor(electionIDBuf []byte, pool pool.Pool, txmngr txn.Man no := s.mino.WithSegment(electionID) rpc := mino.MustCreateRPC(no, RPC, h, s.factory) + log := dela.Logger.With().Str("role", "DKG actor").Logger() + a := &Actor{ rpc: rpc, factory: s.factory, @@ -133,6 +136,7 @@ func (s *Pedersen) NewActor(electionIDBuf []byte, pool pool.Pool, txmngr txn.Man handler: h, electionID: electionID, status: dkg.Status{Status: dkg.Initialized}, + log: log, } evoting.PromElectionDkgStatus.WithLabelValues(electionID).Set(float64(dkg.Initialized)) @@ -164,6 +168,7 @@ type Actor struct { handler *Handler electionID string status dkg.Status + log zerolog.Logger } func (a *Actor) setErr(err error, args map[string]interface{}) { @@ -180,6 +185,7 @@ func (a *Actor) setErr(err error, args map[string]interface{}) { // participating nodes. This function updates the actor's status in case of // error to allow asynchronous call of this function. func (a *Actor) Setup() (kyber.Point, error) { + a.log.Info().Msg("setup") if a.handler.startRes.Done() { err := xerrors.New("setup() was already called, only one call is allowed") @@ -211,6 +217,8 @@ func (a *Actor) Setup() (kyber.Point, error) { addrs = append(addrs, addrIter.GetNext()) } + a.log.Info().Msgf("sending getkey request to %v", addrs) + // get the peer DKG pub keys getPeerKey := types.NewGetPeerPubKey() errs := sender.Send(getPeerKey, addrs...) @@ -260,6 +268,8 @@ func (a *Actor) Setup() (kyber.Point, error) { message := types.NewStart(associatedAddrs, dkgPeerPubkeys) + a.log.Info().Msgf("sending start to %s", addrs) + errs = sender.Send(message, addrs...) err = <-errs if err != nil { @@ -272,7 +282,7 @@ func (a *Actor) Setup() (kyber.Point, error) { for i := 0; i < lenAddrs; i++ { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*200) defer cancel() addr, msg, err := receiver.Recv(ctx) @@ -299,6 +309,8 @@ func (a *Actor) Setup() (kyber.Point, error) { a.setErr(err, nil) return nil, err } + + a.log.Info().Msgf("ok for %s", addr.String()) } a.status = dkg.Status{Status: dkg.Setup} diff --git a/services/dkg/pedersen/mod_test.go b/services/dkg/pedersen/mod_test.go index 7a44821d9..542bc92ac 100644 --- a/services/dkg/pedersen/mod_test.go +++ b/services/dkg/pedersen/mod_test.go @@ -1,13 +1,16 @@ package pedersen import ( + "bytes" "encoding/hex" "encoding/json" "net/url" "strconv" + "strings" "testing" "time" + "go.dedis.ch/dela" "go.dedis.ch/dela/core/access" "go.dedis.ch/dela/core/ordering" "go.dedis.ch/dela/core/txn/signed" @@ -20,6 +23,7 @@ import ( "github.com/dedis/d-voting/services/dkg" "github.com/dedis/d-voting/services/dkg/pedersen/types" "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" "go.dedis.ch/dela/core/ordering/cosipbft/authority" "go.dedis.ch/dela/core/store/kv" @@ -468,7 +472,7 @@ func TestPedersen_Scenario(t *testing.T) { token := joinable.GenerateToken(time.Hour) - certHash, err := joinable.GetCertificateStore().Hash(joinable.GetCertificate()) + certHash, err := joinable.GetCertificateStore().Hash(joinable.GetCertificateChain()) require.NoError(t, err) for _, n := range minos { @@ -623,8 +627,19 @@ func TestPedersen_ComputePubshares_SenderFailed(t *testing.T) { rpc: fake.NewStreamRPC(nil, fake.NewBadSender()), } + oldLog := dela.Logger + defer func() { + dela.Logger = oldLog + }() + + out := new(bytes.Buffer) + dela.Logger = zerolog.New(out) + + // should only output a warning err := a.ComputePubshares() - require.EqualError(t, err, fake.Err("failed to send decrypt request")) + require.NoError(t, err) + + require.True(t, strings.Contains(out.String(), "failed to send decrypt request"), out.String()) } func TestPedersen_ComputePubshares_OK(t *testing.T) { diff --git a/services/shuffle/neff/controller/action_test.go b/services/shuffle/neff/controller/action_test.go index 18a3665ac..3dcb56d8e 100644 --- a/services/shuffle/neff/controller/action_test.go +++ b/services/shuffle/neff/controller/action_test.go @@ -1,7 +1,7 @@ package controller import ( - "io/ioutil" + "io" "testing" "github.com/stretchr/testify/require" @@ -12,7 +12,7 @@ func TestInitAction_Execute(t *testing.T) { ctx := node.Context{ Injector: node.NewInjector(), Flags: make(node.FlagSet), - Out: ioutil.Discard, + Out: io.Discard, } action := InitAction{} diff --git a/services/shuffle/neff/mod_test.go b/services/shuffle/neff/mod_test.go index c6125f84d..c310e15dd 100644 --- a/services/shuffle/neff/mod_test.go +++ b/services/shuffle/neff/mod_test.go @@ -1,9 +1,12 @@ package neff import ( + "bytes" "encoding/hex" + "strings" "testing" + "go.dedis.ch/dela" "go.dedis.ch/dela/core/ordering/cosipbft/authority" "go.dedis.ch/dela/crypto" "go.dedis.ch/dela/mino" @@ -13,6 +16,7 @@ import ( etypes "github.com/dedis/d-voting/contracts/evoting/types" "github.com/dedis/d-voting/internal/testing/fake" "github.com/dedis/d-voting/services/shuffle/neff/types" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" ) @@ -72,8 +76,18 @@ func TestNeffShuffle_Shuffle(t *testing.T) { rpc := fake.NewStreamRPC(fake.NewReceiver(), fake.NewBadSender()) actor.rpc = rpc + oldLog := dela.Logger + defer func() { + dela.Logger = oldLog + }() + + out := new(bytes.Buffer) + dela.Logger = zerolog.New(out) + + // should only output a warning err = actor.Shuffle(electionIDBuf) - require.EqualError(t, err, fake.Err("failed to start shuffle")) + require.NoError(t, err) + require.True(t, strings.Contains(out.String(), "failed to start shuffle"), out.String()) rpc = fake.NewStreamRPC(fake.NewBadReceiver(), fake.Sender{}) actor.rpc = rpc