Skip to content

Commit

Permalink
Merge pull request #19 from karlfloersch/feat/testing_language
Browse files Browse the repository at this point in the history
Feat/testing language
  • Loading branch information
djrtwo committed Oct 11, 2017
2 parents 79553df + bec1274 commit 6c38fef
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 5 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ pip install -r requirements.txt
## Run Simulations
This code is marked up for use as follows:
```
kernprof -l casper.py rounds && python -m line_profiler casper.py.lprof > results.txt
kernprof -l casper.py (rand | rrob | full | nofinal)
```
OR

## Run Tests
To run all unit tests:
```
python -m unittest discover
```
To run a specific test, use (or the equivalent for whatever test you wish to run)
```
kernprof -l casper.py blockchain && python -m line_profiler casper.py.lprof > results.txt
python -m unittest test.test_safety_oracle
```
2 changes: 0 additions & 2 deletions safety_oracles/clique_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ def __init__(self, candidate_estimate, view):
# c) none of them can see a new message from another not on the candidate_estimate
# NOTE: if biggest clique can easily be determined to be < 50% by weight, will
# return with empty set and 0 weight.
@profile
def find_biggest_clique(self):

# only consider validators whose messages are compatable w/ candidate_estimate
Expand Down Expand Up @@ -81,7 +80,6 @@ def find_biggest_clique(self):
return set(max_clique), max_weight


@profile
def check_estimate_safety(self):

biggest_clique, clique_weight = self.find_biggest_clique()
Expand Down
12 changes: 12 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,15 @@ def init():
REPORT_SUBJECTIVE_VIEWS = False

init()


def update(val_weights):
global NUM_VALIDATORS
global VALIDATOR_NAMES
global WEIGHTS
global TOTAL_WEIGHT

NUM_VALIDATORS = len(val_weights)
VALIDATOR_NAMES = set(range(NUM_VALIDATORS))
WEIGHTS = {i: val_weights[i] for i in VALIDATOR_NAMES}
TOTAL_WEIGHT = sum(val_weights)
Empty file added test/__init__.py
Empty file.
81 changes: 81 additions & 0 deletions test/test_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from testing_language import TestLangCBC
from block import Block
from justification import Justification
import unittest
import settings as s
import copy


class TestUtils(unittest.TestCase):

def test_equality_of_copies_off_genesis(self):
s.update([10]) # necessary due to assertions during block creation
block = Block(None, Justification(), 0)

shallow_copy = copy.copy(block)
deep_copy = copy.deepcopy(block)

self.assertEqual(block, shallow_copy)
self.assertEqual(block, deep_copy)
self.assertEqual(shallow_copy, deep_copy)

def test_equality_of_copies_of_non_genesis(self):
test_string = "B0-A S1-A B1-B S0-B B0-C S1-C B1-D S0-D H0-D"
testLang = TestLangCBC(test_string, [10, 11])
testLang.parse()

for b in testLang.blocks:
shallow_copy = copy.copy(b)
deep_copy = copy.deepcopy(b)

self.assertEqual(b, shallow_copy)
self.assertEqual(b, deep_copy)
self.assertEqual(shallow_copy, deep_copy)

def test_non_equality_of_copies_off_genesis(self):
s.update([10, 11])
block_0 = Block(None, Justification(), 0)
block_1 = Block(None, Justification(), 1)

self.assertNotEqual(block_0, block_1)

def test_unique_block_creation_in_test_lang(self):
test_string = "B0-A S1-A B1-B S0-B B0-C S1-C B1-D S0-D H0-D"
testLang = TestLangCBC(test_string, [10, 11])
testLang.parse()

num_equal = 0
for b in testLang.blocks:
for b1 in testLang.blocks:
if b1 == b:
num_equal += 1
continue

self.assertNotEqual(b, b1)

self.assertEqual(num_equal, len(testLang.blocks))

def test_is_in_blockchain__separate_genesis(self):
s.update([10, 11])
block_0 = Block(None, Justification(), 0)
block_1 = Block(None, Justification(), 1)

self.assertFalse(block_0.is_in_blockchain(block_1))
self.assertFalse(block_1.is_in_blockchain(block_0))

def test_is_in_blockchain__test_lang(self):
test_string = "B0-A S1-A B1-B S0-B B0-C S1-C B1-D S0-D H0-D"
testLang = TestLangCBC(test_string, [11, 10])
testLang.parse()

prev = testLang.blocks['A']
for b in ['B', 'C', 'D']:
block = testLang.blocks[b]
self.assertTrue(prev.is_in_blockchain(block))
self.assertFalse(block.is_in_blockchain(prev))

prev = block


if __name__ == "__main__":
unittest.main()
127 changes: 127 additions & 0 deletions test/test_forkchoice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from testing_language import TestLangCBC
import unittest
import forkchoice
import random as r


class TestForkchoice(unittest.TestCase):

def test_single_validator_correct_forkchoice(self):
""" This tests that a single validator remains on their own chain """
test_string = ""
for i in xrange(100):
test_string += "B0-" + str(i) + " " + "H0-" + str(i) + " "
test_string = test_string[:-1]

testLang = TestLangCBC(test_string, [10])
testLang.parse()

def test_two_validators_round_robin_forkchoice(self):
test_string = "B0-A S1-A B1-B S0-B B0-C S1-C B1-D S0-D H0-D R"
testLang = TestLangCBC(test_string, [10, 11])
testLang.parse()

def test_many_val_round_robin_forkchoice(self):
"""
Tests that during a perfect round robin,
validators choose the one chain as their fork choice
"""
test_string = ""
for i in xrange(100):
test_string += "B" + str(i % 10) + "-" + str(i) + " " \
+ "S" + str((i+1) % 10) + "-" + str(i) + " " \
+ "H" + str((i+1) % 10) + "-" + str(i) + " "
test_string = test_string[:-1]

testLang = TestLangCBC(
test_string,
[x + r.random() for x in xrange(10, 0, -1)]
)
testLang.parse()

def test_fail_on_tie(self):
"""
Tests that if there are two subsets of the validator
set with the same weight, the forkchoice fails
"""
test_string = "B1-A S0-A B0-B S1-B S2-A B2-C S1-C H1-C"
testLang = TestLangCBC(test_string, [5, 6, 5])
with self.assertRaises(AssertionError):
testLang.parse()

def test_ignore_zero_weight_validator(self):
"""
Tests that a validator with zero weight
will not affect the forkchoice
"""
test_string = "B0-A S1-A B1-B S0-B H1-A H0-A"
testLang = TestLangCBC(test_string, [1, 0])
testLang.parse()

def test_ignore_zero_weight_block(self):
""" Tests that the forkchoice ignores zero weight blocks """
# for more info about test, see
# https://gist.github.com/naterush/8d8f6ec3509f50939d7911d608f912f4
test_string = (
"B0-A1 B0-A2 H0-A2 B1-B1 B1-B2 S3-B2 B3-D1 H3-D1 "
"S3-A2 H3-A2 B3-D2 S2-B1 H2-B1 B2-C1 H2-C1 S1-D1 "
"S1-D2 S1-C1 H1-B2"
)
testLang = TestLangCBC(test_string, [10, 9, 8, .5])
testLang.parse()

def test_reverse_message_arrival_order_forkchoice_four_val(self):
test_string = (
"B0-A S1-A B1-B S0-B B0-C S1-C B1-D S0-D B1-E S0-E "
"S2-E H2-E S3-A S3-B S3-C S3-D S3-E H3-E"
)
testLang = TestLangCBC(test_string, [5, 6, 7, 8.1])
testLang.parse()

def test_different_message_arrival_order_forkchoice_many_val(self):
# TODO
pass

def test_max_weight_indexes(self):
weight = {i: i for i in xrange(10)}
max_weight_indexes = forkchoice.get_max_weight_indexes(weight)
self.assertEqual(len(max_weight_indexes), 1)
self.assertEqual(max_weight_indexes.pop(), 9)

weight = {i: 9 - i for i in xrange(10)}
max_weight_indexes = forkchoice.get_max_weight_indexes(weight)
self.assertEqual(len(max_weight_indexes), 1)
self.assertEqual(max_weight_indexes.pop(), 0)

weight = dict()
for i in xrange(5):
weight[i] = i
weight[9 - i] = i

max_weight_indexes = forkchoice.get_max_weight_indexes(weight)
self.assertEqual(len(max_weight_indexes), 2)
self.assertEqual(set([4, 5]), max_weight_indexes)

def test_max_weight_indexes_empty(self):
weight = dict()
with self.assertRaises(ValueError):
forkchoice.get_max_weight_indexes(weight)

def test_max_weight_indexes_zero_score(self):
weight = {i: 0 for i in xrange(10)}
with self.assertRaises(AssertionError):
forkchoice.get_max_weight_indexes(weight)

def test_max_weight_indexes_tie(self):
weight = dict()
for i in xrange(10):
weight[i] = 10

max_weight_indexes = forkchoice.get_max_weight_indexes(weight)

