@ -27,6 +27,7 @@ defmodule Explorer.Staking.ContractState do
:max_candidates ,
:max_candidates ,
:min_candidate_stake ,
:min_candidate_stake ,
:min_delegator_stake ,
:min_delegator_stake ,
:seen_block ,
:snapshotted_epoch_number ,
:snapshotted_epoch_number ,
:staking_allowed ,
:staking_allowed ,
:staking_contract ,
:staking_contract ,
@ -37,12 +38,14 @@ defmodule Explorer.Staking.ContractState do
:validator_set_contract
:validator_set_contract
]
]
# frequency in blocks
# token renewal frequency in blocks
@token_renew_frequency 10
@token_renew_frequency 10
defstruct [
defstruct [
:seen_block ,
:eth_blocknumber_pull_interval ,
:eth_subscribe_max_delay ,
:snapshotting_finished ,
:snapshotting_finished ,
:timer ,
:contracts ,
:contracts ,
:abi
:abi
]
]
@ -78,7 +81,15 @@ defmodule Explorer.Staking.ContractState do
block_reward_abi = abi ( " BlockRewardAuRa " )
block_reward_abi = abi ( " BlockRewardAuRa " )
token_abi = abi ( " Token " )
token_abi = abi ( " Token " )
staking_contract_address = Application . get_env ( :explorer , __MODULE__ ) [ :staking_contract_address ]
module_env = Application . get_env ( :explorer , __MODULE__ )
# eth_blockNumber pull interval, in milliseconds
eth_blocknumber_pull_interval = String . to_integer ( module_env [ :eth_blocknumber_pull_interval ] )
# eth_subscribe max delay to switch to eth_blockNumber, in seconds
eth_subscribe_max_delay = String . to_integer ( module_env [ :eth_subscribe_max_delay ] )
staking_contract_address = module_env [ :staking_contract_address ]
# 2d21d217 = keccak256(erc677TokenContract())
# 2d21d217 = keccak256(erc677TokenContract())
erc_677_token_contract_signature = " 2d21d217 "
erc_677_token_contract_signature = " 2d21d217 "
@ -103,8 +114,10 @@ defmodule Explorer.Staking.ContractState do
} )
} )
state = % __MODULE__ {
state = % __MODULE__ {
seen_block : 0 ,
eth_blocknumber_pull_interval : eth_blocknumber_pull_interval ,
eth_subscribe_max_delay : eth_subscribe_max_delay ,
snapshotting_finished : false ,
snapshotting_finished : false ,
timer : nil ,
contracts : %{
contracts : %{
staking : staking_contract_address ,
staking : staking_contract_address ,
validator_set : validator_set_contract_address ,
validator_set : validator_set_contract_address ,
@ -117,6 +130,7 @@ defmodule Explorer.Staking.ContractState do
block_reward_contract : %{ abi : block_reward_abi , address : block_reward_contract_address } ,
block_reward_contract : %{ abi : block_reward_abi , address : block_reward_contract_address } ,
is_snapshotting : false ,
is_snapshotting : false ,
last_change_block : 0 ,
last_change_block : 0 ,
seen_block : 0 ,
snapshotted_epoch_number : - 1 ,
snapshotted_epoch_number : - 1 ,
staking_contract : %{ abi : staking_abi , address : staking_contract_address } ,
staking_contract : %{ abi : staking_abi , address : staking_contract_address } ,
token_contract : %{ abi : token_abi , address : token_contract_address } ,
token_contract : %{ abi : token_abi , address : token_contract_address } ,
@ -128,7 +142,10 @@ defmodule Explorer.Staking.ContractState do
end
end
def handle_continue ( _ , state ) do
def handle_continue ( _ , state ) do
{ :noreply , state }
# if eth_subscribe doesn't work during the first eth_subscribe_max_delay seconds
# after server start, use eth_blockNumber
timer = Process . send_after ( self ( ) , :eth_subscribe_stopped , state . eth_subscribe_max_delay * 1000 )
{ :noreply , %{ state | timer : timer } }
end
end
# handles an event about snapshotting finishing
# handles an event about snapshotting finishing
@ -136,58 +153,60 @@ defmodule Explorer.Staking.ContractState do
{ :noreply , %{ state | snapshotting_finished : true } }
{ :noreply , %{ state | snapshotting_finished : true } }
end
end
# handles new blocks and decides to fetch fresh chain info
# received when eth_subscribe is stopped
def handle_info ( { :chain_event , :last_block_number , :realtime , block_number } , state ) do
def handle_info ( :eth_subscribe_stopped , state ) do
if block_number > state . seen_block do
state =
# read general info from the contracts (including pool list and validator list)
if Process . read_timer ( state . timer ) == false do
global_responses =
{ microseconds , state } =
ContractReader . perform_requests ( ContractReader . global_requests ( block_number ) , state . contracts , state . abi )
:timer . tc (
fn st -> fetch_state ( st , get_current_block_number ( ) ) end ,
epoch_very_beginning = global_responses . epoch_start_block == block_number + 1
[ state ]
)
if global_responses . epoch_number > get ( :epoch_number ) and not epoch_very_beginning and state . seen_block > 0 do
# if the previous staking epoch finished and we have blocks gap,
# sleep up to eth_blocknumber_pull_interval milliseconds before the next eth_blockNumber request
# call fetch_state in a loop until the blocks gap is closed
Process . send_after (
loop_block_start = state . seen_block + 1
self ( ) ,
loop_block_end = block_number - 1
:eth_subscribe_stopped ,
max ( state . eth_blocknumber_pull_interval - round ( microseconds / 1000 ) , 0 )
if loop_block_end >= loop_block_start do
)
for bn <- loop_block_start . . loop_block_end do
gr = ContractReader . perform_requests ( ContractReader . global_requests ( bn ) , state . contracts , state . abi )
state
fetch_state ( state , gr , bn , gr . epoch_start_block == bn + 1 )
else
end
state
end
end
end
fetch_state ( state , global_responses , block_number , epoch_very_beginning )
{ :noreply , state }
end
# catches a new block number from eth_subscribe
def handle_info ( { :chain_event , :last_block_number , :realtime , block_number } , state ) do
if state . timer != nil do
Process . cancel_timer ( state . timer )
end
state =
state = fetch_state ( state , block_number )
if state . snapshotting_finished do
%{ state | snapshotting_finished : false }
else
state
end
{ :noreply , %{ state | seen_block : block_number } }
timer = Process . send_after ( self ( ) , :eth_subscribe_stopped , state . eth_subscribe_max_delay * 1000 )
{ :noreply , %{ state | timer : timer } }
end
# handles new block and decides to fetch fresh chain info
defp fetch_state ( state , block_number ) do
if block_number <= get ( :seen_block ) do
state
else
else
{ :noreply , state }
fetch_state_internal ( state , block_number )
end
end
end
end
defp fetch_state ( state , global_responses , block_number , epoch_very_beginning ) do
defp fetch_state_internal ( state , block_number ) do
contracts = state . contracts
# read general info from the contracts (including pool list and validator list)
abi = state . abi
global_responses =
snapshotting_finished = state . snapshotting_finished
ContractReader . perform_requests ( ContractReader . global_requests ( block_number ) , state . contracts , state . abi )
first_fetch = get ( :epoch_end_block , 0 ) == 0
validator_min_reward_percent =
epoch_very_beginning = global_responses . epoch_start_block == block_number + 1
get_validator_min_reward_percent ( global_responses . epoch_number , block_number , contracts , abi )
start_snapshotting =
start_snapshotting = start_snapshotting? ( global_responses )
global_responses . epoch_number > get ( :snapshotted_epoch_number ) && global_responses . epoch_number > 0 &&
not get ( :is_snapshotting )
active_pools_length = Enum . count ( global_responses . active_pools )
# determine if something changed in contracts state since the previous seen block.
# determine if something changed in contracts state since the previous seen block.
# if something changed or the `fetch_state` function is called for the first time
# if something changed or the `fetch_state` function is called for the first time
@ -196,17 +215,13 @@ defmodule Explorer.Staking.ContractState do
last_change_block =
last_change_block =
max ( global_responses . staking_last_change_block , global_responses . validator_set_last_change_block )
max ( global_responses . staking_last_change_block , global_responses . validator_set_last_change_block )
first_fetch = get ( :epoch_end_block , 0 ) == 0
should_update_db =
should_update_db =
start_snapshotting or snapshotting_finished or first_fetch or last_change_block > get ( :last_change_block )
start_snapshotting or state . s napshotting_finished or first_fetch or last_change_block > get ( :last_change_block )
# save the general info to ETS (excluding pool list and validator list)
# save the general info to ETS (excluding pool list and validator list)
settings =
set_settings ( global_responses , state , block_number , last_change_block )
global_responses
|> get_settings ( validator_min_reward_percent , block_number )
|> Enum . concat ( active_pools_length : active_pools_length )
|> Enum . concat ( last_change_block : last_change_block )
:ets . insert ( @table_name , settings )
if epoch_very_beginning or start_snapshotting do
if epoch_very_beginning or start_snapshotting do
# if the block_number is the latest block of the finished staking epoch
# if the block_number is the latest block of the finished staking epoch
@ -218,12 +233,19 @@ defmodule Explorer.Staking.ContractState do
# we should update database as something changed in contracts state
# we should update database as something changed in contracts state
if should_update_db do
if should_update_db do
update_database ( epoch_very_beginning , start_snapshotting , global_responses , contracts , abi , block_number )
update_database (
epoch_very_beginning ,
start_snapshotting ,
global_responses ,
state . contracts ,
state . abi ,
block_number
)
end
end
# notify the UI about a new block
# notify the UI about a new block
data = %{
data = %{
active_pools_length : active_pools_length ,
active_pools_length : get ( : active_pools_length) ,
block_number : block_number ,
block_number : block_number ,
epoch_end_block : global_responses . epoch_end_block ,
epoch_end_block : global_responses . epoch_end_block ,
epoch_number : global_responses . epoch_number ,
epoch_number : global_responses . epoch_number ,
@ -234,6 +256,17 @@ defmodule Explorer.Staking.ContractState do
}
}
Publisher . broadcast ( [ { :staking_update , data } ] , :realtime )
Publisher . broadcast ( [ { :staking_update , data } ] , :realtime )
if state . snapshotting_finished do
%{ state | snapshotting_finished : false }
else
state
end
end
defp start_snapshotting? ( global_responses ) do
global_responses . epoch_number > get ( :snapshotted_epoch_number ) && global_responses . epoch_number > 0 &&
not get ( :is_snapshotting )
end
end
defp update_database ( epoch_very_beginning , start_snapshotting , global_responses , contracts , abi , block_number ) do
defp update_database ( epoch_very_beginning , start_snapshotting , global_responses , contracts , abi , block_number ) do
@ -295,9 +328,6 @@ defmodule Explorer.Staking.ContractState do
block_number
block_number
)
)
# calculate total amount staked into all active pools
staked_total = Enum . sum ( for { _ , pool } <- pool_staking_responses , pool . is_active , do : pool . total_staked_amount )
# calculate likelihood of becoming a validator on the next epoch
# calculate likelihood of becoming a validator on the next epoch
[ likelihood_values , total_likelihood ] = global_responses . pools_likelihood
[ likelihood_values , total_likelihood ] = global_responses . pools_likelihood
# array of pool addresses (staking addresses)
# array of pool addresses (staking addresses)
@ -332,8 +362,7 @@ defmodule Explorer.Staking.ContractState do
global_responses : global_responses ,
global_responses : global_responses ,
snapshotted_epoch_number : snapshotted_epoch_number ,
snapshotted_epoch_number : snapshotted_epoch_number ,
likelihood : likelihood ,
likelihood : likelihood ,
total_likelihood : total_likelihood ,
total_likelihood : total_likelihood
staked_total : staked_total
} )
} )
# perform SQL queries
# perform SQL queries
@ -356,7 +385,29 @@ defmodule Explorer.Staking.ContractState do
end
end
end
end
defp get_settings ( global_responses , validator_min_reward_percent , block_number ) do
defp get_current_block_number do
json_rpc_named_arguments = Application . get_env ( :explorer , :json_rpc_named_arguments )
result =
%{ id : 0 , method : " eth_blockNumber " , params : [ ] }
|> EthereumJSONRPC . request ( )
|> EthereumJSONRPC . json_rpc ( json_rpc_named_arguments )
case result do
{ :ok , response } ->
response
|> String . replace_leading ( " 0x " , " " )
|> String . to_integer ( 16 )
_ ->
0
end
end
defp get_settings ( global_responses , state , block_number ) do
validator_min_reward_percent =
get_validator_min_reward_percent ( global_responses . epoch_number , block_number , state . contracts , state . abi )
base_settings = get_base_settings ( global_responses , validator_min_reward_percent )
base_settings = get_base_settings ( global_responses , validator_min_reward_percent )
update_token =
update_token =
@ -371,6 +422,17 @@ defmodule Explorer.Staking.ContractState do
end
end
end
end
defp set_settings ( global_responses , state , block_number , last_change_block ) do
settings =
global_responses
|> get_settings ( state , block_number )
|> Enum . concat ( active_pools_length : Enum . count ( global_responses . active_pools ) )
|> Enum . concat ( last_change_block : last_change_block )
|> Enum . concat ( seen_block : block_number )
:ets . insert ( @table_name , settings )
end
defp get_mining_to_staking_address ( validators , contracts , abi , block_number ) do
defp get_mining_to_staking_address ( validators , contracts , abi , block_number ) do
validators . all
validators . all
|> Enum . map ( & ContractReader . staking_by_mining_request ( &1 , block_number ) )
|> Enum . map ( & ContractReader . staking_by_mining_request ( &1 , block_number ) )
@ -600,9 +662,11 @@ defmodule Explorer.Staking.ContractState do
global_responses : global_responses ,
global_responses : global_responses ,
snapshotted_epoch_number : snapshotted_epoch_number ,
snapshotted_epoch_number : snapshotted_epoch_number ,
likelihood : likelihood ,
likelihood : likelihood ,
total_likelihood : total_likelihood ,
total_likelihood : total_likelihood
staked_total : staked_total
} ) do
} ) do
# total amount staked into all active pools
staked_total = Enum . sum ( for { _ , pool } <- pool_staking_responses , pool . is_active , do : pool . total_staked_amount )
Enum . map ( pools , fn pool_staking_address ->
Enum . map ( pools , fn pool_staking_address ->
staking_resp = pool_staking_responses [ pool_staking_address ]
staking_resp = pool_staking_responses [ pool_staking_address ]
mining_resp = pool_mining_responses [ pool_staking_address ]
mining_resp = pool_mining_responses [ pool_staking_address ]
@ -622,6 +686,15 @@ defmodule Explorer.Staking.ContractState do
0
0
end
end
is_unremovable = address_bytes_to_string ( pool_staking_address ) == global_responses . unremovable_validator
likelihood_value =
if get ( :active_pools_length ) > global_responses . max_validators and not is_unremovable do
ratio ( likelihood [ pool_staking_address ] || 0 , total_likelihood )
else
100
end
%{
%{
staking_address_hash : pool_staking_address ,
staking_address_hash : pool_staking_address ,
delegators_count : delegators_count ,
delegators_count : delegators_count ,
@ -632,11 +705,11 @@ defmodule Explorer.Staking.ContractState do
0
0
end ,
end ,
validator_reward_ratio : Float . floor ( candidate_reward_resp . validator_share / 10_000 , 2 ) ,
validator_reward_ratio : Float . floor ( candidate_reward_resp . validator_share / 10_000 , 2 ) ,
likelihood : ratio ( likelihood [ pool_staking_address ] || 0 , total_likelihood ) ,
likelihood : likelihood_value ,
validator_reward_percent : staking_resp . validator_reward_percent / 10_000 ,
validator_reward_percent : staking_resp . validator_reward_percent / 10_000 ,
is_deleted : false ,
is_deleted : false ,
is_validator : is_validator ,
is_validator : is_validator ,
is_unremovable : address_bytes_to_str ing ( pool _staking_address ) == global_responses . unremovable_validator ,
is_unremovable : is _unremovable ,
ban_reason : binary_to_string ( mining_resp . ban_reason )
ban_reason : binary_to_string ( mining_resp . ban_reason )
}
}
|> Map . merge (
|> Map . merge (