2017-02-21 12:27:32 +00:00
|
|
|
# Information about validators
|
2017-03-23 10:48:28 +00:00
|
|
|
validators: public({
|
2017-02-21 12:27:32 +00:00
|
|
|
# Amount of wei the validator holds
|
|
|
|
deposit: wei_value,
|
2017-03-07 02:51:13 +00:00
|
|
|
# The dynasty the validator is joining
|
|
|
|
dynasty_start: num,
|
|
|
|
# The dynasty the validator is leaving
|
|
|
|
dynasty_end: num,
|
2017-02-21 12:27:32 +00:00
|
|
|
# The timestamp at which the validator can withdraw
|
2017-03-23 05:18:15 +00:00
|
|
|
withdrawal_epoch: num,
|
2017-02-21 12:27:32 +00:00
|
|
|
# The address which the validator's signatures must verify to (to be later replaced with validation code)
|
|
|
|
addr: address,
|
|
|
|
# Addess to withdraw to
|
|
|
|
withdrawal_addr: address,
|
|
|
|
# The max epoch at which the validator prepared
|
|
|
|
max_prepared: num,
|
|
|
|
# The max epoch at which the validator committed
|
|
|
|
max_committed: num
|
2017-03-23 10:48:28 +00:00
|
|
|
}[num])
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-03-07 02:51:13 +00:00
|
|
|
# The current dynasty (validator set changes between dynasties)
|
2017-03-22 10:58:50 +00:00
|
|
|
dynasty: public(num)
|
2017-03-07 02:51:13 +00:00
|
|
|
# Amount of wei added to the total deposits in the next dynasty
|
|
|
|
next_dynasty_wei_delta: wei_value
|
|
|
|
# Amount of wei added to the total deposits in the dynasty after that
|
|
|
|
second_next_dynasty_wei_delta: wei_value
|
|
|
|
|
|
|
|
# Total deposits during this dynasty
|
2017-03-22 10:58:50 +00:00
|
|
|
total_deposits: public(wei_value[num])
|
2017-03-07 02:51:13 +00:00
|
|
|
|
2017-04-02 03:54:20 +00:00
|
|
|
# Mapping of dynasty to start epoch of that dynasty
|
|
|
|
dynasty_start_epoch: public(num[num])
|
|
|
|
|
2017-02-21 12:27:32 +00:00
|
|
|
# Information for use in processing cryptoeconomic commitments
|
2017-03-22 10:58:50 +00:00
|
|
|
consensus_messages: public({
|
2017-03-07 02:51:13 +00:00
|
|
|
# How many prepares are there for this hash (hash of message hash + view source) from the current dynasty
|
2017-02-21 12:27:32 +00:00
|
|
|
prepares: wei_value[bytes32],
|
2017-03-07 02:51:13 +00:00
|
|
|
# From the previous dynasty
|
|
|
|
prev_dyn_prepares: wei_value[bytes32],
|
2017-03-22 20:58:53 +00:00
|
|
|
# Is a prepare referencing the given ancestry hash justified?
|
|
|
|
ancestry_hash_justified: bool[bytes32],
|
2017-02-21 12:27:32 +00:00
|
|
|
# Is a commit on the given hash justified?
|
2017-03-22 20:58:53 +00:00
|
|
|
hash_justified: bool[bytes32],
|
2017-03-07 02:51:13 +00:00
|
|
|
# How many commits are there for this hash
|
|
|
|
commits: wei_value[bytes32],
|
|
|
|
# And from the previous dynasty
|
|
|
|
prev_dyn_commits: wei_value[bytes32],
|
|
|
|
# Was the block committed?
|
2017-04-02 03:54:20 +00:00
|
|
|
committed: bool,
|
|
|
|
# Value used to calculate the per-epoch fee that validators should be charged
|
|
|
|
deposit_scale_factor: decimal
|
2017-03-22 10:58:50 +00:00
|
|
|
}[num]) # index: epoch
|
2017-02-21 12:27:32 +00:00
|
|
|
|
|
|
|
# ancestry[x][y] = k > 0: x is a kth generation ancestor of y
|
2017-04-02 03:54:20 +00:00
|
|
|
ancestry: public(num[bytes32][bytes32])
|
2017-02-21 12:27:32 +00:00
|
|
|
|
|
|
|
# Number of validators
|
2017-03-22 10:58:50 +00:00
|
|
|
nextValidatorIndex: public(num)
|
2017-02-21 12:27:32 +00:00
|
|
|
|
|
|
|
# Time between blocks
|
|
|
|
block_time: timedelta
|
|
|
|
|
|
|
|
# Length of an epoch in blocks
|
|
|
|
epoch_length: num
|
|
|
|
|
|
|
|
# Withdrawal delay
|
|
|
|
withdrawal_delay: timedelta
|
|
|
|
|
|
|
|
# Delay after which a message can be slashed due to absence of justification
|
|
|
|
insufficiency_slash_delay: timedelta
|
|
|
|
|
|
|
|
# Current epoch
|
2017-03-22 10:58:50 +00:00
|
|
|
current_epoch: public(num)
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-03-07 02:51:13 +00:00
|
|
|
# Can withdraw destroyed deposits
|
|
|
|
owner: address
|
|
|
|
|
|
|
|
# Total deposits destroyed
|
|
|
|
total_destroyed: wei_value
|
|
|
|
|
2017-03-22 10:58:50 +00:00
|
|
|
# Sighash calculator library address
|
|
|
|
sighasher: address
|
2017-03-10 14:47:04 +00:00
|
|
|
|
2017-04-02 03:54:20 +00:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2017-04-03 04:57:00 +00:00
|
|
|
# Log topic for prepare
|
|
|
|
prepare_log_topic: bytes32
|
|
|
|
|
|
|
|
# Log topic for commit
|
|
|
|
commit_log_topic: bytes32
|
|
|
|
|
2017-04-02 03:54:20 +00:00
|
|
|
def initiate():
|
|
|
|
assert not self.initialized
|
|
|
|
self.initialized = True
|
2017-03-07 02:55:09 +00:00
|
|
|
# Set Casper parameters
|
2017-03-22 20:58:53 +00:00
|
|
|
self.block_time = 14
|
2017-04-02 03:54:20 +00:00
|
|
|
self.epoch_length = 100
|
2017-03-11 14:43:39 +00:00
|
|
|
# Only ~11.5 days, for testing purposes
|
|
|
|
self.withdrawal_delay = 1000000
|
2017-03-22 20:58:53 +00:00
|
|
|
# Only ~1 day, for testing purposes
|
|
|
|
self.insufficiency_slash_delay = 86400
|
2017-03-07 02:55:09 +00:00
|
|
|
# Temporary backdoor for testing purposes (to allow recovering destroyed deposits)
|
2017-03-07 02:51:13 +00:00
|
|
|
self.owner = 0x1db3439a222c519ab44bb1144fc28167b4fa6ee6
|
2017-03-07 02:55:09 +00:00
|
|
|
# Add an initial validator
|
2017-03-07 02:51:13 +00:00
|
|
|
self.validators[0] = {
|
2017-04-02 03:54:20 +00:00
|
|
|
deposit: as_wei_value(3, ether),
|
2017-03-07 02:51:13 +00:00
|
|
|
dynasty_start: 0,
|
|
|
|
dynasty_end: 1000000000000000000000000000000,
|
2017-03-23 05:18:15 +00:00
|
|
|
withdrawal_epoch: 1000000000000000000000000000000,
|
2017-03-07 02:51:13 +00:00
|
|
|
addr: 0x1db3439a222c519ab44bb1144fc28167b4fa6ee6,
|
|
|
|
withdrawal_addr: 0x1db3439a222c519ab44bb1144fc28167b4fa6ee6,
|
|
|
|
max_prepared: 0,
|
|
|
|
max_committed: 0
|
|
|
|
}
|
2017-03-22 10:58:50 +00:00
|
|
|
self.nextValidatorIndex = 1
|
2017-03-07 02:55:09 +00:00
|
|
|
# Initialize the epoch counter
|
2017-03-07 02:51:13 +00:00
|
|
|
self.current_epoch = block.number / self.epoch_length
|
2017-03-22 10:58:50 +00:00
|
|
|
# Set the sighash calculator address
|
2017-04-03 04:57:00 +00:00
|
|
|
self.sighasher = 0x476c2cA9a7f3B16FeCa86512276271FAf63B6a24
|
2017-03-22 20:58:53 +00:00
|
|
|
# Set an initial root of the epoch hash chain
|
|
|
|
self.consensus_messages[0].ancestry_hash_justified[0x0000000000000000000000000000000000000000000000000000000000000000] = True
|
2017-03-23 10:48:28 +00:00
|
|
|
# Set initial total deposit counter
|
2017-04-02 03:54:20 +00:00
|
|
|
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
|
2017-04-03 04:57:00 +00:00
|
|
|
# Log topics for prepare and commit
|
|
|
|
self.prepare_log_topic = sha3("prepare()")
|
|
|
|
self.commit_log_topic = sha3("commit()")
|
2017-03-07 02:51:13 +00:00
|
|
|
|
|
|
|
# Called at the start of any epoch
|
2017-02-21 12:27:32 +00:00
|
|
|
def initialize_epoch(epoch: num):
|
2017-04-02 03:54:20 +00:00
|
|
|
# Check that the epoch actually has started
|
2017-02-21 12:27:32 +00:00
|
|
|
computed_current_epoch = block.number / self.epoch_length
|
2017-03-22 10:58:50 +00:00
|
|
|
assert epoch <= computed_current_epoch and epoch == self.current_epoch + 1
|
2017-04-02 03:54:20 +00:00
|
|
|
# Set the epoch number
|
2017-03-22 10:58:50 +00:00
|
|
|
self.current_epoch = epoch
|
2017-04-02 03:54:20 +00:00
|
|
|
# Increment the dynasty
|
2017-03-22 10:58:50 +00:00
|
|
|
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
|
2017-04-02 03:54:20 +00:00
|
|
|
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)
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-03-07 02:51:13 +00:00
|
|
|
# Send a deposit to join the validator set
|
2017-02-21 12:27:32 +00:00
|
|
|
def deposit(validation_addr: address, withdrawal_addr: address):
|
|
|
|
assert self.current_epoch == block.number / self.epoch_length
|
|
|
|
self.validators[self.nextValidatorIndex] = {
|
|
|
|
deposit: msg.value,
|
2017-03-07 02:51:13 +00:00
|
|
|
dynasty_start: self.dynasty + 2,
|
|
|
|
dynasty_end: 1000000000000000000000000000000,
|
2017-03-23 05:18:15 +00:00
|
|
|
withdrawal_epoch: 1000000000000000000000000000000,
|
2017-02-21 12:27:32 +00:00
|
|
|
addr: validation_addr,
|
|
|
|
withdrawal_addr: withdrawal_addr,
|
|
|
|
max_prepared: 0,
|
|
|
|
max_committed: 0,
|
|
|
|
}
|
|
|
|
self.nextValidatorIndex += 1
|
2017-03-07 02:51:13 +00:00
|
|
|
self.second_next_dynasty_wei_delta += msg.value
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-03-23 10:48:28 +00:00
|
|
|
# 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):
|
2017-03-23 05:18:15 +00:00
|
|
|
assert self.current_epoch == block.number / self.epoch_length
|
2017-03-23 10:48:28 +00:00
|
|
|
# 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
|
2017-03-23 05:18:15 +00:00
|
|
|
# Signature check
|
|
|
|
assert len(sig) == 96
|
2017-03-23 10:48:28 +00:00
|
|
|
assert ecrecover(sighash,
|
2017-03-23 05:18:15 +00:00
|
|
|
as_num256(extract32(sig, 0)),
|
|
|
|
as_num256(extract32(sig, 32)),
|
|
|
|
as_num256(extract32(sig, 64))) == self.validators[validator_index].addr
|
2017-03-23 10:48:28 +00:00
|
|
|
# Logging in
|
|
|
|
if login_flag:
|
|
|
|
# Check that we are logged out
|
|
|
|
assert self.validators[validator_index].dynasty_end < self.dynasty
|
2017-04-02 03:54:20 +00:00
|
|
|
# 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
|
2017-03-23 10:48:28 +00:00
|
|
|
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
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-03-07 02:51:13 +00:00
|
|
|
# Withdraw deposited ether
|
2017-03-23 05:18:15 +00:00
|
|
|
def withdraw(validator_index: num):
|
2017-03-07 02:51:13 +00:00
|
|
|
# Check that we can withdraw
|
2017-03-23 05:18:15 +00:00
|
|
|
assert self.current_epoch >= self.validators[validator_index].withdrawal_epoch
|
2017-04-02 03:54:20 +00:00
|
|
|
# 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))
|
2017-03-07 02:51:13 +00:00
|
|
|
# Withdraw
|
2017-03-23 05:18:15 +00:00
|
|
|
send(self.validators[validator_index].withdrawal_addr, self.validators[validator_index].deposit)
|
|
|
|
self.validators[validator_index] = {
|
2017-03-07 02:51:13 +00:00
|
|
|
deposit: 0,
|
|
|
|
dynasty_start: 0,
|
|
|
|
dynasty_end: 0,
|
2017-03-23 05:18:15 +00:00
|
|
|
withdrawal_epoch: 0,
|
2017-03-07 02:51:13 +00:00
|
|
|
addr: None,
|
|
|
|
withdrawal_addr: None,
|
|
|
|
max_prepared: 0,
|
|
|
|
max_committed: 0,
|
|
|
|
}
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-03-07 02:51:13 +00:00
|
|
|
# Process a prepare message
|
2017-03-22 10:58:50 +00:00
|
|
|
def prepare(validator_index: num, prepare_msg: bytes <= 1024):
|
2017-03-11 14:43:39 +00:00
|
|
|
# Get hash for signature, and implicitly assert that it is an RLP list
|
|
|
|
# consisting solely of RLP elements
|
2017-03-22 10:58:50 +00:00
|
|
|
sighash = extract32(raw_call(self.sighasher, prepare_msg, gas=200000, outsize=32), 0)
|
2017-03-11 14:43:39 +00:00
|
|
|
# Extract parameters
|
2017-03-22 10:58:50 +00:00
|
|
|
values = RLPList(prepare_msg, [num, bytes32, bytes32, num, bytes32, bytes])
|
|
|
|
epoch = values[0]
|
|
|
|
hash = values[1]
|
|
|
|
ancestry_hash = values[2]
|
|
|
|
source_epoch = values[3]
|
|
|
|
source_ancestry_hash = values[4]
|
|
|
|
sig = values[5]
|
2017-03-11 14:43:39 +00:00
|
|
|
# For now, the sig is a simple ECDSA sig
|
2017-03-22 20:58:53 +00:00
|
|
|
# Check the signature
|
2017-02-21 12:27:32 +00:00
|
|
|
assert len(sig) == 96
|
|
|
|
assert ecrecover(sighash,
|
2017-03-22 10:58:50 +00:00
|
|
|
extract32(sig, 0, type=num256),
|
|
|
|
extract32(sig, 32, type=num256),
|
|
|
|
extract32(sig, 64, type=num256)) == self.validators[validator_index].addr
|
2017-02-21 12:27:32 +00:00
|
|
|
# Check that we are in the right epoch
|
|
|
|
assert self.current_epoch == block.number / self.epoch_length
|
|
|
|
assert self.current_epoch == epoch
|
2017-04-02 03:54:20 +00:00
|
|
|
# Check that we are at least (epoch length / 4) blocks into the epoch
|
|
|
|
# assert block.number % self.epoch_length >= self.epoch_length / 4
|
2017-03-07 02:51:13 +00:00
|
|
|
# Check that this validator was active in either the previous dynasty or the current one
|
2017-03-22 10:58:50 +00:00
|
|
|
ds = self.validators[validator_index].dynasty_start
|
|
|
|
de = self.validators[validator_index].dynasty_end
|
2017-03-07 02:51:13 +00:00
|
|
|
dc = self.dynasty
|
|
|
|
in_current_dynasty = (ds <= dc) and (dc < de)
|
|
|
|
in_prev_dynasty = (ds <= (dc - 1)) and ((dc - 1) < de)
|
|
|
|
assert in_current_dynasty or in_prev_dynasty
|
2017-03-22 20:58:53 +00:00
|
|
|
# Check that the prepare is on top of a justified prepare
|
|
|
|
assert self.consensus_messages[source_epoch].ancestry_hash_justified[source_ancestry_hash]
|
2017-02-21 12:27:32 +00:00
|
|
|
# Check that we have not yet prepared for this epoch
|
2017-03-22 20:58:53 +00:00
|
|
|
#assert self.validators[validator_index].max_prepared == epoch - 1
|
2017-02-21 12:27:32 +00:00
|
|
|
# Pay the reward if the blockhash is correct
|
2017-04-02 03:54:20 +00:00
|
|
|
if True: #if blockhash(epoch * self.epoch_length) == hash:
|
|
|
|
reward = floor(self.validators[validator_index].deposit * self.reward_factor)
|
2017-03-22 10:58:50 +00:00
|
|
|
self.validators[validator_index].deposit += reward
|
2017-03-07 02:51:13 +00:00
|
|
|
self.total_deposits[self.dynasty] += reward
|
2017-02-21 12:27:32 +00:00
|
|
|
# Can't prepare for this epoch again
|
2017-03-22 10:58:50 +00:00
|
|
|
self.validators[validator_index].max_prepared = epoch
|
2017-02-21 12:27:32 +00:00
|
|
|
# Record that this prepare took place
|
|
|
|
new_ancestry_hash = sha3(concat(hash, ancestry_hash))
|
2017-03-07 02:51:13 +00:00
|
|
|
if in_current_dynasty:
|
2017-03-22 10:58:50 +00:00
|
|
|
self.consensus_messages[epoch].prepares[sighash] += self.validators[validator_index].deposit
|
2017-03-07 02:51:13 +00:00
|
|
|
if in_prev_dynasty:
|
2017-03-22 10:58:50 +00:00
|
|
|
self.consensus_messages[epoch].prev_dyn_prepares[sighash] += self.validators[validator_index].deposit
|
2017-02-21 12:27:32 +00:00
|
|
|
# If enough prepares with the same epoch_source and hash are made,
|
|
|
|
# then the hash value is justified for commitment
|
2017-03-07 02:51:13 +00:00
|
|
|
if (self.consensus_messages[epoch].prepares[sighash] >= self.total_deposits[self.dynasty] * 2 / 3 and \
|
2017-03-22 10:58:50 +00:00
|
|
|
self.consensus_messages[epoch].prev_dyn_prepares[sighash] >= self.total_deposits[self.dynasty - 1] * 2 / 3) and \
|
2017-03-22 20:58:53 +00:00
|
|
|
not self.consensus_messages[epoch].ancestry_hash_justified[new_ancestry_hash]:
|
|
|
|
self.consensus_messages[epoch].ancestry_hash_justified[new_ancestry_hash] = True
|
|
|
|
self.consensus_messages[epoch].hash_justified[hash] = True
|
2017-02-21 12:27:32 +00:00
|
|
|
# Add a parent-child relation between ancestry hashes to the ancestry table
|
|
|
|
self.ancestry[ancestry_hash][new_ancestry_hash] = 1
|
2017-04-03 04:57:00 +00:00
|
|
|
raw_log([self.prepare_log_topic], prepare_msg)
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-03-07 02:51:13 +00:00
|
|
|
# Process a commit message
|
2017-03-22 10:58:50 +00:00
|
|
|
def commit(validator_index: num, commit_msg: bytes <= 1024):
|
|
|
|
sighash = extract32(raw_call(self.sighasher, commit_msg, gas=200000, outsize=32), 0)
|
|
|
|
# Extract parameters
|
|
|
|
values = RLPList(commit_msg, [num, bytes32, bytes])
|
|
|
|
epoch = values[0]
|
|
|
|
hash = values[1]
|
|
|
|
sig = values[2]
|
2017-03-22 20:58:53 +00:00
|
|
|
# Check the signature
|
2017-02-21 12:27:32 +00:00
|
|
|
assert len(sig) == 96
|
|
|
|
assert ecrecover(sighash,
|
2017-03-22 10:58:50 +00:00
|
|
|
extract32(sig, 0, type=num256),
|
|
|
|
extract32(sig, 32, type=num256),
|
|
|
|
extract32(sig, 64, type=num256)) == self.validators[validator_index].addr
|
2017-02-21 12:27:32 +00:00
|
|
|
# Check that we are in the right epoch
|
|
|
|
assert self.current_epoch == block.number / self.epoch_length
|
|
|
|
assert self.current_epoch == epoch
|
2017-04-02 03:54:20 +00:00
|
|
|
# Check that we are at least (epoch length / 2) blocks into the epoch
|
|
|
|
# assert block.number % self.epoch_length >= self.epoch_length / 2
|
2017-03-22 10:58:50 +00:00
|
|
|
# Check that the commit is justified
|
2017-03-22 20:58:53 +00:00
|
|
|
assert self.consensus_messages[epoch].hash_justified[hash]
|
2017-03-07 02:51:13 +00:00
|
|
|
# Check that this validator was active in either the previous dynasty or the current one
|
2017-03-22 10:58:50 +00:00
|
|
|
ds = self.validators[validator_index].dynasty_start
|
|
|
|
de = self.validators[validator_index].dynasty_end
|
2017-03-07 02:51:13 +00:00
|
|
|
dc = self.dynasty
|
|
|
|
in_current_dynasty = (ds <= dc) and (dc < de)
|
|
|
|
in_prev_dynasty = (ds <= (dc - 1)) and ((dc - 1) < de)
|
|
|
|
assert in_current_dynasty or in_prev_dynasty
|
2017-02-21 12:27:32 +00:00
|
|
|
# Check that we have not yet committed for this epoch
|
2017-03-23 10:48:28 +00:00
|
|
|
#assert self.validators[validator_index].max_committed == epoch - 1
|
2017-02-21 12:27:32 +00:00
|
|
|
# Pay the reward if the blockhash is correct
|
2017-04-02 03:54:20 +00:00
|
|
|
if True: #if blockhash(epoch * self.epoch_length) == hash:
|
|
|
|
reward = floor(self.validators[validator_index].deposit * self.reward_factor)
|
2017-03-22 10:58:50 +00:00
|
|
|
self.validators[validator_index].deposit += reward
|
2017-03-07 02:51:13 +00:00
|
|
|
self.total_deposits[self.dynasty] += reward
|
2017-02-21 12:27:32 +00:00
|
|
|
# Can't commit for this epoch again
|
2017-03-22 10:58:50 +00:00
|
|
|
self.validators[validator_index].max_committed = epoch
|
2017-03-07 02:51:13 +00:00
|
|
|
# Record that this commit took place
|
|
|
|
if in_current_dynasty:
|
2017-03-22 10:58:50 +00:00
|
|
|
self.consensus_messages[epoch].commits[hash] += self.validators[validator_index].deposit
|
2017-03-07 02:51:13 +00:00
|
|
|
if in_prev_dynasty:
|
2017-03-22 10:58:50 +00:00
|
|
|
self.consensus_messages[epoch].prev_dyn_commits[hash] += self.validators[validator_index].deposit
|
2017-03-07 02:51:13 +00:00
|
|
|
# Record if sufficient commits have been made for the block to be finalized
|
2017-03-22 10:58:50 +00:00
|
|
|
if (self.consensus_messages[epoch].commits[hash] >= self.total_deposits[self.dynasty] * 2 / 3 and \
|
|
|
|
self.consensus_messages[epoch].prev_dyn_commits[hash] >= self.total_deposits[self.dynasty - 1] * 2 / 3) and \
|
2017-03-07 02:51:13 +00:00
|
|
|
not self.consensus_messages[epoch].committed:
|
|
|
|
self.consensus_messages[epoch].committed = True
|
2017-04-03 04:57:00 +00:00
|
|
|
raw_log([self.commit_log_topic], commit_msg)
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-03-22 20:58:53 +00:00
|
|
|
# Cannot make two prepares in the same epoch
|
|
|
|
def double_prepare_slash(validator_index: num, prepare1: bytes <= 1000, prepare2: bytes <= 1000):
|
|
|
|
# Get hash for signature, and implicitly assert that it is an RLP list
|
|
|
|
# consisting solely of RLP elements
|
|
|
|
sighash1 = extract32(raw_call(self.sighasher, prepare1, gas=200000, outsize=32), 0)
|
|
|
|
sighash2 = extract32(raw_call(self.sighasher, prepare2, gas=200000, outsize=32), 0)
|
|
|
|
# Extract parameters
|
|
|
|
values1 = RLPList(prepare1, [num, bytes32, bytes32, num, bytes32, bytes])
|
|
|
|
values2 = RLPList(prepare2, [num, bytes32, bytes32, num, bytes32, bytes])
|
|
|
|
epoch1 = values1[0]
|
|
|
|
sig1 = values1[5]
|
|
|
|
epoch2 = values2[0]
|
|
|
|
sig2 = values2[5]
|
|
|
|
# Check the signatures
|
2017-02-21 12:27:32 +00:00
|
|
|
assert ecrecover(sighash1,
|
|
|
|
as_num256(extract32(sig1, 0)),
|
|
|
|
as_num256(extract32(sig1, 32)),
|
2017-03-22 20:58:53 +00:00
|
|
|
as_num256(extract32(sig1, 64))) == self.validators[validator_index].addr
|
2017-02-21 12:27:32 +00:00
|
|
|
assert ecrecover(sighash2,
|
|
|
|
as_num256(extract32(sig2, 0)),
|
|
|
|
as_num256(extract32(sig2, 32)),
|
2017-03-22 20:58:53 +00:00
|
|
|
as_num256(extract32(sig2, 64))) == self.validators[validator_index].addr
|
|
|
|
# Check that they're from the same epoch
|
|
|
|
assert epoch1 == epoch2
|
2017-02-21 12:27:32 +00:00
|
|
|
# Check that they're not the same message
|
|
|
|
assert sighash1 != sighash2
|
|
|
|
# Delete the offending validator, and give a 4% "finder's fee"
|
2017-03-22 20:58:53 +00:00
|
|
|
validator_deposit = self.validators[validator_index].deposit
|
2017-02-21 12:27:32 +00:00
|
|
|
send(msg.sender, validator_deposit / 25)
|
2017-03-07 02:51:13 +00:00
|
|
|
self.total_destroyed += validator_deposit * 24 / 25
|
|
|
|
self.total_deposits[self.dynasty] -= (validator_deposit - validator_deposit / 25)
|
2017-03-22 20:58:53 +00:00
|
|
|
self.validators[validator_index] = {
|
2017-02-21 12:27:32 +00:00
|
|
|
deposit: 0,
|
2017-03-07 02:51:13 +00:00
|
|
|
dynasty_start: 0,
|
|
|
|
dynasty_end: 0,
|
2017-03-23 05:18:15 +00:00
|
|
|
withdrawal_epoch: 0,
|
2017-02-21 12:27:32 +00:00
|
|
|
addr: None,
|
|
|
|
withdrawal_addr: None,
|
|
|
|
max_prepared: 0,
|
|
|
|
max_committed: 0,
|
|
|
|
}
|
|
|
|
|
2017-03-22 20:58:53 +00:00
|
|
|
def prepare_commit_inconsistency_slash(validator_index: num, prepare_msg: bytes <= 1024, commit_msg: bytes <= 1024):
|
|
|
|
# Get hash for signature, and implicitly assert that it is an RLP list
|
|
|
|
# consisting solely of RLP elements
|
|
|
|
sighash1 = extract32(raw_call(self.sighasher, prepare_msg, gas=200000, outsize=32), 0)
|
|
|
|
sighash2 = extract32(raw_call(self.sighasher, commit_msg, gas=200000, outsize=32), 0)
|
|
|
|
# Extract parameters
|
|
|
|
values1 = RLPList(prepare_msg, [num, bytes32, bytes32, num, bytes32, bytes])
|
|
|
|
values2 = RLPList(commit_msg, [num, bytes32, bytes])
|
|
|
|
prepare_epoch = values1[0]
|
|
|
|
prepare_source_epoch = values1[3]
|
|
|
|
sig1 = values1[5]
|
|
|
|
commit_epoch = values2[0]
|
|
|
|
sig2 = values2[2]
|
|
|
|
# Check the signatures
|
2017-02-21 12:27:32 +00:00
|
|
|
assert ecrecover(sighash1,
|
|
|
|
as_num256(extract32(sig1, 0)),
|
|
|
|
as_num256(extract32(sig1, 32)),
|
2017-03-22 20:58:53 +00:00
|
|
|
as_num256(extract32(sig1, 64))) == self.validators[validator_index].addr
|
2017-02-21 12:27:32 +00:00
|
|
|
assert ecrecover(sighash2,
|
|
|
|
as_num256(extract32(sig2, 0)),
|
|
|
|
as_num256(extract32(sig2, 32)),
|
2017-03-22 20:58:53 +00:00
|
|
|
as_num256(extract32(sig2, 64))) == self.validators[validator_index].addr
|
2017-02-21 12:27:32 +00:00
|
|
|
# Check that they're not the same message
|
|
|
|
assert sighash1 != sighash2
|
|
|
|
# Check that the prepare refers to something older than the commit
|
|
|
|
assert prepare_source_epoch < commit_epoch
|
|
|
|
# Check that the prepare is newer than the commit
|
|
|
|
assert commit_epoch < prepare_epoch
|
|
|
|
# Delete the offending validator, and give a 4% "finder's fee"
|
2017-03-22 20:58:53 +00:00
|
|
|
validator_deposit = self.validators[validator_index].deposit
|
2017-02-21 12:27:32 +00:00
|
|
|
send(msg.sender, validator_deposit / 25)
|
2017-03-07 02:51:13 +00:00
|
|
|
self.total_destroyed += validator_deposit * 24 / 25
|
|
|
|
self.total_deposits[self.dynasty] -= validator_deposit
|
2017-03-22 20:58:53 +00:00
|
|
|
self.validators[validator_index] = {
|
2017-02-21 12:27:32 +00:00
|
|
|
deposit: 0,
|
2017-03-07 02:51:13 +00:00
|
|
|
dynasty_start: 0,
|
|
|
|
dynasty_end: 0,
|
2017-03-23 05:18:15 +00:00
|
|
|
withdrawal_epoch: 0,
|
2017-02-21 12:27:32 +00:00
|
|
|
addr: None,
|
|
|
|
withdrawal_addr: None,
|
|
|
|
max_prepared: 0,
|
|
|
|
max_committed: 0,
|
|
|
|
}
|
|
|
|
|
2017-03-22 20:58:53 +00:00
|
|
|
def commit_non_justification_slash(validator_index: num, commit_msg: bytes <= 1024):
|
|
|
|
sighash = extract32(raw_call(self.sighasher, commit_msg, gas=200000, outsize=32), 0)
|
|
|
|
# Extract parameters
|
|
|
|
values = RLPList(commit_msg, [num, bytes32, bytes])
|
|
|
|
epoch = values[0]
|
|
|
|
hash = values[1]
|
|
|
|
sig = values[2]
|
|
|
|
# Check the signature
|
|
|
|
assert len(sig) == 96
|
2017-02-21 12:27:32 +00:00
|
|
|
assert ecrecover(sighash,
|
2017-03-22 20:58:53 +00:00
|
|
|
extract32(sig, 0, type=num256),
|
|
|
|
extract32(sig, 32, type=num256),
|
|
|
|
extract32(sig, 64, type=num256)) == self.validators[validator_index].addr
|
2017-02-21 12:27:32 +00:00
|
|
|
# Check that the commit is old enough
|
|
|
|
assert self.current_epoch == block.number / self.epoch_length
|
2017-03-22 20:58:53 +00:00
|
|
|
assert (self.current_epoch - epoch) * self.epoch_length * self.block_time > self.insufficiency_slash_delay
|
|
|
|
assert not self.consensus_messages[epoch].hash_justified[hash]
|
2017-02-21 12:27:32 +00:00
|
|
|
# Delete the offending validator, and give a 4% "finder's fee"
|
2017-03-22 20:58:53 +00:00
|
|
|
validator_deposit = self.validators[validator_index].deposit
|
2017-02-21 12:27:32 +00:00
|
|
|
send(msg.sender, validator_deposit / 25)
|
2017-03-07 02:51:13 +00:00
|
|
|
self.total_destroyed += validator_deposit * 24 / 25
|
|
|
|
self.total_deposits[self.dynasty] -= validator_deposit
|
2017-03-22 20:58:53 +00:00
|
|
|
self.validators[validator_index] = {
|
2017-02-21 12:27:32 +00:00
|
|
|
deposit: 0,
|
2017-03-07 02:51:13 +00:00
|
|
|
dynasty_start: 0,
|
|
|
|
dynasty_end: 0,
|
2017-03-23 05:18:15 +00:00
|
|
|
withdrawal_epoch: 0,
|
2017-02-21 12:27:32 +00:00
|
|
|
addr: None,
|
|
|
|
withdrawal_addr: None,
|
|
|
|
max_prepared: 0,
|
|
|
|
max_committed: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
# Fill in the table for which hash is what-degree ancestor of which other hash
|
2017-04-02 03:54:20 +00:00
|
|
|
def derive_parenthood(older: bytes32, hash: bytes32, newer: bytes32):
|
|
|
|
assert sha3(concat(hash, older)) == newer
|
|
|
|
self.ancestry[older][newer] = 1
|
|
|
|
|
|
|
|
# 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]
|
2017-02-21 12:27:32 +00:00
|
|
|
|
2017-04-02 03:54:20 +00:00
|
|
|
def prepare_non_justification_slash(validator_index: num, prepare_msg: bytes <= 1024) -> num:
|
2017-03-22 20:58:53 +00:00
|
|
|
# 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)
|
|
|
|
# Extract parameters
|
|
|
|
values = RLPList(prepare_msg, [num, bytes32, bytes32, num, bytes32, bytes])
|
|
|
|
epoch = values[0]
|
|
|
|
hash = values[1]
|
|
|
|
ancestry_hash = values[2]
|
|
|
|
source_epoch = values[3]
|
|
|
|
source_ancestry_hash = values[4]
|
|
|
|
sig = values[5]
|
|
|
|
# Check the signature
|
2017-02-21 12:27:32 +00:00
|
|
|
assert ecrecover(sighash,
|
2017-03-22 20:58:53 +00:00
|
|
|
extract32(sig, 0, type=num256),
|
|
|
|
extract32(sig, 32, type=num256),
|
|
|
|
extract32(sig, 64, type=num256)) == self.validators[validator_index].addr
|
2017-02-21 12:27:32 +00:00
|
|
|
# Check that the view change is old enough
|
|
|
|
assert self.current_epoch == block.number / self.epoch_length
|
2017-03-22 20:58:53 +00:00
|
|
|
assert (self.current_epoch - epoch) * self.block_time * self.epoch_length > self.insufficiency_slash_delay
|
2017-04-02 03:54:20 +00:00
|
|
|
# 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)
|
2017-02-21 12:27:32 +00:00
|
|
|
# Delete the offending validator, and give a 4% "finder's fee"
|
2017-03-22 20:58:53 +00:00
|
|
|
validator_deposit = self.validators[validator_index].deposit
|
2017-02-21 12:27:32 +00:00
|
|
|
send(msg.sender, validator_deposit / 25)
|
2017-03-07 02:51:13 +00:00
|
|
|
self.total_destroyed += validator_deposit * 24 / 25
|
|
|
|
self.total_deposits[self.dynasty] -= validator_deposit
|
2017-03-22 20:58:53 +00:00
|
|
|
self.validators[validator_index] = {
|
2017-02-21 12:27:32 +00:00
|
|
|
deposit: 0,
|
2017-03-07 02:51:13 +00:00
|
|
|
dynasty_start: 0,
|
|
|
|
dynasty_end: 0,
|
2017-03-23 05:18:15 +00:00
|
|
|
withdrawal_epoch: 0,
|
2017-02-21 12:27:32 +00:00
|
|
|
addr: None,
|
|
|
|
withdrawal_addr: None,
|
|
|
|
max_prepared: 0,
|
|
|
|
max_committed: 0,
|
|
|
|
}
|
2017-03-07 02:51:13 +00:00
|
|
|
|
2017-03-07 02:55:09 +00:00
|
|
|
# Temporary backdoor for testing purposes (to allow recovering destroyed deposits)
|
2017-03-07 02:51:13 +00:00
|
|
|
def owner_withdraw():
|
|
|
|
send(self.owner, self.total_destroyed)
|
|
|
|
self.total_destroyed = 0
|
2017-03-07 02:55:09 +00:00
|
|
|
|
|
|
|
# Change backdoor address (set to zero to remove entirely)
|
|
|
|
def change_owner(new_owner: address):
|
|
|
|
if self.owner == msg.sender:
|
|
|
|
self.owner = new_owner
|