diff --git a/.gitignore b/.gitignore index 19e3370bb..a36b4ff68 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ pyethereum/monkeypatch.py .eggs .cache .env +.python-version +.idea +.vscode diff --git a/.python-version b/.python-version deleted file mode 100644 index d70c8f8d8..000000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.6 diff --git a/casper b/casper index 0026df8f7..34503973a 160000 --- a/casper +++ b/casper @@ -1 +1 @@ -Subproject commit 0026df8f73bb03f5479fd01dc1738bc4db3b987c +Subproject commit 34503973abceed0f0267fe35e229a40e7a94270a diff --git a/ethereum/config.py b/ethereum/config.py index 7cd14258f..970fc3272 100644 --- a/ethereum/config.py +++ b/ethereum/config.py @@ -92,6 +92,11 @@ SERENITY_HEADER_POST_FINALIZER=utils.int_to_addr(254), SERENITY_GETTER_CODE=decode_hex( '60ff331436604014161560155760203560003555005b6000355460205260206020f3'), + # Casper FFG + EPOCH_LENGTH=10, + WITHDRAWAL_DELAY=100, + BASE_INTEREST_FACTOR=0.02, + BASE_PENALTY_FACTOR=0.002, # Custom specials CUSTOM_SPECIALS={}, ) diff --git a/ethereum/hybrid_casper/casper_utils.py b/ethereum/hybrid_casper/casper_utils.py index 7b4d0741d..7faf31b14 100644 --- a/ethereum/hybrid_casper/casper_utils.py +++ b/ethereum/hybrid_casper/casper_utils.py @@ -1,8 +1,7 @@ import os import sys -import copy -from ethereum import utils, abi, genesis_helpers, config +from ethereum import utils, abi, genesis_helpers from ethereum.hybrid_casper.casper_initiating_transactions import mk_initializers, purity_checker_address, purity_checker_abi from ethereum.hybrid_casper import consensus from ethereum.hybrid_casper.config import config @@ -21,18 +20,21 @@ purity_translator = abi.ContractTranslator(purity_checker_abi) # Get a genesis state which is primed for Casper -def make_casper_genesis(alloc, epoch_length, withdrawal_delay, base_interest_factor, base_penalty_factor): +def make_casper_genesis(env, header=None, **kwargs): + assert isinstance(env, config.Env) + # The Casper-specific dynamic config declaration - config.casper_config['EPOCH_LENGTH'] = epoch_length - config.casper_config['WITHDRAWAL_DELAY'] = withdrawal_delay config.casper_config['OWNER'] = a0 - config.casper_config['BASE_INTEREST_FACTOR'] = base_interest_factor - config.casper_config['BASE_PENALTY_FACTOR'] = base_penalty_factor + config.casper_config['EPOCH_LENGTH'] = kwargs.get('epoch_length', env.config['EPOCH_LENGTH']) + config.casper_config['WITHDRAWAL_DELAY'] = kwargs.get('withdrawal_delay', env.config['WITHDRAWAL_DELAY']) + config.casper_config['BASE_INTEREST_FACTOR'] = kwargs.get('base_interest_factor', env.config['BASE_INTEREST_FACTOR']) + config.casper_config['BASE_PENALTY_FACTOR'] = kwargs.get('base_penalty_factor', env.config['BASE_PENALTY_FACTOR']) + alloc = kwargs.get('alloc', env.config['GENESIS_INITIAL_ALLOC']) # Get initialization txs init_txs, casper_address = mk_initializers(config.casper_config, config.casper_config['NULL_SENDER']) config.casper_config['CASPER_ADDRESS'] = casper_address # Create state and apply required state_transitions for initializing Casper - state = genesis_helpers.mk_basic_state(alloc, None, env=config.Env(config=config.casper_config)) + state = genesis_helpers.mk_basic_state(alloc, header=header, env=config.Env(config=config.casper_config)) state.gas_limit = 10**8 for tx in init_txs: state.set_balance(utils.privtoaddr(config.casper_config['NULL_SENDER']), 15**18) diff --git a/ethereum/hybrid_casper/config.py b/ethereum/hybrid_casper/config.py index b51140f49..fa3e2dc00 100644 --- a/ethereum/hybrid_casper/config.py +++ b/ethereum/hybrid_casper/config.py @@ -4,7 +4,7 @@ casper_config = dict( # The Casper-specific config declaration - HOMESTEAD_FORK_BLKNUM=0, + METROPOLIS_FORK_BLKNUM=0, ANTI_DOS_FORK_BLKNUM=0, CLEARING_FORK_BLKNUM=0, CONSENSUS_STRATEGY='hybrid_casper', diff --git a/ethereum/messages.py b/ethereum/messages.py index bb0075102..bc3ef8939 100644 --- a/ethereum/messages.py +++ b/ethereum/messages.py @@ -185,7 +185,16 @@ def apply_message(state, msg=None, **kwargs): return bytearray_to_bytestr(data) if result else None -def apply_casper_vote_transaction(state, tx): +def apply_transaction(state, tx): + if tx.to == state.config['CASPER_ADDRESS'] and tx.data[0:4] == b'\xe9\xdc\x06\x14': + log_tx.debug("Applying CASPER VOTE transaction: {}".format(tx)) + return _apply_casper_vote_transaction(state, tx) + else: + log_tx.debug("Applying transaction (non-CASPER VOTE): {}".format(tx)) + return _apply_transaction(state, tx) + + +def _apply_casper_vote_transaction(state, tx): if tx.sender == state.config['NULL_SENDER'] or not tx.to == state.config['CASPER_ADDRESS']: raise InvalidTransaction("Sender must be not be null sender and to must be the Casper contract address") state.logs = [] @@ -241,8 +250,6 @@ def apply_casper_vote_transaction(state, tx): output = bytearray_to_bytestr(data) success = 1 - state.gas_used += gas_used - # Pre-Metropolis: commit state after every tx if not state.is_METROPOLIS() and not SKIP_MEDSTATES: state.commit() @@ -258,8 +265,7 @@ def apply_casper_vote_transaction(state, tx): return success, output - -def apply_transaction(state, tx): +def _apply_transaction(state, tx): state.logs = [] state.suicides = [] state.refunds = 0 diff --git a/ethereum/meta.py b/ethereum/meta.py index 58b6fe7ef..67526bb5d 100644 --- a/ethereum/meta.py +++ b/ethereum/meta.py @@ -6,7 +6,7 @@ set_execution_results, add_transactions, post_finalize, \ validate_casper_vote_transaction_precedence from ethereum.consensus_strategy import get_consensus_strategy -from ethereum.messages import apply_transaction, apply_casper_vote_transaction +from ethereum.messages import apply_transaction from ethereum.state import State from ethereum.utils import sha3, encode_hex import rlp @@ -29,11 +29,7 @@ def apply_block(state, block): assert validate_casper_vote_transaction_precedence(state, block) # Process transactions for tx in block.transactions: - # Handles casper vote transactions - if tx.to == state.config['CASPER_ADDRESS'] and tx.data[0:4] == b'\xe9\xdc\x06\x14': - apply_casper_vote_transaction(state, tx) - else: - apply_transaction(state, tx) + apply_transaction(state, tx) # Finalize (incl paying block rewards) cs.finalize(state, block) # Verify state root, tx list root, receipt root diff --git a/ethereum/tests/hybrid_casper/test_chain.py b/ethereum/tests/hybrid_casper/test_chain.py index d8884a357..ab97a9c15 100644 --- a/ethereum/tests/hybrid_casper/test_chain.py +++ b/ethereum/tests/hybrid_casper/test_chain.py @@ -1,6 +1,6 @@ import pytest -from ethereum.utils import privtoaddr from ethereum.tools import tester +from ethereum.config import Env from ethereum.tests.utils import new_db from ethereum.db import EphemDB from ethereum.hybrid_casper import casper_utils @@ -28,7 +28,13 @@ def db(): alt_db = db def init_chain_and_casper(): - genesis = casper_utils.make_casper_genesis(ALLOC, EPOCH_LENGTH, 100, 0.02, 0.002) + genesis = casper_utils.make_casper_genesis( + env=Env(), + alloc=ALLOC, + epoch_length=EPOCH_LENGTH, + withdrawal_delay=100, + base_interest_factor=0.02, + base_penalty_factor=0.002) t = tester.Chain(genesis=genesis) casper = tester.ABIContract(t, casper_utils.casper_abi, t.chain.config['CASPER_ADDRESS']) return t, casper @@ -57,10 +63,11 @@ def test_mining_block_rewards(db): blk3 = t.mine(coinbase=a1) blk4 = t.mine(coinbase=a1) t.mine(coinbase=a1) - assert t.chain.state.get_balance(a1) == t.chain.env.config['BLOCK_REWARD'] + t.chain.mk_poststate_of_blockhash(blk4.hash).get_balance(a1) - assert t.chain.state.get_balance(a1) == t.chain.env.config['BLOCK_REWARD'] * 2 + t.chain.mk_poststate_of_blockhash(blk3.hash).get_balance(a1) - assert t.chain.state.get_balance(a1) == t.chain.env.config['BLOCK_REWARD'] * 3 + t.chain.mk_poststate_of_blockhash(blk2.hash).get_balance(a1) - assert t.chain.state.get_balance(a1) == t.chain.env.config['BLOCK_REWARD'] * 4 + t.chain.mk_poststate_of_blockhash(genesis.hash).get_balance(a1) + balance = t.chain.state.get_balance(a1) + assert balance == t.chain.env.config['BYZANTIUM_BLOCK_REWARD'] + t.chain.mk_poststate_of_blockhash(blk4.hash).get_balance(a1) + assert balance == t.chain.env.config['BYZANTIUM_BLOCK_REWARD'] * 2 + t.chain.mk_poststate_of_blockhash(blk3.hash).get_balance(a1) + assert balance == t.chain.env.config['BYZANTIUM_BLOCK_REWARD'] * 3 + t.chain.mk_poststate_of_blockhash(blk2.hash).get_balance(a1) + assert balance == t.chain.env.config['BYZANTIUM_BLOCK_REWARD'] * 4 + t.chain.mk_poststate_of_blockhash(genesis.hash).get_balance(a1) assert blk2.prevhash == genesis.hash @@ -109,10 +116,13 @@ def test_no_gas_cost_for_successful_casper_vote(db): test = TestLangHybrid(15, 100, 0.02, 0.002) test.parse(test_string) pre_balance = test.t.head_state.get_balance(sender) + pre_block_gas_used = test.t.head_state.gas_used test_string = 'V0' test.parse(test_string) post_balance = test.t.head_state.get_balance(sender) + post_block_gas_used = test.t.head_state.gas_used assert pre_balance == post_balance + assert pre_block_gas_used == post_block_gas_used def test_costs_gas_for_failed_casper_vote(db): @@ -167,3 +177,37 @@ def test_vote_surround_slash(db): test_string = 'B J0 J1 J2 J3 B B S0 V0 V1 V2 V3 B V0 V1 V2 V3 B V0 V1 V2 V3 R0 B B B B B B B V0 B1' test = TestLangHybrid(15, 100, 0.02, 0.002) test.parse(test_string) + +def test_rewards_when_validation_is_finalizing(db): + """ Test that the validation rewards are positive when validators are finalizing epochs """ + number_of_epochs = 5 + vote_string = 'B1' + for i in range(number_of_epochs): + vote_string += ' B1 V0 V1 V2 V3 B' + test_string = 'B J0 J1 J2 J3 B B' + test = TestLangHybrid(15, 100, 0.02, 0.002, 5000 * 18**10) + test.parse(test_string) + casper = tester.ABIContract(test.t, casper_utils.casper_abi, test.t.chain.casper_address) + deposits_in_first_epoch = casper.get_total_curdyn_deposits() + test.parse(vote_string) + deposits_after_votes = casper.get_total_curdyn_deposits() + print('Total deposits in first dynasty: {}'.format(deposits_in_first_epoch)) + print('Total deposits after {} rounds of voting: {}'.format(number_of_epochs, deposits_after_votes)) + assert deposits_in_first_epoch < deposits_after_votes + +def test_rewards_when_validation_is_not_finalizing(db): + """ Test that the validation rewards are positive when validators are NOT finalizing epochs """ + number_of_epochs = 5 + vote_string = 'B1' + for i in range(number_of_epochs): + vote_string += ' B1 V0 B' + test_string = 'B J0 J1 J2 J3 B B' + test = TestLangHybrid(15, 100, 0.02, 0.002, 5000 * 18**10) + test.parse(test_string) + casper = tester.ABIContract(test.t, casper_utils.casper_abi, test.t.chain.casper_address) + deposits_in_first_epoch = casper.get_total_curdyn_deposits() + test.parse(vote_string) + deposits_after_votes = casper.get_total_curdyn_deposits() + print('Total deposits in first dynasty: {}'.format(deposits_in_first_epoch)) + print('Total deposits after {} rounds of voting: {}'.format(number_of_epochs, deposits_after_votes)) + assert deposits_in_first_epoch > deposits_after_votes diff --git a/ethereum/tests/hybrid_casper/testing_lang.py b/ethereum/tests/hybrid_casper/testing_lang.py index fc61a8e6a..1e2efbdc8 100644 --- a/ethereum/tests/hybrid_casper/testing_lang.py +++ b/ethereum/tests/hybrid_casper/testing_lang.py @@ -1,9 +1,10 @@ +from ethereum.config import Env from ethereum.tools import tester from ethereum.utils import encode_hex, privtoaddr from ethereum.hybrid_casper import casper_utils import re -ALLOC = {a: {'balance': 500*10**19} for a in tester.accounts[:10]} +ALLOC = {a: {'balance': 500*10**20} for a in tester.accounts[:10]} class Validator(object): def __init__(self, withdrawal_addr, key): @@ -60,8 +61,15 @@ def get_validator_index(self, casper): class TestLangHybrid(object): # For a custom Casper parser, overload generic parser and construct your chain - def __init__(self, epoch_length, withdrawal_delay, base_interest_factor, base_penalty_factor): - self.genesis = casper_utils.make_casper_genesis(ALLOC, epoch_length, withdrawal_delay, base_interest_factor, base_penalty_factor) + def __init__(self, epoch_length, withdrawal_delay, base_interest_factor, base_penalty_factor, validator_deposit_size=5000 * 10**18): + self.genesis = casper_utils.make_casper_genesis( + env=Env(), + alloc=ALLOC, + epoch_length=epoch_length, + withdrawal_delay=withdrawal_delay, + base_interest_factor=base_interest_factor, + base_penalty_factor=base_penalty_factor) + self.validator_deposit_size = validator_deposit_size self.t = tester.Chain(genesis=self.genesis) self.casper = tester.ABIContract(self.t, casper_utils.casper_abi, self.t.chain.env.config['CASPER_ADDRESS']) self.saved_blocks = dict() @@ -88,7 +96,7 @@ def mine_blocks(self, number): def join(self, number): withdrawal_addr = privtoaddr(tester.keys[number]) - casper_utils.induct_validator(self.t, self.casper, tester.keys[number], 200 * 10**18) + casper_utils.induct_validator(self.t, self.casper, tester.keys[number], self.validator_deposit_size) self.validators[number] = Validator(withdrawal_addr, tester.keys[number]) def vote(self, validator_index): diff --git a/ethereum/tools/tester.py b/ethereum/tools/tester.py index 87f2fd37f..5e33f6007 100644 --- a/ethereum/tools/tester.py +++ b/ethereum/tools/tester.py @@ -4,7 +4,7 @@ from ethereum.consensus_strategy import get_consensus_strategy from ethereum.config import config_homestead, config_tangerine, config_spurious, config_metropolis, default_config, Env from ethereum.pow.ethpow import Miner -from ethereum.messages import apply_transaction, apply_message, apply_casper_vote_transaction +from ethereum.messages import apply_transaction, apply_message from ethereum.common import verify_execution_results, mk_block_from_prevstate, set_execution_results from ethereum.meta import make_head_candidate from ethereum.abi import ContractTranslator @@ -179,10 +179,7 @@ def direct_tx(self, transaction): if self.last_sender is not None and privtoaddr( self.last_sender) != transaction.sender: self.last_sender = None - if transaction.to == self.head_state.env.config['CASPER_ADDRESS'] and transaction.data[0:4] == b'\xe9\xdc\x06\x14': - success, output = apply_casper_vote_transaction(self.head_state, transaction) - else: - success, output = apply_transaction(self.head_state, transaction) + success, output = apply_transaction(self.head_state, transaction) self.block.transactions.append(transaction) if not success: raise TransactionFailed()