Skip to content
This repository has been archived by the owner on Mar 5, 2024. It is now read-only.

Commit

Permalink
Merge pull request #768 from helium/rg/payment-v2-memos
Browse files Browse the repository at this point in the history
Add support for payment-v2 memo field
  • Loading branch information
evanmcc authored Apr 21, 2021
2 parents 7e196de + aa3a0a6 commit 7943a9b
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 37 deletions.
2 changes: 2 additions & 0 deletions include/blockchain_vars.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@
-define(max_payments, max_payments).
%% Var to switch off legacy payment txn
-define(deprecate_payment_v1, deprecate_payment_v1).
%% Enable payment-v2 memos
-define(allow_payment_v2_memos, allow_payment_v2_memos).

%% Set this var to false to disable zero amount txns (payment_v1, payment_v2, htlc_create)
-define(allow_zero_amount, allow_zero_amount).
Expand Down
2 changes: 1 addition & 1 deletion rebar.lock
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
0},
{<<"helium_proto">>,
{git,"https://github.com/helium/proto.git",
{ref,"456591707ec9061776819ffba45de4940acae956"}},
{ref,"4a4f44e98b2973d71aa8a2ebb34196ded320acfb"}},
0},
{<<"inert">>,{pkg,<<"inert">>,<<"1.0.1">>},2},
{<<"inet_cidr">>,{pkg,<<"erl_cidr">>,<<"1.0.2">>},2},
Expand Down
7 changes: 7 additions & 0 deletions src/transactions/v1/blockchain_txn_vars_v1.erl
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,13 @@ validate_var(?deprecate_payment_v1, Value) ->
_ -> throw({error, {invalid_deprecate_payment_v1, Value}})
end;

validate_var(?allow_payment_v2_memos, Value) ->
case Value of
true -> ok;
false -> ok;
_ -> throw({error, {invalid_allow_payment_v2_memos, Value}})
end;

validate_var(?allow_zero_amount, Value) ->
case Value of
true -> ok;
Expand Down
42 changes: 37 additions & 5 deletions src/transactions/v2/blockchain_payment_v2.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
-include_lib("helium_proto/include/blockchain_txn_payment_v2_pb.hrl").

-export([
new/2,
new/2, new/3,
payee/1,
amount/1,
memo/1, memo/2,
is_valid_memo/1,
print/1,
to_json/2
]).
Expand All @@ -32,7 +34,18 @@
new(Payee, Amount) ->
#payment_pb{
payee=Payee,
amount=Amount
amount=Amount,
memo=0
}.

-spec new(Payee :: libp2p_crypto:pubkey_bin(),
Amount :: non_neg_integer(),
Memo :: non_neg_integer()) -> payment().
new(Payee, Amount, Memo) ->
#payment_pb{
payee=Payee,
amount=Amount,
memo=Memo
}.

-spec payee(Payment :: payment()) -> libp2p_crypto:pubkey_bin().
Expand All @@ -43,16 +56,35 @@ payee(Payment) ->
amount(Payment) ->
Payment#payment_pb.amount.

-spec memo(Payment :: payment()) -> undefined | non_neg_integer().
memo(Payment) ->
Payment#payment_pb.memo.

-spec memo(Payment :: payment(), Memo :: non_neg_integer()) -> payment().
memo(Payment, Memo) ->
Payment#payment_pb{memo=Memo}.

