Skip to content

Commit

Permalink
HC: compute block difficulty according to the stake power (#4108)
Browse files Browse the repository at this point in the history
HC: compute block difficulty according to the stake power
  • Loading branch information
velzevur committed Apr 7, 2023
1 parent 7c4494d commit 584b47d
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 76 deletions.
118 changes: 91 additions & 27 deletions apps/aecontract/test/aecontract_staking_contract_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
]).

-export([ entropy_impacts_leader_election/1,
commitments_determine_who_participates/1
commitments_determine_who_participates/1,
added_stake_power/1
]).

-include_lib("aecontract/include/hard_forks.hrl").
Expand Down Expand Up @@ -160,7 +161,8 @@ groups() ->
]},
{hc_election, [sequence],
[ entropy_impacts_leader_election,
commitments_determine_who_participates
commitments_determine_who_participates,
added_stake_power
]}
].

Expand Down Expand Up @@ -216,7 +218,8 @@ inspect_validator(_Config) ->
{ok, _, ElectionContractState0} = get_election_contract_state_(Alice, TxEnv, Trees0),
{tuple, { StakingCT,
Entropy,
Leader
Leader,
_AddedDifficulty
}} = ElectionContractState0,
{contract, StakingContractPubkey} = StakingCT,
StakingContractPubkey = staking_contract_address(),
Expand Down Expand Up @@ -298,7 +301,8 @@ inspect_validator(_Config) ->
{ok, _, ElectionContractState1} = get_election_contract_state_(Alice, TxEnv, Trees5),
{tuple, { StakingCT, %% same
Entropy, %% same
Leader2
Leader2,
_
}} = ElectionContractState1,
{address, Alice} = Leader2, %% Alice is being elected as a leader
%% give away some rewards; this changes the total staking power but does
Expand Down Expand Up @@ -377,7 +381,8 @@ inspect_two_validators(_Config) ->
{ok, _, ElectionContractState0} = get_election_contract_state_(Alice, TxEnv, Trees0),
{tuple, { _StakingCT,
Entropy,
Leader
Leader,
_
}} = ElectionContractState0,
{bytes, _} = Entropy,
ElectionContractPubkey = election_contract_address(),
Expand Down Expand Up @@ -552,8 +557,8 @@ single_validator_gets_elected_every_time(_Config) ->
fun(Height, TreesAccum) ->
%% Alice is expected to be next
TxEnvPrev = aetx_env:set_height(TxEnv, Height - 1),
{ok, _ , {address, Alice}} = elect_next_(Alice, TxEnvPrev,
TreesAccum),
{ok, _ , {tuple, {{address, Alice}, _}}} = elect_next_(Alice, TxEnvPrev,
TreesAccum),
TxEnv1 = aetx_env:set_height(TxEnv, Height),
{ok, TreesAccum1, {tuple, {}}} = elect_(?OWNER_PUBKEY, TxEnv1, TreesAccum),
{ok, _, {address, Alice}} = leader_(Alice, TxEnv1, TreesAccum1),
Expand All @@ -567,7 +572,7 @@ single_validator_gets_elected_every_time(_Config) ->
%% Alice is expected to be next
TxEnvPrev = aetx_env:set_height(TxEnv, Height - 1),
{ok, TreesAccum1, {tuple, {}}} = reward_(Alice, 1000, ?OWNER_PUBKEY, TxEnvPrev, TreesAccum),
{ok, _ , {address, Alice}} = elect_next_(Alice, TxEnvPrev, TreesAccum1),
{ok, _ , {tuple, {{address, Alice}, _}}} = elect_next_(Alice, TxEnvPrev, TreesAccum1),
TxEnv1 = aetx_env:set_height(TxEnv, Height),
{ok, TreesAccum2, {tuple, {}}} = elect_(?OWNER_PUBKEY, TxEnv1, TreesAccum1),
{ok, _, {address, Alice}} = leader_(Alice, TxEnv1, TreesAccum2),
Expand Down Expand Up @@ -1860,20 +1865,14 @@ entropy_impacts_leader_election(_Config) ->
{Carol, ?VALIDATOR_MIN}]),
{ok, Trees2, {tuple, {}}} = set_validator_online_(Alice, TxEnv, Trees1),
{ok, Trees3, {tuple, {}}} = set_validator_online_(Bob, TxEnv, Trees2),
Hash =
fun([C]) ->
list_to_binary(lists:duplicate(32, C));
(S) when length(S) =:= 32 ->
list_to_binary(S)
end,
Entropy1 = Hash("A"),
Entropy1 = hash($A),
TopHash = aetx_env:key_hash(TxEnv),
Commitments = commitments(#{TopHash => [pubkey(?ALICE), pubkey(?BOB)]}),
{ok, Trees4, {tuple, {}}} = hc_elect_(Entropy1, Commitments, ?OWNER_PUBKEY, TxEnv, Trees3),
{ok, Trees4, {tuple, {{address, Bob}, _}}} = hc_elect_(Entropy1, Commitments, ?OWNER_PUBKEY, TxEnv, Trees3),
{ok, _, {address, Bob}} = leader_(?OWNER_PUBKEY, TxEnv, Trees4),
%% same context, different entropy leads to different leader
Entropy2 = Hash("a"),
{ok, Trees5, {tuple, {}}} = hc_elect_(Entropy2, Commitments, ?OWNER_PUBKEY, TxEnv, Trees3),
Entropy2 = hash($a),
{ok, Trees5, {tuple, {{address, Alice}, _}}} = hc_elect_(Entropy2, Commitments, ?OWNER_PUBKEY, TxEnv, Trees3),
{ok, _, {address, Alice}} = leader_(?OWNER_PUBKEY, TxEnv, Trees5),
ok.

Expand All @@ -1896,20 +1895,14 @@ commitments_determine_who_participates(_Config) ->
{Carol, ?VALIDATOR_MIN}]),
{ok, Trees2, {tuple, {}}} = set_validator_online_(Alice, TxEnv, Trees1),
{ok, Trees3, {tuple, {}}} = set_validator_online_(Bob, TxEnv, Trees2),
Hash =
fun([C]) ->
list_to_binary(lists:duplicate(32, C));
(S) when length(S) =:= 32 ->
list_to_binary(S)
end,
TopHash = aetx_env:key_hash(TxEnv),
Test =
fun(Pubkey) ->
lists:foreach(
fun(Char) ->
Entropy = Hash([Char]),
Entropy = hash(Char),
Commitments = commitments(#{TopHash => [Pubkey]}),
{ok, Trees4, {tuple, {}}} = hc_elect_(Entropy, Commitments, ?OWNER_PUBKEY, TxEnv, Trees3),
{ok, Trees4, {tuple, {{address, Pubkey}, _}}} = hc_elect_(Entropy, Commitments, ?OWNER_PUBKEY, TxEnv, Trees3),
{ok, _, {address, Pubkey}} = leader_(?OWNER_PUBKEY, TxEnv, Trees4),
ok
end,
Expand All @@ -1919,6 +1912,67 @@ commitments_determine_who_participates(_Config) ->
Test(pubkey(?BOB)),
ok.

added_stake_power(_Config) ->
Alice = pubkey(?ALICE),
Bob = pubkey(?BOB),
Carol = pubkey(?CAROL), %% will be offline
Trees0 = genesis_trees(?HC),
TxEnv = aetx_env:tx_env(?GENESIS_HEIGHT),
AliceAmt = ?VALIDATOR_MIN + 1,
BobAmt = CarolAmt = ?VALIDATOR_MIN,
Trees1 =
lists:foldl(
fun({Pubkey, Amount}, TreesAccum) ->
{ok, TreesAccum1, _} = new_validator_(Pubkey, Amount, TxEnv, TreesAccum),
TreesAccum1
end,
Trees0,
[{Alice, AliceAmt},
{Bob, BobAmt},
{Carol, CarolAmt}]),
{ok, Trees2, {tuple, {}}} = set_validator_online_(Alice, TxEnv, Trees1),
{ok, Trees3, {tuple, {}}} = set_validator_online_(Bob, TxEnv, Trees2),
TopHash = aetx_env:key_hash(TxEnv),
TestOnlyOneCommiter =
fun(Pubkey) ->
lists:foreach(
fun(Char) ->
Entropy = hash(Char),
TopHash = aetx_env:key_hash(TxEnv),
Commitments = commitments(#{TopHash => [Pubkey]}),
Expected = [{tuple, {{address, Bob}, BobAmt}}, {tuple, {{address, Alice}, AliceAmt}}],
{ok, _, Expected} = sorted_validators_(Alice, TxEnv, Trees3),
{ok, Trees4, {tuple, {{address, Pubkey}, StakePower}}} = hc_elect_(Entropy, Commitments, ?OWNER_PUBKEY, TxEnv, Trees3),
ExpectedStakingPower =
case Pubkey of
Alice -> AliceAmt;
Bob -> BobAmt
end,
{StakePower, StakePower} = {StakePower, ExpectedStakingPower},
{ok, _, {address, Pubkey}} = leader_(?OWNER_PUBKEY, TxEnv, Trees4),
ok
end,
lists:seq(65, 122)) %% A to z
end,
TestOnlyOneCommiter(pubkey(?ALICE)),
TestOnlyOneCommiter(pubkey(?BOB)),
%% test two commiters
lists:foreach(
fun(Char) ->
Entropy = hash(Char),
TopHash = aetx_env:key_hash(TxEnv),
Commitments = commitments(#{TopHash => [Alice, Bob]}),
Expected = [{tuple, {{address, Bob}, BobAmt}}, {tuple, {{address, Alice}, AliceAmt}}],
{ok, _, Expected} = sorted_validators_(Alice, TxEnv, Trees3),
{ok, Trees4, {tuple, {{address, Pubkey}, StakePower}}} = hc_elect_(Entropy, Commitments, ?OWNER_PUBKEY, TxEnv, Trees3),
ExpectedStakingPower = AliceAmt + BobAmt,
{StakePower, StakePower} = {StakePower, ExpectedStakingPower},
{ok, _, {address, Pubkey}} = leader_(?OWNER_PUBKEY, TxEnv, Trees4),
ok
end,
lists:seq(65, 122)), %% A to z
ok.

set_up_accounts(Trees) ->
lists:foldl(fun set_up_account/2,
Trees,
Expand Down Expand Up @@ -2047,7 +2101,7 @@ test_elect_calls(StartHeight, GenerenationsCnt, TxEnv, StartTrees) ->
lists:foldl(
fun(Height, {TreesAccum1, Ls}) ->
TxEnvPrev = aetx_env:set_height(TxEnv, Height - 1),
{ok, _ , {address, ExpectedNextLeader}} = elect_next_(?OWNER_PUBKEY, TxEnvPrev, TreesAccum1),
{ok, _ , {tuple, {{address, ExpectedNextLeader}, _}}} = elect_next_(?OWNER_PUBKEY, TxEnvPrev, TreesAccum1),
TxEnv1 = aetx_env:set_height(TxEnv, Height),
{ok, TreesAccum2, {tuple, {}}} = elect_(?OWNER_PUBKEY, TxEnv1, TreesAccum1),
{ok, _, {address, NextLeader}} = leader_(?OWNER_PUBKEY, TxEnv1, TreesAccum2),
Expand Down Expand Up @@ -2129,6 +2183,11 @@ offline_validators_(Caller, TxEnv, Trees0) ->
{ok, CallData} = aeb_fate_abi:create_calldata("offline_validators", []),
call_contract(ContractPubkey, Caller, CallData, 0, TxEnv, Trees0).

sorted_validators_(Caller, TxEnv, Trees0) ->
ContractPubkey = staking_contract_address(),
{ok, CallData} = aeb_fate_abi:create_calldata("sorted_validators", []),
call_contract(ContractPubkey, Caller, CallData, 0, TxEnv, Trees0).

elect_next_(Caller, TxEnv, Trees0) ->
ContractPubkey = election_contract_address(),
{ok, CallData} = aeb_fate_abi:create_calldata("elect_next", []),
Expand Down Expand Up @@ -2295,3 +2354,8 @@ commitments(CommitmentsMap) ->
#{},
CommitmentsMap),
aeb_fate_data:make_map(Commitments).

hash(Str) when is_list(Str), length(Str) =:= 32 ->
list_to_binary(Str);
hash(C) when is_integer(C), C < 255 ->
list_to_binary(lists:duplicate(32, C)).
57 changes: 42 additions & 15 deletions apps/aecore/src/aec_consensus_hc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ recent_cache_n() -> 1.
recent_cache_trim_key_header(_) -> ok.

keyblocks_for_target_calc() -> 0.
keyblock_create_adjust_target(Block, []) -> {ok, Block}.
keyblock_create_adjust_target(Block0, []) ->
{ok, Stake} = aeu_ets_cache:lookup(?ETS_CACHE_TABLE, added_stake),
Block = aec_blocks:set_target(Block0, aeminer_pow:integer_to_scientific(Stake)),
{ok, Block}.

dirty_validate_block_pre_conductor(_) -> ok.
dirty_validate_header_pre_conductor(_) -> ok.
Expand Down Expand Up @@ -204,11 +207,16 @@ state_pre_transform_key_node(_Node, Trees) ->
]),
CallData = aeser_api_encoder:encode(contract_bytearray, CD),
case call_consensus_contract_(?ELECTION_CONTRACT, TxEnv, Trees, CallData, "elect", 0) of
{ok, Trees1, _} ->
{ok, Trees1, Call} ->
{tuple, {{address, Leader}, AddedStake}} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
aeu_ets_cache:reinit(
?ETS_CACHE_TABLE,
current_leader,
fun () -> beneficiary_(TxEnv, Trees1) end),
fun () -> Leader end ),
aeu_ets_cache:reinit(
?ETS_CACHE_TABLE,
added_stake,
fun () -> AddedStake end ),
Trees1;
{error, What} ->
%% maybe a softer approach than crash and burn?
Expand Down Expand Up @@ -284,8 +292,10 @@ validate_key_header_seal(Header, _Protocol) ->
Validators = [ fun seal_correct_padding/3
, fun seal_correct_signature/3
],
Res = aeu_validation:run(Validators, [Header, Signature, Padding]),
Res.
case aeu_validation:run(Validators, [Header, Signature, Padding]) of
{error, _} = Err -> Err;
ok -> ok
end.

seal_correct_padding(_Header, _Signature, Padding) ->
PaddingSize = seal_padding_size(),
Expand Down Expand Up @@ -320,7 +330,7 @@ generate_key_header_seal(_, Candidate, PCHeight, #{expected_key_block_rate := _E
CallData,
"elect_next",
0),
{address, Leader} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
{tuple, {{address, Leader}, _Stake}} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
SignModule = get_sign_module(),
case SignModule:set_candidate(Leader) of
{error, key_not_found} ->
Expand Down Expand Up @@ -360,10 +370,11 @@ set_key_block_seal(KeyBlock0, Seal) ->
CallData,
"elect_next",
0),
{address, Leader} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
{tuple, {{address, Leader}, Stake}} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
KeyBlock1 = aec_blocks:set_beneficiary(KeyBlock0, Leader),
KeyBlock2 = aec_blocks:set_miner(KeyBlock1, Leader),
aec_blocks:set_key_seal(KeyBlock2, Seal).
KeyBlock3 = aec_blocks:set_target(KeyBlock2, aeminer_pow:integer_to_scientific(Stake)),
aec_blocks:set_key_seal(KeyBlock3, Seal).

nonce_for_sealing(Header) ->
Height = aec_headers:height(Header),
Expand All @@ -377,13 +388,14 @@ trim_sealing_nonce(PCHeight, _) ->
PCHeight.

default_target() ->
?TAG.
-1. %% this is an impossible value will be rewritten later on

assert_key_target_range(?TAG) ->
assert_key_target_range(_) ->
ok.

key_header_difficulty(_) ->
?TAG.
key_header_difficulty(H) ->
Target = aec_headers:target(H),
aeminer_pow:scientific_to_integer(Target).

%% This is initial height; if neeeded shall be reinit at fork height
election_contract_pubkey() ->
Expand Down Expand Up @@ -515,7 +527,10 @@ call_consensus_contract_(ContractType, TxEnv, Trees, EncodedCallData, Keyword, A
CallId = aect_call_tx:call_id(CallTx),
Call = aect_call_state_tree:get_call(ContractPubkey, CallId,
Calls),
ok = aect_call:return_type(Call),
case aect_call:return_type(Call) of
ok -> pass;
error -> error({consensus_call_failed, aect_call:return_value(Call)})
end,
%% prune the call being produced. If not done, the fees for it
%% would be redistributed to the corresponding leaders
Height = aetx_env:height(TxEnv),
Expand Down Expand Up @@ -565,7 +580,7 @@ next_beneficiary() ->
TxEnv, Trees,
CallData,
"elect_next", 0),
{address, Leader} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
{tuple, {{address, Leader}, _Stake}} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
SignModule = get_sign_module(),
case SignModule:set_candidate(Leader) of
{error, key_not_found} ->
Expand Down Expand Up @@ -596,7 +611,19 @@ is_leader_valid(Node, Trees, TxEnv) ->
{ok, _Trees1, Call} ->
{address, ExpectedLeader} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
Leader = aec_headers:miner(Header),
ExpectedLeader =:= Leader;
Target = aec_headers:target(Header),
IsDefaultT = Target =:= default_target(),
case ExpectedLeader =:= Leader of
true when IsDefaultT -> true;
true ->
{ok, CD2} = aeb_fate_abi:create_calldata("added_stake", []),
CallData2 = aeser_api_encoder:encode(contract_bytearray, CD2),
{ok, _, Call2} = call_consensus_contract_(?ELECTION_CONTRACT, TxEnv, Trees, CallData2, "added_stake", 0),
AddedStake = aeb_fate_encoding:deserialize(aect_call:return_value(Call2)),
ExpectedTarget = aeminer_pow:integer_to_scientific(AddedStake),
ExpectedTarget =:= Target;
false -> false
end;
{error, What} ->
lager:info("Block validation failed with a reason ~p", [What]),
false
Expand Down
2 changes: 1 addition & 1 deletion apps/aecore/src/aec_consensus_smart_contract.erl
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ next_beneficiary() ->
CallData = aeser_api_encoder:encode(contract_bytearray, CD),
case call_consensus_contract_(?ELECTION_CONTRACT, TxEnv, Trees, CallData, "elect_next()", 0) of
{ok, _Trees1, Call} ->
{address, Leader} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
{tuple, {{address, Leader}, _}} = aeb_fate_encoding:deserialize(aect_call:return_value(Call)),
SignModule = get_sign_module(),
SignModule:set_candidate(Leader),
{ok, Leader};
Expand Down
11 changes: 10 additions & 1 deletion apps/aecore/test/aecore_suite_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@
internal_address/0,
external_address/0,
rosetta_address/0,
rosetta_offline_address/0
rosetta_offline_address/0,
block_peer/2,
unblock_peer/2
]).

-export([generate_key_pair/0]).
Expand Down Expand Up @@ -1920,6 +1922,13 @@ meta_tx(Owner, AuthOpts, AuthData, InnerTx0) ->
MetaTx = aega_test_utils:ga_meta_tx(Owner, Options1),
aetx_sign:new(MetaTx, []).

block_peer(Node, PeerNode) ->
{ok, PeerInfo} = aec_peers:parse_peer_address(peer_info(PeerNode)),
rpc(Node, aec_peers, block_peer, [PeerInfo]).

unblock_peer(Node, PeerNode) ->
rpc(Node, aec_peers, unblock_peer, [pubkey(PeerNode)]).

get_key_hash_by_delta(Node, Delta) ->
TopHeader = rpc(Node, aec_chain, top_header, []),
TopHeight = aec_headers:height(TopHeader),
Expand Down

0 comments on commit 584b47d

Please sign in to comment.