Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stress tests #43

Merged
merged 8 commits into from
Aug 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions crypto/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package crypto

import (
"bytes"
"crypto/rand"
"errors"
"testing"
)

func TestDigest(t *testing.T) {
msg := []byte("test message")
d := Digest(msg)
if len(d) != HashSizeByte {
t.Fatal("Computation of Hash failed.")
}
if bytes.Equal(d, make([]byte, HashSizeByte)) {
t.Fatal("Hash is all zeros.")
}
}

type testErrorRandReader struct{}

func (er testErrorRandReader) Read([]byte) (int, error) {
return 0, errors.New("Not enough entropy!")
}

func TestMakeRand(t *testing.T) {
r, err := MakeRand()
if err != nil {
t.Fatal(err)
}
// check if hashed the random output:
if len(r) != HashSizeByte {
t.Fatal("Looks like Digest wasn't called correctly.")
}
orig := rand.Reader
rand.Reader = testErrorRandReader{}
r, err = MakeRand()
if err == nil {
t.Fatal("No error returned")
}
rand.Reader = orig
}
191 changes: 191 additions & 0 deletions merkletree/pad_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import (
"bytes"
"testing"

"crypto/rand"
"errors"
"fmt"
"github.com/coniks-sys/coniks-go/crypto/sign"
"io"
)

var signKey sign.PrivateKey
Expand Down Expand Up @@ -235,3 +239,190 @@ func TestPoliciesChange(t *testing.T) {
t.Error(key3, "value mismatch")
}
}

func TestTB(t *testing.T) {
key1 := "key"
val1 := []byte("value")

pad, err := NewPAD(NewPolicies(3, vrfPrivKey1), signKey, 3)
if err != nil {
t.Fatal(err)
}
tb, err := pad.TB(key1, val1)
if err != nil {
t.Fatal(err)
}

pk, ok := pad.signKey.Public()
if !ok {
t.Fatal("Couldn't retrieve public-key.")
}
tbb := tb.Serialize(pad.latestSTR.Signature)
if !pk.Verify(tbb, tb.Signature) {
t.Fatal("Couldn't validate signature")
}
// create next epoch and see if the TB is inserted as promised:
pad.Update(nil)

ap, err := pad.Lookup(key1)
if !bytes.Equal(ap.LookupIndex, tb.Index) || !bytes.Equal(ap.Leaf.Value(), tb.Value) {
t.Error("Value wasn't inserted as promised")
}
}

func TestNewPADMissingPolicies(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("Expected NewPAD to panic if policies are missing.")
}
}()
if _, err := NewPAD(nil, signKey, 10); err != nil {
t.Fatal("Expected NewPAD to panic but got error.")
}
}

// TODO move the following to some (internal?) testutils package
type testErrorRandReader struct{}

func (er testErrorRandReader) Read([]byte) (int, error) {
return 0, errors.New("Not enough entropy!")
}

func mockRandReadWithErroringReader() (orig io.Reader) {
orig = rand.Reader
rand.Reader = testErrorRandReader{}
return
}

func unMockRandReader(orig io.Reader) {
rand.Reader = orig
}

func TestNewPADErrorWhileCreatingTree(t *testing.T) {
origRand := mockRandReadWithErroringReader()
defer unMockRandReader(origRand)

pad, err := NewPAD(NewPolicies(3, vrfPrivKey1), signKey, 3)
if err == nil || pad != nil {
t.Fatal("NewPad should return an error in case the tree creation failed")
}
}

func BenchmarkCreateLargePAD(b *testing.B) {
snapLen := uint64(10)
keyPrefix := "key"
valuePrefix := []byte("value")

// total number of entries in tree:
NumEntries := uint64(1000000)
// tree.Clone and update STR every:
noUpdate := uint64(NumEntries + 1)

b.ResetTimer()
// benchmark creating a large tree (don't Update tree)
for n := 0; n < b.N; n++ {
_, err := createPad(NumEntries, keyPrefix, valuePrefix, snapLen,
noUpdate)
if err != nil {
b.Fatal(err)
}
}
}

//
// Benchmarks which can be used produce data similar to Figure 7. in Section 5.
//
func BenchmarkPADUpdate100K(b *testing.B) { benchPADUpdate(b, 100000) }
func BenchmarkPADUpdate500K(b *testing.B) { benchPADUpdate(b, 500000) }

// make sure you have enough memory/cpu power if you want to run the benchmarks
// below; also give the benchmarks enough time to finish using the -timeout flag
func BenchmarkPADUpdate1M(b *testing.B) { benchPADUpdate(b, 1000000) }
func BenchmarkPADUpdate2_5M(b *testing.B) { benchPADUpdate(b, 2500000) }
func BenchmarkPADUpdate5M(b *testing.B) { benchPADUpdate(b, 5000000) }
func BenchmarkPADUpdate7_5M(b *testing.B) { benchPADUpdate(b, 7500000) }
func BenchmarkPADUpdate10M(b *testing.B) { benchPADUpdate(b, 10000000) }

func benchPADUpdate(b *testing.B, entries uint64) {
keyPrefix := "key"
valuePrefix := []byte("value")
snapLen := uint64(10)
noUpdate := uint64(entries + 1)
pad, err := createPad(uint64(entries), keyPrefix, valuePrefix, snapLen, noUpdate)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
pad.Update(nil)
}
}

//
// END Benchmarks for Figure 7. in Section 5
//

func BenchmarkPADLookUpFrom10K(b *testing.B) { benchPADLookup(b, 10000) }
func BenchmarkPADLookUpFrom50K(b *testing.B) { benchPADLookup(b, 50000) }
func BenchmarkPADLookUpFrom100K(b *testing.B) { benchPADLookup(b, 100000) }
func BenchmarkPADLookUpFrom500K(b *testing.B) { benchPADLookup(b, 500000) }
func BenchmarkPADLookUpFrom1M(b *testing.B) { benchPADLookup(b, 1000000) }
func BenchmarkPADLookUpFrom5M(b *testing.B) { benchPADLookup(b, 5000000) }
func BenchmarkPADLookUpFrom10M(b *testing.B) { benchPADLookup(b, 10000000) }

func benchPADLookup(b *testing.B, entries uint64) {
snapLen := uint64(10)
keyPrefix := "key"
valuePrefix := []byte("value")
updateOnce := uint64(entries - 1)
pad, err := createPad(entries, keyPrefix, valuePrefix, snapLen,
updateOnce)
if err != nil {
b.Fatal(err)
}
// ignore the tree creation:
b.ResetTimer()
//fmt.Println("Done creating large pad/tree.")

// measure LookUps in large tree (with NumEntries leafs)
for n := 0; n < b.N; n++ {
b.StopTimer()
var key string
if n < int(entries) {
key = keyPrefix + string(n)
} else {
key = keyPrefix + string(n%int(entries))
}
b.StartTimer()
_, err := pad.Lookup(key)
if err != nil {
b.Fatalf("Coudldn't lookup key=%s", key)
}
}
}

// creates a PAD containing a tree with N entries (+ potential emptyLeafNodes)
// each key value pair has the form (keyPrefix+string(i), valuePrefix+string(i))
// for i = 0,...,N
// The STR will get updated every epoch defined by every multiple of
// `updateEvery`. If `updateEvery > N` createPAD won't update the STR.
func createPad(N uint64, keyPrefix string, valuePrefix []byte, snapLen uint64,
updateEvery uint64) (*PAD, error) {
pad, err := NewPAD(NewPolicies(3, vrfPrivKey1), signKey, snapLen)
if err != nil {
return nil, err
}

for i := uint64(0); i < N; i++ {
key := keyPrefix + string(i)
value := append(valuePrefix, byte(i))
if err := pad.Set(key, value); err != nil {
return nil, fmt.Errorf("Couldn't set key=%s and value=%s. Error: %v",
key, value, err)
}
if i != 0 && (i%updateEvery == 0) {
pad.Update(nil)
}
}
return pad, nil
}
8 changes: 8 additions & 0 deletions merkletree/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,11 @@ func TestVerifyProofSamePrefix(t *testing.T) {
t.Error("Proof of absence verification failed.")
}
}

func TestEmptyNodeCommitment(t *testing.T) {
n := node{parent: nil, level: 1}
e := emptyNode{node: n, index: []byte("some index")}
if c := e.Commitment(); c != nil {
t.Fatal("Commitment of emptyNode should be nil")
}
}
9 changes: 0 additions & 9 deletions merkletree/str.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package merkletree

import (
"errors"

"github.com/coniks-sys/coniks-go/crypto/sign"
"github.com/coniks-sys/coniks-go/utils"
)

var (
ErrorBadEpoch = errors.New("[merkletree] Bad STR Epoch. STR's epoch must be nonzero")
)

// SignedTreeRoot represents a signed tree root, which is generated
// at the beginning of every epoch.
// Signed tree roots contain the current root node,
Expand All @@ -27,9 +21,6 @@ type SignedTreeRoot struct {
}

func NewSTR(key sign.PrivateKey, policies Policies, m *MerkleTree, epoch uint64, prevHash []byte) *SignedTreeRoot {
if epoch < 0 {
panic(ErrorBadEpoch)
}
prevEpoch := epoch - 1
if epoch == 0 {
prevEpoch = 0
Expand Down
33 changes: 33 additions & 0 deletions utils/util_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package util

import (
"encoding/binary"
"math/rand"
"testing"
"time"
Expand All @@ -27,3 +28,35 @@ func TestBitsBytesConvert(t *testing.T) {
}
}
}

func TestIntToBytes(t *testing.T) {
numInt := 42
b := IntToBytes(numInt)
if int(binary.LittleEndian.Uint32(b)) != numInt {
t.Fatal("Conversion to bytes looks wrong!")
}
// TODO It is possible to call `IntToBytes` with a signed int < 0
// isn't that problematic?
Copy link
Member

@masomel masomel Aug 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I don't expect the server will use signed ints. Do you think IntToBytes() should throw an error when it receives a negative value? Or should we change to UIntToBytes()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think to let it panic or only use UIntToBytes would make sense. As far as I can see node.level can only grow (or never is a value subtracted from level). Maybe we could even refactor level to be unsigned?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactoring level to be unsigned makes a lot of sense to me. The only scenario in which level could decrease is when the tree is re-randomized after the VRF key is changed. But this should still work with an unsigned level.

Copy link
Member Author

@liamsi liamsi Aug 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW (unsigned) inters can overflow in golang without panic/error or something similar: https://golang.org/ref/spec#Integer_overflow

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactoring level to be unsigned makes a lot of sense to me. The only scenario in which level could decrease is when the tree is re-randomized after the VRF key is changed. But this should still work with an unsigned level.

OK, should this go into another issue/PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, should this go into another issue/PR?

I think yes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks! Opened #53 for that.

// for instance this will fail:
// b := IntToBytes(-42)
// if int(binary.LittleEndian.Uint32(b)) != numInt {
// t.Fatal("Conversion to bytes looks wrong!")
// }
}

func TestULongToBytes(t *testing.T) {
numInt := uint64(42)
b := ULongToBytes(numInt)
if binary.LittleEndian.Uint64(b) != numInt {
t.Fatal("Conversion to bytes looks wrong!")
}
}

func TestLongToBytes(t *testing.T) {
numInt := int64(42)
b := LongToBytes(numInt)
if int64(binary.LittleEndian.Uint64(b)) != numInt {
t.Fatal("Conversion to bytes looks wrong!")
}
// TODO similar problem as in TestIntToBytes
}