Skip to content
This repository has been archived by the owner on Jan 6, 2025. It is now read-only.

Commit

Permalink
Merge pull request #166 from ethereum/develop
Browse files Browse the repository at this point in the history
Release 0.2.0
  • Loading branch information
Nate Rush authored Jan 25, 2018
2 parents b6ddecb + a19d47d commit 4053cb3
Show file tree
Hide file tree
Showing 120 changed files with 3,738 additions and 1,562 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
- run:
command: |
. venv/bin/activate
flake8 --ignore E501 casper/ casper.py
pytest
- store_artifacts:
path: test-reports/
Expand Down
11 changes: 11 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Contributing
Hello fellow contributor! This document will provide an overview of the process for contributing to the CBC casper codebase! Please note that these guidelines may be changed in the future.

# Getting Started
To get started with the codebase, see the download and installation instructions in the readme.

# Making a Pull Request
Once you have implemented your proposed changes, create a Pull Request to the `develop` branch. Ensure there are no merge conflicts with your branch and that all tests pass.

# Resources
See the [wiki](https://github.com/ethereum/cbc-casper/wiki) for a collection of resources related to the project.
5 changes: 5 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Thank you for contributing to CBC Casper!

Please make your Pull Request against the `develop` branch and ensure that all tests pass.

Unless fixing a typo or other small issue, PRs should link to specific issues.
15 changes: 0 additions & 15 deletions CONTRIBUTING.md

This file was deleted.

9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pip install using `requirements.txt`
### Standard
Standard simulations are marked up for use as follows:

NOTE: after each viewgraph appears, you must manually exit the window for the simulation to continue to run!

```
make run-[rand | rrob | full | nofinal | binary]
```
Expand All @@ -54,7 +54,7 @@ make run-[rand | rrob | full | nofinal | binary]

`binary:` unlike the above message propagation schemes, this changes the protocol to cbc-casper with binary data structures! Instead of a blockchain, this protocol just comes to consensus on a single bit.

The number of validators, the number of messages that propagate per round, and the report interval can be edited in `casper/settings.py`.
By default, a gif and associated images of the simulation will be saved in `graphs/graph_num_0/`. These settings can be modified, along with the number of validators, the number of messages that propagate per round, and the report interval in the `config.ini`.

### Advanced
Advanced simulations can be run with a little command line wizardy.
Expand Down Expand Up @@ -99,7 +99,10 @@ The following are the fields that make up an experiment to be defined in a `.jso
available schemes are "rand", "rrob", "full", and "nofinal".

`protocol` (string): Specifies the protocol to test. Available protocols are
"blockchain" and "binary" (for now!).
"blockchain", "integer", and "binary".

`network` (string): Specifies the network model test. Available networks are
"no-delay", "constant", "linear", and "gaussian".

`num_simulations` (number): Specifies the number of simulations to run. Each
simulation starts with a fresh setup -- messages, validators, etc.
Expand Down
34 changes: 29 additions & 5 deletions casper.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,23 @@
from simulations.utils import (
generate_random_gaussian_validator_set,
message_maker,
select_network,
select_protocol,
MESSAGE_MODES,
NETWORKS,
PROTOCOLS
)


def str2bool(v):
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')


def default_configuration():
config = ConfigParser()
config.read("config.ini")
Expand All @@ -39,6 +50,11 @@ def main():
choices=PROTOCOLS,
help='specifies the protocol for the simulation'
)
parser.add_argument(
'--network', type=str, default=config.get("DefaultNetwork"),
choices=NETWORKS,
help='specifies the network model for the simulation'
)
parser.add_argument(
'--validators', type=int, default=config.getint("NumValidators"),
help='specifies the number of validators in validator set'
Expand All @@ -52,30 +68,38 @@ def main():
help='specifies the interval in rounds at which to plot results'
)
parser.add_argument(
'--hide-display', help='hide simulation display', action='store_true'
'--hide-display', action="store_true",
help='display simulations round by round'
)
parser.add_argument(
'--save', help='hide simulation display', action='store_true'
'--save', type=str2bool, default=config.getboolean("Save"),
help='save the simulation in graphs/ directory'
)
parser.add_argument(
'--justify-messages', type=str2bool, default=config.getboolean("JustifyMessages"),
help='force full propagation of all messages in justification of message when sending'
)

args = parser.parse_args()
protocol = select_protocol(args.protocol)
network_type = select_network(args.network)

validator_set = generate_random_gaussian_validator_set(
protocol,
args.validators
)
network = network_type(validator_set, protocol)

msg_gen = message_maker(args.mode)
display = not args.hide_display

simulation_runner = SimulationRunner(
validator_set,
msg_gen,
protocol,
protocol=protocol,
network=network,
total_rounds=args.rounds,
report_interval=args.report_interval,
display=display,
display=(not args.hide_display),
save=args.save,
)
simulation_runner.run()
Expand Down
139 changes: 84 additions & 55 deletions casper/abstract_view.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""The view module ... """
from casper.justification import Justification
"""The parent view module that specific protocol views inherit from"""


class AbstractView(object):
Expand All @@ -9,68 +8,98 @@ def __init__(self, messages=None):
if messages is None:
messages = set()

self.add_messages(messages)

self.messages = set()
self.latest_messages = dict()

def __str__(self):
output = "View: \n"
for bet in self.messages:
output += str(bet) + "\n"
return output

def justification(self):
"""Returns the latest messages seen from other validators, to justify estimate."""
return Justification(self.latest_messages)

def get_new_messages(self, showed_messages):
"""This method returns the set of messages out of showed_messages
and their dependency that isn't part of the view."""

new_messages = set()
# The memo will keep track of messages we've already looked at, so we don't redo work.
memo = set()

# At the start, our working set will be the "showed messages" parameter.
current_set = set(showed_messages)
while current_set != set():

next_set = set()
# If there's no message in the current working set.
for message in current_set:

# Which we haven't seen it in the view or during this loop.
if message not in self.messages and message not in memo:

# But if we do have a new message, then we add it to our pile..
new_messages.add(message)
self.justified_messages = dict() # message hash => message
self.pending_messages = dict() # message hash => message

# and add the bet in its justification to our next working set
for bet in message.justification.latest_messages.values():
next_set.add(bet)
# Keeping a record of very message we inspect, being sure not
# to do any extra (exponential complexity) work.
memo.add(message)
self.num_missing_dependencies = dict() # message hash => number of message hashes
self.dependents_of_message = dict() # message hash => list(message hashes)

current_set = next_set
self.latest_messages = dict() # validator => message

# After the loop is done, we return a set of new messages.
return new_messages
self.add_messages(messages)

def estimate(self):
'''Must be defined in child class.
Returns estimate based on current messages in the view'''
pass
raise NotImplementedError

def add_messages(self, showed_messages):
def update_safe_estimates(self, validator_set):
'''Must be defined in child class.'''
pass
raise NotImplementedError

def make_new_message(self, validator):
'''Must be defined in child class.'''
def add_messages(self, showed_messages):
"""Adds a set of newly received messages to pending or justified"""
for message in showed_messages:
if message.hash in self.pending_messages or message.hash in self.justified_messages:
continue

missing_message_hashes = self._missing_messages_in_justification(message)
if not any(missing_message_hashes):
self.receive_justified_message(message)
else:
self.receive_pending_message(message, missing_message_hashes)

def receive_justified_message(self, message):
"""Upon receiving a justified message, resolves waiting messages and adds to view"""
newly_justified_messages = self.get_newly_justified_messages(message)

for justified_message in newly_justified_messages:
self._add_to_latest_messages(justified_message)
self._add_justified_remove_pending(justified_message)
self._update_protocol_specific_view(justified_message)

def receive_pending_message(self, message, missing_message_hashes):
"""Updates and stores pending messages and dependencies"""
self.pending_messages[message.hash] = message
self.num_missing_dependencies[message.hash] = len(missing_message_hashes)

for missing_message_hash in missing_message_hashes:
if missing_message_hash not in self.dependents_of_message:
self.dependents_of_message[missing_message_hash] = []

self.dependents_of_message[missing_message_hash].append(message.hash)

def get_newly_justified_messages(self, message):
"""Given a new justified message, get all messages that are now justified
due to its receipt"""
newly_justified_messages = set([message])

for dependent_hash in self.dependents_of_message.get(message.hash, set()):
self.num_missing_dependencies[dependent_hash] -= 1

if self.num_missing_dependencies[dependent_hash] == 0:
new_message = self.pending_messages[dependent_hash]
newly_justified_messages.update(self.get_newly_justified_messages(new_message))

return newly_justified_messages

def _update_protocol_specific_view(self, message):
""" Can be implemented by child, though not necessary
Updates a view's specific info, given a justified message"""
pass

def update_safe_estimates(self, validator_set):
'''Must be defined in child class.'''
pass
def _add_to_latest_messages(self, message):
"""Updates a views most recent messages, if this message is later"""
if message.sender not in self.latest_messages:
self.latest_messages[message.sender] = message
elif self.latest_messages[message.sender].sequence_number < message.sequence_number:
self.latest_messages[message.sender] = message

def _add_justified_remove_pending(self, message):
"""Atomic action that:
- removes all data related to tracking the not yet justified message
- adds message to justified dict"""
self.justified_messages[message.hash] = message
if message.hash in self.num_missing_dependencies:
del self.num_missing_dependencies[message.hash]
if message.hash in self.dependents_of_message:
del self.dependents_of_message[message.hash]
if message.hash in self.pending_messages:
del self.pending_messages[message.hash]

def _missing_messages_in_justification(self, message):
"""Returns the set of not seen messages hashes from the justification of a message"""
return {
message_hash for message_hash in message.justification.values()
if message_hash not in self.justified_messages
}
10 changes: 0 additions & 10 deletions casper/binary/binary_protocol.py

This file was deleted.

68 changes: 0 additions & 68 deletions casper/binary/binary_view.py

This file was deleted.

Loading

0 comments on commit 4053cb3

Please sign in to comment.