Skip to content
Snippets Groups Projects
Commit 07ab865b authored by capossele's avatar capossele
Browse files

:white_check_mark: adds dRNG integration test

parent 03e98db4
No related branches found
No related tags found
No related merge requests found
Showing
with 517 additions and 15 deletions
......@@ -139,3 +139,85 @@ func dkgShares(t *testing.T, n, threshold int) *payload.Payload {
return payload.New(1, 1, sig, newSig, dpk)
}
func TestCBSequence(t *testing.T) {
cbSequence(t, 5, 5, 3)
}
func cbSequence(t *testing.T, l, n, threshold int) []*payload.Payload {
var priPoly *share.PriPoly
var pubPoly *share.PubPoly
var err error
// create shares and commitments
for i := 0; i < n; i++ {
pri := share.NewPriPoly(key.KeyGroup, threshold, key.KeyGroup.Scalar().Pick(random.New()), random.New())
pub := pri.Commit(key.KeyGroup.Point().Base())
if priPoly == nil {
priPoly = pri
pubPoly = pub
continue
}
priPoly, err = priPoly.Add(pri)
require.NoError(t, err)
pubPoly, err = pubPoly.Add(pub)
require.NoError(t, err)
}
shares := priPoly.Shares(n)
secret, err := share.RecoverSecret(key.KeyGroup, shares, threshold, n)
require.NoError(t, err)
require.True(t, secret.Equal(priPoly.Secret()))
msg := []byte("first message")
sigs := make([][]byte, n)
_, commits := pubPoly.Info()
dkgShares := make([]*key.Share, n)
// partial signatures
for i := 0; i < n; i++ {
sigs[i], err = key.Scheme.Sign(shares[i], msg)
require.NoError(t, err)
dkgShares[i] = &key.Share{
Share: shares[i],
Commits: commits,
}
}
// reconstruct collective signature
sig, err := key.Scheme.Recover(pubPoly, msg, sigs, threshold, n)
require.NoError(t, err)
// verify signature against distributed public key
err = key.Scheme.VerifyRecovered(pubPoly.Commit(), msg, sig)
require.NoError(t, err)
result := make([]*payload.Payload, l)
for i := 0; i < l; i++ {
msg = beacon.Message(uint64(i+1), sig)
sigs = make([][]byte, n)
// partial signatures
for i := 0; i < n; i++ {
sigs[i], err = key.Scheme.Sign(shares[i], msg)
require.NoError(t, err)
}
// reconstruct collective signature
newSig, err := key.Scheme.Recover(pubPoly, msg, sigs, threshold, n)
require.NoError(t, err)
dpk, err := pubPoly.Commit().MarshalBinary()
require.NoError(t, err)
log.Println(hex.EncodeToString(sig))
log.Println(hex.EncodeToString(newSig))
log.Println(hex.EncodeToString(dpk))
result[i] = payload.New(1, uint64(i+1), sig, newSig, dpk)
sig = newSig
}
return result
}
#!/bin/sh
# read this container's IP address
IP_ADDR=`ip a | grep global | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | head -n 1`
IP_ADDR_PORT="${IP_ADDR}:8000"
# SHARED_FOLDER="/data"
# PUBLIC_KEY_FILE="${SHARED_FOLDER}/${PORT}.public"
# GROUP_FILE="${SHARED_FOLDER}/group.toml"
# Generate key pair, add it to the shared file /data/group.toml
echo "Generating key pair..."
rm -rf /root/.drand/
drand generate-keypair --tls-disable "${IP_ADDR_PORT}"
# cp /root/.drand/key/drand_id.public "${PUBLIC_KEY_FILE}"
# chmod ugo+rwx "${PUBLIC_KEY_FILE}"
# PUBLIC_KEY=`cat /root/.drand/key/drand_id.public`
# # echo -en "[[Nodes]]\n${PUBLIC_KEY}\n\n" >> "${GROUP_FILE}"
# # echo
# # On MacOS, you can't ping/curl the container from the host. Here's a fix
# apk add curl
# cat << FIX_SCRIPT > /bin/call_api
# #!/bin/sh
# echo "Running curl -s ${IP_ADDR_PORT}/api/public from inside the container:"
# curl -s ${IP_ADDR_PORT}/api/public
# FIX_SCRIPT
# chmod ug+x /bin/call_api
# Boot the drand deamon in background
nohup drand start --tls-disable --goshimmerAPIurl "http://iota_goshimmer:8080" & # add "--verbose 2" here for more details
# Wait for all containers to have done the same
sleep 5
# Now nodes wait for the leader to run DKG; leader starts DKG
if [[ "$LEADER" == 1 ]]; then
# sleep 5
# echo "We are the leader, checking group..."
# drand check-group "${GROUP_FILE}"
# echo
echo "Running DKG..."
# drand share --leader "${GROUP_FILE}"
drand share --leader --nodes 5 --threshold 3 --secret mysecret --period 10s
else
# drand share "${GROUP_FILE}"
sleep 5
drand share --connect "drand_1:8000" --tls-disable --nodes 5 --threshold 3 --secret mysecret
fi
# Let the deamon alive for long enough
while true
do
sleep 2
done
echo "Done"
\ No newline at end of file
......@@ -23,7 +23,7 @@ func newDockerClient() (*client.Client, error) {
)
}
// Wrapper object for a Docker container.
// DockerContainer is a wrapper object for a Docker container.
type DockerContainer struct {
client *client.Client
id string
......@@ -69,7 +69,7 @@ func (d *DockerContainer) CreateGoShimmerEntryNode(name string, seed string) err
}
// CreateGoShimmerPeer creates a new container with the GoShimmer peer's configuration.
func (d *DockerContainer) CreateGoShimmerPeer(name string, seed string, entryNodeHost string, entryNodePublicKey string, bootstrap bool) error {
func (d *DockerContainer) CreateGoShimmerPeer(conf GoShimmerConfig) error {
// configure GoShimmer container instance
containerConfig := &container.Config{
Image: "iotaledger/goshimmer",
......@@ -77,17 +77,41 @@ func (d *DockerContainer) CreateGoShimmerPeer(name string, seed string, entryNod
nat.Port("8080/tcp"): {},
},
Cmd: strslice.StrSlice{
fmt.Sprintf("--node.disablePlugins=%s", disabledPluginsPeer),
fmt.Sprintf("--node.disablePlugins=%s", conf.DisabledPlugins),
fmt.Sprintf("--node.enablePlugins=%s", func() string {
if bootstrap {
if conf.Bootstrap {
return "Bootstrap"
}
return ""
}()),
"--webapi.bindAddress=0.0.0.0:8080",
fmt.Sprintf("--autopeering.seed=%s", seed),
fmt.Sprintf("--autopeering.entryNodes=%s@%s:14626", entryNodePublicKey, entryNodeHost),
fmt.Sprintf("--autopeering.seed=%s", conf.Seed),
fmt.Sprintf("--autopeering.entryNodes=%s@%s:14626", conf.EntryNodePublicKey, conf.EntryNodeHost),
fmt.Sprintf("--drng.instanceId=%d", conf.DRNGInstance),
fmt.Sprintf("--drng.threshold=%d", conf.DRNGThreshold),
fmt.Sprintf("--drng.committeeMembers=%s", conf.DRNGCommittee),
fmt.Sprintf("--drng.distributedPubKey=%s", conf.DRNGDistKey),
},
}
return d.CreateContainer(conf.Name, containerConfig)
}
// CreateDrandMember creates a new container with the drand configuration.
func (d *DockerContainer) CreateDrandMember(name string, goShimmerAPI string, leader bool) error {
// configure drand container instance
env := []string{}
if leader {
env = append(env, "LEADER=1")
}
env = append(env, "GOSHIMMER=http://"+goShimmerAPI)
containerConfig := &container.Config{
Image: "angelocapossele/drand:latest",
ExposedPorts: nat.PortSet{
nat.Port("8000/tcp"): {},
},
Env: env,
Entrypoint: strslice.StrSlice{"/data/client-script.sh"},
}
return d.CreateContainer(name, containerConfig)
......@@ -105,13 +129,13 @@ func (d *DockerContainer) CreateContainer(name string, containerConfig *containe
}
// ConnectToNetwork connects a container to an existent network in the docker host.
func (d *DockerContainer) ConnectToNetwork(networkId string) error {
return d.client.NetworkConnect(context.Background(), networkId, d.id, nil)
func (d *DockerContainer) ConnectToNetwork(networkID string) error {
return d.client.NetworkConnect(context.Background(), networkID, d.id, nil)
}
// DisconnectFromNetwork disconnects a container from an existent network in the docker host.
func (d *DockerContainer) DisconnectFromNetwork(networkId string) error {
return d.client.NetworkDisconnect(context.Background(), networkId, d.id, true)
func (d *DockerContainer) DisconnectFromNetwork(networkID string) error {
return d.client.NetworkDisconnect(context.Background(), networkID, d.id, true)
}
// Start sends a request to the docker daemon to start a container.
......
package framework
import (
"fmt"
"github.com/drand/drand/core"
)
// Drand represents a drand node (committe member) inside the Docker network
type Drand struct {
// name of the drand instance, Docker container and hostname
name string
// Web API of this drand node
*core.Client
// the DockerContainer that this peer is running in
*DockerContainer
}
// newDrand creates a new instance of Drand with the given information.
func newDrand(name string, dockerContainer *DockerContainer) *Drand {
return &Drand{
name: name,
Client: core.NewGrpcClient(),
DockerContainer: dockerContainer,
}
}
func (d *Drand) String() string {
return fmt.Sprintf("Drand:{%s}", d.name)
}
package framework
import (
"encoding/hex"
"fmt"
"log"
"time"
"github.com/docker/docker/client"
hive_ed25519 "github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/identity"
)
// DRNGNetwork represents a complete drand with GoShimmer network within Docker.
// Including an entry node, drand members and arbitrary many peers.
type DRNGNetwork struct {
network *Network
members []*Drand
distKey []byte
}
// newDRNGNetwork returns a Network instance, creates its underlying Docker network and adds the tester container to the network.
func newDRNGNetwork(dockerClient *client.Client, name string, tester *DockerContainer) (*DRNGNetwork, error) {
network, err := newNetwork(dockerClient, name, tester)
if err != nil {
return nil, err
}
return &DRNGNetwork{
network: network,
}, nil
}
// CreatePeer creates a new peer/GoShimmer node in the network and returns it.
func (n *DRNGNetwork) CreatePeer(c GoShimmerConfig, publicKey hive_ed25519.PublicKey) (*Peer, error) {
name := n.network.namePrefix(fmt.Sprintf("%s%d", containerNameReplica, len(n.network.peers)))
conf := c
conf.Name = name
conf.EntryNodeHost = n.network.namePrefix(containerNameEntryNode)
conf.EntryNodePublicKey = n.network.entryNodePublicKey()
conf.DisabledPlugins = disabledPluginsPeer
// create Docker container
container := NewDockerContainer(n.network.dockerClient)
err := container.CreateGoShimmerPeer(conf)
if err != nil {
return nil, err
}
err = container.ConnectToNetwork(n.network.id)
if err != nil {
return nil, err
}
err = container.Start()
if err != nil {
return nil, err
}
peer := newPeer(name, identity.New(publicKey), container)
n.network.peers = append(n.network.peers, peer)
return peer, nil
}
// CreateMember creates a new member/drand node in the network and returns it.
// Passing leader true enables the leadership on the given peer.
func (n *DRNGNetwork) CreateMember(leader bool) (*Drand, error) {
name := n.network.namePrefix(fmt.Sprintf("%s%d", containerNameDrand, len(n.members)))
// create Docker container
container := NewDockerContainer(n.network.dockerClient)
err := container.CreateDrandMember(name, fmt.Sprintf("%s:8080", n.network.namePrefix(fmt.Sprintf("%s%d", containerNameReplica, len(n.members)))), leader)
if err != nil {
return nil, err
}
err = container.ConnectToNetwork(n.network.id)
if err != nil {
return nil, err
}
err = container.Start()
if err != nil {
return nil, err
}
member := newDrand(name, container)
n.members = append(n.members, member)
return member, nil
}
// Shutdown creates logs and removes network and containers.
// Should always be called when a network is not needed anymore!
func (n *DRNGNetwork) Shutdown() error {
defer n.network.Shutdown()
for _, p := range n.members {
err := p.Stop()
if err != nil {
return err
}
}
// retrieve logs
for _, p := range n.members {
logs, err := p.Logs()
if err != nil {
return err
}
err = createLogFile(p.name, logs)
if err != nil {
return err
}
}
// remove containers
for _, p := range n.members {
err := p.Remove()
if err != nil {
return err
}
}
return nil
}
// WaitForDKG waits until all members have concluded the DKG phase.
func (n *DRNGNetwork) WaitForDKG() error {
log.Printf("Waiting for DKG...\n")
defer log.Printf("Waiting for DKG... done\n")
for i := dkgMaxTries; i > 0; i-- {
//var dkey *drand.DistKeyResponse
for _, m := range n.members {
if dkey, err := m.Client.DistKey(m.name+":8000", false); err != nil {
log.Printf("request error: %v\n", err)
} else {
n.SetDistKey(dkey.Key)
}
}
if len(n.distKey) != 0 {
log.Printf("DistKey: %v", hex.EncodeToString(n.distKey))
return nil
}
log.Println("Not done yet. Try again in 5 seconds...")
time.Sleep(5 * time.Second)
}
return fmt.Errorf("DKG not successful")
}
// SetDistKey sets the distributed key.
func (n *DRNGNetwork) SetDistKey(key []byte) {
n.distKey = key
}
// Peers returns the list of peers.
func (n *DRNGNetwork) Peers() []*Peer {
return n.network.Peers()
}
......@@ -4,11 +4,17 @@
package framework
import (
"crypto/ed25519"
"encoding/base64"
"encoding/hex"
"fmt"
"strings"
"sync"
"time"
"github.com/docker/docker/client"
hive_ed25519 "github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/mr-tron/base58"
)
var (
......@@ -84,3 +90,68 @@ func (f *Framework) CreateNetwork(name string, peers int, minimumNeighbors int)
return network, nil
}
// CreateDRNGNetwork creates and returns a (Docker) Network that contains drand and `peers` GoShimmer nodes.
func (f *Framework) CreateDRNGNetwork(name string, members, peers int) (*DRNGNetwork, error) {
drng, err := newDRNGNetwork(f.dockerClient, strings.ToLower(name), f.tester)
if err != nil {
return nil, err
}
err = drng.network.createEntryNode()
if err != nil {
return nil, err
}
// create members/drand nodes
for i := 0; i < members; i++ {
leader := i == 0
if _, err = drng.CreateMember(leader); err != nil {
return nil, err
}
}
// wait until containers are fully started
time.Sleep(60 * time.Second)
err = drng.WaitForDKG()
if err != nil {
return nil, err
}
// create GoShimmer identities
pubKeys := make([]hive_ed25519.PublicKey, peers)
privKeys := make([]hive_ed25519.PrivateKey, peers)
var drngCommittee string
for i := 0; i < peers; i++ {
pubKeys[i], privKeys[i], err = hive_ed25519.GenerateKey()
if err != nil {
return nil, err
}
if i < members {
if drngCommittee != "" {
drngCommittee += fmt.Sprintf(",")
}
drngCommittee += fmt.Sprintf("%s", base58.Encode(pubKeys[i][:]))
}
}
conf := GoShimmerConfig{
DRNGInstance: 1,
DRNGThreshold: 3,
DRNGDistKey: hex.EncodeToString(drng.distKey),
DRNGCommittee: drngCommittee,
}
// create peers/GoShimmer nodes
for i := 0; i < peers; i++ {
conf.Bootstrap = i == 0
conf.Seed = base64.StdEncoding.EncodeToString(ed25519.PrivateKey(privKeys[i].Bytes()).Seed())
if _, err = drng.CreatePeer(conf, pubKeys[i]); err != nil {
return nil, err
}
}
return drng, nil
}
......@@ -95,7 +95,14 @@ func (n *Network) CreatePeer(bootstrap bool) (*Peer, error) {
// create Docker container
container := NewDockerContainer(n.dockerClient)
err = container.CreateGoShimmerPeer(name, seed, n.namePrefix(containerNameEntryNode), n.entryNodePublicKey(), bootstrap)
err = container.CreateGoShimmerPeer(GoShimmerConfig{
Name: name,
Seed: seed,
EntryNodeHost: n.namePrefix(containerNameEntryNode),
EntryNodePublicKey: n.entryNodePublicKey(),
Bootstrap: bootstrap,
DisabledPlugins: disabledPluginsPeer,
})
if err != nil {
return nil, err
}
......
......@@ -8,6 +8,7 @@ const (
containerNameTester = "/tester"
containerNameEntryNode = "entry_node"
containerNameReplica = "replica_"
containerNameDrand = "drand_"
logsDir = "/tmp/logs/"
......@@ -15,4 +16,21 @@ const (
disabledPluginsPeer = "portcheck,dashboard,analysis"
dockerLogsPrefixLen = 8
dkgMaxTries = 50
)
// GoShimmerConfig defines the config of a goshimmer node.
type GoShimmerConfig struct {
Seed string
Name string
EntryNodeHost string
EntryNodePublicKey string
Bootstrap bool
DisabledPlugins string
DRNGCommittee string
DRNGDistKey string
DRNGInstance int
DRNGThreshold int
}
......@@ -32,7 +32,7 @@ func newPeer(name string, identity *identity.Identity, dockerContainer *DockerCo
return &Peer{
name: name,
Identity: identity,
GoShimmerAPI: client.NewGoShimmerAPI(getWebApiBaseUrl(name), http.Client{Timeout: 30 * time.Second}),
GoShimmerAPI: client.NewGoShimmerAPI(getWebAPIBaseURL(name), http.Client{Timeout: 30 * time.Second}),
DockerContainer: dockerContainer,
}
}
......
......@@ -7,8 +7,8 @@ import (
"os"
)
// getWebApiBaseUrl returns the web API base url for the given IP.
func getWebApiBaseUrl(hostname string) string {
// getWebAPIBaseURL returns the web API base url for the given IP.
func getWebAPIBaseURL(hostname string) string {
return fmt.Sprintf("http://%s:%s", hostname, apiPort)
}
......@@ -25,7 +25,16 @@ func createLogFile(name string, logs io.ReadCloser) error {
// remove non-ascii chars at beginning of line
scanner := bufio.NewScanner(logs)
for scanner.Scan() {
bytes := append(scanner.Bytes()[dockerLogsPrefixLen:], '\n')
line := scanner.Bytes()
// in case of an error there is no Docker prefix
var bytes []byte
if len(line) < dockerLogsPrefixLen {
bytes = append(line, '\n')
} else {
bytes = append(line[dockerLogsPrefixLen:], '\n')
}
_, err = f.Write(bytes)
if err != nil {
return err
......
......@@ -8,8 +8,10 @@ require (
github.com/docker/docker v1.13.1
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0 // indirect
github.com/drand/drand v0.8.1
github.com/iotaledger/goshimmer v0.1.3
github.com/iotaledger/hive.go v0.0.0-20200430073924-0e16f8c3a522
github.com/mr-tron/base58 v1.1.3
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/stretchr/testify v1.5.1
)
......
package tests
import (
"log"
"testing"
"time"
"github.com/stretchr/testify/require"
)
// TestDRNG checks whether drng messages are actually relayed/gossiped through the network
// by checking the messages' existence on all nodes after a cool down.
func TestDRNG(t *testing.T) {
drng, err := f.CreateDRNGNetwork("TestDRNG", 5, 8)
require.NoError(t, err)
defer drng.Shutdown()
// wait for randomness generation to be started
log.Printf("Waiting for randomness generation to be started...\n")
time.Sleep(70 * time.Second)
peers := len(drng.Peers())
// check for randomness on every peer
for i := 0; i < 3; i++ {
randomness := make([][]byte, peers)
for j, peer := range drng.Peers() {
resp, err := peer.GetRandomness()
require.NoError(t, err)
log.Println(resp)
randomness[j] = resp.Randomness
}
for i := 1; i < peers; i++ {
require.Equal(t, randomness[0], randomness[i])
}
time.Sleep(10 * time.Second)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment