Added the square root rewarding scheme, the per-epoch fee and fixed the PREPARE_REQ slashing function

This commit is contained in:
vub 2017-04-01 23:54:20 -04:00
parent 952179fc52
commit e4ee923c93
4 changed files with 141 additions and 35 deletions

View File

@ -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)))

View File

@ -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)

View File

@ -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
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]):

12
casper4/sqrt.se.py Normal file
View File

@ -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