-spec is_valid_memo(Payment :: payment()) -> boolean().
is_valid_memo(#payment_pb{memo = Memo}) ->
try
Bin = binary:encode_unsigned(Memo, big),
bit_size(Bin) =< 64
catch _:_ ->
%% we can't do this, invalid
false
end.

print(undefined) ->
<<"type=payment undefined">>;
print(#payment_pb{payee=Payee, amount=Amount}) ->
io_lib:format("type=payment payee: ~p amount: ~p", [?TO_B58(Payee), Amount]).
print(#payment_pb{payee=Payee, amount=Amount, memo=Memo}) ->
io_lib:format("type=payment payee: ~p amount: ~p, memo: ~p", [?TO_B58(Payee), Amount, Memo]).

-spec to_json(payment(), blockchain_json:opts()) -> blockchain_json:json_object().
to_json(Payment, _Opts) ->
#{
payee => ?BIN_TO_B58(payee(Payment)),
amount => amount(Payment)
amount => amount(Payment),
memo => ?MAYBE_UNDEFINED(memo(Payment))
}.

%% ------------------------------------------------------------------
Expand Down
101 changes: 80 additions & 21 deletions src/transactions/v2/blockchain_txn_payment_v2.erl
Original file line number Diff line number Diff line change
Expand Up @@ -224,28 +224,16 @@ do_is_valid_checks(Txn, Chain, MaxPayments) ->
false ->
{error, duplicate_payees};
true ->
TotAmount = ?MODULE:total_amount(Txn),
TxnFee = ?MODULE:fee(Txn),
AmountCheck = case blockchain:config(?allow_zero_amount, Ledger) of
{ok, false} ->
%% check that none of the payments have a zero amount
has_non_zero_amounts(Payments);
_ ->
%% if undefined or true, use the old check
(TotAmount >= 0)
end,
case AmountCheck of
false ->
AmountCheck = amount_check(Txn, Ledger),
MemoCheck = memo_check(Txn, Ledger),

case {AmountCheck, MemoCheck} of
{false, _} ->
{error, invalid_transaction};
true ->
AreFeesEnabled = blockchain_ledger_v1:txn_fees_active(Ledger),
ExpectedTxnFee = ?MODULE:calculate_fee(Txn, Chain),
case ExpectedTxnFee =< TxnFee orelse not AreFeesEnabled of
false ->
{error, {wrong_txn_fee, {ExpectedTxnFee, TxnFee}}};
true ->
blockchain_ledger_v1:check_dc_or_hnt_balance(Payer, TxnFee, Ledger, AreFeesEnabled)
end
{_, {error, _}=E} ->
E;
{true, ok} ->
fee_check(Txn, Chain, Ledger)
end
end;
true ->
Expand All @@ -259,6 +247,50 @@ do_is_valid_checks(Txn, Chain, MaxPayments) ->
%% Internal functions
%% ------------------------------------------------------------------

-spec fee_check(Txn :: txn_payment_v2(), Chain :: blockchain:blockchain(), Ledger :: blockchain_ledger_v1:ledger()) -> ok | {error, any()}.
fee_check(Txn, Chain, Ledger) ->
AreFeesEnabled = blockchain_ledger_v1:txn_fees_active(Ledger),
ExpectedTxnFee = ?MODULE:calculate_fee(Txn, Chain),
Payer = ?MODULE:payer(Txn),
TxnFee = ?MODULE:fee(Txn),
case ExpectedTxnFee =< TxnFee orelse not AreFeesEnabled of
false ->
{error, {wrong_txn_fee, {ExpectedTxnFee, TxnFee}}};
true ->
blockchain_ledger_v1:check_dc_or_hnt_balance(Payer, TxnFee, Ledger, AreFeesEnabled)
end.

-spec memo_check(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> ok | {error, any()}.
memo_check(Txn, Ledger) ->
Payments = ?MODULE:payments(Txn),
case blockchain:config(?allow_payment_v2_memos, Ledger) of
{ok, true} ->
%% check that the memos are valid
case has_valid_memos(Payments) of
true -> ok;
false -> {error, invalid_memo}
end;
_ ->
%% old behavior before var, allow only if memo=0 (default)
case has_default_memos(Payments) of
true -> ok;
false -> {error, invalid_memo_before_var}
end
end.

-spec amount_check(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> boolean().
amount_check(Txn, Ledger) ->
TotAmount = ?MODULE:total_amount(Txn),
Payments = ?MODULE:payments(Txn),
case blockchain:config(?allow_zero_amount, Ledger) of
{ok, false} ->
%% check that none of the payments have a zero amount
has_non_zero_amounts(Payments);
_ ->
%% if undefined or true, use the old check
(TotAmount >= 0)
end.

-spec has_unique_payees(Payments :: blockchain_payment_v2:payments()) -> boolean().
has_unique_payees(Payments) ->
Payees = [blockchain_payment_v2:payee(P) || P <- Payments],
Expand All @@ -269,6 +301,33 @@ has_non_zero_amounts(Payments) ->
Amounts = [blockchain_payment_v2:amount(P) || P <- Payments],
lists:all(fun(A) -> A > 0 end, Amounts).

-spec has_valid_memos(Payments :: blockchain_payment_v2:payments()) -> boolean().
has_valid_memos(Payments) ->
lists:all(
fun(Payment) ->
%% check that the memo field is valid
FieldCheck = blockchain_txn:validate_fields([ {{memo, blockchain_payment_v2:memo(Payment)}, {is_integer, 0}} ]),
case FieldCheck of
ok ->
%% check that the memo field is within limits
blockchain_payment_v2:is_valid_memo(Payment);
_ ->
false
end
end,
Payments
).

-spec has_default_memos(Payments :: blockchain_payment_v2:payments()) -> boolean().
has_default_memos(Payments) ->
lists:all(
fun(Payment) ->
0 == blockchain_payment_v2:memo(Payment) orelse
undefined == blockchain_payment_v2:memo(Payment)
end,
Payments
).

%% ------------------------------------------------------------------
%% EUNIT Tests
%% ------------------------------------------------------------------
Expand Down
Loading

0 comments on commit 7943a9b

Please sign in to comment.