Added tests for validators logging in, logging out, and multi-validator prepares and commits, as well as current/prev dynasty mechanics

This commit is contained in:
vub 2017-03-23 06:48:28 -04:00
parent 821e2e3be2
commit 00db6345e5
2 changed files with 176 additions and 30 deletions

View File

@ -1,5 +1,5 @@
# Information about validators # Information about validators
validators: { validators: public({
# Amount of wei the validator holds # Amount of wei the validator holds
deposit: wei_value, deposit: wei_value,
# The dynasty the validator is joining # The dynasty the validator is joining
@ -16,7 +16,7 @@ validators: {
max_prepared: num, max_prepared: num,
# The max epoch at which the validator committed # The max epoch at which the validator committed
max_committed: num max_committed: num
}[num] }[num])
# The current dynasty (validator set changes between dynasties) # The current dynasty (validator set changes between dynasties)
dynasty: public(num) dynasty: public(num)
@ -108,6 +108,8 @@ def __init__():
self.sighasher = 0x38920146f10f3956fc09970beededcb2d9638712 self.sighasher = 0x38920146f10f3956fc09970beededcb2d9638712
# Set an initial root of the epoch hash chain # Set an initial root of the epoch hash chain
self.consensus_messages[0].ancestry_hash_justified[0x0000000000000000000000000000000000000000000000000000000000000000] = True self.consensus_messages[0].ancestry_hash_justified[0x0000000000000000000000000000000000000000000000000000000000000000] = True
# Set initial total deposit counter
self.total_deposits[0] = as_wei_value(3, finney)
# Called at the start of any epoch # Called at the start of any epoch
def initialize_epoch(epoch: num): def initialize_epoch(epoch: num):
@ -136,17 +138,35 @@ def deposit(validation_addr: address, withdrawal_addr: address):
self.nextValidatorIndex += 1 self.nextValidatorIndex += 1
self.second_next_dynasty_wei_delta += msg.value self.second_next_dynasty_wei_delta += msg.value
# Exit the validator set. A logged out validator can log back in later, or # Log in or log out from the validator set. A logged out validator can log
# if they do not log in for an entire withdrawal period, they can get their # back in later, if they do not log in for an entire withdrawal period,
# money out # they can get their money out
def logout(validator_index: num, sig: bytes <= 96): def flick_status(validator_index: num, logout_msg: bytes <= 1024):
assert self.current_epoch == block.number / self.epoch_length assert self.current_epoch == block.number / self.epoch_length
# Get hash for signature, and implicitly assert that it is an RLP list
# consisting solely of RLP elements
sighash = extract32(raw_call(self.sighasher, logout_msg, gas=200000, outsize=32), 0)
# Extract parameters
values = RLPList(logout_msg, [num, bool, bytes])
epoch = values[0]
login_flag = values[1]
sig = values[2]
assert self.current_epoch == epoch
# Signature check # Signature check
assert len(sig) == 96 assert len(sig) == 96
assert ecrecover(sha3("withdraw"), assert ecrecover(sighash,
as_num256(extract32(sig, 0)), as_num256(extract32(sig, 0)),
as_num256(extract32(sig, 32)), as_num256(extract32(sig, 32)),
as_num256(extract32(sig, 64))) == self.validators[validator_index].addr as_num256(extract32(sig, 64))) == self.validators[validator_index].addr
# Logging in
if login_flag:
# Check that we are logged out
assert self.validators[validator_index].dynasty_end < self.dynasty
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
# Logging out
else:
# Check that we haven't already withdrawn # Check that we haven't already withdrawn
assert self.validators[validator_index].dynasty_end >= self.dynasty + 2 assert self.validators[validator_index].dynasty_end >= self.dynasty + 2
# Set the end dynasty # Set the end dynasty
@ -155,21 +175,6 @@ def logout(validator_index: num, sig: bytes <= 96):
# Set the withdrawal date # Set the withdrawal date
self.validators[validator_index].withdrawal_epoch = self.current_epoch + self.withdrawal_delay / self.block_time / self.epoch_length self.validators[validator_index].withdrawal_epoch = self.current_epoch + self.withdrawal_delay / self.block_time / self.epoch_length
# Log back in
def login(validator_index: num, sig: bytes <= 96):
assert self.current_epoch == block.number / self.epoch_length
# Signature check
assert len(sig) == 96
assert ecrecover(sha3("withdraw"),
as_num256(extract32(sig, 0)),
as_num256(extract32(sig, 32)),
as_num256(extract32(sig, 64))) == self.validators[validator_index].addr
# Check that we are logged out
assert self.validators[validator_index].dynasty_end < self.dynasty
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
# Withdraw deposited ether # Withdraw deposited ether
def withdraw(validator_index: num): def withdraw(validator_index: num):
# Check that we can withdraw # Check that we can withdraw
@ -271,7 +276,7 @@ def commit(validator_index: num, commit_msg: bytes <= 1024):
in_prev_dynasty = (ds <= (dc - 1)) and ((dc - 1) < de) in_prev_dynasty = (ds <= (dc - 1)) and ((dc - 1) < de)
assert in_current_dynasty or in_prev_dynasty assert in_current_dynasty or in_prev_dynasty
# Check that we have not yet committed for this epoch # Check that we have not yet committed for this epoch
assert self.validators[validator_index].max_committed == epoch - 1 #assert self.validators[validator_index].max_committed == epoch - 1
# Pay the reward if the blockhash is correct # Pay the reward if the blockhash is correct
if True: #~blockhash(epoch * self.epoch_length) == hash: if True: #~blockhash(epoch * self.epoch_length) == hash:
reward = floor(self.validators[validator_index].deposit * self.interest_rate * self.block_time / 2) reward = floor(self.validators[validator_index].deposit * self.interest_rate * self.block_time / 2)

View File

@ -42,11 +42,18 @@ def mk_commit(epoch, hash, key):
sig = utils.encode_int32(v) + utils.encode_int32(r) + utils.encode_int32(s) sig = utils.encode_int32(v) + utils.encode_int32(r) + utils.encode_int32(s)
return rlp.encode([epoch, hash, sig]) return rlp.encode([epoch, hash, sig])
def mk_status_flicker(epoch, login, key):
sighash = utils.sha3(rlp.encode([epoch, login]))
v, r, s = utils.ecdsa_raw_sign(sighash, key)
sig = utils.encode_int32(v) + utils.encode_int32(r) + utils.encode_int32(s)
return rlp.encode([epoch, login, sig])
s.state.block_number = EPOCH_LENGTH s.state.block_number = EPOCH_LENGTH
# Initialize the first epoch # Initialize the first epoch
casper.initialize_epoch(1) casper.initialize_epoch(1)
assert casper.get_nextValidatorIndex() == 1 assert casper.get_nextValidatorIndex() == 1
start = s.snapshot()
# Send a prepare message # Send a prepare message
casper.prepare(0, mk_prepare(1, '\x35' * 32, '\x00' * 32, 0, '\x00' * 32, t.k0)) 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) epoch_1_anchash = utils.sha3(b'\x35' * 32 + b'\x00' * 32)
@ -131,3 +138,137 @@ try:
except: except:
success = False success = False
assert not success assert not success
# Restart the chain
s.revert(start)
assert casper.get_dynasty() == 0
assert casper.get_current_epoch() == 1
assert casper.get_consensus_messages__ancestry_hash_justified(0, b'\x00' * 32)
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.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))
epoch_1_anchash = utils.sha3(b'\x10' * 32 + b'\x00' * 32)
assert casper.get_consensus_messages__committed(1)
s.state.block_number += EPOCH_LENGTH
casper.initialize_epoch(2)
assert casper.get_dynasty() == 1
casper.prepare(0, mk_prepare(2, b'\x20' * 32, epoch_1_anchash, 1, epoch_1_anchash, t.k0))
casper.commit(0, mk_commit(2, b'\x20' * 32, t.k0))
epoch_2_anchash = utils.sha3(b'\x20' * 32 + epoch_1_anchash)
assert casper.get_consensus_messages__committed(2)
s.state.block_number += EPOCH_LENGTH
casper.initialize_epoch(3)
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
try:
# Try to log out, but sign with the wrong key
casper.flick_status(0, mk_status_flicker(3, 0, t.k1))
success = True
except:
success = False
assert not success
# Log out
casper.flick_status(4, mk_status_flicker(3, 0, t.k4))
casper.flick_status(5, mk_status_flicker(3, 0, t.k5))
casper.flick_status(6, mk_status_flicker(3, 0, t.k6))
# Validators leave the fwd validator set in dynasty 4
assert casper.get_validators__dynasty_end(4) == 4
epoch_3_anchash = utils.sha3(b'\x30' * 32 + epoch_2_anchash)
# Prepare from one validator
casper.prepare(0, mk_prepare(3, b'\x30' * 32, epoch_2_anchash, 2, epoch_2_anchash, t.k0))
# Not prepared yet
assert not casper.get_consensus_messages__hash_justified(3, b'\x30' * 32)
# Prepare from 3 more validators
for i, k in ((1, t.k1), (2, t.k2), (3, t.k3)):
casper.prepare(i, mk_prepare(3, b'\x30' * 32, epoch_2_anchash, 2, epoch_2_anchash, k))
# Still not prepared
assert not casper.get_consensus_messages__hash_justified(3, b'\x30' * 32)
# Prepare from a firth validator
casper.prepare(4, mk_prepare(3, b'\x30' * 32, epoch_2_anchash, 2, epoch_2_anchash, t.k4))
# NOW we're prepared!
assert casper.get_consensus_messages__hash_justified(3, b'\x30' * 32)
# Five commits
for i, k in enumerate([t.k0, t.k1, t.k2, t.k3, t.k4]):
casper.commit(i, mk_commit(3, b'\x30' * 32, k))
# And we committed!
assert casper.get_consensus_messages__committed(3)
# Start epoch 4
s.state.block_number += EPOCH_LENGTH
casper.initialize_epoch(4)
assert casper.get_dynasty() == 3
# Prepare and commit
epoch_4_anchash = utils.sha3(b'\x40' * 32 + epoch_3_anchash)
for i, k in enumerate([t.k0, t.k1, t.k2, t.k3, t.k4]):
casper.prepare(i, mk_prepare(4, b'\x40' * 32, epoch_3_anchash, 3, epoch_3_anchash, k))
for i, k in enumerate([t.k0, t.k1, t.k2, t.k3, t.k4]):
casper.commit(i, mk_commit(4, b'\x40' * 32, k))
assert casper.get_consensus_messages__committed(4)
# Start epoch 5 / dynasty 4
s.state.block_number += EPOCH_LENGTH
casper.initialize_epoch(5)
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
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]):
casper.prepare(i, mk_prepare(5, b'\x50' * 32, epoch_4_anchash, 4, epoch_4_anchash, k))
# Three prepares are insufficient because there are still five validators in the rear validator set
assert not casper.get_consensus_messages__hash_justified(5, b'\x50' * 32)
# Do two more prepares
for i, k in [(3, t.k3), (4, t.k4)]:
casper.prepare(i, mk_prepare(5, b'\x50' * 32, epoch_4_anchash, 4, epoch_4_anchash, k))
# Now we're good!
assert casper.get_consensus_messages__hash_justified(5, b'\x50' * 32)
for i, k in enumerate([t.k0, t.k1, t.k2, t.k3, t.k4]):
casper.commit(i, mk_commit(5, b'\x50' * 32, k))
# Committed!
assert casper.get_consensus_messages__committed(5)
# Start epoch 6 / dynasty 5
s.state.block_number += EPOCH_LENGTH
casper.initialize_epoch(6)
assert casper.get_dynasty() == 5
# Log back in
casper.flick_status(4, mk_status_flicker(6, 1, t.k4))
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)
for i, k in enumerate([t.k0, t.k1, t.k2]):
casper.prepare(i, mk_prepare(6, b'\x60' * 32, epoch_5_anchash, 5, epoch_5_anchash, k))
for i, k in enumerate([t.k0, t.k1, t.k2]):
casper.commit(i, mk_commit(6, b'\x60' * 32, k))
assert casper.get_consensus_messages__committed(6)
# Start epoch 7 / dynasty 6
s.state.block_number += EPOCH_LENGTH
casper.initialize_epoch(7)
assert casper.get_dynasty() == 6
# Here three prepares and three commits should be sufficient!
epoch_7_anchash = utils.sha3(b'\x70' * 32 + epoch_6_anchash)
for i, k in enumerate([t.k0, t.k1, t.k2]):
casper.prepare(i, mk_prepare(7, b'\x70' * 32, epoch_6_anchash, 6, epoch_6_anchash, k))
for i, k in enumerate([t.k0, t.k1, t.k2]):
casper.commit(i, mk_commit(7, b'\x70' * 32, k))
assert casper.get_consensus_messages__committed(7)
# Start epoch 8 / dynasty 7
s.state.block_number += EPOCH_LENGTH
casper.initialize_epoch(8)
assert casper.get_dynasty() == 7
assert 12 * 10**15 <= casper.get_total_deposits(6) <= 13 * 10**15
assert 15 * 10**15 <= casper.get_total_deposits(7) <= 16 * 10**15
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]):
casper.prepare(i, mk_prepare(8, b'\x80' * 32, epoch_7_anchash, 7, epoch_7_anchash, k))
# Three prepares are insufficient because there are still five validators in the rear validator set
assert not casper.get_consensus_messages__hash_justified(8, b'\x80' * 32)
# Do one more prepare
for i, k in [(3, t.k3)]:
casper.prepare(i, mk_prepare(8, b'\x80' * 32, epoch_7_anchash, 7, epoch_7_anchash, k))
# Now we're good!
assert casper.get_consensus_messages__hash_justified(8, b'\x80' * 32)
for i, k in enumerate([t.k0, t.k1, t.k2, t.k3, t.k4]):
casper.commit(i, mk_commit(8, b'\x80' * 32, k))
assert casper.get_consensus_messages__committed(8)