Make CheckTx conform to possible block timestamp

parent 5ed054ba
Pipeline #36115817 passed with stages
in 20 minutes and 49 seconds
......@@ -98,13 +98,15 @@
height=0 :: block_height() | 0,
last_epoch_end=(ercoin_timestamp:now() - ?BLOCK_LENGTH) :: ercoin_timestamp:timestamp(),
timestamp=ercoin_timestamp:now() :: ercoin_timestamp:timestamp(),
%% We provide the field below to allow deterministic time progress in tests.
now_fun=fun ercoin_timestamp:now/1 :: fun((data()) -> ercoin_timestamp:timestamp()),
%% Short deposit collects fees for current processing, long deposit collects fees for long term storage.
fee_deposit_short=0 :: fee(),
fee_deposit_long=0 :: fee(),
fresh_txs=?SETS:new() :: ?SETS:set(binary()),
fresh_txs_hash= <<0:256>> :: hash(),
accounts=gb_merkle_trees:empty() :: gb_merkle_trees:tree(),
entropy_fun={ercoin_entropy, simple_entropy} :: {Module :: atom(), Function :: atom()},
entropy_fun=fun ercoin_entropy:simple_entropy/1 :: fun ((data()) -> binary()),
%% After BeginBlock, these are validators for the next block.
validators=gb_merkle_trees:empty() :: gb_merkle_trees:tree(),
future_validators :: undefined | {promise, docile_rpc:key(), hash()} | gb_merkle_trees:tree(),
......
......@@ -52,8 +52,12 @@ init(_Args) ->
terminate(_Reason, _State, _Data) ->
ok.
handle_event({call, From}, #'abci.RequestCheckTx'{tx=MaybeTxBin}, _, Data) ->
{ErrorCode, NewMempoolData} = ercoin_tx:handle_bin(MaybeTxBin, Data#data.mempool_data),
handle_event({call, From}, #'abci.RequestCheckTx'{tx=MaybeTxBin}, _, Data=#data{mempool_data=MempoolData}) ->
{ErrorCode, NewMempoolData} =
ercoin_tx:handle_bin(
MaybeTxBin,
ercoin_data:shift_to_now(MempoolData),
ercoin_tx:default_age_margin(MempoolData) * 3 div 4),
{keep_state, Data#data{mempool_data=NewMempoolData}, {reply, From, #'abci.ResponseCheckTx'{code=ErrorCode}}};
handle_event({call, From}, #'abci.RequestCommit'{}, committing, Data) ->
Response = #'abci.ResponseCommit'{data=ercoin_data:app_hash(Data)},
......
......@@ -17,7 +17,9 @@
construct/1,
grant_fee_deposits/1,
money_supply/1,
now/1,
remove_old_and_unlock_accounts/1,
shift_to_now/1,
shift_to_timestamp/2]).
-compile({parse_transform, dynarec}).
......@@ -106,6 +108,14 @@ grant_fee_deposits(
shift_to_timestamp(Timestamp, Data) ->
remove_old_and_unlock_accounts(Data#data{timestamp=Timestamp}).
-spec now(data()) -> ercoin_timestamp:timestamp().
now(Data=#data{now_fun=F}) ->
apply(F, [Data]).
-spec shift_to_now(data()) -> data().
shift_to_now(Data) ->
shift_to_timestamp(?MODULE:now(Data), Data).
-spec remove_old_and_unlock_accounts(data()) -> data().
remove_old_and_unlock_accounts(Data=#data{accounts=AccountsTree, timestamp=Timestamp}) ->
{NewAccountsTree, _} = remove_old_and_unlock_accounts_from_tree(AccountsTree, Timestamp),
......
......@@ -290,7 +290,7 @@ initial_data_without_votes(Accounts, BurningEnd, Beginning) ->
last_epoch_end=Beginning - ?BLOCK_LENGTH,
timestamp=Beginning - ?BLOCK_LENGTH,
accounts=Accounts,
entropy_fun={ercoin_entropy, reliable_entropy}},
entropy_fun=fun ercoin_entropy:reliable_entropy/1},
Validators = ercoin_validators:draw(Data1#data{timestamp=BurningEnd + 60}),
Data1#data{validators=Validators}.
......
......@@ -19,6 +19,7 @@
[from_iso/1,
from_protobuf/1,
now/0,
now/1,
to_iso/1,
to_protobuf/1]).
......@@ -37,6 +38,10 @@ to_iso(Timestamp) ->
now() ->
'Elixir.DateTime':to_unix('Elixir.DateTime':utc_now(), second).
-spec now(any()) -> timestamp().
now(_) ->
?MODULE:now().
-spec from_iso(binary()) -> timestamp().
from_iso(ISO) ->
{ok, DateTime, 0} = 'Elixir.DateTime':'from_iso8601'(ISO),
......
......@@ -18,16 +18,22 @@
-export(
[apply/2,
default_age_margin/1,
deserialize/1,
error_code/2,
error_code/3,
from/1,
serialize/1,
unpack_binary/2,
unpack_binary/3,
handle_bin/2,
handle_bin/3,
required_balance/2,
valid_since/1,
value/1]).
-type age_margin() :: non_neg_integer().
-spec valid_since(tx()) -> ercoin_timestamp:timestamp() | none.
valid_since(#transfer_tx{valid_since=ValidSince}) ->
ValidSince;
......@@ -165,10 +171,19 @@ deserialize(TxBin) ->
end
end.
-spec default_age_margin(data()) -> age_margin().
default_age_margin(Data) ->
ercoin_data:get_value(epoch_length, Data).
-spec error_code(tx(), data()) -> error_code().
error_code(Tx, Data=#data{timestamp=Timestamp, epoch_length=EpochLength}) ->
error_code(Tx, Data) ->
error_code(Tx, Data, default_age_margin(Data)).
-spec error_code(tx(), data(), age_margin()) -> error_code().
error_code(Tx, Data=#data{timestamp=Timestamp}, AgeMargin) ->
ValidSince = ercoin_tx:valid_since(Tx),
case ValidSince =:= none orelse ValidSince =< Timestamp andalso ValidSince > Timestamp - EpochLength of
case ValidSince =:= none orelse ValidSince =< Timestamp andalso ValidSince >= Timestamp - AgeMargin of
true ->
case is_record(Tx, vote_tx) of
true ->
......@@ -286,11 +301,15 @@ in_fresh_txs(Tx, Data) ->
-spec unpack_binary(binary(), data()) -> {error_code(), tx() | none}.
unpack_binary(MaybeTxBin, Data) ->
unpack_binary(MaybeTxBin, Data, default_age_margin(Data)).
-spec unpack_binary(binary(), data(), age_margin()) -> {error_code(), tx() | none}.
unpack_binary(MaybeTxBin, Data, AgeMargin) ->
case deserialize(MaybeTxBin) of
none ->
{?BAD_REQUEST, none};
Tx ->
case error_code(Tx, Data) of
case error_code(Tx, Data, AgeMargin) of
?OK ->
{?OK, Tx};
ErrorCode ->
......@@ -300,7 +319,11 @@ unpack_binary(MaybeTxBin, Data) ->
-spec handle_bin(binary(), data()) -> {error_code(), data()}.
handle_bin(MaybeTxBin, Data) ->
case unpack_binary(MaybeTxBin, Data) of
handle_bin(MaybeTxBin, Data, default_age_margin(Data)).
-spec handle_bin(binary(), data(), age_margin()) -> {error_code(), data()}.
handle_bin(MaybeTxBin, Data, AgeMargin) ->
case unpack_binary(MaybeTxBin, Data, AgeMargin) of
{?OK, Tx} ->
{?OK, ercoin_tx:apply(Tx, Data)};
{Error, none} ->
......
......@@ -28,8 +28,8 @@ voting_resolution(_) ->
20.
-spec entropy(data()) -> binary().
entropy(Data=#data{entropy_fun={M, F}}) ->
apply(M, F, [Data]).
entropy(Data=#data{entropy_fun=F}) ->
apply(F, [Data]).
-spec draw(data()) -> gb_merkle_trees:tree().
draw(Data=#data{accounts=Accounts, timestamp=Timestamp, epoch_length=EpochLength, last_epoch_end=LastEpochEnd}) ->
......
......@@ -119,10 +119,8 @@ prop_end_block_truncates_fresh_txs() ->
prop_deliver_tx_handles_binary_in_regard_to_data() ->
?FORALL(
{MaybeTxBin, Data},
oneof(
[fun ercoin_tx_gen:data_with_tx_bin/0,
fun ercoin_tx_gen:data_with_invalid_tx_bin/0]),
{Data, MaybeTxBin},
ercoin_tx_gen:data_with_maybe_tx_bin(),
begin
{keep_state, NewData, {reply, "from", #'abci.ResponseDeliverTx'{code=ErrorCode}}} =
ercoin_abci:handle_event(
......@@ -136,14 +134,13 @@ prop_deliver_tx_handles_binary_in_regard_to_data() ->
%% TODO: Slash validators that propose invalid transactions.
%% TODO: Shift data to current timestamp when invoking CheckTx and tighten allowed timespan to not accept transactions that may be invalid when block is created.
prop_check_tx_handles_binary_in_regard_to_mempool_data() ->
%% A transaction may have a higher timestamp than the timestamp of last block created, so we want to check it against current timestamp, not the timestamp of last block.
%% On the other hand, when a block is created, it may have higher timestamp than the current timestamp, so a transaction that is valid now may become invalid in the block. Therefore we want to impose lower age margin in CheckTx.
prop_check_tx_handles_binary_in_regard_to_mempool_data_with_current_timestamp_and_lower_age_margin() ->
?FORALL(
{{MaybeTxBin, MempoolData},
{{MempoolData, MaybeTxBin},
StateName},
{oneof(
[fun ercoin_tx_gen:data_with_tx_bin/0,
fun ercoin_tx_gen:data_with_invalid_tx_bin/0]),
{ercoin_tx_gen:data_with_maybe_tx_bin(),
elements([gossiping, committing])},
begin
{keep_state, #data{mempool_data=NewMempoolData}, {reply, "from", #'abci.ResponseCheckTx'{code=ErrorCode}}} =
......@@ -153,7 +150,11 @@ prop_check_tx_handles_binary_in_regard_to_mempool_data() ->
tx=MaybeTxBin},
StateName,
#data{mempool_data=MempoolData}),
{ErrorCode, NewMempoolData} =:= ercoin_tx:handle_bin(MaybeTxBin, MempoolData)
{ErrorCode, NewMempoolData} =:=
ercoin_tx:handle_bin(
MaybeTxBin,
ercoin_data:shift_to_now(MempoolData),
ercoin_tx:default_age_margin(MempoolData) * 3 div 4)
end).
prop_tx_puts_fee_into_fee_deposits() ->
......
......@@ -173,6 +173,10 @@ future_validators(KeyPool, Stage, Timestamp) ->
epoch_stage() ->
oneof(ercoin_epoch:all_stages()).
-spec dummy_now(data()) -> ercoin_timestamp:timestamp().
dummy_now(Data=#data{timestamp=Timestamp, epoch_length=EpochLength}) ->
Timestamp + (binary:decode_unsigned(ercoin_data:app_hash(Data)) rem EpochLength).
data_sks() ->
%% We use noshrink because shrinking is slow.
noshrink(data_sks_1()).
......@@ -214,6 +218,7 @@ data_sks_1() ->
epoch_stage=EpochStage,
timestamp=Timestamp,
epoch_length=EpochLength,
now_fun=fun dummy_now/1,
fee_deposit_short=FeeDepositShort,
fee_deposit_long=FeeDepositLong,
fresh_txs_hash=FreshTxsHash,
......@@ -223,7 +228,7 @@ data_sks_1() ->
validators=Validators,
future_validators=FutureValidators,
accounts=accounts_tree_from_list(AccountsList),
entropy_fun={ercoin_entropy, simple_entropy}},
entropy_fun=fun ercoin_entropy:simple_entropy/1},
maps:from_list(KeyPool)})
end).
......
......@@ -20,6 +20,7 @@
[data_with_tx/0,
data_with_tx_bin/0,
data_with_invalid_tx_bin/0,
data_with_maybe_tx_bin/0,
data_sks_and_account_tx/0,
data_sks_and_burn_tx/0,
data_sks_and_transfer_tx/0,
......@@ -511,3 +512,8 @@ data_with_tx_bin() ->
{{Data, _}, Tx},
data_sks_and_tx(),
{Data, ercoin_tx:serialize(Tx)}).
data_with_maybe_tx_bin() ->
oneof(
[fun data_with_tx_bin/0,
fun data_with_invalid_tx_bin/0]).
......@@ -52,6 +52,9 @@ tx_serialization_test_() ->
[?_assertEqual(?SAMPLE_TRANSFER_TX_SERIALIZED, ercoin_tx:serialize(?SAMPLE_TRANSFER_TX)),
?_assertEqual(?SAMPLE_TRANSFER_TX, ercoin_tx:deserialize(?SAMPLE_TRANSFER_TX_SERIALIZED))].
age_margin() ->
non_neg_integer().
prop_tx_serialization() ->
?FORALL({_, Tx}, data_with_tx(), ercoin_tx:deserialize(ercoin_tx:serialize(Tx)) =:= Tx).
......@@ -76,9 +79,7 @@ prop_invalid_tx_bin_is_not_unpacked() ->
prop_handle_bin() ->
?FORALL(
{Data, MaybeTxBin},
oneof(
[fun ercoin_tx_gen:data_with_tx_bin/0,
fun ercoin_tx_gen:data_with_invalid_tx_bin/0]),
ercoin_tx_gen:data_with_maybe_tx_bin(),
case ercoin_tx:unpack_binary(MaybeTxBin, Data) of
{?OK, Tx} ->
{?OK, ercoin_tx:apply(Tx, Data)} =:=
......@@ -87,6 +88,20 @@ prop_handle_bin() ->
{Error, Data} =:= ercoin_tx:handle_bin(MaybeTxBin, Data)
end).
prop_lower_age_margin_does_not_decrease_risk_of_invalid_timestamp() ->
?FORALL(
{{Data, MaybeTxBin}, AgeMargin1, AgeMargin2, Fun},
{ercoin_tx_gen:data_with_maybe_tx_bin(),
age_margin(),
age_margin(),
elements(
[fun ercoin_tx:handle_bin/3,
fun ercoin_tx:unpack_binary/3])},
?IMPLIES(
AgeMargin1 > AgeMargin2,
element(1, apply(Fun, [MaybeTxBin, Data, AgeMargin1])) =/= ?INVALID_TIMESTAMP orelse
element(1, apply(Fun, [MaybeTxBin, Data, AgeMargin2])) =:= ?INVALID_TIMESTAMP)).
prop_vote_tx_changes_vote() ->
?FORALL(
{{Data, _}, Tx},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment