Skip to content

Commit

Permalink
Adding cli
Browse files Browse the repository at this point in the history
  • Loading branch information
ineiti committed May 21, 2024
1 parent 20dce82 commit 482c4a8
Show file tree
Hide file tree
Showing 2 changed files with 396 additions and 0 deletions.
99 changes: 99 additions & 0 deletions cli/dvoting-libp2p/mod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Package main implements the dvoting backend
//
// Unix example:
//
// # Expect GOPATH to be correctly set to have dvoting available.
// go install
//
// dvoting --config /tmp/node1 start --port 2001 &
// dvoting --config /tmp/node2 start --port 2002 &
// dvoting --config /tmp/node3 start --port 2003 &
//
// # Share the different certificates among the participants.
// dvoting --config /tmp/node2 minogrpc join --address 127.0.0.1:2001\
// $(dvoting --config /tmp/node1 minogrpc token)
// dvoting --config /tmp/node3 minogrpc join --address 127.0.0.1:2001\
// $(dvoting --config /tmp/node1 minogrpc token)
//
// # Create a chain with two members.
// dvoting --config /tmp/node1 ordering setup\
// --member $(dvoting --config /tmp/node1 ordering export)\
// --member $(dvoting --config /tmp/node2 ordering export)
//
// # Add the third after the chain is set up.
// dvoting --config /tmp/node1 ordering roster add\
// --member $(dvoting --config /tmp/node3 ordering export)
package main

import (
"fmt"
"io"
"os"

dkg "github.com/c4dt/d-voting/services/dkg/pedersen/controller"
"github.com/c4dt/d-voting/services/dkg/pedersen/json"
shuffle "github.com/c4dt/d-voting/services/shuffle/neff/controller"

cosipbft "github.com/c4dt/d-voting/cli/cosipbftcontroller"
"github.com/c4dt/d-voting/cli/postinstall"
evoting "github.com/c4dt/d-voting/contracts/evoting/controller"
metrics "github.com/c4dt/d-voting/metrics/controller"
"go.dedis.ch/dela/cli/node"
access "go.dedis.ch/dela/contracts/access/controller"
db "go.dedis.ch/dela/core/store/kv/controller"
pool "go.dedis.ch/dela/core/txn/pool/controller"
signed "go.dedis.ch/dela/core/txn/signed/controller"
mino "go.dedis.ch/dela/mino/minows"
proxy "go.dedis.ch/dela/mino/proxy/http/controller"

_ "github.com/c4dt/d-voting/services/shuffle/neff/json"

gapi "go.dedis.ch/dela-apps/gapi/controller"
)

func main() {
err := run(os.Args)
if err != nil {
fmt.Printf("%+v\n", err)
}
}

func run(args []string) error {
return runWithCfg(args, config{Writer: os.Stdout})
}

type config struct {
Channel chan os.Signal
Writer io.Writer
}

func runWithCfg(args []string, cfg config) error {
json.Register()

builder := node.NewBuilderWithCfg(
cfg.Channel,
cfg.Writer,
db.NewController(),
mino.NewController(),
cosipbft.NewController(),
dkg.NewController(),
signed.NewManagerController(),
pool.NewController(),
access.NewController(),
proxy.NewController(),
shuffle.NewController(),
evoting.NewController(),
gapi.NewController(),
metrics.NewController(),
postinstall.NewController(),
)

app := builder.Build()

err := app.Run(args)
if err != nil {
return err
}

return nil
}
297 changes: 297 additions & 0 deletions cli/dvoting-libp2p/mod_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
package main

import (
"bytes"
"encoding/base64"
"fmt"
"io"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
"time"

"github.com/stretchr/testify/require"
"go.dedis.ch/kyber/v3/pairing/bn256"
)

func TestDvoting_Main(t *testing.T) {
main()
}

// This test creates a chain with initially 3 nodes. It then adds node 4 and 5
// in two blocks. Node 4 does not share its certificate which means others won't
// be able to communicate, but the chain should proceed because of the
// threshold.
func TestDvoting_Scenario_SetupAndTransactions(t *testing.T) {
dir, err := os.MkdirTemp(os.TempDir(), "dvoting1")
require.NoError(t, err)

defer os.RemoveAll(dir)

sigs := make(chan os.Signal)
wg := sync.WaitGroup{}
wg.Add(5)

node1 := filepath.Join(dir, "node1")
node2 := filepath.Join(dir, "node2")
node3 := filepath.Join(dir, "node3")
node4 := filepath.Join(dir, "node4")
node5 := filepath.Join(dir, "node5")

cfg := config{Channel: sigs, Writer: io.Discard}

runNode(t, node1, cfg, 2111, &wg)
runNode(t, node2, cfg, 2112, &wg)
runNode(t, node3, cfg, 2113, &wg)
runNode(t, node4, cfg, 2114, &wg)
runNode(t, node5, cfg, 2115, &wg)

defer func() {
// Simulate a Ctrl+C
close(sigs)
wg.Wait()
}()

require.True(t, waitDaemon(t, []string{node1, node2, node3}), "daemon failed to start")

// Share the certificates.
shareCert(t, node2, node1, "//127.0.0.1:2111")
shareCert(t, node3, node1, "//127.0.0.1:2111")
shareCert(t, node5, node1, "//127.0.0.1:2111")

// Set up the chain with nodes 1 and 2.
args := append(append(
append(
[]string{os.Args[0], "--config", node1, "ordering", "setup"},
getExport(t, node1)...,
),
getExport(t, node2)...),
getExport(t, node3)...)

err = run(args)
require.NoError(t, err)

// Add node 4 to the current chain. This node is not reachable from the
// others but transactions should work as the threshold is correct.
args = append([]string{
os.Args[0],
"--config", node1, "ordering", "roster", "add",
"--wait", "60s"},
getExport(t, node4)...,
)

err = run(args)
require.NoError(t, err)

// Add the certificate and push two new blocks to make sure node4 is
// fully participating
shareCert(t, node4, node1, "//127.0.0.1:2111")
publicKey, err := bn256.NewSuiteG2().Point().MarshalBinary()
require.NoError(t, err)
publicKeyHex := base64.StdEncoding.EncodeToString(publicKey)
argsAccess := []string{
os.Args[0],
"--config", node1, "access", "add",
"--identity", publicKeyHex,
}
for i := 0; i < 2; i++ {
err = runWithCfg(argsAccess, config{})
require.NoError(t, err)
}

// Add node 5 which should be participating.
// This makes sure that node 4 is actually participating and caught up.
// If node 4 is not participating, there would be too many faulty nodes
// after adding node 5.
args = append([]string{
os.Args[0],
"--config", node1, "ordering", "roster", "add",
"--wait", "60s"},
getExport(t, node5)...,
)

err = run(args)
require.NoError(t, err)

// Run 2 new transactions
for i := 0; i < 2; i++ {
err = runWithCfg(argsAccess, config{})
require.NoError(t, err)
}

// Test a timeout waiting for a transaction.
args[7] = "1ns"
err = runWithCfg(args, config{})
require.EqualError(t, err, "command error: transaction not found after timeout")

// Test a bad command.
err = runWithCfg([]string{os.Args[0], "ordering", "setup"}, cfg)
require.EqualError(t, err, `Required flag "member" not set`)
}