self.assertEqual(len(max_weight_indexes), 10)
self.assertEqual(set(weight.keys()), max_weight_indexes)


if __name__ == "__main__":
unittest.main()
59 changes: 59 additions & 0 deletions test/test_safety_oracle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from testing_language import TestLangCBC
import unittest


class TestUtils(unittest.TestCase):

def test_round_robin_safety(self):
test_string = (
'R B0-A S1-A RR1-B RR1-C RR1-D RR1-E S2-E '
'S3-E S4-E H0-E H1-E H2-E H3-E H4-E C0-A '
'C1-A C2-A C3-A C4-A R'
)
test = TestLangCBC(test_string, [9.3, 8.2, 7.1, 6, 5])
test.parse()

def test_majority_fork_safe(self):
test_string = (
# create right hand side of fork and check for safety
'R B1-A S0-A B0-L0 S1-L0 B1-L1 S0-L1 B0-L2 S1-L2 '
'B1-L3 S0-L3 B0-L4 S1-L4 H1-L4 C1-L0 H0-L4 C0-L0 R '
# other fork shows safe fork blocks, but they remain stuck
'S2-A B2-R0 S0-R0 H0-L4 S1-R0 H0-L4 R'
)

test = TestLangCBC(test_string, [5, 6, 7])
test.parse()

def test_no_majority_fork_unsafe(self):
test_string = (
# create right hand side of fork and check for no safety
'R B2-A S1-A B1-L0 S0-L0 B0-L1 S1-L1 B1-L2 S0-L2 '
'B0-L3 S1-L3 B1-L4 S0-L4 H0-L4 U0-L0 H1-L4 U1-L0 R '
# now, left hand side as well. still no safety
'S3-A B3-R0 S4-R0 B4-R1 S3-R1 B3-R2 S4-R2 B4-R3 '
'S3-R3 B3-R4 S4-R4 H4-R4 U4-R0 H3-R4 U3-R0 R'
)
test = TestLangCBC(test_string, [5, 4.5, 6, 4, 5.25])
test.parse()

def test_no_majority_fork_safe_after_union(self):
test_string = (
# generate both sides of an extended fork
'R B2-A S1-A B1-L0 S0-L0 B0-L1 S1-L1 B1-L2 S0-L2 '
'B0-L3 S1-L3 B1-L4 S0-L4 H0-L4 U0-L0 H1-L4 U1-L0 R '
'S3-A B3-R0 S4-R0 B4-R1 S3-R1 B3-R2 S4-R2 B4-R3 '
'S3-R3 B3-R4 S4-R4 H4-R4 U4-R0 H3-R4 U3-R0 R '
# show all validators all blocks
'S0-R4 S1-R4 S2-R4 S2-L4 S3-L4 S4-L4 '
# check all have correct forkchoice
'H0-L4 H1-L4 H2-L4 H3-L4 H4-L4 '
# two rounds of round robin, check have safety on the correct fork
'RR0-J0 RR0-J1 C0-L0 R'
)
test = TestLangCBC(test_string, [5, 4.5, 6, 4, 5.25])
test.parse()


if __name__ == "__main__":
unittest.main()
49 changes: 49 additions & 0 deletions test/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import unittest
import settings as s
import random as r
import utils


class TestUtils(unittest.TestCase):

def test_get_weight_increasing(self):
weights = [i for i in xrange(10)]
s.update(weights)
self.assertEqual(utils.get_weight(s.VALIDATOR_NAMES), 45)

def test_get_weight_decreasing(self):
weights = [i for i in xrange(9, -1, -1)]
s.update(weights)

self.assertEqual(utils.get_weight(s.VALIDATOR_NAMES), 45)

def test_get_weight_random(self):
weights = [r.random() for i in xrange(10)]
s.update(weights)

self.assertEqual(utils.get_weight(s.VALIDATOR_NAMES), sum(weights))

def test_get_weight_partial_set(self):
weights = [i*2 for i in xrange(10)]
s.update(weights)

subset = set([0, 1, 2, 3])
self.assertEqual(utils.get_weight(subset), 12)

def test_get_weight_partial_list(self):
weights = [i*2 for i in xrange(10)]
s.update(weights)

self.assertEqual(utils.get_weight([0, 1, 2, 3]), 12)

def test_get_weight_none(self):
weight = utils.get_weight(None)
self.assertEqual(weight, 0)

def test_get_weight_empty(self):
weight = utils.get_weight(set())
self.assertEqual(weight, 0)


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 6c38fef

Please sign in to comment.