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:
parent
821e2e3be2
commit
00db6345e5
|
@ -1,5 +1,5 @@
|
|||
# Information about validators
|
||||
validators: {
|
||||
validators: public({
|
||||
# Amount of wei the validator holds
|
||||
deposit: wei_value,
|
||||
# The dynasty the validator is joining
|
||||
|
@ -16,7 +16,7 @@ validators: {
|
|||
max_prepared: num,
|
||||
# The max epoch at which the validator committed
|
||||
max_committed: num
|
||||
}[num]
|
||||
}[num])
|
||||
|
||||
# The current dynasty (validator set changes between dynasties)
|
||||
dynasty: public(num)
|
||||
|
@ -108,6 +108,8 @@ def __init__():
|
|||
self.sighasher = 0x38920146f10f3956fc09970beededcb2d9638712
|
||||
# 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)
|
||||
|
||||
# Called at the start of any epoch
|
||||
def initialize_epoch(epoch: num):
|
||||
|
@ -136,39 +138,42 @@ def deposit(validation_addr: address, withdrawal_addr: address):
|
|||
self.nextValidatorIndex += 1
|
||||
self.second_next_dynasty_wei_delta += msg.value
|
||||
|
||||
# Exit the validator set. A logged out validator can log back in later, or
|
||||
# if they do not log in for an entire withdrawal period, they can get their
|
||||
# money out
|
||||
def logout(validator_index: num, sig: bytes <= 96):
|
||||
# Log in or log out from the validator set. A logged out validator can log
|
||||
# back in later, if they do not log in for an entire withdrawal period,
|
||||
# they can get their money out
|
||||
def flick_status(validator_index: num, logout_msg: bytes <= 1024):
|
||||
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
|
||||
assert len(sig) == 96
|
||||
assert ecrecover(sha3("withdraw"),
|
||||
assert ecrecover(sighash,
|
||||
as_num256(extract32(sig, 0)),
|
||||
as_num256(extract32(sig, 32)),
|
||||
as_num256(extract32(sig, 64))) == self.validators[validator_index].addr
|
||||
# Check that we haven't already withdrawn
|
||||
assert self.validators[validator_index].dynasty_end >= self.dynasty + 2
|
||||
# Set the end dynasty
|
||||
self.validators[validator_index].dynasty_end = self.dynasty + 2
|
||||
self.second_next_dynasty_wei_delta -= self.validators[validator_index].deposit
|
||||
# Set the withdrawal date
|
||||
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
|
||||
# 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
|
||||
assert self.validators[validator_index].dynasty_end >= self.dynasty + 2
|
||||
# Set the end dynasty
|
||||
self.validators[validator_index].dynasty_end = self.dynasty + 2
|
||||
self.second_next_dynasty_wei_delta -= self.validators[validator_index].deposit
|
||||
# Set the withdrawal date
|
||||
self.validators[validator_index].withdrawal_epoch = self.current_epoch + self.withdrawal_delay / self.block_time / self.epoch_length
|
||||
|
||||
# Withdraw deposited ether
|
||||
def withdraw(validator_index: num):
|
||||
|
@ -271,7 +276,7 @@ def commit(validator_index: num, commit_msg: bytes <= 1024):
|
|||
in_prev_dynasty = (ds <= (dc - 1)) and ((dc - 1) < de)
|
||||
assert in_current_dynasty or in_prev_dynasty
|
||||
# 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
|
||||
if True: #~blockhash(epoch * self.epoch_length) == hash:
|
||||
reward = floor(self.validators[validator_index].deposit * self.interest_rate * self.block_time / 2)
|
||||
|
|
|
@ -42,11 +42,18 @@ def mk_commit(epoch, hash, key):
|
|||
sig = utils.encode_int32(v) + utils.encode_int32(r) + utils.encode_int32(s)
|
||||
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
|
||||
|
||||
# Initialize the first epoch
|
||||
casper.initialize_epoch(1)
|
||||
assert casper.get_nextValidatorIndex() == 1
|
||||
start = s.snapshot()
|
||||
# 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)
|
||||
|
@ -131,3 +138,137 @@ try:
|
|||
except:
|
||||
success = False
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue