diff --git a/casper4/fixed_address_creator.py b/casper4/fixed_address_creator.py index ee282d1..65a362c 100644 --- a/casper4/fixed_address_creator.py +++ b/casper4/fixed_address_creator.py @@ -16,3 +16,18 @@ print('Send %d wei to %s' % (t.startgas * t.gasprice, print('Contract address: 0x'+utils.encode_hex(utils.mk_contract_address(t.sender, 0))) print('Code: 0x'+utils.encode_hex(rlp.encode(t))) + +sighash = serpent.compile('sqrt.se.py') + +# Create transaction +t = transactions.Transaction(0, 30 * 10**9, 2999999, '', 0, sighash) +t.startgas = t.intrinsic_gas_used + 50000 + 200 * len(sighash) +t.v = 27 +t.r = 45 +t.s = 79 +print("Sqrt") +print('Send %d wei to %s' % (t.startgas * t.gasprice, + '0x'+utils.encode_hex(t.sender))) + +print('Contract address: 0x'+utils.encode_hex(utils.mk_contract_address(t.sender, 0))) +print('Code: 0x'+utils.encode_hex(rlp.encode(t))) diff --git a/casper4/simple_casper.v.py b/casper4/simple_casper.v.py index d483114..3b5dcc1 100644 --- a/casper4/simple_casper.v.py +++ b/casper4/simple_casper.v.py @@ -28,6 +28,9 @@ second_next_dynasty_wei_delta: wei_value # Total deposits during this dynasty total_deposits: public(wei_value[num]) +# Mapping of dynasty to start epoch of that dynasty +dynasty_start_epoch: public(num[num]) + # Information for use in processing cryptoeconomic commitments consensus_messages: public({ # How many prepares are there for this hash (hash of message hash + view source) from the current dynasty @@ -43,18 +46,17 @@ consensus_messages: public({ # And from the previous dynasty prev_dyn_commits: wei_value[bytes32], # Was the block committed? - committed: bool + committed: bool, + # Value used to calculate the per-epoch fee that validators should be charged + deposit_scale_factor: decimal }[num]) # index: epoch # ancestry[x][y] = k > 0: x is a kth generation ancestor of y -ancestry: num[bytes32][bytes32] +ancestry: public(num[bytes32][bytes32]) # Number of validators nextValidatorIndex: public(num) -# Constant that guides the size of validator rewards -interest_rate: decimal(1 / sec) - # Time between blocks block_time: timedelta @@ -79,11 +81,22 @@ total_destroyed: wei_value # Sighash calculator library address sighasher: address -def __init__(): + +# Reward for preparing or committing, as fraction of deposit size +reward_factor: public(decimal) + +# Desired total ether given out assuming 1M ETH deposited +reward_at_1m_eth: decimal + +# Have I already been initialized? +initialized: bool + +def initiate(): + assert not self.initialized + self.initialized = True # Set Casper parameters - self.interest_rate = 0.000000001 self.block_time = 14 - self.epoch_length = 256 + self.epoch_length = 100 # Only ~11.5 days, for testing purposes self.withdrawal_delay = 1000000 # Only ~1 day, for testing purposes @@ -92,7 +105,7 @@ def __init__(): self.owner = 0x1db3439a222c519ab44bb1144fc28167b4fa6ee6 # Add an initial validator self.validators[0] = { - deposit: as_wei_value(3, finney), + deposit: as_wei_value(3, ether), dynasty_start: 0, dynasty_end: 1000000000000000000000000000000, withdrawal_epoch: 1000000000000000000000000000000, @@ -109,18 +122,40 @@ def __init__(): # Set an initial root of the epoch hash chain self.consensus_messages[0].ancestry_hash_justified[0x0000000000000000000000000000000000000000000000000000000000000000] = True # Set initial total deposit counter - self.total_deposits[0] = as_wei_value(3, finney) + self.total_deposits[0] = as_wei_value(3, ether) + # Set deposit scale factor + self.consensus_messages[0].deposit_scale_factor = 1000000000000000000.0 + # Total ETH given out assuming 1m ETH deposits + self.reward_at_1m_eth = 12.5 # Called at the start of any epoch def initialize_epoch(epoch: num): + # Check that the epoch actually has started computed_current_epoch = block.number / self.epoch_length assert epoch <= computed_current_epoch and epoch == self.current_epoch + 1 + # Set the epoch number self.current_epoch = epoch + # Increment the dynasty if self.consensus_messages[epoch - 1].committed: self.dynasty += 1 self.total_deposits[self.dynasty] = self.total_deposits[self.dynasty - 1] + self.next_dynasty_wei_delta self.next_dynasty_wei_delta = self.second_next_dynasty_wei_delta self.second_next_dynasty_wei_delta = 0 + self.dynasty_start_epoch[self.dynasty] = epoch + # Compute square root factor + ether_deposited_as_number = self.total_deposits[self.dynasty] / as_wei_value(1, ether) + sqrt = ether_deposited_as_number / 2.0 + for i in range(20): + sqrt = (sqrt + (ether_deposited_as_number / sqrt)) / 2 + # Reward factor is the reward given for preparing or committing as a + # fraction of that validator's deposit size + base_coeff = 1.0 / sqrt * (self.reward_at_1m_eth / 1000) + # Rules: + # * You are penalized 2x per epoch + # * If you prepare, you get 1.5x, and if you commit you get another 1.5x + # Hence, assuming 100% performance, your reward per epoch is x + self.reward_factor = 1.5 * base_coeff + self.consensus_messages[epoch].deposit_scale_factor = self.consensus_messages[epoch - 1].deposit_scale_factor * (1 - 2 * base_coeff) # Send a deposit to join the validator set def deposit(validation_addr: address, withdrawal_addr: address): @@ -162,6 +197,14 @@ def flick_status(validator_index: num, logout_msg: bytes <= 1024): if login_flag: # Check that we are logged out assert self.validators[validator_index].dynasty_end < self.dynasty + # Apply the per-epoch deposit penalty + prev_login_epoch = self.dynasty_start_epoch[self.validators[validator_index].dynasty_start] + prev_logout_epoch = self.dynasty_start_epoch[self.validators[validator_index].dynasty_end + 1] + self.validators[validator_index].deposit = \ + floor(self.validators[validator_index].deposit * + (self.consensus_messages[prev_logout_epoch].deposit_scale_factor / + self.consensus_messages[prev_login_epoch].deposit_scale_factor)) + # Log back in self.validators[validator_index].dynasty_start = self.dynasty + 2 self.validators[validator_index].dynasty_end = 1000000000000000000000000000000 self.second_next_dynasty_wei_delta += self.validators[validator_index].deposit @@ -179,6 +222,13 @@ def flick_status(validator_index: num, logout_msg: bytes <= 1024): def withdraw(validator_index: num): # Check that we can withdraw assert self.current_epoch >= self.validators[validator_index].withdrawal_epoch + # Apply the per-epoch deposit penalty + prev_login_epoch = self.dynasty_start_epoch[self.validators[validator_index].dynasty_start] + prev_logout_epoch = self.dynasty_start_epoch[self.validators[validator_index].dynasty_end + 1] + self.validators[validator_index].deposit = \ + floor(self.validators[validator_index].deposit * + (self.consensus_messages[prev_logout_epoch].deposit_scale_factor / + self.consensus_messages[prev_login_epoch].deposit_scale_factor)) # Withdraw send(self.validators[validator_index].withdrawal_addr, self.validators[validator_index].deposit) self.validators[validator_index] = { @@ -215,6 +265,8 @@ def prepare(validator_index: num, prepare_msg: bytes <= 1024): # Check that we are in the right epoch assert self.current_epoch == block.number / self.epoch_length assert self.current_epoch == epoch + # Check that we are at least (epoch length / 4) blocks into the epoch + # assert block.number % self.epoch_length >= self.epoch_length / 4 # Check that this validator was active in either the previous dynasty or the current one ds = self.validators[validator_index].dynasty_start de = self.validators[validator_index].dynasty_end @@ -227,8 +279,8 @@ def prepare(validator_index: num, prepare_msg: bytes <= 1024): # Check that we have not yet prepared for this epoch #assert self.validators[validator_index].max_prepared == epoch - 1 # Pay the reward if the blockhash is correct - if True: #~blockhash(epoch * self.epoch_length) == hash: - reward = floor(self.validators[validator_index].deposit * self.interest_rate * self.block_time / 2) + if True: #if blockhash(epoch * self.epoch_length) == hash: + reward = floor(self.validators[validator_index].deposit * self.reward_factor) self.validators[validator_index].deposit += reward self.total_deposits[self.dynasty] += reward # Can't prepare for this epoch again @@ -266,6 +318,8 @@ def commit(validator_index: num, commit_msg: bytes <= 1024): # Check that we are in the right epoch assert self.current_epoch == block.number / self.epoch_length assert self.current_epoch == epoch + # Check that we are at least (epoch length / 2) blocks into the epoch + # assert block.number % self.epoch_length >= self.epoch_length / 2 # Check that the commit is justified assert self.consensus_messages[epoch].hash_justified[hash] # Check that this validator was active in either the previous dynasty or the current one @@ -278,8 +332,8 @@ def commit(validator_index: num, commit_msg: bytes <= 1024): # Check that we have not yet committed for this epoch #assert self.validators[validator_index].max_committed == epoch - 1 # Pay the reward if the blockhash is correct - if True: #~blockhash(epoch * self.epoch_length) == hash: - reward = floor(self.validators[validator_index].deposit * self.interest_rate * self.block_time / 2) + if True: #if blockhash(epoch * self.epoch_length) == hash: + reward = floor(self.validators[validator_index].deposit * self.reward_factor) self.validators[validator_index].deposit += reward self.total_deposits[self.dynasty] += reward # Can't commit for this epoch again @@ -415,12 +469,17 @@ def commit_non_justification_slash(validator_index: num, commit_msg: bytes <= 10 } # Fill in the table for which hash is what-degree ancestor of which other hash -def derive_ancestry(top: bytes32, middle: bytes32, bottom: bytes32): - assert self.ancestry[middle][top] - assert self.ancestry[bottom][middle] - self.ancestry[bottom][top] = self.ancestry[bottom][middle] + self.ancestry[middle][top] +def derive_parenthood(older: bytes32, hash: bytes32, newer: bytes32): + assert sha3(concat(hash, older)) == newer + self.ancestry[older][newer] = 1 -def prepare_non_justification_slash(validator_index: num, prepare_msg: bytes <= 1024): +# Fill in the table for which hash is what-degree ancestor of which other hash +def derive_ancestry(oldest: bytes32, middle: bytes32, recent: bytes32): + assert self.ancestry[middle][recent] + assert self.ancestry[oldest][middle] + self.ancestry[oldest][recent] = self.ancestry[oldest][middle] + self.ancestry[middle][recent] + +def prepare_non_justification_slash(validator_index: num, prepare_msg: bytes <= 1024) -> num: # Get hash for signature, and implicitly assert that it is an RLP list # consisting solely of RLP elements sighash = extract32(raw_call(self.sighasher, prepare_msg, gas=200000, outsize=32), 0) @@ -440,8 +499,14 @@ def prepare_non_justification_slash(validator_index: num, prepare_msg: bytes <= # Check that the view change is old enough assert self.current_epoch == block.number / self.epoch_length assert (self.current_epoch - epoch) * self.block_time * self.epoch_length > self.insufficiency_slash_delay - # Check that the source ancestry hash not had enough prepares - assert not self.consensus_messages[source_epoch].ancestry_hash_justified[source_ancestry_hash] + # Check that the source ancestry hash not had enough prepares, OR that there is not the + # correct ancestry link between the current ancestry hash and source ancestry hash + c1 = self.consensus_messages[source_epoch].ancestry_hash_justified[source_ancestry_hash] + if epoch - 1 > source_epoch: + c2 = self.ancestry[source_ancestry_hash][ancestry_hash] == epoch - 1 - source_epoch + else: + c2 = source_ancestry_hash == ancestry_hash + assert not (c1 and c2) # Delete the offending validator, and give a 4% "finder's fee" validator_deposit = self.validators[validator_index].deposit send(msg.sender, validator_deposit / 25) diff --git a/casper4/simple_casper_tester.py b/casper4/simple_casper_tester.py index 1a725c9..99cf08b 100644 --- a/casper4/simple_casper_tester.py +++ b/casper4/simple_casper_tester.py @@ -9,7 +9,7 @@ s = t.state() t.languages['viper'] = compiler.Compiler() t.gas_limit = 9999999 -EPOCH_LENGTH = 256 +EPOCH_LENGTH = 100 # Install RLP decoder library s.state.set_balance('0xfe2ec957647679d210034b65e9c7db2452910b0c', 9350880000000000) @@ -56,23 +56,27 @@ def mk_status_flicker(epoch, login, key): # Begin the test print("Starting tests") +casper.initiate() # Initialize the first epoch -s.state.block_number = EPOCH_LENGTH +s.state.block_number = EPOCH_LENGTH casper.initialize_epoch(1) assert casper.get_nextValidatorIndex() == 1 start = s.snapshot() print("Epoch initialized") +print("Reward factor: %.8f" % (casper.get_reward_factor() * 2 / 3)) # Send a prepare message casper.prepare(0, mk_prepare(1, '\x35' * 32, '\x00' * 32, 0, '\x00' * 32, t.k0)) epoch_1_anchash = utils.sha3(b'\x35' * 32 + b'\x00' * 32) assert casper.get_consensus_messages__hash_justified(1, b'\x35' * 32) assert casper.get_consensus_messages__ancestry_hash_justified(1, epoch_1_anchash) +print('Gas consumed for a prepare', s.state.receipts[-1].gas_used - s.state.receipts[-2].gas_used) print("Prepare message processed") # Send a commit message casper.commit(0, mk_commit(1, '\x35' * 32, t.k0)) # Check that we committed assert casper.get_consensus_messages__committed(1) print("Commit message processed") +print('Gas consumed for a commit', s.state.receipts[-1].gas_used - s.state.receipts[-2].gas_used) # Initialize the second epoch s.state.block_number += EPOCH_LENGTH casper.initialize_epoch(2) @@ -130,11 +134,12 @@ p5 = mk_prepare(5, '\x78' * 32, epoch_4_anchash, 3, epoch_3_anchash, t.k0) casper.prepare(0, p5) # Test the COMMIT_REQ slashing condition kommit = mk_commit(5, b'\x80' * 32, t.k0) -s.state.block_number += EPOCH_LENGTH * 30 +epoch_inc = 1 + int(86400 / 14 / EPOCH_LENGTH) +s.state.block_number += EPOCH_LENGTH * epoch_inc print("Speeding up time to test remaining two slashing conditions") -for i in range(6, 36): +for i in range(6, 6 + epoch_inc): casper.initialize_epoch(i) -print("Epochs up to 36 initialized") +print("Epochs up to %d initialized" % (6 + epoch_inc)) snapshot = s.snapshot() casper.commit_non_justification_slash(0, kommit) s.revert(snapshot) @@ -146,7 +151,11 @@ except: assert not success print("COMMIT_REQ slashing condition works") # Test the PREPARE_REQ slashing condition -casper.derive_ancestry(epoch_3_anchash, epoch_2_anchash, epoch_1_anchash) +casper.derive_parenthood(epoch_3_anchash, b'\x67' * 32, epoch_4_anchash) +assert casper.get_ancestry(epoch_3_anchash, epoch_4_anchash) == 1 +assert casper.get_ancestry(epoch_4_anchash, epoch_5_anchash) == 1 +casper.derive_ancestry(epoch_3_anchash, epoch_4_anchash, epoch_5_anchash) +assert casper.get_ancestry(epoch_3_anchash, epoch_5_anchash) == 2 snapshot = s.snapshot() casper.prepare_non_justification_slash(0, p4) s.revert(snapshot) @@ -166,7 +175,7 @@ assert casper.get_current_epoch() == 1 assert casper.get_consensus_messages__ancestry_hash_justified(0, b'\x00' * 32) print("Epoch 1 initialized") for k in (t.k1, t.k2, t.k3, t.k4, t.k5, t.k6): - casper.deposit(utils.privtoaddr(k), utils.privtoaddr(k), value=3 * 10**15) + casper.deposit(utils.privtoaddr(k), utils.privtoaddr(k), value=3 * 10**18) print("Processed 6 deposits") casper.prepare(0, mk_prepare(1, b'\x10' * 32, b'\x00' * 32, 0, b'\x00' * 32, t.k0)) casper.commit(0, mk_commit(1, b'\x10' * 32, t.k0)) @@ -186,9 +195,9 @@ s.state.block_number += EPOCH_LENGTH casper.initialize_epoch(3) print("Epoch 3 initialized") assert casper.get_dynasty() == 2 -assert 3 * 10**15 <= casper.get_total_deposits(0) < 4 * 10**15 -assert 3 * 10**15 <= casper.get_total_deposits(1) < 4 * 10**15 -assert 21 * 10**15 <= casper.get_total_deposits(2) < 22 * 10**15 +assert 3 * 10**18 <= casper.get_total_deposits(0) < 4 * 10**18 +assert 3 * 10**18 <= casper.get_total_deposits(1) < 4 * 10**18 +assert 21 * 10**18 <= casper.get_total_deposits(2) < 22 * 10**18 print("Confirmed new total_deposits") try: # Try to log out, but sign with the wrong key @@ -245,8 +254,8 @@ s.state.block_number += EPOCH_LENGTH casper.initialize_epoch(5) print("Epoch 5 initialized") assert casper.get_dynasty() == 4 -assert 21 * 10**15 <= casper.get_total_deposits(3) <= 22 * 10**15 -assert 12 * 10**15 <= casper.get_total_deposits(4) <= 13 * 10**15 +assert 21 * 10**18 <= casper.get_total_deposits(3) <= 22 * 10**18 +assert 12 * 10**18 <= casper.get_total_deposits(4) <= 13 * 10**18 epoch_5_anchash = utils.sha3(b'\x50' * 32 + epoch_4_anchash) # Do three prepares for i, k in enumerate([t.k0, t.k1, t.k2]): @@ -270,8 +279,13 @@ casper.initialize_epoch(6) assert casper.get_dynasty() == 5 print("Epoch 6 initialized") # Log back in +old_deposit_start = casper.get_dynasty_start_epoch(casper.get_validators__dynasty_start(4)) +old_deposit_end = casper.get_dynasty_start_epoch(casper.get_validators__dynasty_end(4) + 1) +old_deposit = casper.get_validators__deposit(4) casper.flick_status(4, mk_status_flicker(6, 1, t.k4)) +new_deposit = casper.get_validators__deposit(4) print("One validator logging back in") +print("Penalty from %d epochs: %.4f" % (old_deposit_end - old_deposit_start, 1 - new_deposit / old_deposit)) assert casper.get_validators__dynasty_start(4) == 7 # Here three prepares and three commits should be sufficient! epoch_6_anchash = utils.sha3(b'\x60' * 32 + epoch_5_anchash) @@ -299,8 +313,8 @@ s.state.block_number += EPOCH_LENGTH casper.initialize_epoch(8) assert casper.get_dynasty() == 7 print("Epoch 8 initialized") -assert 12 * 10**15 <= casper.get_total_deposits(6) <= 13 * 10**15 -assert 15 * 10**15 <= casper.get_total_deposits(7) <= 16 * 10**15 +assert 12 * 10**18 <= casper.get_total_deposits(6) <= 13 * 10**18 +assert 15 * 10**18 <= casper.get_total_deposits(7) <= 16 * 10**18 epoch_8_anchash = utils.sha3(b'\x80' * 32 + epoch_7_anchash) # Do three prepares for i, k in enumerate([t.k0, t.k1, t.k2]): diff --git a/casper4/sqrt.se.py b/casper4/sqrt.se.py new file mode 100644 index 0000000..fe4775a --- /dev/null +++ b/casper4/sqrt.se.py @@ -0,0 +1,12 @@ +with inp = ~calldataload(0): + foo = inp + exp = 0 + while foo >= 256: + foo = ~div(foo, 256) + exp += 1 + with x = ~div(inp, 16 ** exp): + while 1: + y = ~div(x + ~div(inp, x) + 1, 2) + if x == y: + return x + x = y