// This test creates a chain with two nodes, then gracefully close them. It
// finally restarts both of them to make sure the chain can proceed after the
// restart. It basically tests if the components are correctly loaded from the
// persisten storage.
func TestDvoting_Scenario_RestartNode(t *testing.T) {
dir, err := os.MkdirTemp(os.TempDir(), "dvoting2")
require.NoError(t, err)

defer os.RemoveAll(dir)

node1 := filepath.Join(dir, "node1")
node2 := filepath.Join(dir, "node2")

// Setup the chain and closes the node.
setupChain(t, []string{node1, node2}, []uint16{2210, 2211})

sigs := make(chan os.Signal)
wg := sync.WaitGroup{}
wg.Add(2)

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.
runNode(t, node1, cfg, 2210, &wg)
runNode(t, node2, cfg, 2211, &wg)

defer func() {
// Simulate a Ctrl+C
close(sigs)
wg.Wait()
}()

require.True(t, waitDaemon(t, []string{node1, node2}), "daemon failed to start")

args := append([]string{
os.Args[0],
"--config", node1, "ordering", "roster", "add",
"--wait", "60s"},
getExport(t, node1)...,
)

err = run(args)
require.EqualError(t, err, "command error: transaction refused: duplicate in roster: grpcs://127.0.0.1:2210")
}

// -----------------------------------------------------------------------------
// Utility functions

const testDialTimeout = 500 * time.Millisecond

func runNode(t *testing.T, node string, cfg config, port uint16, wg *sync.WaitGroup) {
go func() {
defer wg.Done()

err := runWithCfg(makeNodeArg(node, port), cfg)
require.NoError(t, err)
}()
}

func setupChain(t *testing.T, nodes []string, ports []uint16) {
sigs := make(chan os.Signal)
wg := sync.WaitGroup{}
wg.Add(len(nodes))

cfg := config{Channel: sigs, Writer: io.Discard}

for i, node := range nodes {
runNode(t, node, cfg, ports[i], &wg)
}

defer func() {
// Simulate a Ctrl+C
close(sigs)
wg.Wait()
}()

waitDaemon(t, nodes)

shareCert(t, nodes[1], nodes[0], fmt.Sprintf("//127.0.0.1:%d", ports[0]))

args := append(append(
[]string{os.Args[0], "--config", nodes[0], "ordering", "setup"},
getExport(t, nodes[0])...),
getExport(t, nodes[1])...,
)

err := run(args)
require.NoError(t, err)
}

func waitDaemon(t *testing.T, daemons []string) bool {
num := 50

for _, daemon := range daemons {
path := filepath.Join(daemon, "daemon.sock")

for i := 0; i < num; i++ {
// Windows: we have to check the file as Dial on Windows creates the
// file and prevent to listen.
_, err := os.Stat(path)
if !os.IsNotExist(err) {
conn, err := net.DialTimeout("unix", path, testDialTimeout)
if err == nil {
conn.Close()
break
}
}

time.Sleep(100 * time.Millisecond)

if i+1 >= num {
return false
}
}
}

return true
}

func makeNodeArg(path string, port uint16) []string {
return []string{
os.Args[0], "--config", path, "start", "--listen", "tcp://127.0.0.1:" + strconv.Itoa(int(port)),
}
}

func shareCert(t *testing.T, path string, src string, addr string) {
args := append(
[]string{os.Args[0], "--config", path, "minogrpc", "join", "--address", addr},
getToken(t, src)...,
)

err := run(args)
require.NoError(t, err)
}

func getToken(t *testing.T, path string) []string {
buffer := new(bytes.Buffer)
cfg := config{
Writer: buffer,
}

args := []string{os.Args[0], "--config", path, "minogrpc", "token"}
err := runWithCfg(args, cfg)
require.NoError(t, err)

return strings.Split(buffer.String(), " ")
}

func getExport(t *testing.T, path string) []string {
buffer := bytes.NewBufferString("--member ")
cfg := config{
Writer: buffer,
}

args := []string{os.Args[0], "--config", path, "ordering", "export"}

err := runWithCfg(args, cfg)
require.NoError(t, err)

return strings.Split(buffer.String(), " ")
}

0 comments on commit 482c4a8

Please sign in to comment.