diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1bdfc15..0000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM python:3.6.3-alpine3.6 - -RUN apk update && apk add --no-cache build-base libffi-dev openssl-dev gmp-dev git - -# karlfloersch/pyethereum need this https://github.com/ethereum/pyethereum/pull/831 -RUN pip install --no-cache-dir setuptools==37.0.0 tinyrpc==0.6 - -WORKDIR /ethereum -RUN git clone https://github.com/ethereum/vyper.git &&\ - cd vyper &&\ - git reset --hard a154d579062ae67cc3d79c942a30a384bcc1b24e &&\ - python setup.py develop -RUN git clone https://github.com/karlfloersch/pyethereum.git &&\ - cd pyethereum &&\ - git reset --hard 9089d8ecd914c23b233f27c4a6b65d346695f844 - -COPY ./casper pyethereum/casper/casper - -RUN cd pyethereum && python setup.py develop \ No newline at end of file diff --git a/casper/contracts/simple_casper.v.py b/casper/contracts/simple_casper.v.py index 116fab6..115fe33 100644 --- a/casper/contracts/simple_casper.v.py +++ b/casper/contracts/simple_casper.v.py @@ -52,10 +52,12 @@ # Mapping of epoch to what dynasty it is dynasty_in_epoch: public(int128[int128]) -votes: public({ - # How many votes are there for this source epoch from the current dynasty +checkpoints: public({ + # track size of scaled deposits for use in client fork choice + cur_dyn_deposits: wei_value, + prev_dyn_deposits: wei_value, + # track total votes for each dynasty cur_dyn_votes: decimal(wei / m)[int128], - # From the previous dynasty prev_dyn_votes: decimal(wei / m)[int128], # Bitmap of which validator IDs have already voted vote_bitmap: uint256[int128], @@ -108,6 +110,7 @@ BASE_INTEREST_FACTOR: public(decimal) BASE_PENALTY_FACTOR: public(decimal) MIN_DEPOSIT_SIZE: public(wei_value) +START_EPOCH: public(int128) DEFAULT_END_DYNASTY: int128 SIGHASHER_GAS_LIMIT: int128 VALIDATION_GAS_LIMIT: int128 @@ -129,6 +132,8 @@ def __init__( self.BASE_PENALTY_FACTOR = base_penalty_factor self.MIN_DEPOSIT_SIZE = min_deposit_size + self.START_EPOCH = floor(block.number / self.EPOCH_LENGTH) + # helper contracts self.SIGHASHER = sighasher self.PURITY_CHECKER = purity_checker @@ -137,7 +142,7 @@ def __init__( self.next_validator_index = 1 self.dynasty = 0 - self.current_epoch = floor(block.number / self.EPOCH_LENGTH) + self.current_epoch = self.START_EPOCH # TODO: test deposit_scale_factor when deploying when current_epoch > 0 self.deposit_scale_factor[self.current_epoch] = 10000000000.0 self.total_curdyn_deposits = 0 @@ -147,12 +152,12 @@ def __init__( self.VALIDATION_GAS_LIMIT = 200000 -# ***** Constants ***** +# ***** Public Constants ***** @public @constant def main_hash_voted_frac() -> decimal: - return min(self.votes[self.current_epoch].cur_dyn_votes[self.expected_source_epoch] / self.total_curdyn_deposits, - self.votes[self.current_epoch].prev_dyn_votes[self.expected_source_epoch] / self.total_prevdyn_deposits) + return min(self.checkpoints[self.current_epoch].cur_dyn_votes[self.expected_source_epoch] / self.total_curdyn_deposits, + self.checkpoints[self.current_epoch].prev_dyn_votes[self.expected_source_epoch] / self.total_prevdyn_deposits) @public @@ -173,7 +178,10 @@ def total_prevdyn_deposits_in_wei() -> wei_value: return floor(self.total_prevdyn_deposits * self.deposit_scale_factor[self.current_epoch]) +# # Helper functions that clients can call to know what to vote +# + @public @constant def recommended_source_epoch() -> int128: @@ -186,6 +194,79 @@ def recommended_target_hash() -> bytes32: return blockhash(self.current_epoch*self.EPOCH_LENGTH - 1) +# +# Helper methods for client fork choice +# NOTE: both methods use a non-conventional loop structure +# with an incredibly high range and a return/break to exit. +# This is to bypass vyper's prevention of unbounded loops. +# This has been assessed as a reasonable tradeoff because these +# methods are 'constant' and are only to be called locally rather +# than as a part of an actual block tx. +# + +@public +@constant +def highest_justified_epoch(min_total_deposits: wei_value) -> int128: + epoch: int128 + for i in range(1000000000000000000000000000000): + epoch = self.current_epoch - i + is_justified: bool = self.checkpoints[epoch].is_justified + enough_cur_dyn_deposits: bool = self.checkpoints[epoch].cur_dyn_deposits >= min_total_deposits + enough_prev_dyn_deposits: bool = self.checkpoints[epoch].prev_dyn_deposits >= min_total_deposits + + if is_justified and (enough_cur_dyn_deposits and enough_prev_dyn_deposits): + return epoch + + if epoch == self.START_EPOCH: + break + + # no justified epochs found, use 0 as default + # to 0 out the affect of casper on fork choice + return 0 + +@public +@constant +def highest_finalized_epoch(min_total_deposits: wei_value) -> int128: + epoch: int128 + for i in range(1000000000000000000000000000000): + epoch = self.current_epoch - i + is_finalized: bool = self.checkpoints[epoch].is_finalized + enough_cur_dyn_deposits: bool = self.checkpoints[epoch].cur_dyn_deposits >= min_total_deposits + enough_prev_dyn_deposits: bool = self.checkpoints[epoch].prev_dyn_deposits >= min_total_deposits + + if is_finalized and (enough_cur_dyn_deposits and enough_prev_dyn_deposits): + return epoch + + if epoch == self.START_EPOCH: + break + + # no finalized epochs found, use -1 as default + # to signal not to locally finalize anything + return -1 + + +# ****** Private Constants ***** + +# Returns number of epochs since finalization. +@private +@constant +def esf() -> int128: + return self.current_epoch - self.last_finalized_epoch + + +# Compute square root factor +@private +@constant +def sqrt_of_total_deposits() -> decimal: + epoch: int128 = self.current_epoch + ether_deposited_as_number: int128 = floor(max(self.total_prevdyn_deposits, self.total_curdyn_deposits) * + self.deposit_scale_factor[epoch - 1] / as_wei_value(1, "ether")) + 1 + sqrt: decimal = ether_deposited_as_number / 2.0 + for i in range(20): + sqrt = (sqrt + (ether_deposited_as_number / sqrt)) / 2 + return sqrt + + @private @constant def deposit_exists() -> bool: @@ -200,7 +281,7 @@ def deposit_exists() -> bool: def increment_dynasty(): epoch: int128 = self.current_epoch # Increment the dynasty if finalized - if self.votes[epoch - 2].is_finalized: + if self.checkpoints[epoch - 2].is_finalized: self.dynasty += 1 self.total_prevdyn_deposits = self.total_curdyn_deposits self.total_curdyn_deposits += self.dynasty_wei_delta[self.dynasty] @@ -211,10 +292,16 @@ def increment_dynasty(): self.main_hash_justified = False -# Returns number of epochs since finalization. @private -def esf() -> int128: - return self.current_epoch - self.last_finalized_epoch +def insta_finalize(): + epoch: int128 = self.current_epoch + self.main_hash_justified = True + self.checkpoints[epoch - 1].is_justified = True + self.checkpoints[epoch - 1].is_finalized = True + self.last_justified_epoch = epoch - 1 + self.last_finalized_epoch = epoch - 1 + # Log previous Epoch status update + log.Epoch(epoch - 1, self.checkpoint_hashes[epoch - 1], True, True) # Returns the current collective reward factor, which rewards the dynasty for high-voting levels. @@ -225,36 +312,46 @@ def collective_reward() -> decimal: if not self.deposit_exists() or not live: return 0.0 # Fraction that voted - cur_vote_frac: decimal = self.votes[epoch - 1].cur_dyn_votes[self.expected_source_epoch] / self.total_curdyn_deposits - prev_vote_frac: decimal = self.votes[epoch - 1].prev_dyn_votes[self.expected_source_epoch] / self.total_prevdyn_deposits + cur_vote_frac: decimal = self.checkpoints[epoch - 1].cur_dyn_votes[self.expected_source_epoch] / self.total_curdyn_deposits + prev_vote_frac: decimal = self.checkpoints[epoch - 1].prev_dyn_votes[self.expected_source_epoch] / self.total_prevdyn_deposits vote_frac: decimal = min(cur_vote_frac, prev_vote_frac) return vote_frac * self.reward_factor / 2 +# Reward the given validator & miner, and reflect this in total deposit figured @private -def insta_finalize(): - epoch: int128 = self.current_epoch - self.main_hash_justified = True - self.votes[epoch - 1].is_justified = True - self.votes[epoch - 1].is_finalized = True - self.last_justified_epoch = epoch - 1 - self.last_finalized_epoch = epoch - 1 - # Log previous Epoch status update - log.Epoch(epoch - 1, self.checkpoint_hashes[epoch - 1], True, True) +def proc_reward(validator_index: int128, reward: int128(wei/m)): + # Reward validator + self.validators[validator_index].deposit += reward + start_dynasty: int128 = self.validators[validator_index].start_dynasty + end_dynasty: int128 = self.validators[validator_index].end_dynasty + current_dynasty: int128 = self.dynasty + past_dynasty: int128 = current_dynasty - 1 + if ((start_dynasty <= current_dynasty) and (current_dynasty < end_dynasty)): + self.total_curdyn_deposits += reward + if ((start_dynasty <= past_dynasty) and (past_dynasty < end_dynasty)): + self.total_prevdyn_deposits += reward + if end_dynasty < self.DEFAULT_END_DYNASTY: # validator has submit `logout` + self.dynasty_wei_delta[end_dynasty] -= reward + # Reward miner + send(block.coinbase, floor(reward * self.deposit_scale_factor[self.current_epoch] / 8)) -# Compute square root factor +# Removes a validator from the validator pool @private -def sqrt_of_total_deposits() -> decimal: - epoch: int128 = self.current_epoch - ether_deposited_as_number: int128 = floor(max(self.total_prevdyn_deposits, self.total_curdyn_deposits) * - self.deposit_scale_factor[epoch - 1] / as_wei_value(1, "ether")) + 1 - sqrt: decimal = ether_deposited_as_number / 2.0 - for i in range(20): - sqrt = (sqrt + (ether_deposited_as_number / sqrt)) / 2 - return sqrt +def delete_validator(validator_index: int128): + self.validator_indexes[self.validators[validator_index].withdrawal_addr] = 0 + self.validators[validator_index] = { + deposit: 0, + start_dynasty: 0, + end_dynasty: 0, + addr: None, + withdrawal_addr: None + } +# cannot be labeled @constant because of external call +# even though the call is to a pure contract call @private def validate_signature(sighash: bytes32, sig: bytes <= 1024, validator_index: int128) -> bool: return extract32(raw_call(self.validators[validator_index].addr, concat(sighash, sig), gas=self.VALIDATION_GAS_LIMIT, outsize=32), 0) == convert(1, 'bytes32') @@ -269,7 +366,10 @@ def initialize_epoch(epoch: int128): computed_current_epoch: int128 = floor(block.number / self.EPOCH_LENGTH) assert epoch <= computed_current_epoch and epoch == self.current_epoch + 1 - # Setup + # must track the deposits related to the checkpoint _before_ updating current_epoch + self.checkpoints[epoch].cur_dyn_deposits = self.total_curdyn_deposits_in_wei() + self.checkpoints[epoch].prev_dyn_deposits = self.total_prevdyn_deposits_in_wei() + self.current_epoch = epoch self.last_voter_rescale = 1 + self.collective_reward() @@ -287,11 +387,12 @@ def initialize_epoch(epoch: int128): self.insta_finalize() self.reward_factor = 0 + # Store checkpoint hash for easy access + self.checkpoint_hashes[epoch] = self.recommended_target_hash() + # Increment the dynasty if finalized self.increment_dynasty() - # Store checkpoint hash for easy access - self.checkpoint_hashes[epoch] = self.recommended_target_hash() # Log new epoch creation log.Epoch(epoch, self.checkpoint_hashes[epoch], False, False) @@ -345,18 +446,6 @@ def logout(logout_msg: bytes <= 1024): log.Logout(self.validators[validator_index].withdrawal_addr, validator_index, self.validators[validator_index].end_dynasty) -# Removes a validator from the validator pool -@private -def delete_validator(validator_index: int128): - self.validator_indexes[self.validators[validator_index].withdrawal_addr] = 0 - self.validators[validator_index] = { - deposit: 0, - start_dynasty: 0, - end_dynasty: 0, - addr: None, - withdrawal_addr: None - } - # Withdraw deposited ether @public @@ -373,24 +462,6 @@ def withdraw(validator_index: int128): self.delete_validator(validator_index) -# Reward the given validator & miner, and reflect this in total deposit figured -@private -def proc_reward(validator_index: int128, reward: int128(wei/m)): - # Reward validator - self.validators[validator_index].deposit += reward - start_dynasty: int128 = self.validators[validator_index].start_dynasty - end_dynasty: int128 = self.validators[validator_index].end_dynasty - current_dynasty: int128 = self.dynasty - past_dynasty: int128 = current_dynasty - 1 - if ((start_dynasty <= current_dynasty) and (current_dynasty < end_dynasty)): - self.total_curdyn_deposits += reward - if ((start_dynasty <= past_dynasty) and (past_dynasty < end_dynasty)): - self.total_prevdyn_deposits += reward - if end_dynasty < self.DEFAULT_END_DYNASTY: # validator has submit `logout` - self.dynasty_wei_delta[end_dynasty] -= reward - # Reward miner - send(block.coinbase, floor(reward * self.deposit_scale_factor[self.current_epoch] / 8)) - # Process a vote message @public @@ -408,13 +479,13 @@ def vote(vote_msg: bytes <= 1024): assert self.validate_signature(sighash, sig, validator_index) # Check that this vote has not yet been made - assert not bitwise_and(self.votes[target_epoch].vote_bitmap[floor(validator_index / 256)], + assert not bitwise_and(self.checkpoints[target_epoch].vote_bitmap[floor(validator_index / 256)], shift(convert(1, 'uint256'), validator_index % 256)) # Check that the vote's target epoch and hash are correct assert target_hash == self.recommended_target_hash() assert target_epoch == self.current_epoch # Check that the vote source points to a justified epoch - assert self.votes[source_epoch].is_justified + assert self.checkpoints[source_epoch].is_justified # ensure validator can vote for the target_epoch start_dynasty: int128 = self.validators[validator_index].start_dynasty @@ -426,19 +497,19 @@ def vote(vote_msg: bytes <= 1024): assert in_current_dynasty or in_prev_dynasty # Record that the validator voted for this target epoch so they can't again - self.votes[target_epoch].vote_bitmap[floor(validator_index / 256)] = \ - bitwise_or(self.votes[target_epoch].vote_bitmap[floor(validator_index / 256)], + self.checkpoints[target_epoch].vote_bitmap[floor(validator_index / 256)] = \ + bitwise_or(self.checkpoints[target_epoch].vote_bitmap[floor(validator_index / 256)], shift(convert(1, 'uint256'), validator_index % 256)) # Record that this vote took place - current_dynasty_votes: decimal(wei/m) = self.votes[target_epoch].cur_dyn_votes[source_epoch] - previous_dynasty_votes: decimal(wei/m) = self.votes[target_epoch].prev_dyn_votes[source_epoch] + current_dynasty_votes: decimal(wei/m) = self.checkpoints[target_epoch].cur_dyn_votes[source_epoch] + previous_dynasty_votes: decimal(wei/m) = self.checkpoints[target_epoch].prev_dyn_votes[source_epoch] if in_current_dynasty: current_dynasty_votes += self.validators[validator_index].deposit - self.votes[target_epoch].cur_dyn_votes[source_epoch] = current_dynasty_votes + self.checkpoints[target_epoch].cur_dyn_votes[source_epoch] = current_dynasty_votes if in_prev_dynasty: previous_dynasty_votes += self.validators[validator_index].deposit - self.votes[target_epoch].prev_dyn_votes[source_epoch] = previous_dynasty_votes + self.checkpoints[target_epoch].prev_dyn_votes[source_epoch] = previous_dynasty_votes # Process rewards. # Pay the reward if the vote was submitted in time and the vote is voting the correct data @@ -450,8 +521,8 @@ def vote(vote_msg: bytes <= 1024): # then the hash value is justified if (current_dynasty_votes >= self.total_curdyn_deposits * 2 / 3 and previous_dynasty_votes >= self.total_prevdyn_deposits * 2 / 3) and \ - not self.votes[target_epoch].is_justified: - self.votes[target_epoch].is_justified = True + not self.checkpoints[target_epoch].is_justified: + self.checkpoints[target_epoch].is_justified = True self.last_justified_epoch = target_epoch self.main_hash_justified = True @@ -461,7 +532,7 @@ def vote(vote_msg: bytes <= 1024): # If two epochs are justified consecutively, # then the source_epoch finalized if target_epoch == source_epoch + 1: - self.votes[source_epoch].is_finalized = True + self.checkpoints[source_epoch].is_finalized = True self.last_finalized_epoch = source_epoch # Log source epoch status update log.Epoch(source_epoch, self.checkpoint_hashes[source_epoch], True, True) diff --git a/tests/conftest.py b/tests/conftest.py index 413d8d1..a853148 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -249,7 +249,7 @@ def casper_chain( casper_tx = Transaction( nonce, GAS_PRICE, - 5000000, + 6000000, b'', 0, deploy_code @@ -257,6 +257,8 @@ def casper_chain( test_chain.direct_tx(casper_tx) nonce += 1 + test_chain.mine(1) + # Casper contract needs money for its activity casper_fund_tx = Transaction( nonce, @@ -277,16 +279,19 @@ def deploy_casper_contract( test_chain, casper_code, casper_ct, + casper_abi, + casper_address, dependency_transactions, sig_hasher_address, purity_checker_address, base_sender_privkey): def deploy_casper_contract(contract_args): - casper_chain( + chain = casper_chain( test_chain, contract_args, casper_code, casper_ct, dependency_transactions, sig_hasher_address, purity_checker_address, base_sender_privkey ) + return casper(chain, casper_abi, casper_address) return deploy_casper_contract diff --git a/tests/test_chain_initialization.py b/tests/test_chain_initialization.py index d292c41..579578d 100644 --- a/tests/test_chain_initialization.py +++ b/tests/test_chain_initialization.py @@ -49,6 +49,22 @@ def test_init_first_epoch(casper, new_epoch): assert casper.current_epoch() == 1 +@pytest.mark.parametrize( + 'start_epoch', + [ + (0), (1), (4), (10) + ] +) +def test_start_epoch(test_chain, start_epoch, epoch_length, casper_args, deploy_casper_contract): + test_chain.mine( + epoch_length * start_epoch - test_chain.head_state.block_number + ) + + casper = deploy_casper_contract(casper_args) + assert casper.START_EPOCH() == start_epoch + assert casper.current_epoch() == start_epoch + + @pytest.mark.parametrize( 'epoch_length, success', [ @@ -70,4 +86,5 @@ def test_epoch_length(epoch_length, success, casper_args, ) return - deploy_casper_contract(casper_args) + casper = deploy_casper_contract(casper_args) + assert casper.EPOCH_LENGTH() == epoch_length diff --git a/tests/test_fork_choice_helpers.py b/tests/test_fork_choice_helpers.py new file mode 100644 index 0000000..2a16706 --- /dev/null +++ b/tests/test_fork_choice_helpers.py @@ -0,0 +1,215 @@ +import pytest + + +@pytest.mark.parametrize( + 'start_epoch,min_deposits', + [ + (2, 0), + (3, 1), + (0, int(1e4)), + (7, int(4e10)), + (6, int(2e30)) + ] +) +def test_default_highest_justified_epoch(test_chain, start_epoch, min_deposits, epoch_length, + casper_args, deploy_casper_contract): + test_chain.mine( + epoch_length * start_epoch - test_chain.head_state.block_number + ) + casper = deploy_casper_contract(casper_args) + + assert casper.highest_justified_epoch(min_deposits) == 0 + + +@pytest.mark.parametrize( + 'min_deposits', + [ + (0), + (1), + (int(1e4)), + (int(4e10)), + (int(2e30)) + ] +) +def test_highest_justified_epoch_no_validators(casper, new_epoch, min_deposits): + for i in range(5): + highest_justified_epoch = casper.highest_justified_epoch(min_deposits) + if min_deposits == 0: + assert highest_justified_epoch == casper.last_justified_epoch() + else: + assert highest_justified_epoch == 0 + + new_epoch() + + +@pytest.mark.parametrize( + 'start_epoch,min_deposits', + [ + (2, 0), + (3, 1), + (0, int(1e4)), + (7, int(4e10)), + (6, int(2e30)) + ] +) +def test_default_highest_finalized_epoch(test_chain, start_epoch, min_deposits, epoch_length, + casper_args, deploy_casper_contract): + test_chain.mine( + epoch_length * start_epoch - test_chain.head_state.block_number + ) + casper = deploy_casper_contract(casper_args) + + assert casper.highest_finalized_epoch(min_deposits) == -1 + + +@pytest.mark.parametrize( + 'min_deposits', + [ + (0), + (1), + (int(1e4)), + (int(4e10)), + (int(2e30)) + ] +) +def test_highest_finalized_epoch_no_validators(casper, new_epoch, min_deposits): + for i in range(5): + highest_finalized_epoch = casper.highest_finalized_epoch(min_deposits) + if min_deposits > 0: + expected_epoch = -1 + else: + if casper.current_epoch() == 0: + expected_epoch = -1 + else: + expected_epoch = casper.last_finalized_epoch() + + assert highest_finalized_epoch == expected_epoch + + new_epoch() + + +def test_highest_justified_and_epoch(casper, funded_privkey, deposit_amount, + new_epoch, induct_validator, mk_suggested_vote): + validator_index = induct_validator(funded_privkey, deposit_amount) + higher_deposit = int(deposit_amount * 1.1) + + assert casper.total_curdyn_deposits_in_wei() == deposit_amount + assert casper.current_epoch() == 3 + + assert casper.highest_justified_epoch(deposit_amount) == 0 + assert casper.highest_finalized_epoch(deposit_amount) == -1 + assert casper.highest_justified_epoch(0) == 2 + assert casper.highest_finalized_epoch(0) == 2 + assert casper.highest_justified_epoch(higher_deposit) == 0 + assert casper.highest_finalized_epoch(higher_deposit) == -1 + + # justify current_epoch in contract + casper.vote(mk_suggested_vote(validator_index, funded_privkey)) + + assert casper.checkpoints__cur_dyn_deposits(3) == 0 + assert casper.checkpoints__prev_dyn_deposits(3) == 0 + assert casper.last_justified_epoch() == 3 + assert casper.last_finalized_epoch() == 2 + + assert casper.highest_justified_epoch(deposit_amount) == 0 + assert casper.highest_finalized_epoch(deposit_amount) == -1 + assert casper.highest_justified_epoch(0) == 3 + assert casper.highest_finalized_epoch(0) == 2 + assert casper.highest_justified_epoch(higher_deposit) == 0 + assert casper.highest_finalized_epoch(higher_deposit) == -1 + + new_epoch() + casper.vote(mk_suggested_vote(validator_index, funded_privkey)) + + assert casper.checkpoints__cur_dyn_deposits(4) == deposit_amount + assert casper.checkpoints__prev_dyn_deposits(4) == 0 + assert casper.last_justified_epoch() == 4 + assert casper.last_finalized_epoch() == 3 + + assert casper.highest_justified_epoch(deposit_amount) == 0 + assert casper.highest_finalized_epoch(deposit_amount) == -1 + assert casper.highest_justified_epoch(0) == 4 + assert casper.highest_finalized_epoch(0) == 3 + assert casper.highest_justified_epoch(higher_deposit) == 0 + assert casper.highest_finalized_epoch(higher_deposit) == -1 + + new_epoch() + casper.vote(mk_suggested_vote(validator_index, funded_privkey)) + + assert casper.checkpoints__cur_dyn_deposits(5) == deposit_amount + assert casper.checkpoints__prev_dyn_deposits(5) == deposit_amount + assert casper.last_justified_epoch() == 5 + assert casper.last_finalized_epoch() == 4 + + # enough prev and cur deposits in checkpoint 5 for the justified block + assert casper.highest_justified_epoch(deposit_amount) == 5 + # not enough prev and cur deposits in checkpoint 4 for the finalized block + assert casper.highest_finalized_epoch(deposit_amount) == -1 + assert casper.highest_justified_epoch(0) == 5 + assert casper.highest_finalized_epoch(0) == 4 + assert casper.highest_justified_epoch(higher_deposit) == 0 + assert casper.highest_finalized_epoch(higher_deposit) == -1 + + new_epoch() + casper.vote(mk_suggested_vote(validator_index, funded_privkey)) + + assert casper.checkpoints__cur_dyn_deposits(6) > deposit_amount + assert casper.checkpoints__prev_dyn_deposits(6) > deposit_amount + assert casper.last_justified_epoch() == 6 + assert casper.last_finalized_epoch() == 5 + + # enough deposits in checkpoint 6 for justified and checkpoint 5 for finalized! + assert casper.highest_justified_epoch(deposit_amount) == 6 + assert casper.highest_finalized_epoch(deposit_amount) == 5 + assert casper.highest_justified_epoch(higher_deposit) == 0 + assert casper.highest_finalized_epoch(higher_deposit) == -1 + assert casper.highest_justified_epoch(0) == 6 + assert casper.highest_finalized_epoch(0) == 5 + + new_epoch() + # no vote + + assert casper.checkpoints__cur_dyn_deposits(7) > deposit_amount + assert casper.checkpoints__prev_dyn_deposits(7) > deposit_amount + assert casper.last_justified_epoch() == 6 + assert casper.last_finalized_epoch() == 5 + + assert casper.highest_justified_epoch(deposit_amount) == 6 + assert casper.highest_finalized_epoch(deposit_amount) == 5 + assert casper.highest_justified_epoch(0) == 6 + assert casper.highest_finalized_epoch(0) == 5 + assert casper.highest_justified_epoch(higher_deposit) == 0 + assert casper.highest_finalized_epoch(higher_deposit) == -1 + + new_epoch() + casper.vote(mk_suggested_vote(validator_index, funded_privkey)) + + assert casper.checkpoints__cur_dyn_deposits(8) > deposit_amount + assert casper.checkpoints__prev_dyn_deposits(8) > deposit_amount + # new justified + assert casper.last_justified_epoch() == 8 + # no new finalized because not sequential justified blocks + assert casper.last_finalized_epoch() == 5 + + assert casper.highest_justified_epoch(deposit_amount) == 8 + assert casper.highest_finalized_epoch(deposit_amount) == 5 + assert casper.highest_justified_epoch(0) == 8 + assert casper.highest_finalized_epoch(0) == 5 + assert casper.highest_justified_epoch(higher_deposit) == 0 + assert casper.highest_finalized_epoch(higher_deposit) == -1 + + new_epoch() + casper.vote(mk_suggested_vote(validator_index, funded_privkey)) + + assert casper.checkpoints__cur_dyn_deposits(9) > deposit_amount + assert casper.checkpoints__prev_dyn_deposits(9) > deposit_amount + # new justified and finalized because sequential justified blocks + assert casper.last_justified_epoch() == 9 + assert casper.last_finalized_epoch() == 8 + + assert casper.highest_justified_epoch(deposit_amount) == 9 + assert casper.highest_finalized_epoch(deposit_amount) == 8 + assert casper.highest_justified_epoch(0) == 9 + assert casper.highest_finalized_epoch(0) == 8 + assert casper.highest_justified_epoch(higher_deposit) == 0 + assert casper.highest_finalized_epoch(higher_deposit) == -1 diff --git a/tests/test_initialize_epoch.py b/tests/test_initialize_epoch.py new file mode 100644 index 0000000..dab799e --- /dev/null +++ b/tests/test_initialize_epoch.py @@ -0,0 +1,106 @@ +# ensure that our fixture 'new_epoch' functions properly +def test_new_epoch(casper_chain, casper, new_epoch): + for _ in range(4): + prev_epoch = casper.current_epoch() + prev_block_number = casper_chain.head_state.block_number + expected_jump = casper.EPOCH_LENGTH() - (prev_block_number % casper.EPOCH_LENGTH()) + + new_epoch() + + assert casper.current_epoch() == prev_epoch + 1 + assert casper_chain.head_state.block_number == prev_block_number + expected_jump + + +def test_checkpoint_hashes(casper_chain, casper, new_epoch): + for _ in range(4): + next_epoch = casper.current_epoch() + 1 + epoch_length = casper.EPOCH_LENGTH() + + casper_chain.mine(epoch_length * next_epoch - casper_chain.head_state.block_number) + + casper.initialize_epoch(next_epoch) + current_epoch = casper.current_epoch() + # This looks incorrect but `get_block_hash` actually indexes + # into a list of prev_hashes. The 0th index is the hash of the previous block + expected_hash = casper_chain.head_state.get_block_hash(0) + + assert current_epoch == next_epoch + assert casper.checkpoint_hashes(current_epoch) == expected_hash + + +def test_checkpoint_deposits(casper_chain, casper, funded_privkeys, deposit_amount, + induct_validator, deposit_validator, new_epoch, + mk_suggested_vote): + current_epoch = casper.current_epoch() + assert casper.checkpoints__cur_dyn_deposits(current_epoch) == 0 + assert casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 + + new_epoch() + current_epoch = casper.current_epoch() + + assert casper.checkpoints__cur_dyn_deposits(current_epoch) == 0 + assert casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 + + initial_validator = deposit_validator(funded_privkeys[0], deposit_amount) + + new_epoch() + current_epoch = casper.current_epoch() + + assert casper.checkpoints__cur_dyn_deposits(current_epoch) == 0 + assert casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 + + new_epoch() + current_epoch = casper.current_epoch() + + # checkpoints are for the last block in the previous epoch + # so checkpoint dynasty totals should lag behind + assert casper.total_curdyn_deposits_in_wei() == deposit_amount + assert casper.total_prevdyn_deposits_in_wei() == 0 + assert casper.checkpoints__cur_dyn_deposits(current_epoch) == 0 + assert casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 + + casper.vote(mk_suggested_vote(initial_validator, funded_privkeys[0])) + new_epoch() + current_epoch = casper.current_epoch() + + assert casper.total_curdyn_deposits_in_wei() == deposit_amount + assert casper.total_prevdyn_deposits_in_wei() == deposit_amount + assert casper.checkpoints__cur_dyn_deposits(current_epoch) == deposit_amount + assert casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 + + second_validator = deposit_validator(funded_privkeys[1], deposit_amount) + + casper.vote(mk_suggested_vote(initial_validator, funded_privkeys[0])) + new_epoch() + current_epoch = casper.current_epoch() + + assert casper.total_curdyn_deposits_in_wei() == deposit_amount + assert casper.total_prevdyn_deposits_in_wei() == deposit_amount + assert casper.checkpoints__cur_dyn_deposits(current_epoch) == deposit_amount + assert casper.checkpoints__prev_dyn_deposits(current_epoch) == deposit_amount + + prev_curdyn_deposits = casper.total_curdyn_deposits_in_wei() + prev_prevdyn_deposits = casper.total_prevdyn_deposits_in_wei() + + casper.vote(mk_suggested_vote(initial_validator, funded_privkeys[0])) + new_epoch() + current_epoch = casper.current_epoch() + + assert casper.checkpoints__cur_dyn_deposits(current_epoch) >= prev_curdyn_deposits \ + and casper.checkpoints__cur_dyn_deposits(current_epoch) < prev_curdyn_deposits * 1.01 + assert casper.checkpoints__prev_dyn_deposits(current_epoch) >= prev_prevdyn_deposits \ + and casper.checkpoints__prev_dyn_deposits(current_epoch) < prev_prevdyn_deposits * 1.01 + + for _ in range(3): + prev_curdyn_deposits = casper.total_curdyn_deposits_in_wei() + prev_prevdyn_deposits = casper.total_prevdyn_deposits_in_wei() + + casper.vote(mk_suggested_vote(initial_validator, funded_privkeys[0])) + casper.vote(mk_suggested_vote(second_validator, funded_privkeys[1])) + new_epoch() + current_epoch = casper.current_epoch() + + assert casper.checkpoints__cur_dyn_deposits(current_epoch) >= prev_curdyn_deposits \ + and casper.checkpoints__cur_dyn_deposits(current_epoch) < prev_curdyn_deposits * 1.01 + assert casper.checkpoints__prev_dyn_deposits(current_epoch) >= prev_prevdyn_deposits \ + and casper.checkpoints__prev_dyn_deposits(current_epoch) < prev_prevdyn_deposits * 1.01 diff --git a/tests/test_multiple_validators.py b/tests/test_multiple_validators.py index e1254a9..e3d5f64 100644 --- a/tests/test_multiple_validators.py +++ b/tests/test_multiple_validators.py @@ -38,7 +38,7 @@ def test_justification_and_finalization(casper, funded_privkeys, deposit_amount, for i, validator_index in enumerate(validator_indexes): casper.vote(mk_suggested_vote(validator_index, funded_privkeys[i])) assert casper.main_hash_justified() - assert casper.votes__is_finalized(casper.recommended_source_epoch()) + assert casper.checkpoints__is_finalized(casper.recommended_source_epoch()) new_epoch() assert casper.dynasty() == prev_dynasty + 1 prev_dynasty += 1 @@ -58,7 +58,7 @@ def test_voters_make_more(casper, funded_privkeys, deposit_amount, new_epoch, for i, validator_index in enumerate(voting_indexes): casper.vote(mk_suggested_vote(validator_index, voting_privkeys[i])) assert casper.main_hash_justified() - assert casper.votes__is_finalized(casper.recommended_source_epoch()) + assert casper.checkpoints__is_finalized(casper.recommended_source_epoch()) new_epoch() assert casper.dynasty() == prev_dynasty + 1 prev_dynasty += 1 @@ -95,7 +95,7 @@ def test_partial_online(casper, funded_privkeys, deposit_amount, new_epoch, if ovp >= 0.75: assert casper.main_hash_justified() - assert casper.votes__is_finalized(casper.recommended_source_epoch()) + assert casper.checkpoints__is_finalized(casper.recommended_source_epoch()) break new_epoch() diff --git a/tests/test_voting.py b/tests/test_voting.py index 300d0e1..c426e68 100644 --- a/tests/test_voting.py +++ b/tests/test_voting.py @@ -49,7 +49,7 @@ def test_vote_single_validator(casper, funded_privkey, deposit_amount, for i in range(10): casper.vote(mk_suggested_vote(validator_index, funded_privkey)) assert casper.main_hash_justified() - assert casper.votes__is_finalized(casper.recommended_source_epoch()) + assert casper.checkpoints__is_finalized(casper.recommended_source_epoch()) new_epoch() assert casper.dynasty() == prev_dynasty + 1 prev_dynasty += 1 @@ -149,14 +149,14 @@ def test_consensus_after_non_finalization_streak(casper, funded_privkey, deposit new_epoch() assert not casper.main_hash_justified() - assert not casper.votes__is_finalized(casper.recommended_source_epoch()) + assert not casper.checkpoints__is_finalized(casper.recommended_source_epoch()) casper.vote(mk_suggested_vote(validator_index, funded_privkey)) assert casper.main_hash_justified() - assert not casper.votes__is_finalized(casper.recommended_source_epoch()) + assert not casper.checkpoints__is_finalized(casper.recommended_source_epoch()) new_epoch() casper.vote(mk_suggested_vote(validator_index, funded_privkey)) assert casper.main_hash_justified() - assert casper.votes__is_finalized(casper.recommended_source_epoch()) + assert casper.checkpoints__is_finalized(casper.recommended_source_epoch()) diff --git a/tmux.sh b/tmux.sh deleted file mode 100644 index b40a107..0000000 --- a/tmux.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/zsh - -tmux start-server; - -TMUX= tmux new-session -d -s casper -n casper-daemons - -tmux send-keys -t casper:0.0 source\ \~/.zshrc C-m -tmux send-keys -t casper:0.0 casper\ -l\ casper:debug\ -d\ data0\ run\ 0\ --fake-account C-m - -tmux splitw -t casper:0 -tmux select-layout -t casper:0 tiled -tmux send-keys -t casper:0.1 source\ \~/.zshrc C-m -tmux send-keys -t casper:0.1 casper\ -l\ casper:debug\ -d\ data1\ run\ 1\ --fake-account C-m - -tmux splitw -t casper:0 -tmux select-layout -t casper:0 tiled -tmux send-keys -t casper:0.2 source\ \~/.zshrc C-m -tmux send-keys -t casper:0.2 casper\ -l\ casper:debug\ -d\ data2\ run\ 2\ --fake-account C-m - -tmux splitw -t casper:0 -tmux select-layout -t casper:0 tiled -tmux send-keys -t casper:0.3 source\ \~/.zshrc C-m -tmux send-keys -t casper:0.3 casper\ -l\ casper:debug\ -d\ data3\ run\ 3\ --fake-account C-m - -tmux select-layout -t casper:0 tiled -tmux select-pane -t casper:0.0 -tmux select-window -t 0 -tmux select-pane -t 0 - -if [ -z "$TMUX" ]; then - tmux -u attach-session -t casper -else - tmux -u switch-client -t casper -fi