From 68c7267870885d9554e13b3007f26920f68044ef Mon Sep 17 00:00:00 2001 From: Evan Vigil-McClanahan Date: Fri, 18 Mar 2022 11:23:59 -0700 Subject: [PATCH 01/48] filter bad netids from sync targets --- src/blockchain_worker.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 6c69abf43b..ab2ab10266 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -891,7 +891,9 @@ start_sync(#state{blockchain = Chain, swarm_tid = SwarmTID} = State) -> get_random_peer(SwarmTID) -> Peerbook = libp2p_swarm:peerbook(SwarmTID), %% limit peers to random connections with public addresses + NetID = libp2p_swarm:network_id(SwarmTID), F = fun(Peer) -> + NetID == libp2p_peer:network_id(Peer) andalso case application:get_env(blockchain, testing, false) of false -> lists:any(fun libp2p_transport_tcp:is_public/1, From c129cec7f8ab2b87c231efaa552889385f0d027a Mon Sep 17 00:00:00 2001 From: mikev Date: Wed, 18 May 2022 10:43:43 -0700 Subject: [PATCH 02/48] add as923_1b region --- include/blockchain_vars.hrl | 2 +- src/region/blockchain_region_params_v1.erl | 1 + .../v1/blockchain_state_channel_offer_v1.erl | 1 + .../v1/blockchain_txn_vars_v1.erl | 2 +- test/blockchain_ct_utils.hrl | 6 +- test/blockchain_region_SUITE.erl | 26 ++++++++ test/blockchain_region_suite_helper.erl | 8 +++ test/blockchain_region_test.hrl | 60 +++++++++++++++++++ 8 files changed, 103 insertions(+), 3 deletions(-) diff --git a/include/blockchain_vars.hrl b/include/blockchain_vars.hrl index d80aa18057..3eaa61897d 100644 --- a/include/blockchain_vars.hrl +++ b/include/blockchain_vars.hrl @@ -567,7 +567,7 @@ %% regulatory_region related variables %% This is a comma separated string like so: -%% <<"region_as923_1,region_as923_2,region_as923_3,region_as923_4,region_au915,region_cn470,region_eu433,region_eu868,region_in865,region_kr920,region_ru864,region_us915">> +%% <<"region_as923_1,region_as923_1b,region_as923_2,region_as923_3,region_as923_4,region_au915,region_cn470,region_eu433,region_eu868,region_in865,region_kr920,region_ru864,region_us915">> -define(regulatory_regions, regulatory_regions). %% Each of the former regions is associated with a dynamic var of the same name which is is a serialized form of an h3_region set determined at h3_res: 7 diff --git a/src/region/blockchain_region_params_v1.erl b/src/region/blockchain_region_params_v1.erl index e99706a525..eb1c005372 100644 --- a/src/region/blockchain_region_params_v1.erl +++ b/src/region/blockchain_region_params_v1.erl @@ -58,6 +58,7 @@ for_region(RegionVar, Ledger) -> %% required for the transition period. Maybe we can remove it once a majority %% of the fleet has transitioned after activation of region variables on chain. region_param('AS923_1') -> region_as923_1_params; +region_param('AS923_1B') -> region_as923_1b_params; region_param('AS923_2') -> region_as923_2_params; region_param('AS923_3') -> region_as923_3_params; region_param('AS923_4') -> region_as923_4_params; diff --git a/src/state_channel/v1/blockchain_state_channel_offer_v1.erl b/src/state_channel/v1/blockchain_state_channel_offer_v1.erl index 5c81572768..071b7ff3a9 100644 --- a/src/state_channel/v1/blockchain_state_channel_offer_v1.erl +++ b/src/state_channel/v1/blockchain_state_channel_offer_v1.erl @@ -130,6 +130,7 @@ decode(BinaryOffer) -> %% convert poc11 region names to protobuff enum names maybe_fix_region('region_us915') -> 'US915'; maybe_fix_region('region_as923_1') -> 'AS923_1'; +maybe_fix_region('region_as923_1b') -> 'AS923_1B'; maybe_fix_region('region_as923_2') -> 'AS923_2'; maybe_fix_region('region_as923_3') -> 'AS923_3'; maybe_fix_region('region_as923_4') -> 'AS923_4'; diff --git a/src/transactions/v1/blockchain_txn_vars_v1.erl b/src/transactions/v1/blockchain_txn_vars_v1.erl index e00c0a8a6c..4856cafa04 100644 --- a/src/transactions/v1/blockchain_txn_vars_v1.erl +++ b/src/transactions/v1/blockchain_txn_vars_v1.erl @@ -1490,7 +1490,7 @@ validate_var(?net_emissions_max_rate, Value) -> validate_var(?regulatory_regions, Value) when is_binary(Value) -> %% The regulatory_regions value we support must look like this: - %% <<"region_as923_1,region_as923_2,region_as923_3,region_as923_4,region_au915,region_cn470,region_eu433,region_eu868,region_in865,region_kr920,region_ru864,region_us915">> + %% <<"region_as923_1,region_as923_1b,region_as923_2,region_as923_3,region_as923_4,region_au915,region_cn470,region_eu433,region_eu868,region_in865,region_kr920,region_ru864,region_us915">> %% The order does not matter in validation %% We only check that the binary string is comma separated diff --git a/test/blockchain_ct_utils.hrl b/test/blockchain_ct_utils.hrl index 4c9f8fb6e6..86ffb1f3cf 100644 --- a/test/blockchain_ct_utils.hrl +++ b/test/blockchain_ct_utils.hrl @@ -9,6 +9,10 @@ "https://github.com/helium/lorawan-h3/blob/main/serialized/AS923-1.res7.h3idx?raw=true" ). +-define(region_as923_1b_url, + "https://github.com/helium/lorawan-h3/blob/main/serialized/AS923-1B.res7.h3idx?raw=true" +). + -define(region_as923_2_url, "https://github.com/helium/lorawan-h3/blob/main/serialized/AS923-2.res7.h3idx?raw=true" ). @@ -54,5 +58,5 @@ ). -define(regulatory_region_bin_str, - <<"region_as923_1,region_as923_2,region_as923_3,region_as923_4,region_au915,region_cn470,region_eu433,region_eu868,region_in865,region_kr920,region_ru864,region_us915">> + <<"region_as923_1,region_as923_1b,region_as923_2,region_as923_3,region_as923_4,region_au915,region_cn470,region_eu433,region_eu868,region_in865,region_kr920,region_ru864,region_us915">> ). diff --git a/test/blockchain_region_SUITE.erl b/test/blockchain_region_SUITE.erl index 148f4b4e54..9c51e6b93d 100644 --- a/test/blockchain_region_SUITE.erl +++ b/test/blockchain_region_SUITE.erl @@ -26,6 +26,7 @@ -export([ all_regions_test/1, as923_1_test/1, + as923_1b_test/1, as923_2_test/1, as923_3_test/1, eu433_test/1, @@ -42,6 +43,7 @@ eu868_region_param_test/1, au915_region_param_test/1, as923_1_region_param_test/1, + as923_1b_region_param_test/1, as923_2_region_param_test/1, as923_3_region_param_test/1, as923_4_region_param_test/1, @@ -81,6 +83,7 @@ with_all_data_test_cases() -> with_h3_data_test_cases() -> [ as923_1_test, + as923_1b_test, as923_2_test, as923_3_test, eu433_test, @@ -110,6 +113,7 @@ without_h3_data_test_cases() -> eu868_region_param_test, au915_region_param_test, as923_1_region_param_test, + as923_1b_region_param_test, as923_2_region_param_test, as923_3_region_param_test, as923_4_region_param_test, @@ -213,6 +217,14 @@ as923_1_test(Config) -> false = blockchain_region_v1:h3_in_region(H3, region_us915, Ledger), ok. +as923_1b_test(Config) -> + Ledger = ?config(ledger, Config), + H3 = 631319855840474623, + {ok, region_as923_1b} = blockchain_region_v1:h3_to_region(H3, Ledger), + true = blockchain_region_v1:h3_in_region(H3, region_as923_1b, Ledger), + false = blockchain_region_v1:h3_in_region(H3, region_us915, Ledger), + ok. + as923_2_test(Config) -> Ledger = ?config(ledger, Config), %% Jakarta, Indonesia @@ -454,6 +466,18 @@ as923_1_region_param_test(Config) -> ct:fail("boom") end. +as923_1b_region_param_test(Config) -> + Ledger = ?config(ledger, Config), + case blockchain:config(region_as923_1b_params, Ledger) of + {ok, Bin} -> + ParamsFromBin = blockchain_region_params_v1:region_params(blockchain_region_params_v1:deserialize(Bin)), + 8 = length(ParamsFromBin), + true = length(ParamsFromBin) == length(lists:usort(ParamsFromBin)), + ok; + _ -> + ct:fail("boom") + end. + as923_2_region_param_test(Config) -> Ledger = ?config(ledger, Config), case blockchain:config(region_as923_2_params, Ledger) of @@ -605,6 +629,7 @@ region_param_vars() -> region_eu868_params => blockchain_region_suite_helper:serialized_eu868(), region_au915_params => blockchain_region_suite_helper:serialized_au915(), region_as923_1_params => blockchain_region_suite_helper:serialized_as923_1(), + region_as923_1b_params => blockchain_region_suite_helper:serialized_as923_1b(), region_as923_2_params => blockchain_region_suite_helper:serialized_as923_2(), region_as923_3_params => blockchain_region_suite_helper:serialized_as923_3(), region_as923_4_params => blockchain_region_suite_helper:serialized_as923_4(), @@ -618,6 +643,7 @@ region_param_vars() -> region_urls() -> [ {region_as923_1, ?region_as923_1_url}, + {region_as923_1b, ?region_as923_1b_url}, {region_as923_2, ?region_as923_2_url}, {region_as923_3, ?region_as923_3_url}, {region_as923_4, ?region_as923_4_url}, diff --git a/test/blockchain_region_suite_helper.erl b/test/blockchain_region_suite_helper.erl index 9b4b5e2ba8..c089d5de4a 100644 --- a/test/blockchain_region_suite_helper.erl +++ b/test/blockchain_region_suite_helper.erl @@ -6,6 +6,7 @@ serialized_eu868/0, serialized_au915/0, serialized_as923_1/0, + serialized_as923_1b/0, serialized_as923_2/0, serialized_as923_3/0, serialized_as923_4/0, @@ -33,6 +34,10 @@ serialized_au915() -> serialized_as923_1() -> blockchain_region_params_v1:serialize(fetch(as923_1)). +-spec serialized_as923_1b() -> binary(). +serialized_as923_1b() -> + blockchain_region_params_v1:serialize(fetch(as923_1b)). + -spec serialized_as923_2() -> binary(). serialized_as923_2() -> blockchain_region_params_v1:serialize(fetch(as923_2)). @@ -78,6 +83,9 @@ fetch(au915) -> fetch(as923_1) -> Params = make_params(?REGION_PARAMS_AS923_1), blockchain_region_params_v1:new(Params); +fetch(as923_1b) -> + Params = make_params(?REGION_PARAMS_AS923_1B), + blockchain_region_params_v1:new(Params); fetch(as923_2) -> Params = make_params(?REGION_PARAMS_AS923_2), blockchain_region_params_v1:new(Params); diff --git a/test/blockchain_region_test.hrl b/test/blockchain_region_test.hrl index be609418bf..5b40348626 100644 --- a/test/blockchain_region_test.hrl +++ b/test/blockchain_region_test.hrl @@ -1,5 +1,6 @@ -define(SUPPORTED_REGIONS, [ "region_as923_1", + "region_as923_1b", "region_as923_2", "region_as923_3", "region_as923_4", @@ -339,6 +340,65 @@ ] ]). +-define(REGION_PARAMS_AS923_1B, [ + [ + {<<"channel_frequency">>, 922000000}, + {<<"bandwidth">>, 125000}, + {<<"max_eirp">>, 160}, + {<<"spreading">>, + [{25, 'SF12'}, {25, 'SF11'}, {25, 'SF10'}, {67, 'SF9'}, {139, 'SF8'}, {256, 'SF7'}]} + ], + [ + {<<"channel_frequency">>, 922200000}, + {<<"bandwidth">>, 125000}, + {<<"max_eirp">>, 160}, + {<<"spreading">>, + [{25, 'SF12'}, {25, 'SF11'}, {25, 'SF10'}, {67, 'SF9'}, {139, 'SF8'}, {256, 'SF7'}]} + ], + [ + {<<"channel_frequency">>, 922400000}, + {<<"bandwidth">>, 125000}, + {<<"max_eirp">>, 160}, + {<<"spreading">>, + [{25, 'SF12'}, {25, 'SF11'}, {25, 'SF10'}, {67, 'SF9'}, {139, 'SF8'}, {256, 'SF7'}]} + ], + [ + {<<"channel_frequency">>, 922600000}, + {<<"bandwidth">>, 125000}, + {<<"max_eirp">>, 160}, + {<<"spreading">>, + [{25, 'SF12'}, {25, 'SF11'}, {25, 'SF10'}, {67, 'SF9'}, {139, 'SF8'}, {256, 'SF7'}]} + ], + [ + {<<"channel_frequency">>, 922800000}, + {<<"bandwidth">>, 125000}, + {<<"max_eirp">>, 160}, + {<<"spreading">>, + [{25, 'SF12'}, {25, 'SF11'}, {25, 'SF10'}, {67, 'SF9'}, {139, 'SF8'}, {256, 'SF7'}]} + ], + [ + {<<"channel_frequency">>, 923000000}, + {<<"bandwidth">>, 125000}, + {<<"max_eirp">>, 160}, + {<<"spreading">>, + [{25, 'SF12'}, {25, 'SF11'}, {25, 'SF10'}, {67, 'SF9'}, {139, 'SF8'}, {256, 'SF7'}]} + ], + [ + {<<"channel_frequency">>, 923200000}, + {<<"bandwidth">>, 125000}, + {<<"max_eirp">>, 160}, + {<<"spreading">>, + [{25, 'SF12'}, {25, 'SF11'}, {25, 'SF10'}, {67, 'SF9'}, {139, 'SF8'}, {256, 'SF7'}]} + ], + [ + {<<"channel_frequency">>, 923400000}, + {<<"bandwidth">>, 125000}, + {<<"max_eirp">>, 160}, + {<<"spreading">>, + [{25, 'SF12'}, {25, 'SF11'}, {25, 'SF10'}, {67, 'SF9'}, {139, 'SF8'}, {256, 'SF7'}]} + ] +]). + -define(REGION_PARAMS_AS923_2, [ [ {<<"channel_frequency">>, 921400000}, From 7e075dfd24ae3f16d2994193935968f656e44a81 Mon Sep 17 00:00:00 2001 From: mikev Date: Wed, 18 May 2022 12:21:06 -0700 Subject: [PATCH 03/48] comment FAIL tests --- src/blockchain.erl | 2 +- src/transactions/blockchain_txn.erl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/blockchain.erl b/src/blockchain.erl index 9883fd54d3..111d2a62f2 100644 --- a/src/blockchain.erl +++ b/src/blockchain.erl @@ -3290,7 +3290,7 @@ get_block_test_() -> end }. -block_info_upgrade_test() -> +block_info_upgrade_test_fixme() -> %% boilerplate to get a chain #{secret := Priv, public := Pub} = libp2p_crypto:generate_keys(ecc_compact), BinPub = libp2p_crypto:pubkey_to_bin(Pub), diff --git a/src/transactions/blockchain_txn.erl b/src/transactions/blockchain_txn.erl index b5aebf51a3..7a0eceda1c 100644 --- a/src/transactions/blockchain_txn.erl +++ b/src/transactions/blockchain_txn.erl @@ -1374,7 +1374,7 @@ txn_fees_oui_test() -> ?assertEqual(1, Txn04LegacyStakingFee), ok. -txn_fees_routing_update_router_test() -> +txn_fees_routing_update_router_test_fixme() -> [{Owner, OwnerSigFun}] = gen_payers(1), %% create new txn, and confirm expected fee size @@ -1398,7 +1398,7 @@ txn_fees_routing_update_router_test() -> ?assertEqual(0, Txn03LegacyStakingFee), ok. -txn_fees_routing_new_xor_test() -> +txn_fees_routing_new_xor_test_fixme() -> [{Owner, OwnerSigFun}] = gen_payers(1), {Filter, _} = xor16:to_bin(xor16:new([0], fun xxhash:hash64/1)), @@ -1423,7 +1423,7 @@ txn_fees_routing_new_xor_test() -> ?assertEqual(0, Txn03LegacyStakingFee), ok. -txn_fees_routing_update_xor_test() -> +txn_fees_routing_update_xor_test_fixme() -> [{Owner, OwnerSigFun}] = gen_payers(1), {Filter, _} = xor16:to_bin(xor16:new([0], fun xxhash:hash64/1)), @@ -1448,7 +1448,7 @@ txn_fees_routing_update_xor_test() -> ?assertEqual(0, Txn03LegacyStakingFee), ok. -txn_fees_routing_request_subnet_test() -> +txn_fees_routing_request_subnet_test_fixme() -> [{Owner, OwnerSigFun}] = gen_payers(1), %% create new txn, and confirm expected fee size From 0dddfe66b2f590f26e12281c0ff20ddf26adf8a5 Mon Sep 17 00:00:00 2001 From: mikev Date: Wed, 18 May 2022 12:46:52 -0700 Subject: [PATCH 04/48] comment FAIL tests --- src/blockchain.erl | 4 +++- src/transactions/blockchain_txn.erl | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/blockchain.erl b/src/blockchain.erl index 111d2a62f2..1a92cfe2ad 100644 --- a/src/blockchain.erl +++ b/src/blockchain.erl @@ -3290,7 +3290,8 @@ get_block_test_() -> end }. -block_info_upgrade_test_fixme() -> +-ifdef(FIXME). +block_info_upgrade_test() -> %% boilerplate to get a chain #{secret := Priv, public := Pub} = libp2p_crypto:generate_keys(ecc_compact), BinPub = libp2p_crypto:pubkey_to_bin(Pub), @@ -3328,5 +3329,6 @@ block_info_upgrade_test_fixme() -> penalties = {<<>>, []}}, V2BlockInfo = upgrade_block_info(V1BlockInfo, Block, Chain), ?assertMatch(ExpV2BlockInfo, V2BlockInfo). +-endif. -endif. diff --git a/src/transactions/blockchain_txn.erl b/src/transactions/blockchain_txn.erl index 7a0eceda1c..516d39f003 100644 --- a/src/transactions/blockchain_txn.erl +++ b/src/transactions/blockchain_txn.erl @@ -1374,7 +1374,8 @@ txn_fees_oui_test() -> ?assertEqual(1, Txn04LegacyStakingFee), ok. -txn_fees_routing_update_router_test_fixme() -> +-ifdef(FIXME). +txn_fees_routing_update_router_test() -> [{Owner, OwnerSigFun}] = gen_payers(1), %% create new txn, and confirm expected fee size @@ -1398,7 +1399,7 @@ txn_fees_routing_update_router_test_fixme() -> ?assertEqual(0, Txn03LegacyStakingFee), ok. -txn_fees_routing_new_xor_test_fixme() -> +txn_fees_routing_new_xor_test() -> [{Owner, OwnerSigFun}] = gen_payers(1), {Filter, _} = xor16:to_bin(xor16:new([0], fun xxhash:hash64/1)), @@ -1423,7 +1424,7 @@ txn_fees_routing_new_xor_test_fixme() -> ?assertEqual(0, Txn03LegacyStakingFee), ok. -txn_fees_routing_update_xor_test_fixme() -> +txn_fees_routing_update_xor_test() -> [{Owner, OwnerSigFun}] = gen_payers(1), {Filter, _} = xor16:to_bin(xor16:new([0], fun xxhash:hash64/1)), @@ -1448,7 +1449,7 @@ txn_fees_routing_update_xor_test_fixme() -> ?assertEqual(0, Txn03LegacyStakingFee), ok. -txn_fees_routing_request_subnet_test_fixme() -> +txn_fees_routing_request_subnet_test() -> [{Owner, OwnerSigFun}] = gen_payers(1), %% create new txn, and confirm expected fee size @@ -1471,6 +1472,7 @@ txn_fees_routing_request_subnet_test_fixme() -> ?assertEqual(160000000, Txn03StakingFee), ?assertEqual(0, Txn03LegacyStakingFee), ok. +-endif. txn_fees_security_exchange_v1_test() -> [{Payer, PayerSigFun}] = gen_payers(1), From 0e0423d30f89f7c8831d930af131e538d2d956d0 Mon Sep 17 00:00:00 2001 From: mikev Date: Thu, 19 May 2022 15:28:25 -0700 Subject: [PATCH 05/48] Update hex value for as923_1b and add H3 comment --- test/blockchain_region_SUITE.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/blockchain_region_SUITE.erl b/test/blockchain_region_SUITE.erl index 9c51e6b93d..6301cb1f51 100644 --- a/test/blockchain_region_SUITE.erl +++ b/test/blockchain_region_SUITE.erl @@ -195,6 +195,12 @@ init_per_testcase(TestCase, Config) -> %%-------------------------------------------------------------------- %% test cases %%-------------------------------------------------------------------- + + +%% H3 located from https://github.com/helium/lorawan-h3 + +%% H3 created from HPlans at https://github.com/dewi-alliance/hplans + region_param_test(Config) -> Ledger = ?config(ledger, Config), H3 = 631183727389488639, @@ -219,7 +225,7 @@ as923_1_test(Config) -> as923_1b_test(Config) -> Ledger = ?config(ledger, Config), - H3 = 631319855840474623, + H3 = 609766440970485759, {ok, region_as923_1b} = blockchain_region_v1:h3_to_region(H3, Ledger), true = blockchain_region_v1:h3_in_region(H3, region_as923_1b, Ledger), false = blockchain_region_v1:h3_in_region(H3, region_us915, Ledger), From d037059d228acf8549f473e9c8651537d198fe8a Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Fri, 27 May 2022 15:01:22 -0400 Subject: [PATCH 06/48] Fix ledger context leaks in poc_receipts_v1 and rewards_v2 --- .../v1/blockchain_txn_poc_receipts_v1.erl | 65 ++++++++++--------- .../v2/blockchain_txn_rewards_v2.erl | 41 ++++++++---- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/src/transactions/v1/blockchain_txn_poc_receipts_v1.erl b/src/transactions/v1/blockchain_txn_poc_receipts_v1.erl index e3a004da7d..ad61784213 100644 --- a/src/transactions/v1/blockchain_txn_poc_receipts_v1.erl +++ b/src/transactions/v1/blockchain_txn_poc_receipts_v1.erl @@ -365,37 +365,40 @@ check_is_valid_poc(Txn, Chain) -> [_|LayerHashes] = [crypto:hash(sha256, L) || L <- Layers], StartV = maybe_log_duration(packet_construction, StartP), - case blockchain:config(?poc_version, OldLedger) of - {ok, POCVer} when POCVer >= 9 -> - %% errors get checked lower - Channels = get_channels_(OldLedger, Path, LayerData, POCVer, no_prefetch), - %% We are on poc v9 - %% %% run validations - Ret = case POCVer >= 10 of - true -> - %% check the block hash in the receipt txn is correct - case PoCAbsorbedAtBlockHash == ?MODULE:request_block_hash(Txn) of - true -> - validate(Txn, Path, LayerData, LayerHashes, OldLedger); - false -> - blockchain_ledger_v1:delete_context(OldLedger), - {error, bad_poc_request_block_hash} - end; - false -> - validate(Txn, Path, LayerData, LayerHashes, OldLedger) - end, - maybe_log_duration(receipt_validation, StartV), - case Ret of - ok -> - {ok, Channels}; - {error, _}=E -> E - end; - _ -> - %% We are not on poc v9, just do old behavior - Ret = validate(Txn, Path, LayerData, LayerHashes, OldLedger), - maybe_log_duration(receipt_validation, StartV), - Ret - end + Result = + case blockchain:config(?poc_version, OldLedger) of + {ok, POCVer} when POCVer >= 9 -> + %% errors get checked lower + Channels = get_channels_(OldLedger, Path, LayerData, POCVer, no_prefetch), + %% We are on poc v9 + %% %% run validations + Ret = case POCVer >= 10 of + true -> + %% check the block hash in the receipt txn is correct + case PoCAbsorbedAtBlockHash == ?MODULE:request_block_hash(Txn) of + true -> + validate(Txn, Path, LayerData, LayerHashes, OldLedger); + false -> + blockchain_ledger_v1:delete_context(OldLedger), + {error, bad_poc_request_block_hash} + end; + false -> + validate(Txn, Path, LayerData, LayerHashes, OldLedger) + end, + maybe_log_duration(receipt_validation, StartV), + case Ret of + ok -> + {ok, Channels}; + {error, _}=E -> E + end; + _ -> + %% We are not on poc v9, just do old behavior + Ret = validate(Txn, Path, LayerData, LayerHashes, OldLedger), + maybe_log_duration(receipt_validation, StartV), + Ret + end, + _ = blockchain_ledger_v1:delete_context(OldLedger), + Result end end end diff --git a/src/transactions/v2/blockchain_txn_rewards_v2.erl b/src/transactions/v2/blockchain_txn_rewards_v2.erl index 6782d2b573..8841d13e50 100644 --- a/src/transactions/v2/blockchain_txn_rewards_v2.erl +++ b/src/transactions/v2/blockchain_txn_rewards_v2.erl @@ -237,7 +237,9 @@ aux_absorb(Txn, AuxLedger, Chain) -> %% ordering will depend on (binary) account information. calculate_rewards(Start, End, Chain) -> {ok, Ledger} = blockchain:ledger_at(End, Chain), - calculate_rewards_(Start, End, Ledger, Chain, false). + Result = calculate_rewards_(Start, End, Ledger, Chain, false), + _ = blockchain_ledger_v1:delete_context(Ledger), + Result. -spec calculate_rewards_( Start :: non_neg_integer(), @@ -317,6 +319,20 @@ calculate_rewards_metadata(Start, End, Chain) -> poc_challengee => #{}, poc_witness => #{} }, + Result = calculate_rewards_metadata_(Start, End, Chain, Ledger, Vars, AccInit), + _ = blockchain_ledger_v1:delete_context(Ledger), + Result. + +-spec calculate_rewards_metadata_( + non_neg_integer(), + non_neg_integer(), + blockchain:blockchain(), + blockchain_ledger_v1:ledger(), + reward_vars(), + rewards_share_metadata() +) -> + {ok, rewards_metadata()} | {error, term()}. +calculate_rewards_metadata_(Start, End, Chain, Ledger, Vars, AccInit) -> try %% We only want to fold over the blocks and transaction in an epoch once, %% so we will do that top level work here. If we get a thrown error while @@ -439,16 +455,19 @@ to_json(Txn, Opts) -> {rewards_metadata, M} -> {ok, M}; _ -> ?MODULE:calculate_rewards_metadata(Start, End, Chain) end, - maps:fold( - fun(overages, Amount, Acc) -> - [#{amount => Amount, - type => overages} | Acc]; - (_RewardCategory, Rewards, Acc0) -> - maps:fold( - fun(Entry, Amount, Acc) -> - RewardToJson(Entry, Amount, Ledger, Acc) - end, Acc0, Rewards) - end, [], Metadata); + Rewards0 = + maps:fold( + fun(overages, Amount, Acc) -> + [#{amount => Amount, + type => overages} | Acc]; + (_RewardCategory, Rewards, Acc0) -> + maps:fold( + fun(Entry, Amount, Acc) -> + RewardToJson(Entry, Amount, Ledger, Acc) + end, Acc0, Rewards) + end, [], Metadata), + _ = blockchain_ledger_v1:delete_context(Ledger), + Rewards0; _ -> [ reward_to_json(R, []) || R <- rewards(Txn)] end, From dbdedb8395a1b9480c50b955642275d27ae2ecb7 Mon Sep 17 00:00:00 2001 From: jeffgrunewald Date: Thu, 2 Jun 2022 14:05:03 -0400 Subject: [PATCH 07/48] all eunit tests running green --- eqc/stake_txns_eqc.erl | 1 + src/blockchain.app.src | 4 +- src/blockchain.erl | 2 +- src/blockchain_sup.erl | 1 + src/blockchain_utils.erl | 17 +- src/ledger/v1/blockchain_ledger_v1.erl | 231 ++++++++++++------------ src/transactions/blockchain_txn.erl | 204 ++++++++++++--------- test/blockchain_ct_utils.erl | 1 + test/blockchain_hex_SUITE.erl | 1 + test/blockchain_reward_hip17_SUITE.erl | 1 + test/blockchain_reward_perf_SUITE.erl | 1 + test/blockchain_reward_v2_SUITE.erl | 1 + test/blockchain_snapshot_SUITE.erl | 1 + test/blockchain_state_channel_SUITE.erl | 1 + 14 files changed, 254 insertions(+), 213 deletions(-) diff --git a/eqc/stake_txns_eqc.erl b/eqc/stake_txns_eqc.erl index e0fb18dfca..9f719ab1da 100644 --- a/eqc/stake_txns_eqc.erl +++ b/eqc/stake_txns_eqc.erl @@ -79,6 +79,7 @@ initial_state() -> init_chain_env() -> %% these should be idempotent _ = application:ensure_all_started(lager), + _ = application:ensure_all_started(telemetry), _ = blockchain_lock:start_link(), application:set_env(blockchain, test_mode, true), diff --git a/src/blockchain.app.src b/src/blockchain.app.src index d4f90a8561..01997b6737 100644 --- a/src/blockchain.app.src +++ b/src/blockchain.app.src @@ -19,6 +19,7 @@ h3, erl_angry_purple_tiger, erlang_stats, + telemetry, e2qc, vincenty, helium_proto, @@ -29,8 +30,7 @@ jsx, erbloom, grpc_client, - hackney, - telemetry + hackney ]}, {env,[]}, {modules, []}, diff --git a/src/blockchain.erl b/src/blockchain.erl index 1126bd1b87..e02654b405 100644 --- a/src/blockchain.erl +++ b/src/blockchain.erl @@ -3324,7 +3324,7 @@ block_info_upgrade_test() -> ExpV2BlockInfo = #block_info_v2{height = 1, time = 1, hash = <<"blockhash">>, - pocs = [], + pocs = #{}, hbbft_round = 1, election_info = {1, 0}, penalties = {<<>>, []}}, diff --git a/src/blockchain_sup.erl b/src/blockchain_sup.erl index 186b25af92..64b3c0a04e 100644 --- a/src/blockchain_sup.erl +++ b/src/blockchain_sup.erl @@ -61,6 +61,7 @@ start_link(Args) -> init(Args) -> application:ensure_all_started(ranch), application:ensure_all_started(lager), + application:ensure_all_started(telemetry), application:ensure_all_started(clique), application:ensure_all_started(throttle), %% start http client and ssl here diff --git a/src/blockchain_utils.erl b/src/blockchain_utils.erl index f4a9f88dc4..c0d63fd9ba 100644 --- a/src/blockchain_utils.erl +++ b/src/blockchain_utils.erl @@ -750,13 +750,16 @@ pmap_test() -> ?assertEqual(Input, Results). get_pubkeybin_sigfun_test() -> - BaseDir = test_utils:tmp_dir("get_pubkeybin_sigfun_test"), - {ok, Swarm} = start_swarm(get_pubkeybin_sigfun_test, BaseDir), - {ok, PubKey, PayerSigFun, _} = libp2p_swarm:keys(Swarm), - PubKeyBin = libp2p_crypto:pubkey_to_bin(PubKey), - ?assertEqual({PubKeyBin, PayerSigFun}, get_pubkeybin_sigfun(Swarm)), - libp2p_swarm:stop(Swarm), - ok. + {timeout, 30000, + fun() -> + BaseDir = test_utils:tmp_dir("get_pubkeybin_sigfun_test"), + {ok, Swarm} = start_swarm(get_pubkeybin_sigfun_test, BaseDir), + {ok, PubKey, PayerSigFun, _} = libp2p_swarm:keys(Swarm), + PubKeyBin = libp2p_crypto:pubkey_to_bin(PubKey), + ?assertEqual({PubKeyBin, PayerSigFun}, get_pubkeybin_sigfun(Swarm)), + libp2p_swarm:stop(Swarm), + ok + end}. start_swarm(Name, BaseDir) -> SwarmOpts = [ diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index 56031267ce..1a754c4a44 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -5930,21 +5930,24 @@ active_gateways_test() -> test_utils:cleanup_tmp_dir(BaseDir). add_gateway_test() -> - BaseDir = test_utils:tmp_dir("add_gateway_test"), - Ledger = new(BaseDir), - Ledger1 = new_context(Ledger), - meck:new(blockchain, [passthrough]), - meck:expect(blockchain, config, fun(?election_version, _) -> {ok, 4} end), - - ok = add_gateway(<<"owner_address">>, <<"gw_address">>, Ledger1), - ok = commit_context(Ledger1), - ?assertMatch( - {ok, _}, - find_gateway_info(<<"gw_address">>, Ledger) - ), - ?assertEqual({error, gateway_already_active}, add_gateway(<<"owner_address">>, <<"gw_address">>, Ledger)), - meck:unload(blockchain), - test_utils:cleanup_tmp_dir(BaseDir). + {timeout, 30000, + fun() -> + BaseDir = test_utils:tmp_dir("add_gateway_test"), + Ledger = new(BaseDir), + Ledger1 = new_context(Ledger), + meck:new(blockchain, [passthrough]), + meck:expect(blockchain, config, fun(?election_version, _) -> {ok, 4} end), + + ok = add_gateway(<<"owner_address">>, <<"gw_address">>, Ledger1), + ok = commit_context(Ledger1), + ?assertMatch( + {ok, _}, + find_gateway_info(<<"gw_address">>, Ledger) + ), + ?assertEqual({error, gateway_already_active}, add_gateway(<<"owner_address">>, <<"gw_address">>, Ledger)), + meck:unload(blockchain), + test_utils:cleanup_tmp_dir(BaseDir) + end}. add_gateway_location_test() -> BaseDir = test_utils:tmp_dir("add_gateway_location_test"), @@ -6122,107 +6125,109 @@ fold_test() -> poc_test() -> - BaseDir = test_utils:tmp_dir("poc_test"), - Ledger = new(BaseDir), - - Challenger0 = <<"challenger0">>, - Challenger1 = <<"challenger1">>, - - OnionKeyHash0 = <<"onion_key_hash0">>, - OnionKeyHash1 = <<"onion_key_hash1">>, - - BlockHash = <<"block_hash">>, - - OwnerAddr = <<"owner_address">>, - Location = h3:from_geo({37.78101, -122.465372}, 12), - Nonce = 1, - - SecretHash = <<"secret_hash">>, - - - meck:new(blockchain_swarm, [passthrough]), - meck:expect(blockchain, - config, - fun(min_score, _) -> - {ok, 0.2}; - (h3_exclusion_ring_dist, _) -> - {ok, 3}; - (h3_max_grid_distance, _) -> - {ok, 60}; - (h3_neighbor_res, _) -> - {ok, 12}; - (election_version, _) -> - {ok, 4}; - (full_gateway_capabilities_mask, _) -> - {ok, ?GW_CAPABILITIES_FULL_GATEWAY_V1} - end), - - ?assertEqual({error, not_found}, find_pocs(OnionKeyHash0, Ledger)), - - commit( - fun(L) -> - ok = add_gateway(OwnerAddr, Challenger0, Location, Nonce, full, L), - ok = add_gateway(OwnerAddr, Challenger1, Location, Nonce, full, L), - ok = request_poc(OnionKeyHash0, SecretHash, Challenger0, BlockHash, 0, L) - end, - Ledger - ), - PoC0 = blockchain_ledger_poc_v2:new(SecretHash, OnionKeyHash0, Challenger0, BlockHash), - ?assertEqual({ok, [PoC0]} ,find_pocs(OnionKeyHash0, Ledger)), - ?assertEqual({ok, PoC0} ,find_poc(OnionKeyHash0, Challenger0, Ledger)), - {ok, GwInfo0} = find_gateway_info(Challenger0, Ledger), - ?assertEqual(1, blockchain_ledger_gateway_v2:last_poc_challenge(GwInfo0)), - ?assertEqual(OnionKeyHash0, blockchain_ledger_gateway_v2:last_poc_onion_key_hash(GwInfo0)), - - commit( - fun(L) -> - ok = request_poc(OnionKeyHash0, SecretHash, Challenger1, BlockHash, 0, L) - end, - Ledger - ), - PoC1 = blockchain_ledger_poc_v2:new(SecretHash, OnionKeyHash0, Challenger1, BlockHash), - ?assertEqual({ok, [PoC1, PoC0]}, find_pocs(OnionKeyHash0, Ledger)), - - commit( - fun(L) -> - ok = delete_poc(OnionKeyHash0, Challenger0, L) - end, - Ledger - ), - ?assertEqual({ok, [PoC1]} ,find_pocs(OnionKeyHash0, Ledger)), + {timeout, 30000, + fun() -> + BaseDir = test_utils:tmp_dir("poc_test"), + Ledger = new(BaseDir), + + Challenger0 = <<"challenger0">>, + Challenger1 = <<"challenger1">>, + + OnionKeyHash0 = <<"onion_key_hash0">>, + OnionKeyHash1 = <<"onion_key_hash1">>, + + BlockHash = <<"block_hash">>, + + OwnerAddr = <<"owner_address">>, + Location = h3:from_geo({37.78101, -122.465372}, 12), + Nonce = 1, + + SecretHash = <<"secret_hash">>, + + meck:new(blockchain_swarm, [passthrough]), + meck:expect(blockchain, + config, + fun(min_score, _) -> + {ok, 0.2}; + (h3_exclusion_ring_dist, _) -> + {ok, 3}; + (h3_max_grid_distance, _) -> + {ok, 60}; + (h3_neighbor_res, _) -> + {ok, 12}; + (election_version, _) -> + {ok, 4}; + (full_gateway_capabilities_mask, _) -> + {ok, ?GW_CAPABILITIES_FULL_GATEWAY_V1} + end), + + ?assertEqual({error, not_found}, find_pocs(OnionKeyHash0, Ledger)), + + commit( + fun(L) -> + ok = add_gateway(OwnerAddr, Challenger0, Location, Nonce, full, L), + ok = add_gateway(OwnerAddr, Challenger1, Location, Nonce, full, L), + ok = request_poc(OnionKeyHash0, SecretHash, Challenger0, BlockHash, 0, L) + end, + Ledger + ), + PoC0 = blockchain_ledger_poc_v2:new(SecretHash, OnionKeyHash0, Challenger0, BlockHash), + ?assertEqual({ok, [PoC0]} ,find_pocs(OnionKeyHash0, Ledger)), + ?assertEqual({ok, PoC0} ,find_poc(OnionKeyHash0, Challenger0, Ledger)), + {ok, GwInfo0} = find_gateway_info(Challenger0, Ledger), + ?assertEqual(1, blockchain_ledger_gateway_v2:last_poc_challenge(GwInfo0)), + ?assertEqual(OnionKeyHash0, blockchain_ledger_gateway_v2:last_poc_onion_key_hash(GwInfo0)), + + commit( + fun(L) -> + ok = request_poc(OnionKeyHash0, SecretHash, Challenger1, BlockHash, 0, L) + end, + Ledger + ), + PoC1 = blockchain_ledger_poc_v2:new(SecretHash, OnionKeyHash0, Challenger1, BlockHash), + ?assertEqual({ok, [PoC1, PoC0]}, find_pocs(OnionKeyHash0, Ledger)), + + commit( + fun(L) -> + ok = delete_poc(OnionKeyHash0, Challenger0, L) + end, + Ledger + ), + ?assertEqual({ok, [PoC1]} ,find_pocs(OnionKeyHash0, Ledger)), - commit( - fun(L) -> - ok = delete_poc(OnionKeyHash0, Challenger1, L) - end, - Ledger - ), - ?assertEqual({error, not_found} ,find_pocs(OnionKeyHash0, Ledger)), + commit( + fun(L) -> + ok = delete_poc(OnionKeyHash0, Challenger1, L) + end, + Ledger + ), + ?assertEqual({error, not_found} ,find_pocs(OnionKeyHash0, Ledger)), - commit( - fun(L) -> - ok = request_poc(OnionKeyHash0, SecretHash, Challenger0, BlockHash, 0, L) - end, - Ledger - ), - ?assertEqual({ok, [PoC0]} ,find_pocs(OnionKeyHash0, Ledger)), + commit( + fun(L) -> + ok = request_poc(OnionKeyHash0, SecretHash, Challenger0, BlockHash, 0, L) + end, + Ledger + ), + ?assertEqual({ok, [PoC0]} ,find_pocs(OnionKeyHash0, Ledger)), - commit( - fun(L) -> - ok = request_poc(OnionKeyHash1, SecretHash, Challenger0, BlockHash, 0, L) - end, - Ledger - ), - ?assertEqual({error, not_found} ,find_pocs(OnionKeyHash0, Ledger)), - PoC2 = blockchain_ledger_poc_v2:new(SecretHash, OnionKeyHash1, Challenger0, BlockHash), - ?assertEqual({ok, [PoC2]}, find_pocs(OnionKeyHash1, Ledger)), - {ok, GwInfo1} = find_gateway_info(Challenger0, Ledger), - ?assertEqual(1, blockchain_ledger_gateway_v2:last_poc_challenge(GwInfo1)), - ?assertEqual(OnionKeyHash1, blockchain_ledger_gateway_v2:last_poc_onion_key_hash(GwInfo1)), - meck:unload(blockchain_swarm), - meck:unload(blockchain), - test_utils:cleanup_tmp_dir(BaseDir), - ok. + commit( + fun(L) -> + ok = request_poc(OnionKeyHash1, SecretHash, Challenger0, BlockHash, 0, L) + end, + Ledger + ), + ?assertEqual({error, not_found} ,find_pocs(OnionKeyHash0, Ledger)), + PoC2 = blockchain_ledger_poc_v2:new(SecretHash, OnionKeyHash1, Challenger0, BlockHash), + ?assertEqual({ok, [PoC2]}, find_pocs(OnionKeyHash1, Ledger)), + {ok, GwInfo1} = find_gateway_info(Challenger0, Ledger), + ?assertEqual(1, blockchain_ledger_gateway_v2:last_poc_challenge(GwInfo1)), + ?assertEqual(OnionKeyHash1, blockchain_ledger_gateway_v2:last_poc_onion_key_hash(GwInfo1)), + meck:unload(blockchain_swarm), + meck:unload(blockchain), + test_utils:cleanup_tmp_dir(BaseDir), + ok + end}. commit(Fun, Ledger0) -> Ledger1 = new_context(Ledger0), diff --git a/src/transactions/blockchain_txn.erl b/src/transactions/blockchain_txn.erl index ddfb8b7fc4..f663f3a87d 100644 --- a/src/transactions/blockchain_txn.erl +++ b/src/transactions/blockchain_txn.erl @@ -1379,102 +1379,126 @@ txn_fees_oui_test() -> ok. txn_fees_routing_update_router_test() -> - [{Owner, OwnerSigFun}] = gen_payers(1), - - %% create new txn, and confirm expected fee size - Txn00 = blockchain_txn_routing_v1:update_router_addresses(0, Owner, [?ADDRESS_KEY1, ?ADDRESS_KEY2], 1), - Txn00Fee = blockchain_txn_routing_v1:calculate_fee(Txn00, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), - Txn00StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_ROUTER_STAKING_FEE, [], true), - Txn00LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_ROUTER_STAKING_FEE, [], false), - ?assertEqual(40000, Txn00Fee), - ?assertEqual(0, Txn00StakingFee), - ?assertEqual(0, Txn00LegacyStakingFee), - - %% set the fee values of the txn, sign it and confirm the fees remains the same and unaffected by signatures - Txn01 = blockchain_txn_routing_v1:fee(Txn00, Txn00Fee), - Txn02 = blockchain_txn_routing_v1:staking_fee(Txn01, Txn00StakingFee), - Txn03 = blockchain_txn_routing_v1:sign(Txn02, OwnerSigFun), - Txn03Fee = blockchain_txn_routing_v1:calculate_fee(Txn03, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), - Txn03StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_ROUTER_STAKING_FEE, [], true), - Txn03LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_ROUTER_STAKING_FEE, [], false), - ?assertEqual(40000, Txn03Fee), - ?assertEqual(0, Txn03StakingFee), - ?assertEqual(0, Txn03LegacyStakingFee), - ok. + {timeout, 30000, + fun() -> + meck:new(blockchain_ledger_v1, [passthrough]), + meck:expect(blockchain_ledger_v1, config, fun(txn_routing_update_xor_fees_version, _) -> {ok, 1} end), + [{Owner, OwnerSigFun}] = gen_payers(1), + + %% create new txn, and confirm expected fee size + Txn00 = blockchain_txn_routing_v1:update_router_addresses(0, Owner, [?ADDRESS_KEY1, ?ADDRESS_KEY2], 1), + Txn00Fee = blockchain_txn_routing_v1:calculate_fee(Txn00, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), + Txn00StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_ROUTER_STAKING_FEE, [], true), + Txn00LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_ROUTER_STAKING_FEE, [], false), + ?assertEqual(40000, Txn00Fee), + ?assertEqual(0, Txn00StakingFee), + ?assertEqual(0, Txn00LegacyStakingFee), + + %% set the fee values of the txn, sign it and confirm the fees remains the same and unaffected by signatures + Txn01 = blockchain_txn_routing_v1:fee(Txn00, Txn00Fee), + Txn02 = blockchain_txn_routing_v1:staking_fee(Txn01, Txn00StakingFee), + Txn03 = blockchain_txn_routing_v1:sign(Txn02, OwnerSigFun), + Txn03Fee = blockchain_txn_routing_v1:calculate_fee(Txn03, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), + Txn03StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_ROUTER_STAKING_FEE, [], true), + Txn03LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_ROUTER_STAKING_FEE, [], false), + ?assertEqual(40000, Txn03Fee), + ?assertEqual(0, Txn03StakingFee), + ?assertEqual(0, Txn03LegacyStakingFee), + meck:unload(blockchain_ledger_v1), + ok + end}. txn_fees_routing_new_xor_test() -> - [{Owner, OwnerSigFun}] = gen_payers(1), - {Filter, _} = xor16:to_bin(xor16:new([0], fun xxhash:hash64/1)), - - %% create new txn, and confirm expected fee size - Txn00 = blockchain_txn_routing_v1:new_xor(1, Owner, Filter, 1), - Txn00Fee = blockchain_txn_routing_v1:calculate_fee(Txn00, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), - Txn00StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_NEW_XOR_STAKING_FEE, [], true), - Txn00LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_NEW_XOR_STAKING_FEE, [], false), - ?assertEqual(40000, Txn00Fee), - ?assertEqual(0, Txn00StakingFee), - ?assertEqual(0, Txn00LegacyStakingFee), - - %% set the fee values of the txn, sign it and confirm the fees remains the same and unaffected by signatures - Txn01 = blockchain_txn_routing_v1:fee(Txn00, Txn00Fee), - Txn02 = blockchain_txn_routing_v1:staking_fee(Txn01, Txn00StakingFee), - Txn03 = blockchain_txn_routing_v1:sign(Txn02, OwnerSigFun), - Txn03Fee = blockchain_txn_routing_v1:calculate_fee(Txn03, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), - Txn03StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_NEW_XOR_STAKING_FEE, [], true), - Txn03LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_NEW_XOR_STAKING_FEE, [], false), - ?assertEqual(40000, Txn03Fee), - ?assertEqual(0, Txn03StakingFee), - ?assertEqual(0, Txn03LegacyStakingFee), - ok. + {timeout, 30000, + fun() -> + meck:new(blockchain_ledger_v1, [passthrough]), + meck:expect(blockchain_ledger_v1, config, fun(txn_routing_update_xor_fees_version, _) -> {ok, 1} end), + [{Owner, OwnerSigFun}] = gen_payers(1), + {Filter, _} = xor16:to_bin(xor16:new([0], fun xxhash:hash64/1)), + + %% create new txn, and confirm expected fee size + Txn00 = blockchain_txn_routing_v1:new_xor(1, Owner, Filter, 1), + Txn00Fee = blockchain_txn_routing_v1:calculate_fee(Txn00, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), + Txn00StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_NEW_XOR_STAKING_FEE, [], true), + Txn00LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_NEW_XOR_STAKING_FEE, [], false), + ?assertEqual(40000, Txn00Fee), + ?assertEqual(0, Txn00StakingFee), + ?assertEqual(0, Txn00LegacyStakingFee), + + %% set the fee values of the txn, sign it and confirm the fees remains the same and unaffected by signatures + Txn01 = blockchain_txn_routing_v1:fee(Txn00, Txn00Fee), + Txn02 = blockchain_txn_routing_v1:staking_fee(Txn01, Txn00StakingFee), + Txn03 = blockchain_txn_routing_v1:sign(Txn02, OwnerSigFun), + Txn03Fee = blockchain_txn_routing_v1:calculate_fee(Txn03, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), + Txn03StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_NEW_XOR_STAKING_FEE, [], true), + Txn03LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_NEW_XOR_STAKING_FEE, [], false), + ?assertEqual(40000, Txn03Fee), + ?assertEqual(0, Txn03StakingFee), + ?assertEqual(0, Txn03LegacyStakingFee), + meck:unload(blockchain_ledger_v1), + ok + end}. txn_fees_routing_update_xor_test() -> - [{Owner, OwnerSigFun}] = gen_payers(1), - {Filter, _} = xor16:to_bin(xor16:new([0], fun xxhash:hash64/1)), - - %% create new txn, and confirm expected fee size - Txn00 = blockchain_txn_routing_v1:update_xor(1, Owner, 0, Filter, 1), - Txn00Fee = blockchain_txn_routing_v1:calculate_fee(Txn00, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), - Txn00StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_XOR_STAKING_FEE, [], true), - Txn00LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_XOR_STAKING_FEE, [], false), - ?assertEqual(45000, Txn00Fee), - ?assertEqual(0, Txn00StakingFee), - ?assertEqual(0, Txn00LegacyStakingFee), - - %% set the fee values of the txn, sign it and confirm the fees remains the same and unaffected by signatures - Txn01 = blockchain_txn_routing_v1:fee(Txn00, Txn00Fee), - Txn02 = blockchain_txn_routing_v1:staking_fee(Txn01, Txn00StakingFee), - Txn03 = blockchain_txn_routing_v1:sign(Txn02, OwnerSigFun), - Txn03Fee = blockchain_txn_routing_v1:calculate_fee(Txn03, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), - Txn03StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_XOR_STAKING_FEE, [], true), - Txn03LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_XOR_STAKING_FEE, [], false), - ?assertEqual(45000, Txn03Fee), - ?assertEqual(0, Txn03StakingFee), - ?assertEqual(0, Txn03LegacyStakingFee), - ok. + {timeout, 30000, + fun() -> + meck:new(blockchain_ledger_v1, [passthrough]), + meck:expect(blockchain_ledger_v1, config, fun(txn_routing_update_xor_fees_version, _) -> {ok, 1} end), + [{Owner, OwnerSigFun}] = gen_payers(1), + {Filter, _} = xor16:to_bin(xor16:new([0], fun xxhash:hash64/1)), + + %% create new txn, and confirm expected fee size + Txn00 = blockchain_txn_routing_v1:update_xor(1, Owner, 0, Filter, 1), + Txn00Fee = blockchain_txn_routing_v1:calculate_fee(Txn00, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), + Txn00StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_XOR_STAKING_FEE, [], true), + Txn00LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_XOR_STAKING_FEE, [], false), + ?assertEqual(45000, Txn00Fee), + ?assertEqual(0, Txn00StakingFee), + ?assertEqual(0, Txn00LegacyStakingFee), + + %% set the fee values of the txn, sign it and confirm the fees remains the same and unaffected by signatures + Txn01 = blockchain_txn_routing_v1:fee(Txn00, Txn00Fee), + Txn02 = blockchain_txn_routing_v1:staking_fee(Txn01, Txn00StakingFee), + Txn03 = blockchain_txn_routing_v1:sign(Txn02, OwnerSigFun), + Txn03Fee = blockchain_txn_routing_v1:calculate_fee(Txn03, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), + Txn03StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_XOR_STAKING_FEE, [], true), + Txn03LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_UPDATE_XOR_STAKING_FEE, [], false), + ?assertEqual(45000, Txn03Fee), + ?assertEqual(0, Txn03StakingFee), + ?assertEqual(0, Txn03LegacyStakingFee), + meck:unload(blockchain_ledger_v1), + ok + end}. txn_fees_routing_request_subnet_test() -> - [{Owner, OwnerSigFun}] = gen_payers(1), - - %% create new txn, and confirm expected fee size - Txn00 = blockchain_txn_routing_v1:request_subnet(1, Owner, 16, 1), - Txn00Fee = blockchain_txn_routing_v1:calculate_fee(Txn00, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), - Txn00StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_REQUEST_SUBNET_STAKING_FEE, [], true), - Txn00LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_REQUEST_SUBNET_STAKING_FEE, [], false), - ?assertEqual(25000, Txn00Fee), - ?assertEqual(160000000, Txn00StakingFee), - ?assertEqual(0, Txn00LegacyStakingFee), - - %% set the fee values of the txn, sign it and confirm the fees remains the same and unaffected by signatures - Txn01 = blockchain_txn_routing_v1:fee(Txn00, Txn00Fee), - Txn02 = blockchain_txn_routing_v1:staking_fee(Txn01, Txn00StakingFee), - Txn03 = blockchain_txn_routing_v1:sign(Txn02, OwnerSigFun), - Txn03Fee = blockchain_txn_routing_v1:calculate_fee(Txn03, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), - Txn03StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_REQUEST_SUBNET_STAKING_FEE, [], true), - Txn03LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_REQUEST_SUBNET_STAKING_FEE, [], false), - ?assertEqual(25000, Txn03Fee), - ?assertEqual(160000000, Txn03StakingFee), - ?assertEqual(0, Txn03LegacyStakingFee), - ok. + {timeout, 30000, + fun() -> + meck:new(blockchain_ledger_v1, [passthrough]), + meck:expect(blockchain_ledger_v1, config, fun(txn_routing_update_xor_fees_version, _) -> {ok, 1} end), + [{Owner, OwnerSigFun}] = gen_payers(1), + + %% create new txn, and confirm expected fee size + Txn00 = blockchain_txn_routing_v1:request_subnet(1, Owner, 16, 1), + Txn00Fee = blockchain_txn_routing_v1:calculate_fee(Txn00, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), + Txn00StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_REQUEST_SUBNET_STAKING_FEE, [], true), + Txn00LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_REQUEST_SUBNET_STAKING_FEE, [], false), + ?assertEqual(25000, Txn00Fee), + ?assertEqual(160000000, Txn00StakingFee), + ?assertEqual(0, Txn00LegacyStakingFee), + + %% set the fee values of the txn, sign it and confirm the fees remains the same and unaffected by signatures + Txn01 = blockchain_txn_routing_v1:fee(Txn00, Txn00Fee), + Txn02 = blockchain_txn_routing_v1:staking_fee(Txn01, Txn00StakingFee), + Txn03 = blockchain_txn_routing_v1:sign(Txn02, OwnerSigFun), + Txn03Fee = blockchain_txn_routing_v1:calculate_fee(Txn03, ignore_ledger, ?DC_PAYLOAD_SIZE, ?TXN_MULTIPLIER, true), + Txn03StakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_REQUEST_SUBNET_STAKING_FEE, [], true), + Txn03LegacyStakingFee = blockchain_txn_routing_v1:calculate_staking_fee(Txn00, ignore_ledger, ?ROUTER_REQUEST_SUBNET_STAKING_FEE, [], false), + ?assertEqual(25000, Txn03Fee), + ?assertEqual(160000000, Txn03StakingFee), + ?assertEqual(0, Txn03LegacyStakingFee), + meck:unload(blockchain_ledger_v1), + ok + end}. txn_fees_security_exchange_v1_test() -> [{Payer, PayerSigFun}] = gen_payers(1), diff --git a/test/blockchain_ct_utils.erl b/test/blockchain_ct_utils.erl index 6cb1abecb1..838039be1b 100644 --- a/test/blockchain_ct_utils.erl +++ b/test/blockchain_ct_utils.erl @@ -177,6 +177,7 @@ init_per_suite(Config) -> application:ensure_all_started(lager), lager:set_loglevel(lager_console_backend, debug), application:ensure_all_started(throttle), + application:ensure_all_started(telemetry), Config. init_per_testcase(TestCase, Config) -> diff --git a/test/blockchain_hex_SUITE.erl b/test/blockchain_hex_SUITE.erl index 176cdd8c81..590efe31d7 100644 --- a/test/blockchain_hex_SUITE.erl +++ b/test/blockchain_hex_SUITE.erl @@ -90,6 +90,7 @@ init_per_suite(Config) -> blockchain_ledger_v1:compact(Ledger), application:ensure_all_started(lager), + application:ensure_all_started(telemetry), %% Check that the pinned ledger is at the height we expect it to be {ok, 632627} = blockchain_ledger_v1:current_height(Ledger), diff --git a/test/blockchain_reward_hip17_SUITE.erl b/test/blockchain_reward_hip17_SUITE.erl index 154bf3b8b0..8a5c6c227f 100644 --- a/test/blockchain_reward_hip17_SUITE.erl +++ b/test/blockchain_reward_hip17_SUITE.erl @@ -181,6 +181,7 @@ init_per_testcase(TestCase, Config) -> {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config0)), ok = application:set_env(blockchain, hip17_test_mode, true), application:ensure_all_started(lager), + application:ensure_all_started(telemetry), BothHip15And17Vars = maps:merge(hip15_vars(), hip17_vars()), diff --git a/test/blockchain_reward_perf_SUITE.erl b/test/blockchain_reward_perf_SUITE.erl index 08033ed3d3..41ccf1761b 100644 --- a/test/blockchain_reward_perf_SUITE.erl +++ b/test/blockchain_reward_perf_SUITE.erl @@ -60,6 +60,7 @@ init_per_testcase(TestCase, Config) -> HtStr = integer_to_list(TgtHeight), {ok, _} = application:ensure_all_started(lager), + {ok, _} = application:ensure_all_started(telemetry), {ok, Dir} = file:get_cwd(), PrivDir = filename:join([Dir, "priv"]), diff --git a/test/blockchain_reward_v2_SUITE.erl b/test/blockchain_reward_v2_SUITE.erl index 9261966826..6681273821 100644 --- a/test/blockchain_reward_v2_SUITE.erl +++ b/test/blockchain_reward_v2_SUITE.erl @@ -20,6 +20,7 @@ suite() -> init_per_suite(Config) -> {ok, _} = application:ensure_all_started(lager), + {ok, _} = application:ensure_all_started(telemetry), Dir = ?config(priv_dir, Config), PrivDir = filename:join(Dir, "priv"), diff --git a/test/blockchain_snapshot_SUITE.erl b/test/blockchain_snapshot_SUITE.erl index a07d3a5966..f80b054285 100644 --- a/test/blockchain_snapshot_SUITE.erl +++ b/test/blockchain_snapshot_SUITE.erl @@ -18,6 +18,7 @@ init_per_suite(Cfg) -> {ok, _} = application:ensure_all_started(lager), + {ok, _} = application:ensure_all_started(telemetry), Cfg. end_per_suite(_) -> diff --git a/test/blockchain_state_channel_SUITE.erl b/test/blockchain_state_channel_SUITE.erl index 318b470740..5d523988a5 100644 --- a/test/blockchain_state_channel_SUITE.erl +++ b/test/blockchain_state_channel_SUITE.erl @@ -110,6 +110,7 @@ debug_modules_for_node(Node, Filename, [Module | Rest]) -> init_per_testcase(Test, Config) -> application:ensure_all_started(throttle), application:ensure_all_started(lager), + application:ensure_all_started(telemetry), InitConfig0 = blockchain_ct_utils:init_base_dir_config(?MODULE, Test, Config), InitConfig = blockchain_ct_utils:init_per_testcase(Test, InitConfig0), From 7ba1d43704f5cd70d88d67754aed659ebe4b5503 Mon Sep 17 00:00:00 2001 From: jeffgrunewald Date: Thu, 2 Jun 2022 16:45:34 -0400 Subject: [PATCH 08/48] fixing simple suite ct --- test/blockchain_simple_SUITE.erl | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/blockchain_simple_SUITE.erl b/test/blockchain_simple_SUITE.erl index 7a678c5991..60448f4562 100644 --- a/test/blockchain_simple_SUITE.erl +++ b/test/blockchain_simple_SUITE.erl @@ -330,7 +330,6 @@ init_per_testcase(TestCase, Config) -> %%-------------------------------------------------------------------- end_per_testcase(_, Config) -> Sup = ?config(sup, Config), - meck:unload(), % Make sure blockchain saved on file = in memory case erlang:is_process_alive(Sup) of true -> @@ -395,7 +394,7 @@ reload_test(Config) -> ?assertEqual({ok, 11}, blockchain:height(Chain0)), %% Kill this blockchain sup - OldSwarm = blockchain_swarm:tid(), + OldSwarm = blockchain_swarm:swarm(), Worker = whereis(blockchain_worker), ok = gen_server:stop(Sup), ok = test_utils:wait_until(fun() -> not erlang:is_process_alive(Sup) end), @@ -420,7 +419,7 @@ reload_test(Config) -> ct:pal("new start"), {ok, Sup1} = blockchain_sup:start_link([{update_dir, GenDir}|Opts]), - ?assert(erlang:is_pid(blockchain_swarm:tid())), + ?assert(erlang:is_pid(blockchain_swarm:swarm())), Chain = blockchain_worker:blockchain(), {ok, HeadBlock} = blockchain:head_block(Chain), @@ -457,7 +456,7 @@ restart_test(Config) -> ?assertEqual({ok, 11}, blockchain:height(Chain0)), %% Kill this blockchain sup - OldSwarm = blockchain_swarm:tid(), + OldSwarm = blockchain_swarm:swarm(), ok = gen_server:stop(Sup), ok = test_utils:wait_until(fun() -> not erlang:is_process_alive(Sup) end), ok = test_utils:wait_until(fun() -> not erlang:is_process_alive(OldSwarm) end), @@ -467,7 +466,7 @@ restart_test(Config) -> % Restart with an empty 'GenDir' {ok, Sup1} = blockchain_sup:start_link([{update_dir, GenDir}|Opts]), - ?assert(erlang:is_pid(blockchain_swarm:tid())), + ?assert(erlang:is_pid(blockchain_swarm:swarm())), Chain = blockchain_worker:blockchain(), {ok, HeadBlock} = blockchain:head_block(Chain), @@ -478,7 +477,7 @@ restart_test(Config) -> ?assertEqual({ok, 11}, blockchain:height(Chain)), %% Kill this blockchain sup - OldSwarm2 = blockchain_swarm:tid(), + OldSwarm2 = blockchain_swarm:swarm(), ok = gen_server:stop(Sup1), ok = test_utils:wait_until(fun() -> not erlang:is_process_alive(Sup1) end), ok = test_utils:wait_until(fun() -> not erlang:is_process_alive(OldSwarm2) end), @@ -490,7 +489,7 @@ restart_test(Config) -> ok = file:write_file(filename:join([GenDir, "genesis"]), blockchain_block:serialize(GenBlock)), {ok, Sup2} = blockchain_sup:start_link([{update_dir, GenDir}|Opts]), - ?assert(erlang:is_pid(blockchain_swarm:tid())), + ?assert(erlang:is_pid(blockchain_swarm:swarm())), Chain1 = blockchain_worker:blockchain(), {ok, HeadBlock1} = blockchain:head_block(Chain1), @@ -1167,6 +1166,7 @@ routing_test(Config) -> %% and then mecking out the is_valid to prevent it being rejected due to expected /= presented fee %% since we can no longer override the default fee, now have to meck out the check_db & debit fee functions instead %% as the account does not have any credits, but the mecking of the is_valid can be removed + meck:new(blockchain_ledger_v1, [passthrough]), meck:expect(blockchain_ledger_v1, check_dc_or_hnt_balance, fun(_, _, _, _) -> ok end), meck:expect(blockchain_ledger_v1, debit_fee, fun(_, _, _, _, _, _) -> ok end), @@ -1341,6 +1341,7 @@ routing_test(Config) -> ?assert(meck:validate(blockchain_txn_oui_v1)), meck:unload(blockchain_txn_oui_v1), + meck:unload(blockchain_ledger_v1), ok. max_subnet_test(Config) -> @@ -1357,6 +1358,7 @@ max_subnet_test(Config) -> %% and then mecking out the is_valid to prevent it being rejected due to expected /= presented fee %% since we can no longer override the default fee, now have to meck out the check_db & debit fee functions instead %% as the account does not have any credits, but the mecking of the is_valid can be removed + meck:new(blockchain_ledger_v1, [passthrough]), meck:expect(blockchain_ledger_v1, check_dc_or_hnt_balance, fun(_, _, _, _) -> ok end), meck:expect(blockchain_ledger_v1, debit_fee, fun(_, _, _, _, _, _) -> ok end), @@ -1404,7 +1406,7 @@ max_subnet_test(Config) -> ?assert(meck:validate(blockchain_txn_oui_v1)), meck:unload(blockchain_txn_oui_v1), - + meck:unload(blockchain_ledger_v1), ok. block_save_failed_test(Config) -> @@ -3619,7 +3621,7 @@ failed_txn_error_handling(Config) -> %% confirm the txn passes validation with the mecks removed {[SignedPoCReqTxn0], []} = blockchain_txn:validate([SignedPoCReqTxn0], Chain), - + meck:unload(blockchain_ledger_v1), ok. genesis_no_var_validation_stay_invalid_test(Config) -> From 5407343dbd0fc91213b384e548b28003d7d3ac92 Mon Sep 17 00:00:00 2001 From: joecaswell <1626320+joecaswell@users.noreply.github.com> Date: Tue, 15 Mar 2022 18:27:09 -0700 Subject: [PATCH 09/48] validate size and hash after download --- src/blockchain_worker.erl | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 41c2222cdc..114363708c 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -1186,7 +1186,16 @@ build_url(BaseUrl, Filename) -> set_filename(BaseFilename) -> BaseFilename ++ ".gz". -attempt_fetch_snap_source_snapshot(BaseUrl, #snapshot_info{height = Height, file_hash = Hash, +attempt_fetch_snap_source_snapshot(BaseUrl, SnapInfo) -> + case validate_snapshot_file(SnapInfo) of + {ok, Filename} -> + {ok, Filename} + {invalid, Filename, Filepath -> + _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath), + validate_snapshot_file(SnapInfo) + end. + +validate_snapshot_file(#snapshot_info{height = Height, file_hash = Hash, file_size = Size}) -> %% httpc and ssl applications are started in the top level blockchain supervisor Filename = set_filename(build_filename(Height)), @@ -1222,17 +1231,15 @@ attempt_fetch_snap_source_snapshot(BaseUrl, #snapshot_info{height = Height, file lager:info("Already have snapshot file for height ~p with hash ~p", [Height, Hash]), ok = file:write_file(HashStateFile, Hash), {ok, Filepath}; - {smaller, _} -> - %% see if this is a a resumable download - _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath); _ -> - %% file is bigger than it should be, or the hash is wrong, scrap it + %% file size or the hash is wrong, scrap it, + %% we'll try to resume from the scratch file, if there is one safe_delete(Filename), - _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath) + {invalid, Filename, Filepath} end end; - false -> - _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath) + %% no file? same as failed hash + false -> {invalid, Filename, Filepath} end. same_stored_hash(File, Hash) -> From 78782feb58c922ad542c91fbdec5a2a0400945f4 Mon Sep 17 00:00:00 2001 From: joecaswell <1626320+joecaswell@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:35:36 -0700 Subject: [PATCH 10/48] explicitly match validate result to ensure error is thrown --- src/blockchain_worker.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 114363708c..5297db3f83 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -1190,9 +1190,9 @@ attempt_fetch_snap_source_snapshot(BaseUrl, SnapInfo) -> case validate_snapshot_file(SnapInfo) of {ok, Filename} -> {ok, Filename} - {invalid, Filename, Filepath -> + {invalid, Filename, Filepath} -> _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath), - validate_snapshot_file(SnapInfo) + {ok, _} = validate_snapshot_file(SnapInfo) end. validate_snapshot_file(#snapshot_info{height = Height, file_hash = Hash, From 0e4e07d79f3d4ba0ff6c0d19cb8f0d295a819ead Mon Sep 17 00:00:00 2001 From: joecaswell <1626320+joecaswell@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:36:29 -0700 Subject: [PATCH 11/48] remove unnecessary match --- src/blockchain_worker.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 5297db3f83..33b1984432 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -1192,7 +1192,7 @@ attempt_fetch_snap_source_snapshot(BaseUrl, SnapInfo) -> {ok, Filename} {invalid, Filename, Filepath} -> _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath), - {ok, _} = validate_snapshot_file(SnapInfo) + validate_snapshot_file(SnapInfo) end. validate_snapshot_file(#snapshot_info{height = Height, file_hash = Hash, From 636f1997ac49c5cc4ea08852e7056d93d1a0081a Mon Sep 17 00:00:00 2001 From: Joe Caswell <1626320+joecaswell@users.noreply.github.com> Date: Thu, 14 Apr 2022 15:13:12 -0700 Subject: [PATCH 12/48] missing ; Co-authored-by: Rahul Garg <3199183+vihu@users.noreply.github.com> --- src/blockchain_worker.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 33b1984432..6f126c6d37 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -1189,7 +1189,7 @@ set_filename(BaseFilename) -> attempt_fetch_snap_source_snapshot(BaseUrl, SnapInfo) -> case validate_snapshot_file(SnapInfo) of {ok, Filename} -> - {ok, Filename} + {ok, Filename}; {invalid, Filename, Filepath} -> _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath), validate_snapshot_file(SnapInfo) From 897bbde835598f2f374a855f6a883d3f68087b4e Mon Sep 17 00:00:00 2001 From: Evan Vigil-McClanahan Date: Fri, 3 Jun 2022 15:30:30 -0700 Subject: [PATCH 13/48] try to streamline poc v6 targeting --- src/blockchain_utils.erl | 14 ++- src/poc/blockchain_poc_target_v6.erl | 127 ++++++++++++--------------- 2 files changed, 68 insertions(+), 73 deletions(-) diff --git a/src/blockchain_utils.erl b/src/blockchain_utils.erl index f4a9f88dc4..d3f2748b18 100644 --- a/src/blockchain_utils.erl +++ b/src/blockchain_utils.erl @@ -11,7 +11,7 @@ -export([ shuffle_from_hash/2, - shuffle/1, + shuffle/1, shuffle/2, rand_from_hash/1, rand_state/1, normalize_float/1, challenge_interval/1, @@ -122,6 +122,18 @@ shuffle_from_hash(Hash, L) -> shuffle(Xs) -> [X || {_, X} <- lists:sort([{rand:uniform(), X} || X <- Xs])]. +-spec shuffle([A], rand:state()) -> {[A], rand:state()}. +shuffle(Xs, RandState) -> + {Shuffled, RandState1} = + lists:foldl( + fun(X, {A, St}) -> + {R, St1} = rand:uniform_s(St), + {[{R, X} | A ], St1} + end, {[], RandState}, + Xs), + {_Rands, Positions} = lists:unzip(lists:sort(Shuffled)), + {Positions, RandState1}. + %%-------------------------------------------------------------------- %% @doc %% @end diff --git a/src/poc/blockchain_poc_target_v6.erl b/src/poc/blockchain_poc_target_v6.erl index 771cff2b7f..bc6a4b8510 100644 --- a/src/poc/blockchain_poc_target_v6.erl +++ b/src/poc/blockchain_poc_target_v6.erl @@ -39,7 +39,7 @@ target_zone(RandState, Ledger) -> Vars :: map(), HexList :: [h3:h3_index()], Attempted :: [{h3:h3_index(), rand:state()}] -) -> {ok, [libp2p_crypto:pubkey_bin()]} | {error, any()}. +) -> {ok, libp2p_crypto:pubkey_bin(), rand:state()} | {error, any()}. gateways_for_zone( ChallengerPubkeyBin, Ledger, @@ -57,7 +57,7 @@ gateways_for_zone( HexList :: [h3:h3_index()], Attempted :: [{h3:h3_index(), rand:state()}], NumAttempts :: integer() -) -> {ok, [libp2p_crypto:pubkey_bin()]} | {error, any()}. +) -> {ok, libp2p_crypto:pubkey_bin(), rand:state()} | {error, any()}. gateways_for_zone( _ChallengerPubkeyBin, _Ledger, @@ -66,7 +66,7 @@ gateways_for_zone( _Attempted, 0 )-> - {ok, []}; + {error, no_gateways_found}; gateways_for_zone( ChallengerPubkeyBin, Ledger, @@ -75,20 +75,15 @@ gateways_for_zone( [{Hex, HexRandState0} | Tail] = _Attempted, NumAttempts ) -> - {ok, Height} = blockchain_ledger_v1:current_height(Ledger), %% Get a list of gateway pubkeys within this hex AddrMap = blockchain_ledger_v1:lookup_gateways_from_hex(Hex, Ledger), AddrList0 = lists:flatten(maps:values(AddrMap)), lager:debug("gateways for hex ~p: ~p", [Hex, AddrList0]), %% Limit max number of potential targets in the zone - {HexRandState, AddrList} = limit_addrs(Vars, HexRandState0, AddrList0), - - %% filter the selected hex - case filter(AddrList, Height, Ledger, Vars) of - FilteredList when length(FilteredList) >= 1 -> - lager:debug("*** filtered gateways for hex ~p: ~p", [Hex, FilteredList]), - {ok, FilteredList}; - _ -> + case find_active_addr(Vars, HexRandState0, AddrList0, Ledger) of + {ok, Addr, HexRandState} -> + {ok, Addr, HexRandState}; + {no_target, HexRandState} -> lager:debug("*** failed to find any filtered gateways for hex ~p, trying again", [Hex]), %% no eligible target in this zone %% find a new zone @@ -112,15 +107,14 @@ target(ChallengerPubkeyBin, InitTargetRandState, ZoneRandState, Ledger, Vars) -> case target_zone(ZoneRandState, Ledger) of {error, _} = ErrorResp -> ErrorResp; - {ok, {HexList, Hex, HexRandState}} -> + {ok, {HexList, Hex, _HexRandState}} -> target_( ChallengerPubkeyBin, InitTargetRandState, Ledger, Vars, HexList, - Hex, - HexRandState + Hex ) end. @@ -131,8 +125,7 @@ target(ChallengerPubkeyBin, InitTargetRandState, ZoneRandState, Ledger, Vars) -> Ledger :: blockchain_ledger_v1:ledger(), Vars :: map(), HexList :: [h3:h3_index()], - InitHex :: h3:h3_index(), - InitHexRandState :: rand:state() + InitHex :: h3:h3_index() ) -> {ok, {libp2p_crypto:pubkey_bin(), rand:state()}} | {error, any()}. target_( ChallengerPubkeyBin, @@ -140,71 +133,66 @@ target_( Ledger, Vars, HexList, - InitHex, - InitHexRandState + InitHex ) -> - case gateways_for_zone(ChallengerPubkeyBin, - Ledger, Vars, HexList, [{InitHex, InitHexRandState}]) of - - {ok, []} -> + case gateways_for_zone( + ChallengerPubkeyBin, + Ledger, Vars, HexList, + [{InitHex, InitTargetRandState}]) of + {error, no_gateways_found} -> + {error, no_gateways_found}; + {error, empty_hex_list} -> {error, no_gateways_found}; - {ok, ZoneGWs} -> - %% Assign probabilities to each of these gateways - Prob = blockchain_utils:normalize_float(prob_randomness_wt(Vars) * 1.0), - ProbTargets = lists:map( - fun(A) -> - {A, Prob} - end, - ZoneGWs), - %% Sort the scaled probabilities in default order by gateway pubkey_bin - %% make sure that we carry the rand_state through for determinism - {RandVal, TargetRandState} = rand:uniform_s(InitTargetRandState), - {ok, TargetPubkeybin} = blockchain_utils:icdf_select( - lists:keysort(1, ProbTargets), - RandVal - ), - {ok, {TargetPubkeybin, TargetRandState}} + {ok, TargetPubkeyBin, RandState} -> + {ok, {TargetPubkeyBin, RandState}} end. - - -%% @doc Filter gateways based on these conditions: -%% - gateways which do not have the relevant capability -%% - gateways which are inactive --spec filter( +-spec find_active_addr( + Vars :: map(), + RandState :: rand:state(), AddrList :: [libp2p_crypto:pubkey_bin()], - Height :: non_neg_integer(), - Ledger :: blockchain_ledger_v1:ledger(), - Vars :: map() -) -> [libp2p_crypto:pubkey_bin()]. -filter(AddrList, Height, Ledger, Vars) -> + Ledger :: blockchain_ledger_v1:ledger() +) -> {ok, libp2p_crypto:pubkey_bin(), rand:state()} | + {no_target, rand:state()}. +find_active_addr(Vars, RandState, AddrList, Ledger) -> + {ok, Height} = blockchain_ledger_v1:current_height(Ledger), ActivityFilterEnabled = case blockchain:config(poc_activity_filter_enabled, Ledger) of {ok, V} -> V; _ -> false end, MaxActivityAge = max_activity_age(Vars), - lists:filter( - fun(A) -> - {ok, Gateway} = blockchain_ledger_v1:find_gateway_info(A, Ledger), - Mode = blockchain_ledger_gateway_v2:mode(Gateway), - LastActivity = blockchain_ledger_gateway_v2:last_poc_challenge(Gateway), - is_active(ActivityFilterEnabled, LastActivity, MaxActivityAge, Height) andalso - blockchain_ledger_gateway_v2:is_valid_capability( - Mode, - ?GW_CAPABILITY_POC_CHALLENGEE, - Ledger - ) - end, - AddrList - ). + {ShuffledList, RandState1} = blockchain_utils:shuffle(AddrList, RandState), + + case find_active_addr_(ShuffledList, ActivityFilterEnabled, + Height, MaxActivityAge, Ledger) of + {ok, Addr} -> + {ok, Addr, RandState1}; + not_found -> + {no_target, RandState1} + end. + +find_active_addr_([], _Filter, _Height, _MaxAge, _Ledger) -> + not_found; +find_active_addr_([Addr | Tail], FilterEnabled, Height, MaxActivityAge, Ledger) -> + {ok, Gateway} = blockchain_ledger_v1:find_gateway_info(Addr, Ledger), + Mode = blockchain_ledger_gateway_v2:mode(Gateway), + LastActivity = blockchain_ledger_gateway_v2:last_poc_challenge(Gateway), + case is_active(FilterEnabled, LastActivity, MaxActivityAge, Height) andalso + blockchain_ledger_gateway_v2:is_valid_capability( + Mode, + ?GW_CAPABILITY_POC_CHALLENGEE, + Ledger + ) of + true -> + Addr; + _ -> + find_active_addr_(Tail, FilterEnabled, Height, MaxActivityAge, Ledger) + end. %%%------------------------------------------------------------------- %% Helpers %%%------------------------------------------------------------------- --spec prob_randomness_wt(Vars :: map()) -> float(). -prob_randomness_wt(Vars) -> - maps:get(poc_v5_target_prob_randomness_wt, Vars). -spec hex_list(Ledger :: blockchain_ledger_v1:ledger(), RandState :: rand:state()) -> {[{h3:h3_index(), pos_integer()}], rand:state()}. hex_list(Ledger, RandState) -> @@ -241,11 +229,6 @@ choose_zone(RandState, HexList) -> {ok, {Hex, HexRandState}} end. -limit_addrs(#{?poc_witness_consideration_limit := Limit}, RandState, Witnesses) -> - blockchain_utils:deterministic_subset(Limit, RandState, Witnesses); -limit_addrs(_Vars, RandState, Witnesses) -> - {RandState, Witnesses}. - -spec is_active(ActivityFilterEnabled :: boolean(), LastActivity :: pos_integer(), MaxActivityAge :: pos_integer(), From 2ff00a5bbf02f151403c0c578d6c374bd4f8896c Mon Sep 17 00:00:00 2001 From: jeffgrunewald Date: Mon, 6 Jun 2022 04:37:08 -0400 Subject: [PATCH 14/48] pull in follower proto --- rebar.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.lock b/rebar.lock index f402b0ecb5..53646e69d6 100644 --- a/rebar.lock +++ b/rebar.lock @@ -77,7 +77,7 @@ {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},0}, {<<"helium_proto">>, {git,"https://github.com/helium/proto.git", - {ref,"425610c233af0dd9688c1695668d208174695562"}}, + {ref,"7c90dde32ff582a856aeb6c8926e52abfcc8611f"}}, 0}, {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1}, From 320c4eec9f2ba7d433cfb2a00aa292c4fa20964b Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 6 Jun 2022 11:45:33 -0700 Subject: [PATCH 15/48] Un-export gateways_for_zone as it should not be used --- src/poc/blockchain_poc_target_v5.erl | 1 - src/poc/blockchain_poc_target_v6.erl | 1 - 2 files changed, 2 deletions(-) diff --git a/src/poc/blockchain_poc_target_v5.erl b/src/poc/blockchain_poc_target_v5.erl index a15e143353..7965392137 100644 --- a/src/poc/blockchain_poc_target_v5.erl +++ b/src/poc/blockchain_poc_target_v5.erl @@ -14,7 +14,6 @@ -export([ target_zone/2, - gateways_for_zone/5, target/5 ]). diff --git a/src/poc/blockchain_poc_target_v6.erl b/src/poc/blockchain_poc_target_v6.erl index bc6a4b8510..47eef7794c 100644 --- a/src/poc/blockchain_poc_target_v6.erl +++ b/src/poc/blockchain_poc_target_v6.erl @@ -14,7 +14,6 @@ -export([ target_zone/2, - gateways_for_zone/5, target/5 ]). From 22324be747844c8829cfe869fd27b84763c2c547 Mon Sep 17 00:00:00 2001 From: Jade Allen Date: Mon, 6 Jun 2022 23:15:21 -0500 Subject: [PATCH 16/48] Pass through force_network_id Erlang env var --- src/blockchain_sup.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/blockchain_sup.erl b/src/blockchain_sup.erl index 186b25af92..d1120941fb 100644 --- a/src/blockchain_sup.erl +++ b/src/blockchain_sup.erl @@ -94,6 +94,7 @@ init(Args) -> [{signed_metadata_fun, MetadataFun}, {notify_peer_gossip_limit, application:get_env(blockchain, gossip_width, 100)}, {notify_time, application:get_env(blockchain, peerbook_update_interval, timer:minutes(5))}, + {force_network_id, application:get_env(blockchain, force_network_id, undefined)}, {allow_rfc1918, application:get_env(blockchain, peerbook_allow_rfc1918, false)} ]}, {libp2p_group_gossip, From c21c536f9a0be5f1a61445055b1eb85c39067f79 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Tue, 7 Jun 2022 08:21:35 -0700 Subject: [PATCH 17/48] Don't invalidate h3->region lookups on var nonce change Previously all the h3->region lookups would be invalidated when any chain var was changed. This was very expensive when unrelated vars changed. This patch changes things so that the lookup is keyed by the hash of the h3 region binaries, so as long as those do not change the cache should not be invalidated. It also adds caching for the h3 region binary hashes and for the h3 binaries themselves. --- src/region/blockchain_region_v1.erl | 81 +++++++++++++++++------------ 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/src/region/blockchain_region_v1.erl b/src/region/blockchain_region_v1.erl index 31a8876aa5..09047f0231 100644 --- a/src/region/blockchain_region_v1.erl +++ b/src/region/blockchain_region_v1.erl @@ -38,21 +38,28 @@ get_all_regions(Ledger) -> -spec get_all_region_bins(Ledger :: blockchain_ledger_v1:ledger()) -> {ok, [{atom(), binary() | {error, any()}}]} | {error, any()}. get_all_region_bins(Ledger) -> - case get_all_regions(Ledger) of - {ok, Regions} -> - Map = lists:foldl( - fun(Reg, Acc) -> - case blockchain:config(Reg, Ledger) of - {ok, Bin} -> - [{Reg, Bin}|Acc]; - _ -> - [{error, {region_var_not_set, Reg}}|Acc] - end - end, [], Regions), - {ok, lists:reverse(Map)}; - Error -> - Error - end. + {ok, VarsNonce} = blockchain_ledger_v1:vars_nonce(Ledger), + HasAux = blockchain_ledger_v1:has_aux(Ledger), + e2qc:cache( + ?H3_TO_REGION_CACHE, + {region_bins, HasAux, VarsNonce}, + fun() -> + case get_all_regions(Ledger) of + {ok, Regions} -> + Map = lists:foldl( + fun(Reg, Acc) -> + case blockchain:config(Reg, Ledger) of + {ok, Bin} -> + [{Reg, Bin}|Acc]; + _ -> + [{error, {region_var_not_set, Reg}}|Acc] + end + end, [], Regions), + {ok, lists:reverse(Map)}; + Error -> + Error + end + end). -spec h3_to_region(H3 :: h3:h3_index(), Ledger :: blockchain_ledger_v1:ledger()) -> {ok, atom()} | {error, any()}. @@ -69,23 +76,33 @@ h3_to_region(H3, Ledger, RegionBins) -> Res = polyfill_resolution(Ledger), HasAux = blockchain_ledger_v1:has_aux(Ledger), Parent = h3:parent(H3, Res), - e2qc:cache( - ?H3_TO_REGION_CACHE, - {HasAux, VarsNonce, Parent}, - fun() -> - MaybeBins = - case RegionBins of - no_prefetch -> get_all_region_bins(Ledger); - {error, _} = Err -> Err; - B -> {ok, B} - end, - case MaybeBins of - {ok, Bins} -> - h3_to_region_(Parent, Bins); - {error, _} = Error -> Error - end - end - ). + MaybeBins = + case RegionBins of + no_prefetch -> get_all_region_bins(Ledger); + {error, _} = Err -> Err; + B -> {ok, B} + end, + case MaybeBins of + {error, _} = Error -> Error; + {ok, Bins} -> + %% use a hash of the region bins as the cache key + %% so if any region changes the lookup will be recalculated + %% + %% We can cache the hash of the bins because that + %% is much cheaper to recalculate if invalidated + Hash = e2qc:cache(?H3_TO_REGION_CACHE, + {bin_hash, HasAux, VarsNonce}, + fun() -> + crypto:hash(sha256, list_to_binary(Bins)) + end), + e2qc:cache( + ?H3_TO_REGION_CACHE, + {HasAux, Hash, Parent}, + fun() -> + h3_to_region_(Parent, Bins) + end + ) + end. -spec h3_in_region( H3 :: h3:h3_index(), From 850006ba2018eb86709655d1aa3967c45d4fc5de Mon Sep 17 00:00:00 2001 From: Evan Vigil-McClanahan Date: Tue, 7 Jun 2022 19:05:57 -0700 Subject: [PATCH 18/48] use the proper binary convert function --- src/region/blockchain_region_v1.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/region/blockchain_region_v1.erl b/src/region/blockchain_region_v1.erl index 09047f0231..6310c68cf3 100644 --- a/src/region/blockchain_region_v1.erl +++ b/src/region/blockchain_region_v1.erl @@ -93,7 +93,7 @@ h3_to_region(H3, Ledger, RegionBins) -> Hash = e2qc:cache(?H3_TO_REGION_CACHE, {bin_hash, HasAux, VarsNonce}, fun() -> - crypto:hash(sha256, list_to_binary(Bins)) + crypto:hash(sha256, term_to_binary(Bins)) end), e2qc:cache( ?H3_TO_REGION_CACHE, From 05a8c518e67d1741049b54ba7f9125e21d68b53f Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Wed, 8 Jun 2022 11:14:16 +0100 Subject: [PATCH 19/48] fix response format --- src/poc/blockchain_poc_target_v6.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/poc/blockchain_poc_target_v6.erl b/src/poc/blockchain_poc_target_v6.erl index 47eef7794c..1102961b3c 100644 --- a/src/poc/blockchain_poc_target_v6.erl +++ b/src/poc/blockchain_poc_target_v6.erl @@ -184,7 +184,7 @@ find_active_addr_([Addr | Tail], FilterEnabled, Height, MaxActivityAge, Ledger) Ledger ) of true -> - Addr; + {ok, Addr}; _ -> find_active_addr_(Tail, FilterEnabled, Height, MaxActivityAge, Ledger) end. From 4933807e7e635db20131823c8ed37e4239009d37 Mon Sep 17 00:00:00 2001 From: mikev Date: Wed, 8 Jun 2022 12:08:32 -0700 Subject: [PATCH 20/48] remove FIXME ifdef --- src/transactions/blockchain_txn.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/transactions/blockchain_txn.erl b/src/transactions/blockchain_txn.erl index 2050ca0180..f663f3a87d 100644 --- a/src/transactions/blockchain_txn.erl +++ b/src/transactions/blockchain_txn.erl @@ -1378,7 +1378,6 @@ txn_fees_oui_test() -> ?assertEqual(1, Txn04LegacyStakingFee), ok. --ifdef(FIXME). txn_fees_routing_update_router_test() -> {timeout, 30000, fun() -> From 47d0240da29b4885c890ba20412dbecf84d1e8c3 Mon Sep 17 00:00:00 2001 From: jeffgrunewald Date: Thu, 9 Jun 2022 17:49:58 -0400 Subject: [PATCH 21/48] upgrade libp2p for removal of old ledger handling code --- rebar.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.lock b/rebar.lock index 53646e69d6..1f241846a4 100644 --- a/rebar.lock +++ b/rebar.lock @@ -92,7 +92,7 @@ {<<"lager">>,{pkg,<<"lager">>,<<"3.9.2">>},0}, {<<"libp2p">>, {git,"https://github.com/helium/erlang-libp2p.git", - {ref,"95fd37f967e6d83b9fd7796ef7d060abff47a018"}}, + {ref,"f2a33f4165b1597adb26b429f54dc50dd4afee22"}}, 0}, {<<"libp2p_crypto">>, {git,"https://github.com/helium/libp2p-crypto.git", From 097ca8cce8d1025b9468a96c7151f1e2b4393644 Mon Sep 17 00:00:00 2001 From: Evan Vigil-McClanahan Date: Fri, 17 Jun 2022 14:07:31 -0700 Subject: [PATCH 22/48] Revert "Don't invalidate h3->region lookups on var nonce change" This reverts commit c21c536f9a0be5f1a61445055b1eb85c39067f79. --- src/region/blockchain_region_v1.erl | 81 ++++++++++++----------------- 1 file changed, 32 insertions(+), 49 deletions(-) diff --git a/src/region/blockchain_region_v1.erl b/src/region/blockchain_region_v1.erl index 6310c68cf3..31a8876aa5 100644 --- a/src/region/blockchain_region_v1.erl +++ b/src/region/blockchain_region_v1.erl @@ -38,28 +38,21 @@ get_all_regions(Ledger) -> -spec get_all_region_bins(Ledger :: blockchain_ledger_v1:ledger()) -> {ok, [{atom(), binary() | {error, any()}}]} | {error, any()}. get_all_region_bins(Ledger) -> - {ok, VarsNonce} = blockchain_ledger_v1:vars_nonce(Ledger), - HasAux = blockchain_ledger_v1:has_aux(Ledger), - e2qc:cache( - ?H3_TO_REGION_CACHE, - {region_bins, HasAux, VarsNonce}, - fun() -> - case get_all_regions(Ledger) of - {ok, Regions} -> - Map = lists:foldl( - fun(Reg, Acc) -> - case blockchain:config(Reg, Ledger) of - {ok, Bin} -> - [{Reg, Bin}|Acc]; - _ -> - [{error, {region_var_not_set, Reg}}|Acc] - end - end, [], Regions), - {ok, lists:reverse(Map)}; - Error -> - Error - end - end). + case get_all_regions(Ledger) of + {ok, Regions} -> + Map = lists:foldl( + fun(Reg, Acc) -> + case blockchain:config(Reg, Ledger) of + {ok, Bin} -> + [{Reg, Bin}|Acc]; + _ -> + [{error, {region_var_not_set, Reg}}|Acc] + end + end, [], Regions), + {ok, lists:reverse(Map)}; + Error -> + Error + end. -spec h3_to_region(H3 :: h3:h3_index(), Ledger :: blockchain_ledger_v1:ledger()) -> {ok, atom()} | {error, any()}. @@ -76,33 +69,23 @@ h3_to_region(H3, Ledger, RegionBins) -> Res = polyfill_resolution(Ledger), HasAux = blockchain_ledger_v1:has_aux(Ledger), Parent = h3:parent(H3, Res), - MaybeBins = - case RegionBins of - no_prefetch -> get_all_region_bins(Ledger); - {error, _} = Err -> Err; - B -> {ok, B} - end, - case MaybeBins of - {error, _} = Error -> Error; - {ok, Bins} -> - %% use a hash of the region bins as the cache key - %% so if any region changes the lookup will be recalculated - %% - %% We can cache the hash of the bins because that - %% is much cheaper to recalculate if invalidated - Hash = e2qc:cache(?H3_TO_REGION_CACHE, - {bin_hash, HasAux, VarsNonce}, - fun() -> - crypto:hash(sha256, term_to_binary(Bins)) - end), - e2qc:cache( - ?H3_TO_REGION_CACHE, - {HasAux, Hash, Parent}, - fun() -> - h3_to_region_(Parent, Bins) - end - ) - end. + e2qc:cache( + ?H3_TO_REGION_CACHE, + {HasAux, VarsNonce, Parent}, + fun() -> + MaybeBins = + case RegionBins of + no_prefetch -> get_all_region_bins(Ledger); + {error, _} = Err -> Err; + B -> {ok, B} + end, + case MaybeBins of + {ok, Bins} -> + h3_to_region_(Parent, Bins); + {error, _} = Error -> Error + end + end + ). -spec h3_in_region( H3 :: h3:h3_index(), From 8e1f348f87f5e6f9fdd1325f6ba58e5d6954b625 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Mon, 20 Jun 2022 16:21:10 -0400 Subject: [PATCH 23/48] Fix unclosed rocks iterators --- src/blockchain.erl | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/blockchain.erl b/src/blockchain.erl index d39c7ee0d8..47cf9faceb 100644 --- a/src/blockchain.erl +++ b/src/blockchain.erl @@ -892,12 +892,18 @@ find_first_height_after(MinHeight0, #blockchain{db=DB, heights=HeightsCF}) -> MinHeight = max(0, MinHeight0), {ok, Iter} = rocksdb:iterator(DB, HeightsCF, []), rocksdb:iterator_move(Iter, {seek, <<(MinHeight):64/integer-unsigned-big>>}), - case rocksdb:iterator_move(Iter, next) of - {ok, <>, Hash} -> - {ok, Height, Hash}; - {error, _} -> - {error, not_found} - end. + Result = + case rocksdb:iterator_move(Iter, next) of + {ok, <>, Hash} -> + {ok, Height, Hash}; + {error, invalid_iterator} -> + {error, not_found}; + {error, Reason} -> + lager:error("Unexpected iterator error: ~p", [Reason]), + {error, not_found} + end, + catch rocksdb:iterator_close(Iter), + Result. find_first_block_after(MinHeight, Blockchain) -> case find_first_height_after(MinHeight, Blockchain) of @@ -2082,10 +2088,17 @@ rocksdb_gc(BytesToDrop, #blockchain{db=DB, heights=HeightsCF}=Blockchain) -> CutoffHeight = max(2, Height - application:get_env(blockchain, blocks_to_protect_from_gc, 10000)), %% start at 2 here so we don't GC the genesis block {ok, Itr} = rocksdb:iterator(DB, HeightsCF, [{iterate_lower_bound, <<2:64/integer-unsigned-big>>}, {iterate_upper_bound, <>}]), - do_rocksdb_gc(BytesToDrop, Itr, Blockchain, rocksdb:iterator_move(Itr, first)). + ok = do_rocksdb_gc(BytesToDrop, Itr, Blockchain, rocksdb:iterator_move(Itr, first)), + catch rocksdb:iterator_close(Itr), + ok. -do_rocksdb_gc(_Bytes, _Itr, _Blockchain, {error, _}) -> - ok; +do_rocksdb_gc(_, _, _, {error, Reason}) -> + case Reason of + invalid_iterator -> + ok; + _ -> + lager:error("Unexpected iterator error: ~p", [Reason]) + end; do_rocksdb_gc(Bytes, _Itr, _Blockchain, _Res) when Bytes < 1 -> ok; do_rocksdb_gc(Bytes, Itr, #blockchain{dir=Dir, db=DB, heights=HeightsCF, blocks=BlocksCF, snapshots=SnapshotsCF}=Blockchain, {ok, <>=Height, Hash}) -> From bf185b2de6a19fb0204638cecbe52ff936064168 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Mon, 20 Jun 2022 17:09:15 -0400 Subject: [PATCH 24/48] Don't warn on normal iterator error --- src/ledger/v1/blockchain_ledger_v1.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index 1a754c4a44..0fc5a2bbfe 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -2190,6 +2190,8 @@ promote_proposals(K, BlockHash, BlockHeight, POCValKeyProposalTimeout, Acc end end; + {error, invalid_iterator} -> % No reason to panic here - just end of stream. + Acc; {error, _Reason} -> lager:warning("promote_proposals failed, iterator failed ~p", [_Reason]), %% we probably fell off the end. Simply drop this as we may not have enough From 5d9aeb2bffe20c86eb8a3c006e04354756071a42 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Tue, 21 Jun 2022 08:22:40 -0400 Subject: [PATCH 25/48] Log any rocksdb:iterator_close errors --- src/blockchain.erl | 7 ++++--- src/blockchain_rocks.hrl | 10 ++++++++++ src/ledger/v1/blockchain_aux_ledger_v1.erl | 11 ++++++----- src/ledger/v1/blockchain_ledger_v1.erl | 9 +++++---- .../blockchain_state_channels_client.erl | 3 ++- 5 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 src/blockchain_rocks.hrl diff --git a/src/blockchain.erl b/src/blockchain.erl index 47cf9faceb..1f22826f3c 100644 --- a/src/blockchain.erl +++ b/src/blockchain.erl @@ -87,6 +87,7 @@ ]). -include("blockchain.hrl"). +-include("blockchain_rocks.hrl"). -include("blockchain_vars.hrl"). -ifdef(TEST). @@ -902,7 +903,7 @@ find_first_height_after(MinHeight0, #blockchain{db=DB, heights=HeightsCF}) -> lager:error("Unexpected iterator error: ~p", [Reason]), {error, not_found} end, - catch rocksdb:iterator_close(Iter), + ?ROCKSDB_ITERATOR_CLOSE(Iter), Result. find_first_block_after(MinHeight, Blockchain) -> @@ -2089,7 +2090,7 @@ rocksdb_gc(BytesToDrop, #blockchain{db=DB, heights=HeightsCF}=Blockchain) -> %% start at 2 here so we don't GC the genesis block {ok, Itr} = rocksdb:iterator(DB, HeightsCF, [{iterate_lower_bound, <<2:64/integer-unsigned-big>>}, {iterate_upper_bound, <>}]), ok = do_rocksdb_gc(BytesToDrop, Itr, Blockchain, rocksdb:iterator_move(Itr, first)), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), ok. do_rocksdb_gc(_, _, _, {error, Reason}) -> @@ -3023,7 +3024,7 @@ check_plausible_blocks(#blockchain{db=DB}=Chain, GossipedHash) -> get_plausible_blocks(#blockchain{db=DB, plausible_blocks=CF}) -> {ok, Itr} = rocksdb:iterator(DB, CF, []), Res = get_plausible_blocks(Itr, rocksdb:iterator_move(Itr, first), []), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), Res. get_plausible_blocks(_Itr, {error, _}, Acc) -> diff --git a/src/blockchain_rocks.hrl b/src/blockchain_rocks.hrl new file mode 100644 index 0000000000..6971a09ea9 --- /dev/null +++ b/src/blockchain_rocks.hrl @@ -0,0 +1,10 @@ +-define(ROCKSDB_ITERATOR_CLOSE(IteratorHandle), + %% Wrapping in fun to avoid scope leakage: + (fun() -> + try + rocksdb:iterator_close(IteratorHandle) + catch ErrorClass:ErrorReason -> + lager:error("rocksdb:iterator_close error: ~p:~p", [ErrorClass, ErrorReason]) + end + end)() +). diff --git a/src/ledger/v1/blockchain_aux_ledger_v1.erl b/src/ledger/v1/blockchain_aux_ledger_v1.erl index 5c6495abba..8e776739ea 100644 --- a/src/ledger/v1/blockchain_aux_ledger_v1.erl +++ b/src/ledger/v1/blockchain_aux_ledger_v1.erl @@ -30,6 +30,7 @@ ]). -include("blockchain_vars.hrl"). +-include("blockchain_rocks.hrl"). -include("blockchain_ledger_v1.hrl"). -include_lib("helium_proto/include/blockchain_txn_rewards_v2_pb.hrl"). @@ -484,7 +485,7 @@ overall_diff_rewards_md_sums(Ledger) -> [] ), Res = overall_diff_rewards_md_sums_(rocksdb:iterator_move(Itr, last), #{}), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), Res. %% ================================================================== @@ -498,7 +499,7 @@ get_rewards_md_(Ledger) -> [] ), Res = get_aux_rewards_md_(Itr, rocksdb:iterator_move(Itr, first), #{}), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), Res. get_aux_rewards_md_(_Itr, {error, _}, Acc) -> @@ -527,7 +528,7 @@ get_rewards_md_sums_(Ledger) -> [] ), Res = get_aux_rewards_md_sums_(Itr, rocksdb:iterator_move(Itr, first), #{}), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), Res. get_aux_rewards_md_sums_(_Itr, {error, _}, Acc) -> @@ -556,7 +557,7 @@ get_rewards_md_diff_(Ledger) -> [] ), Res = get_aux_rewards_md_diff_(Itr, rocksdb:iterator_move(Itr, first), #{}), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), Res. get_aux_rewards_md_diff_(_Itr, {error, _}, Acc) -> @@ -722,7 +723,7 @@ get_rewards_(Ledger) -> [] ), Res = get_rewards_(Itr, rocksdb:iterator_move(Itr, first), #{}), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), Res. get_rewards_(_Itr, {error, _}, Acc) -> diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index 0fc5a2bbfe..2c3370d598 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -273,6 +273,7 @@ -include("blockchain.hrl"). -include("blockchain_ledger_v1.hrl"). +-include("blockchain_rocks.hrl"). -include("blockchain_vars.hrl"). -include("blockchain_caps.hrl"). -include("blockchain_txn_fees.hrl"). @@ -2143,7 +2144,7 @@ process_poc_proposals(BlockHeight, BlockHash, Ledger) -> {ok, Itr} = rocksdb:iterator(DB, CF, []), POCSubset = promote_proposals(K, BlockHash, BlockHeight, POCValKeyProposalTimeout, ProposalGCWindowCheck, RandState, Ledger, Name, Itr, []), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), lager:debug("Selected POCs ~p", [POCSubset]), lager:info("Selected ~p POCs for block height ~p", [length(POCSubset), BlockHeight]), @@ -3601,7 +3602,7 @@ find_routing_via_devaddr(DevAddr0, Ledger) -> {_Name, DB, SubnetCF} = subnets_cf(Ledger), {ok, Itr} = rocksdb:iterator(DB, SubnetCF, []), Dest = subnet_lookup(Itr, DevAddr, rocksdb:iterator_move(Itr, {seek_for_prev, <>})), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), case Dest of error -> {error, subnet_not_found}; @@ -3811,7 +3812,7 @@ allocate_subnet(Size, Ledger) -> {_, DB, SubnetCF} = subnets_cf(Ledger), {ok, Itr} = rocksdb:iterator(DB, SubnetCF, []), Result = allocate_subnet(Size, Itr, rocksdb:iterator_move(Itr, first), none), - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), Result. allocate_subnet(Size, _Itr, {error, invalid_iterator}, none) -> @@ -4369,7 +4370,7 @@ rocks_fold(Ledger, DB, CF, Opts0, Fun, Acc) -> %% catch _:_ -> %% Acc after - catch rocksdb:iterator_close(Itr) + ?ROCKSDB_ITERATOR_CLOSE(Itr) end. mk_cache_fold_fun(Cache, CF, Start, End, Fun) -> diff --git a/src/state_channel/blockchain_state_channels_client.erl b/src/state_channel/blockchain_state_channels_client.erl index 069a65e7e1..f72b32c94c 100644 --- a/src/state_channel/blockchain_state_channels_client.erl +++ b/src/state_channel/blockchain_state_channels_client.erl @@ -30,6 +30,7 @@ code_change/3]). -include("blockchain.hrl"). +-include("blockchain_rocks.hrl"). -include("blockchain_vars.hrl"). -define(SERVER, ?MODULE). @@ -933,7 +934,7 @@ state_channels(#state{db=DB, cf=CF}) -> state_channels(Itr, rocksdb:iterator_move(Itr, first), []). state_channels(Itr, {error, invalid_iterator}, Acc) -> - catch rocksdb:iterator_close(Itr), + ?ROCKSDB_ITERATOR_CLOSE(Itr), Acc; state_channels(Itr, {ok, _, SCBin}, Acc) -> state_channels(Itr, rocksdb:iterator_move(Itr, next), [binary_to_term(SCBin)|Acc]). From 773b0de296f3eeadf23862f1399d490af301cd61 Mon Sep 17 00:00:00 2001 From: Evan Vigil-McClanahan Date: Tue, 21 Jun 2022 12:06:05 -0700 Subject: [PATCH 26/48] automatically prewarm region cache on startup and var txn --- src/blockchain_worker.erl | 9 +++++++++ src/region/blockchain_region_v1.erl | 18 +++++++++++++++++- src/transactions/v1/blockchain_txn_vars_v1.erl | 13 ++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 01b035a687..9c1e07e606 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -389,6 +389,15 @@ init(Args) -> gossip_ref = Ref}, {Mode, Info} = get_sync_mode(NewState), SnapshotTimerRef = schedule_snapshot_timer(), + case application:get_env(blockchain, disable_prewarm, false) of + true -> ok; + false -> + Ledger = blockchain:ledger(Blockchain), + spawn(fun() -> + timer:sleep(90000), + blockchain_region_v1:prewarm_cache(Ledger) + end) + end, {ok, NewState#state{snapshot_timer=SnapshotTimerRef, mode=Mode, snapshot_info=Info}}. handle_call({integrate_genesis_block_synchronously, GenesisBlock}, _From, #state{}=S0) -> diff --git a/src/region/blockchain_region_v1.erl b/src/region/blockchain_region_v1.erl index 31a8876aa5..60f3411f67 100644 --- a/src/region/blockchain_region_v1.erl +++ b/src/region/blockchain_region_v1.erl @@ -10,7 +10,9 @@ -export([ get_all_regions/1, get_all_region_bins/1, h3_to_region/2, h3_to_region/3, - h3_in_region/3, h3_in_region/4 + h3_in_region/3, h3_in_region/4, + + prewarm_cache/1 ]). -type regions() :: [atom()]. @@ -165,3 +167,17 @@ polyfill_resolution(Ledger) -> {ok, Res} -> Res; _ -> ?POLYFILL_RESOLUTION end. + +prewarm_cache(Ledger) -> + {ok, RB} = get_all_region_bins(Ledger), + blockchain_ledger_v1:cf_fold( + active_gateways, + fun({_, BG}, Acc) -> + G = blockchain_ledger_gateway_v2:deserialize(BG), + case blockchain_ledger_gateway_v2:location(G) of + undefined -> Acc; + Loc -> _ = h3_to_region(Loc, Ledger, RB) + end + end, + 0, Ledger), + ok. diff --git a/src/transactions/v1/blockchain_txn_vars_v1.erl b/src/transactions/v1/blockchain_txn_vars_v1.erl index ed7ae20ab0..9b4c80bd30 100644 --- a/src/transactions/v1/blockchain_txn_vars_v1.erl +++ b/src/transactions/v1/blockchain_txn_vars_v1.erl @@ -615,7 +615,18 @@ delayed_absorb(Txn, Ledger) -> Key -> ok = blockchain_ledger_v1:master_key(Key, Ledger) end - end. + end, + case blockchain_ledger_v1:mode(Ledger) of + active -> + %% we've invalidated the region cache, so prewarm it. + spawn(fun() -> + timer:sleep(30000), + blockchain_region_v1:prewarm_cache(Ledger) + end); + _ -> + ok + end, + ok. sum_higher(Target, Proplist) -> sum_higher(Target, Proplist, 0). From 7bce1af886a8c692b2939fe58b4ac55067677295 Mon Sep 17 00:00:00 2001 From: Jade Allen Date: Tue, 21 Jun 2022 16:18:42 -0500 Subject: [PATCH 27/48] Handle no hexes on a new network Previously, this would cause a crash in sibyl. Now if there are no hexes available, we return an empty list instead of crashing. --- src/poc/blockchain_poc_target_v5.erl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/poc/blockchain_poc_target_v5.erl b/src/poc/blockchain_poc_target_v5.erl index 7965392137..d932aa35c9 100644 --- a/src/poc/blockchain_poc_target_v5.erl +++ b/src/poc/blockchain_poc_target_v5.erl @@ -177,8 +177,12 @@ prob_randomness_wt(Vars) -> -spec sorted_hex_list(Ledger :: blockchain_ledger_v1:ledger()) -> [h3:h3_index()]. sorted_hex_list(Ledger) -> %% Grab the list of parent hexes - {ok, Hexes} = blockchain_ledger_v1:get_hexes(Ledger), - lists:keysort(1, maps:to_list(Hexes)). + %% + %% On a new network, there will be _no_ hexes + case blockchain_ledger_v1:get_hexes(Ledger) of + {ok, Hexes} -> lists:keysort(1, maps:to_list(Hexes)); + _ -> [] + end. -spec choose_zone( RandState :: rand:state(), From 79cc96d05f881fd5aff2b75fc2e9ca1395db0cbf Mon Sep 17 00:00:00 2001 From: PaulVMo Date: Sun, 19 Jun 2022 13:32:31 -0500 Subject: [PATCH 28/48] Add cache cli --- scripts/extensions/ledger | 5 +++- scripts/extensions/peer | 5 +++- scripts/extensions/repair | 5 +++- scripts/extensions/sc | 5 +++- scripts/extensions/snapshot | 5 +++- scripts/extensions/trace | 5 +++- scripts/extensions/txn | 5 +++- src/cli/blockchain_cli_ledger.erl | 39 ++++++++++++++++++++++++++++- src/region/blockchain_region_v1.erl | 13 +++++++++- 9 files changed, 78 insertions(+), 9 deletions(-) diff --git a/scripts/extensions/ledger b/scripts/extensions/ledger index 1357b2e50f..ae77c19c8e 100644 --- a/scripts/extensions/ledger +++ b/scripts/extensions/ledger @@ -4,7 +4,10 @@ if [ -t 0 ] ; then export CLIQUE_COLUMNS fi -export RELX_RPC_TIMEOUT=${RELX_RPC_TIMEOUT:-900} +# extend relx default timeout (60s) if it has not already been overwritten +if [ $RELX_RPC_TIMEOUT -eq 60 ] ; then + export RELX_RPC_TIMEOUT=900 +fi j=1 l=$# diff --git a/scripts/extensions/peer b/scripts/extensions/peer index 035c9579f3..9e693c702c 100644 --- a/scripts/extensions/peer +++ b/scripts/extensions/peer @@ -4,7 +4,10 @@ if [ -t 0 ] ; then export CLIQUE_COLUMNS fi -export RELX_RPC_TIMEOUT=${RELX_RPC_TIMEOUT:-900} +# extend relx default timeout (60s) if it has not already been overwritten +if [ $RELX_RPC_TIMEOUT -eq 60 ] ; then + export RELX_RPC_TIMEOUT=900 +fi j=1 l=$# diff --git a/scripts/extensions/repair b/scripts/extensions/repair index e3ecef81c0..4dc636f2ec 100644 --- a/scripts/extensions/repair +++ b/scripts/extensions/repair @@ -4,7 +4,10 @@ if [ -t 0 ] ; then export CLIQUE_COLUMNS fi -export RELX_RPC_TIMEOUT=${RELX_RPC_TIMEOUT:-900} +# extend relx default timeout (60s) if it has not already been overwritten +if [ $RELX_RPC_TIMEOUT -eq 60 ] ; then + export RELX_RPC_TIMEOUT=900 +fi j=1 l=$# diff --git a/scripts/extensions/sc b/scripts/extensions/sc index 463a779363..39d33693bc 100644 --- a/scripts/extensions/sc +++ b/scripts/extensions/sc @@ -4,7 +4,10 @@ if [ -t 0 ] ; then export CLIQUE_COLUMNS fi -export RELX_RPC_TIMEOUT=${RELX_RPC_TIMEOUT:-900} +# extend relx default timeout (60s) if it has not already been overwritten +if [ $RELX_RPC_TIMEOUT -eq 60 ] ; then + export RELX_RPC_TIMEOUT=900 +fi j=1 l=$# diff --git a/scripts/extensions/snapshot b/scripts/extensions/snapshot index 6c21368ef4..b95789ffdc 100644 --- a/scripts/extensions/snapshot +++ b/scripts/extensions/snapshot @@ -4,7 +4,10 @@ if [ -t 0 ] ; then export CLIQUE_COLUMNS fi -export RELX_RPC_TIMEOUT=${RELX_RPC_TIMEOUT:-900} +# extend relx default timeout (60s) if it has not already been overwritten +if [ $RELX_RPC_TIMEOUT -eq 60 ] ; then + export RELX_RPC_TIMEOUT=900 +fi j=1 l=$# diff --git a/scripts/extensions/trace b/scripts/extensions/trace index 2052d7e737..b8106cc5fc 100644 --- a/scripts/extensions/trace +++ b/scripts/extensions/trace @@ -4,7 +4,10 @@ if [ -t 0 ] ; then export CLIQUE_COLUMNS fi -export RELX_RPC_TIMEOUT=${RELX_RPC_TIMEOUT:-900} +# extend relx default timeout (60s) if it has not already been overwritten +if [ $RELX_RPC_TIMEOUT -eq 60 ] ; then + export RELX_RPC_TIMEOUT=900 +fi j=1 l=$# diff --git a/scripts/extensions/txn b/scripts/extensions/txn index f0f2034b19..48c94e0589 100644 --- a/scripts/extensions/txn +++ b/scripts/extensions/txn @@ -4,7 +4,10 @@ if [ -t 0 ] ; then export CLIQUE_COLUMNS fi -export RELX_RPC_TIMEOUT=${RELX_RPC_TIMEOUT:-900} +# extend relx default timeout (60s) if it has not already been overwritten +if [ $RELX_RPC_TIMEOUT -eq 60 ] ; then + export RELX_RPC_TIMEOUT=900 +fi j=1 l=$# diff --git a/src/cli/blockchain_cli_ledger.erl b/src/cli/blockchain_cli_ledger.erl index 6aa8ac3c8c..5fac04a4b9 100644 --- a/src/cli/blockchain_cli_ledger.erl +++ b/src/cli/blockchain_cli_ledger.erl @@ -23,6 +23,7 @@ register_all_usage() -> ledger_balance_usage(), ledger_export_usage(), ledger_gateways_usage(), + ledger_cache_usage(), ledger_validators_usage(), ledger_variables_usage(), ledger_usage() @@ -36,6 +37,7 @@ register_all_cmds() -> ledger_balance_cmd(), ledger_export_cmd(), ledger_gateways_cmd(), + ledger_cache_cmd(), ledger_validators_cmd(), ledger_variables_cmd(), ledger_cmd() @@ -48,8 +50,10 @@ ledger_usage() -> [["ledger"], ["blockchain ledger commands\n\n", " ledger balance - Get the balance for one or all addresses.\n" + " ledger cache - Pre-load or clear h3 hex to region cache.\n" " ledger export - Export transactions from the ledger to .\n" " ledger gateways - Display the list of active gateways.\n" + " ledger validators - Retrieve all validators and a table of information about them.\n" " ledger variables - Interact with chain variables.\n" ] ]. @@ -127,6 +131,34 @@ get_ledger() -> blockchain:ledger(Chain) end. + +%%-------------------------------------------------------------------- +%% ledger cache +%%-------------------------------------------------------------------- +ledger_cache_cmd() -> + [ + [["ledger", "cache", '*'], [], [], fun ledger_cache/3] + ]. + +ledger_cache_usage() -> + [["ledger", "cache"], + ["ledger cache [ prewarm | clear ]\n\n", + " ledger cache prewarm - Pre-load h3 hex-to-region mapping for all gateways into cache.\n" + " ledger cache clear - Clear h3 hex-to-region cache.\n" + ] + ]. + +ledger_cache(["ledger","cache","prewarm"], [], []) -> + Ledger = blockchain:ledger(), + Ret = blockchain_region_v1:prewarm_cache(Ledger), + [clique_status:text(io_lib:format("~p", [Ret]))]; +ledger_cache(["ledger","cache","clear"], [], []) -> + Ret = blockchain_region_v1:clear_cache(), + [clique_status:text(io_lib:format("~p", [Ret]))]; +ledger_cache(["ledger","cache",_], [], []) -> + ledger_cache_usage(). + + %%-------------------------------------------------------------------- %% ledger export %%-------------------------------------------------------------------- @@ -192,6 +224,10 @@ ledger_gw_fold(Verbose) -> [], Ledger). + +%%-------------------------------------------------------------------- +%% ledger validators +%%-------------------------------------------------------------------- ledger_validators_cmd() -> [ [["ledger", "validators"], [], [], fun ledger_validators/3], @@ -237,8 +273,9 @@ format_ledger_validator(ValAddr, Validator, Ledger, Verbose) -> [{name, blockchain_utils:addr2name(ValAddr)} | blockchain_ledger_validator_v1:print(Validator, Height, Verbose, Ledger)]. +%%-------------------------------------------------------------------- %% ledger variables - +%%-------------------------------------------------------------------- ledger_variables_cmd() -> [ [["ledger", "variables", '*'], [], [], fun ledger_variables/3], diff --git a/src/region/blockchain_region_v1.erl b/src/region/blockchain_region_v1.erl index 60f3411f67..b0f7b57f11 100644 --- a/src/region/blockchain_region_v1.erl +++ b/src/region/blockchain_region_v1.erl @@ -12,7 +12,8 @@ h3_to_region/2, h3_to_region/3, h3_in_region/3, h3_in_region/4, - prewarm_cache/1 + prewarm_cache/1, + clear_cache/0 ]). -type regions() :: [atom()]. @@ -116,6 +117,7 @@ h3_in_region(H3, RegionVar, Ledger, RegionBins) -> Other -> Other end. + %%-------------------------------------------------------------------- %% helpers %%-------------------------------------------------------------------- @@ -169,6 +171,8 @@ polyfill_resolution(Ledger) -> end. prewarm_cache(Ledger) -> + lager:info("starting cache prewarm: ~p", [h3_to_region]), + Before = erlang:monotonic_time(second), {ok, RB} = get_all_region_bins(Ledger), blockchain_ledger_v1:cf_fold( active_gateways, @@ -180,4 +184,11 @@ prewarm_cache(Ledger) -> end end, 0, Ledger), + Duration = erlang:monotonic_time(second) - Before, + lager:info("completed cache prewarm in ~p seconds: ~p", [Duration, h3_to_region]), + ok. + +clear_cache() -> + e2qc:teardown(h3_to_region), + lager:info("cleared cache: ~p", [h3_to_region]), ok. From 3c53fd080c6edd26838a949dd791c0beb0f2002a Mon Sep 17 00:00:00 2001 From: Jade Allen Date: Thu, 23 Jun 2022 09:43:58 -0500 Subject: [PATCH 29/48] Ledger lookup happens in spawn --- src/blockchain_worker.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 9c1e07e606..7d2861c93b 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -392,9 +392,9 @@ init(Args) -> case application:get_env(blockchain, disable_prewarm, false) of true -> ok; false -> - Ledger = blockchain:ledger(Blockchain), spawn(fun() -> timer:sleep(90000), + Ledger = blockchain:ledger(), blockchain_region_v1:prewarm_cache(Ledger) end) end, From 84deb8fbd53bd997f197056e2c24459525653293 Mon Sep 17 00:00:00 2001 From: PaulVMo Date: Thu, 23 Jun 2022 13:25:31 -0500 Subject: [PATCH 30/48] h3dex remove gateway bugfix --- include/blockchain_vars.hrl | 3 +++ src/ledger/v1/blockchain_ledger_v1.erl | 26 ++++++++++++------- .../v1/blockchain_txn_vars_v1.erl | 5 ++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/include/blockchain_vars.hrl b/include/blockchain_vars.hrl index 03701cd0a8..4a4fcbfe9c 100644 --- a/include/blockchain_vars.hrl +++ b/include/blockchain_vars.hrl @@ -219,6 +219,9 @@ %% max number of hexes to GC in the h3dex per block: integer -define(h3dex_gc_width, h3dex_gc_width). +%% determines whether or not to use the fix for a bug in removing gateways from h3dex : boolean +-define(h3dex_remove_gw_fix, h3dex_remove_gw_fix). + %% the version number of poc targeting in use: integer %% if not set, code paths with default to 3 ( blockchain_poc_target_v3 ) -define(poc_targeting_version, poc_targeting_version). diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index 1a754c4a44..b13dc19c74 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -4883,16 +4883,24 @@ remove_gw_from_h3dex(Hex, GWAddr, Res, Ledger) -> {ok, BinGws} -> case lists:delete(GWAddr, binary_to_term(BinGws)) of [] -> - case count_gateways_in_hex(h3:parent(Hex, Res), Ledger) of - 0 -> - %% removing a hex means we need to recalculate the set of populated - %% hexes - build_random_hex_targeting_lookup(Res, Ledger); + %% need to remove the hex and maybe recalc targeting lookup if no gateways remain in parent hex + %% includes chain var protected bug fix + case config(?h3dex_remove_gw_fix, Ledger) of + %% if fix enabled, delete the hex first, then count the parent hex's gateways + {ok, true} -> + cache_delete(Ledger, H3CF, BinHex), + case count_gateways_in_hex(h3:parent(Hex, Res), Ledger) of + 0 -> build_random_hex_targeting_lookup(Res, Ledger); + _ -> ok + end; + %% otherwise, keep the wrong behavior of counting gateways then deleting the hex _ -> - ok - end, - - cache_delete(Ledger, H3CF, BinHex); + case count_gateways_in_hex(h3:parent(Hex, Res), Ledger) of + 0 -> build_random_hex_targeting_lookup(Res, Ledger); + _ -> ok + end, + cache_delete(Ledger, H3CF, BinHex) + end; NewGWs -> cache_put(Ledger, H3CF, BinHex, term_to_binary(lists:sort(NewGWs), [compressed])) end; diff --git a/src/transactions/v1/blockchain_txn_vars_v1.erl b/src/transactions/v1/blockchain_txn_vars_v1.erl index 5acbd0db42..2358a47d95 100644 --- a/src/transactions/v1/blockchain_txn_vars_v1.erl +++ b/src/transactions/v1/blockchain_txn_vars_v1.erl @@ -1132,6 +1132,11 @@ validate_var(?polyfill_resolution, Value) -> validate_int(Value, "polyfill_resolution", 0, 15, false); validate_var(?h3dex_gc_width, Value) -> validate_int(Value, "h3dex_gc_width", 1, 10000, false); +validate_var(?h3dex_remove_gw_fix, Value) -> + case Value of + Val when is_boolean(Val) -> ok; + _ -> throw({error, {h3dex_gw_remove_fix, Value}}) + end; validate_var(?poc_target_pool_size, Value) -> validate_int(Value, "poc_target_pool_size", 1, 1000000, false); validate_var(?poc_targeting_version, Value) -> From 95cc9ac773421eac894163c3dd554c5d5f2bb173 Mon Sep 17 00:00:00 2001 From: Jade Allen Date: Thu, 23 Jun 2022 16:14:27 -0500 Subject: [PATCH 31/48] Add function to convert B58 keypair to seed words --- src/blockchain_utils.erl | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/blockchain_utils.erl b/src/blockchain_utils.erl index ec7179462f..bcc2820b64 100644 --- a/src/blockchain_utils.erl +++ b/src/blockchain_utils.erl @@ -43,6 +43,7 @@ get_boolean_os_env_var/2, streaming_file_hash/1, streaming_transform_iolist/2, + b58_keys_to_seed_words/2, %% exports for simulations free_space_path_loss/4, @@ -60,6 +61,8 @@ init_var_cache/0, target_v_to_mod/1 + + ]). -define(FREQUENCY, 915). @@ -676,6 +679,30 @@ do_transform_iolist(L, Pos, End, Fun) -> Fun(lists:sublist(L, Pos, 32000)), do_transform_iolist(L, Pos+32000, End, Fun). +%% @doc Given a libp2p_crypto public+private keypair encoded as B58, +%% return the BIP39 seed words for private key import into the helium +%% wallet cli +%% +%% This is mostly useful for test networks where the genesis block +%% establishes a set of keypairs with premine tokens to be used for +%% things like asserting gateways and the like. +%% +%% Once you have the seed phrase, you can import the seed phrase to +%% the helium wallet by doing something like: +%% +%% helium-wallet create basic --seed bip39 0 1 +%% +%% The word list can be found at +%% https://github.com/helium/helium-wallet-rs/blob/master/src/mnemonic/wordlists/english.txt +b58_keys_to_seed_words(B58Key, WordPath) -> + {ok, WordsBlob} = file:read_file(WordPath), + WordList = string:tokens(erlang:binary_to_list(WordsBlob), "\n"), + + <<_Network:4, _KeyType:4, PrivVec:32/binary, _Rest/binary>> = base58:base58_to_binary(B58Key), + <> = crypto:hash(sha256, PrivVec), + WordIdxs = [ X || <> <= <> ], + [ lists:nth(X+1, WordList) || X <- WordIdxs ]. + majority(N) -> (N div 2) + 1. From 6834b370d2823a8ba039983909cfd57b755dc3b6 Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Mon, 20 Jun 2022 13:29:27 +0100 Subject: [PATCH 32/48] sc handler optimisations --- src/blockchain_sup.erl | 3 +- src/blockchain_worker.erl | 47 +++++++++++- .../blockchain_grpc_sc_server_handler.erl | 73 +++++++++++-------- .../blockchain_state_channel_common.erl | 11 +++ 4 files changed, 101 insertions(+), 33 deletions(-) diff --git a/src/blockchain_sup.erl b/src/blockchain_sup.erl index 4ce9aec757..e399e19146 100644 --- a/src/blockchain_sup.erl +++ b/src/blockchain_sup.erl @@ -114,7 +114,8 @@ init(Args) -> BWorkerOpts = [ {port, proplists:get_value(port, Args, 0)}, {base_dir, BaseDir}, - {update_dir, proplists:get_value(update_dir, Args, undefined)} + {update_dir, proplists:get_value(update_dir, Args, undefined)}, + {ets_cache, blockchain_worker:make_ets_table()} ], BEventOpts = [], diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 7d2861c93b..8a57dba8af 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -15,7 +15,9 @@ %% ------------------------------------------------------------------ -export([ start_link/1, + make_ets_table/0, blockchain/0, blockchain/1, + cached_blockchain/0, num_consensus_members/0, consensus_addrs/0, integrate_genesis_block/1, @@ -78,6 +80,9 @@ -define(WEEK_OLD_SECONDS, 7*24*60*60). %% a week's worth of seconds -define(MAX_ATTEMPTS, 3). +-define(CACHE, worker_cache). +-define(CHAIN, chain). + -ifdef(TEST). -define(SYNC_TIME, 1000). -else. @@ -121,7 +126,29 @@ %% API Function Definitions %% ------------------------------------------------------------------ start_link(Args) -> - gen_server:start_link({local, ?SERVER}, ?SERVER, Args, [{hibernate_after, 5000}]). + Res = server:start_link({local, ?SERVER}, ?SERVER, Args, [{hibernate_after, 5000}]), + case Res of + {ok, Pid} -> + %% if we have an ETS table reference, give ownership to the new process + %% we likely are the `heir', so we'll get it back if this process dies + case proplists:get_value(ets_cache, Args, not_found) of + not_found -> + %% should ever hit here but ok.... + ok; + Tab -> + true = ets:give_away(Tab, Pid, undefined) + end; + _ -> + ok + end, + Res. + + +make_ets_table() -> + ets:new(?CACHE, + [named_table, + protected, + {heir, self(), undefined}]). %%-------------------------------------------------------------------- %% @doc @@ -135,6 +162,14 @@ blockchain() -> blockchain(Chain) -> gen_server:call(?SERVER, {blockchain, Chain}, infinity). +-spec cached_blockchain() -> blockchain:blockchain() | undefined. +cached_blockchain() -> + try ets:lookup_element(?CACHE, ?CHAIN, 2) of + X -> X + catch + _:_ -> undefined + end. + %%-------------------------------------------------------------------- %% @doc %% @end @@ -387,6 +422,7 @@ init(Args) -> [ libp2p_swarm:listen(SwarmTID, Addr) || Addr <- ListenAddrs ]), NewState = #state{swarm_tid = SwarmTID, blockchain = Blockchain, gossip_ref = Ref}, + ok = ets:insert(?CACHE, {?CHAIN, Blockchain}), {Mode, Info} = get_sync_mode(NewState), SnapshotTimerRef = schedule_snapshot_timer(), case application:get_env(blockchain, disable_prewarm, false) of @@ -420,6 +456,7 @@ handle_call({blockchain, NewChain}, _From, #state{swarm_tid = SwarmTID} = State) notify({new_chain, NewChain}), remove_handlers(SwarmTID), {ok, GossipRef} = add_handlers(SwarmTID, NewChain), + ok = ets:insert(?CACHE, {?CHAIN, NewChain}), {reply, ok, State#state{blockchain = NewChain, gossip_ref = GossipRef}}; handle_call({new_ledger, Dir}, _From, #state{blockchain=Chain}=State) -> %% We do this here so the same process that normally owns the ledger @@ -486,6 +523,7 @@ handle_call({install_snapshot, Height, Hash, Snapshot, BinSnap}, _From, set_resyncing(ChainHeight, LedgerHeight, NewChain) end, blockchain_lock:release(), + ok = ets:insert(?CACHE, {?CHAIN, NewChain}), {reply, ok, maybe_sync(State#state{mode = normal, sync_paused = false, blockchain = NewChain, gossip_ref = GossipRef})}; true -> @@ -506,6 +544,7 @@ handle_call({install_aux_snapshot, Snapshot}, _From, {ok, GossipRef} = add_handlers(SwarmTID, NewChain), notify({new_chain, NewChain}), blockchain_lock:release(), + ok = ets:insert(?CACHE, {?CHAIN, NewChain}), {reply, ok, maybe_sync(State#state{mode = normal, sync_paused = false, blockchain = NewChain, gossip_ref = GossipRef})}; @@ -538,16 +577,19 @@ handle_call({add_commit_hook, CF, HookIncFun, HookEndFun} , _From, #state{blockc Ledger = blockchain:ledger(Chain), {Ref, Ledger1} = blockchain_ledger_v1:add_commit_hook(CF, HookIncFun, HookEndFun, Ledger), Chain1 = blockchain:ledger(Ledger1, Chain), + ok = ets:insert(?CACHE, {?CHAIN, Chain1}), {reply, Ref, State#state{blockchain = Chain1}}; handle_call({add_commit_hook, CF, HookIncFun, HookEndFun, Pred} , _From, #state{blockchain = Chain} = State) -> Ledger = blockchain:ledger(Chain), {Ref, Ledger1} = blockchain_ledger_v1:add_commit_hook(CF, HookIncFun, HookEndFun, Pred, Ledger), Chain1 = blockchain:ledger(Ledger1, Chain), + ok = ets:insert(?CACHE, {?CHAIN, Chain1}), {reply, Ref, State#state{blockchain = Chain1}}; handle_call({remove_commit_hook, RefOrCF} , _From, #state{blockchain = Chain} = State) -> Ledger = blockchain:ledger(Chain), Ledger1 = blockchain_ledger_v1:remove_commit_hook(RefOrCF, Ledger), Chain1 = blockchain:ledger(Ledger1, Chain), + ok = ets:insert(?CACHE, {?CHAIN, Chain1}), {reply, ok, State#state{blockchain = Chain1}}; handle_call(_Msg, _From, State) -> @@ -557,6 +599,7 @@ handle_call(_Msg, _From, State) -> handle_cast({load, BaseDir, GenDir}, #state{blockchain=undefined}=State) -> {Blockchain, Ref} = load_chain(State#state.swarm_tid, BaseDir, GenDir), {Mode, Info} = get_sync_mode(State#state{blockchain=Blockchain, gossip_ref=Ref}), + ok = ets:insert(?CACHE, {?CHAIN, Blockchain}), NewState = State#state{blockchain = Blockchain, gossip_ref = Ref, mode=Mode, snapshot_info=Info}, notify({new_chain, Blockchain}), {noreply, NewState}; @@ -743,6 +786,7 @@ handle_info({'DOWN', RocksGCRef, process, RocksGCPid, Reason}, {noreply, State#state{rocksdb_gc_mref = undefined}}; handle_info({blockchain_event, {new_chain, NC}}, State) -> + ok = ets:insert(?CACHE, {?CHAIN, NC}), {noreply, State#state{blockchain = NC}}; handle_info(_Msg, State) -> lager:warning("rcvd unknown info msg: ~p", [_Msg]), @@ -805,6 +849,7 @@ integrate_genesis_block_( blockchain = Chain, gossip_ref = GossipRef }, + ok = ets:insert(?CACHE, {?CHAIN, Chain}), {Mode, SyncInfo} = get_sync_mode(S1), {ok, S1#state{mode=Mode, snapshot_info=SyncInfo}} end; diff --git a/src/grpc/blockchain_grpc_sc_server_handler.erl b/src/grpc/blockchain_grpc_sc_server_handler.erl index 224e5b5ed6..05ce40c5f3 100644 --- a/src/grpc/blockchain_grpc_sc_server_handler.erl +++ b/src/grpc/blockchain_grpc_sc_server_handler.erl @@ -32,39 +32,11 @@ close(_HandlerPid)-> -spec init(atom(), grpcbox_stream:t()) -> grpcbox_stream:t(). init(_RPC, StreamState)-> lager:debug("initiating grpc state channel server handler with state ~p", [StreamState]), - HandlerMod = application:get_env(blockchain, sc_packet_handler, undefined), - OfferLimit = application:get_env(blockchain, sc_pending_offer_limit, 5), - Blockchain = blockchain_worker:blockchain(), - Ledger = blockchain:ledger(Blockchain), - Self = self(), - case blockchain:config(?sc_version, Ledger) of - %% In this case only sc_version=2 is handling banners - %% version 1 never had them and banner will be removed form future versions - {ok, 2} -> - ActiveSCs = - e2qc:cache( - ?MODULE, - active_list, - 10, - fun() -> maps:to_list(blockchain_state_channels_server:get_actives()) end - ), - case ActiveSCs of - [] -> - SCBanner = blockchain_state_channel_banner_v1:new(), - lager:debug("blockchain_grpc_sc_server_handler, empty banner: ~p", [SCBanner]), - Self ! {send_banner, SCBanner}; - ActiveSCs -> - [{_SCID, {ActiveSC, _, _}}|_] = ActiveSCs, - SCBanner = blockchain_state_channel_banner_v1:new(ActiveSC), - Self ! {send_banner, SCBanner} - end; - _ -> - noop - end, - HandlerState = blockchain_state_channel_common:new_handler_state(Blockchain, Ledger, #{}, [], HandlerMod,OfferLimit, false), + HandlerState = grpcbox_stream:stream_handler_state(StreamState), + NewHandlerState = maybe_initialize_state(HandlerState), grpcbox_stream:stream_handler_state( StreamState, - HandlerState + NewHandlerState ). -spec msg(blockchain_state_channel_v1:message(), grpcbox_stream:t()) -> grpcbox_stream:t(). @@ -133,3 +105,42 @@ is_chain_ready(undefined) -> false; is_chain_ready(_Chain) -> true. + +%% handler state if not initialized will be undefined otherwise will be a record +-spec maybe_initialize_state( + blockchain_state_channel_common:handler_state() | undefined) -> + blockchain_state_channel_common:handler_state(). +maybe_initialize_state(undefined) -> + HandlerMod = application:get_env(blockchain, sc_packet_handler, undefined), + OfferLimit = application:get_env(blockchain, sc_pending_offer_limit, 5), + Blockchain = blockchain_worker:cached_blockchain(), + Ledger = blockchain:ledger(Blockchain), + Self = self(), + case blockchain:config(?sc_version, Ledger) of + %% In this case only sc_version=2 is handling banners + %% version 1 never had them and banner will be removed form future versions + {ok, 2} -> + ActiveSCs = + e2qc:cache( + ?MODULE, + active_list, + 30, + fun() -> maps:to_list(blockchain_state_channels_server:get_actives()) end + ), + case ActiveSCs of + [] -> + SCBanner = blockchain_state_channel_banner_v1:new(), + lager:debug("blockchain_grpc_sc_server_handler, empty banner: ~p", [SCBanner]), + Self ! {send_banner, SCBanner}; + ActiveSCs -> + [{_SCID, {ActiveSC, _, _}}|_] = ActiveSCs, + SCBanner = blockchain_state_channel_banner_v1:new(ActiveSC), + Self ! {send_banner, SCBanner} + end; + _ -> + noop + end, + blockchain_state_channel_common:new_handler_state(Blockchain, Ledger, #{}, [], HandlerMod,OfferLimit, false); +maybe_initialize_state(HandlerState) -> + HandlerState. + diff --git a/src/state_channel/blockchain_state_channel_common.erl b/src/state_channel/blockchain_state_channel_common.erl index a9eab48cce..86f076da63 100644 --- a/src/state_channel/blockchain_state_channel_common.erl +++ b/src/state_channel/blockchain_state_channel_common.erl @@ -11,6 +11,7 @@ handler_mod/1, handler_mod/2, pending_offer_limit/1, pending_offer_limit/2, chain/1, chain/2, + streaming_initialized/1, streaming_initialized/2, encode_pb/1, encode_pb/2, state_channel/1, state_channel/2 ]). @@ -33,6 +34,7 @@ ]). -record(handler_state, { + streaming_initialized :: boolean(), chain :: undefined | blockchain:blockchain(), ledger :: undefined | blockchain_ledger_v1:ledger(), pending_packet_offers = #{} :: #{binary() => {blockchain_state_channel_packet_offer_v1:offer(), pos_integer()}}, @@ -60,6 +62,7 @@ new_handler_state()-> ) -> handler_state(). new_handler_state(Chain, Ledger, PendingPacketOffers, OfferQueue, HandlerMod, PendingOfferLimit, EncodePB)-> #handler_state{ + streaming_initialized = true, chain = Chain, ledger = Ledger, pending_packet_offers = PendingPacketOffers, @@ -77,6 +80,10 @@ new_handler_state(Chain, Ledger, PendingPacketOffers, OfferQueue, HandlerMod, Pe chain(#handler_state{chain=V}) -> V. +-spec streaming_initialized(handler_state()) -> undefined | boolean(). +streaming_initialized(#handler_state{streaming_initialized=V}) -> + V. + -spec ledger(handler_state()) -> undefined | blockchain_ledger_v1:ledger(). ledger(#handler_state{ledger=V}) -> V. @@ -112,6 +119,10 @@ state_channel(#handler_state{state_channel=V}) -> chain(NewV, HandlerState) -> HandlerState#handler_state{chain=NewV}. +-spec streaming_initialized(boolean(), handler_state()) -> handler_state(). +streaming_initialized(NewV, HandlerState) -> + HandlerState#handler_state{streaming_initialized=NewV}. + -spec ledger(blockchain_ledger_v1:ledger(), handler_state()) -> handler_state(). ledger(NewV, HandlerState) -> HandlerState#handler_state{ledger=NewV}. From ee9ae8744d5f861205715befcadea555a7d07671 Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Mon, 20 Jun 2022 14:25:55 +0100 Subject: [PATCH 33/48] eliminate use of state chain and ledger --- src/blockchain_worker.erl | 26 +++---- .../blockchain_grpc_sc_server_handler.erl | 7 +- .../blockchain_state_channel_common.erl | 68 ++++--------------- .../blockchain_state_channel_handler.erl | 23 ++----- 4 files changed, 36 insertions(+), 88 deletions(-) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 8a57dba8af..173146330f 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -126,7 +126,7 @@ %% API Function Definitions %% ------------------------------------------------------------------ start_link(Args) -> - Res = server:start_link({local, ?SERVER}, ?SERVER, Args, [{hibernate_after, 5000}]), + Res = gen_server:start_link({local, ?SERVER}, ?SERVER, Args, [{hibernate_after, 5000}]), case Res of {ok, Pid} -> %% if we have an ETS table reference, give ownership to the new process @@ -143,11 +143,11 @@ start_link(Args) -> end, Res. - +-spec make_ets_table() -> ets:tab(). make_ets_table() -> ets:new(?CACHE, [named_table, - protected, + public, %% public as ?MODULE:init needs to write chain to the table. TODO: move chain load out of init and make this table protected {heir, self(), undefined}]). %%-------------------------------------------------------------------- @@ -422,7 +422,7 @@ init(Args) -> [ libp2p_swarm:listen(SwarmTID, Addr) || Addr <- ListenAddrs ]), NewState = #state{swarm_tid = SwarmTID, blockchain = Blockchain, gossip_ref = Ref}, - ok = ets:insert(?CACHE, {?CHAIN, Blockchain}), + true = ets:insert(?CACHE, {?CHAIN, Blockchain}), {Mode, Info} = get_sync_mode(NewState), SnapshotTimerRef = schedule_snapshot_timer(), case application:get_env(blockchain, disable_prewarm, false) of @@ -456,7 +456,7 @@ handle_call({blockchain, NewChain}, _From, #state{swarm_tid = SwarmTID} = State) notify({new_chain, NewChain}), remove_handlers(SwarmTID), {ok, GossipRef} = add_handlers(SwarmTID, NewChain), - ok = ets:insert(?CACHE, {?CHAIN, NewChain}), + true = ets:insert(?CACHE, {?CHAIN, NewChain}), {reply, ok, State#state{blockchain = NewChain, gossip_ref = GossipRef}}; handle_call({new_ledger, Dir}, _From, #state{blockchain=Chain}=State) -> %% We do this here so the same process that normally owns the ledger @@ -523,7 +523,7 @@ handle_call({install_snapshot, Height, Hash, Snapshot, BinSnap}, _From, set_resyncing(ChainHeight, LedgerHeight, NewChain) end, blockchain_lock:release(), - ok = ets:insert(?CACHE, {?CHAIN, NewChain}), + true = ets:insert(?CACHE, {?CHAIN, NewChain}), {reply, ok, maybe_sync(State#state{mode = normal, sync_paused = false, blockchain = NewChain, gossip_ref = GossipRef})}; true -> @@ -544,7 +544,7 @@ handle_call({install_aux_snapshot, Snapshot}, _From, {ok, GossipRef} = add_handlers(SwarmTID, NewChain), notify({new_chain, NewChain}), blockchain_lock:release(), - ok = ets:insert(?CACHE, {?CHAIN, NewChain}), + true = ets:insert(?CACHE, {?CHAIN, NewChain}), {reply, ok, maybe_sync(State#state{mode = normal, sync_paused = false, blockchain = NewChain, gossip_ref = GossipRef})}; @@ -577,19 +577,19 @@ handle_call({add_commit_hook, CF, HookIncFun, HookEndFun} , _From, #state{blockc Ledger = blockchain:ledger(Chain), {Ref, Ledger1} = blockchain_ledger_v1:add_commit_hook(CF, HookIncFun, HookEndFun, Ledger), Chain1 = blockchain:ledger(Ledger1, Chain), - ok = ets:insert(?CACHE, {?CHAIN, Chain1}), + true = ets:insert(?CACHE, {?CHAIN, Chain1}), {reply, Ref, State#state{blockchain = Chain1}}; handle_call({add_commit_hook, CF, HookIncFun, HookEndFun, Pred} , _From, #state{blockchain = Chain} = State) -> Ledger = blockchain:ledger(Chain), {Ref, Ledger1} = blockchain_ledger_v1:add_commit_hook(CF, HookIncFun, HookEndFun, Pred, Ledger), Chain1 = blockchain:ledger(Ledger1, Chain), - ok = ets:insert(?CACHE, {?CHAIN, Chain1}), + true = ets:insert(?CACHE, {?CHAIN, Chain1}), {reply, Ref, State#state{blockchain = Chain1}}; handle_call({remove_commit_hook, RefOrCF} , _From, #state{blockchain = Chain} = State) -> Ledger = blockchain:ledger(Chain), Ledger1 = blockchain_ledger_v1:remove_commit_hook(RefOrCF, Ledger), Chain1 = blockchain:ledger(Ledger1, Chain), - ok = ets:insert(?CACHE, {?CHAIN, Chain1}), + true = ets:insert(?CACHE, {?CHAIN, Chain1}), {reply, ok, State#state{blockchain = Chain1}}; handle_call(_Msg, _From, State) -> @@ -599,7 +599,7 @@ handle_call(_Msg, _From, State) -> handle_cast({load, BaseDir, GenDir}, #state{blockchain=undefined}=State) -> {Blockchain, Ref} = load_chain(State#state.swarm_tid, BaseDir, GenDir), {Mode, Info} = get_sync_mode(State#state{blockchain=Blockchain, gossip_ref=Ref}), - ok = ets:insert(?CACHE, {?CHAIN, Blockchain}), + true = ets:insert(?CACHE, {?CHAIN, Blockchain}), NewState = State#state{blockchain = Blockchain, gossip_ref = Ref, mode=Mode, snapshot_info=Info}, notify({new_chain, Blockchain}), {noreply, NewState}; @@ -786,7 +786,7 @@ handle_info({'DOWN', RocksGCRef, process, RocksGCPid, Reason}, {noreply, State#state{rocksdb_gc_mref = undefined}}; handle_info({blockchain_event, {new_chain, NC}}, State) -> - ok = ets:insert(?CACHE, {?CHAIN, NC}), + true = ets:insert(?CACHE, {?CHAIN, NC}), {noreply, State#state{blockchain = NC}}; handle_info(_Msg, State) -> lager:warning("rcvd unknown info msg: ~p", [_Msg]), @@ -849,7 +849,7 @@ integrate_genesis_block_( blockchain = Chain, gossip_ref = GossipRef }, - ok = ets:insert(?CACHE, {?CHAIN, Chain}), + true = ets:insert(?CACHE, {?CHAIN, Chain}), {Mode, SyncInfo} = get_sync_mode(S1), {ok, S1#state{mode=Mode, snapshot_info=SyncInfo}} end; diff --git a/src/grpc/blockchain_grpc_sc_server_handler.erl b/src/grpc/blockchain_grpc_sc_server_handler.erl index 05ce40c5f3..e9b7c252af 100644 --- a/src/grpc/blockchain_grpc_sc_server_handler.erl +++ b/src/grpc/blockchain_grpc_sc_server_handler.erl @@ -43,10 +43,9 @@ init(_RPC, StreamState)-> msg(#blockchain_state_channel_message_v1_pb{msg = Msg}, StreamState) -> lager:debug("grpc msg called with ~p and state ~p", [Msg, StreamState]), HandlerState = grpcbox_stream:stream_handler_state(StreamState), - Chain = blockchain_state_channel_common:chain(HandlerState), - %% get our chain and only handle the request if the chain is up %% if chain not up we have no way to return routing data so just return a 14/503 + Chain = blockchain_worker:cached_blockchain(), case is_chain_ready(Chain) of false -> {grpc_error, @@ -116,7 +115,7 @@ maybe_initialize_state(undefined) -> Blockchain = blockchain_worker:cached_blockchain(), Ledger = blockchain:ledger(Blockchain), Self = self(), - case blockchain:config(?sc_version, Ledger) of + case blockchain_ledger_v1:config(?sc_version, Ledger) of %% In this case only sc_version=2 is handling banners %% version 1 never had them and banner will be removed form future versions {ok, 2} -> @@ -140,7 +139,7 @@ maybe_initialize_state(undefined) -> _ -> noop end, - blockchain_state_channel_common:new_handler_state(Blockchain, Ledger, #{}, [], HandlerMod,OfferLimit, false); + blockchain_state_channel_common:new_handler_state(#{}, [], HandlerMod,OfferLimit, false); maybe_initialize_state(HandlerState) -> HandlerState. diff --git a/src/state_channel/blockchain_state_channel_common.erl b/src/state_channel/blockchain_state_channel_common.erl index 86f076da63..1c1ac48321 100644 --- a/src/state_channel/blockchain_state_channel_common.erl +++ b/src/state_channel/blockchain_state_channel_common.erl @@ -4,14 +4,11 @@ -export([ new_handler_state/0, - new_handler_state/7, - ledger/1, ledger/2, + new_handler_state/5, pending_packet_offers/1, pending_packet_offers/2, offer_queue/1, offer_queue/2, handler_mod/1, handler_mod/2, pending_offer_limit/1, pending_offer_limit/2, - chain/1, chain/2, - streaming_initialized/1, streaming_initialized/2, encode_pb/1, encode_pb/2, state_channel/1, state_channel/2 ]). @@ -34,9 +31,6 @@ ]). -record(handler_state, { - streaming_initialized :: boolean(), - chain :: undefined | blockchain:blockchain(), - ledger :: undefined | blockchain_ledger_v1:ledger(), pending_packet_offers = #{} :: #{binary() => {blockchain_state_channel_packet_offer_v1:offer(), pos_integer()}}, offer_queue = [] :: [{blockchain_state_channel_packet_offer_v1:offer(), pos_integer()}], handler_mod = undefined :: atom(), @@ -52,19 +46,14 @@ -spec new_handler_state() -> handler_state(). new_handler_state()-> #handler_state{}. --spec new_handler_state(Chain :: blockchain:blockchain(), - Ledger :: undefined | blockchain_ledger_v1:ledger(), - PendingPacketOffers :: #{binary() => {blockchain_state_channel_packet_offer_v1:offer(), pos_integer()}}, +-spec new_handler_state(PendingPacketOffers :: #{binary() => {blockchain_state_channel_packet_offer_v1:offer(), pos_integer()}}, OfferQueue :: [{blockchain_state_channel_packet_offer_v1:offer(), pos_integer()}], HandlerMod :: atom(), PendingOfferLimit :: undefined | pos_integer(), EncodePB :: boolean() ) -> handler_state(). -new_handler_state(Chain, Ledger, PendingPacketOffers, OfferQueue, HandlerMod, PendingOfferLimit, EncodePB)-> +new_handler_state(PendingPacketOffers, OfferQueue, HandlerMod, PendingOfferLimit, EncodePB)-> #handler_state{ - streaming_initialized = true, - chain = Chain, - ledger = Ledger, pending_packet_offers = PendingPacketOffers, offer_queue = OfferQueue, handler_mod = HandlerMod, @@ -76,18 +65,6 @@ new_handler_state(Chain, Ledger, PendingPacketOffers, OfferQueue, HandlerMod, Pe %% %% State getters %% --spec chain(handler_state()) -> undefined | blockchain:blockchain(). -chain(#handler_state{chain=V}) -> - V. - --spec streaming_initialized(handler_state()) -> undefined | boolean(). -streaming_initialized(#handler_state{streaming_initialized=V}) -> - V. - --spec ledger(handler_state()) -> undefined | blockchain_ledger_v1:ledger(). -ledger(#handler_state{ledger=V}) -> - V. - -spec pending_packet_offers(handler_state()) -> #{binary() => {blockchain_state_channel_packet_offer_v1:offer(), pos_integer()}}. pending_packet_offers(#handler_state{pending_packet_offers=V}) -> V. @@ -115,18 +92,6 @@ state_channel(#handler_state{state_channel=V}) -> %% %% State setters %% --spec chain(blockchain:blockchain(), handler_state()) -> handler_state(). -chain(NewV, HandlerState) -> - HandlerState#handler_state{chain=NewV}. - --spec streaming_initialized(boolean(), handler_state()) -> handler_state(). -streaming_initialized(NewV, HandlerState) -> - HandlerState#handler_state{streaming_initialized=NewV}. - --spec ledger(blockchain_ledger_v1:ledger(), handler_state()) -> handler_state(). -ledger(NewV, HandlerState) -> - HandlerState#handler_state{ledger=NewV}. - -spec pending_packet_offers(#{binary() => {blockchain_state_channel_packet_offer_v1:offer(), pos_integer()}}, handler_state()) -> handler_state(). pending_packet_offers(NewV, HandlerState) -> HandlerState#handler_state{pending_packet_offers=NewV}. @@ -203,11 +168,12 @@ send_response(Pid, Resp) -> handle_server_msg( Msg, #handler_state{ - ledger = Ledger, pending_packet_offers = PendingOffers, pending_offer_limit = PendingOfferLimit }=HandlerState )-> + Chain = blockchain_worker:cached_blockchain(), + Ledger = blockchain:ledger(Chain), Time = erlang:system_time(millisecond), PendingOfferCount = maps:size(PendingOffers), case Msg of @@ -221,13 +187,13 @@ handle_server_msg( case maps:get(PacketHash, PendingOffers, undefined) of undefined -> lager:debug("sc_handler server got packet: ~p", [Packet]), - blockchain_state_channels_server:handle_packet(Packet, Time, HandlerState#handler_state.handler_mod, HandlerState#handler_state.ledger, self()), + blockchain_state_channels_server:handle_packet(Packet, Time, HandlerState#handler_state.handler_mod, Ledger, self()), {ok, HandlerState}; {PendingOffer, PendingOfferTime} -> case blockchain_state_channel_packet_v1:validate(Packet, PendingOffer) of {error, packet_offer_mismatch} -> %% might as well try it, it's free - blockchain_state_channels_server:handle_packet(Packet, Time, HandlerState#handler_state.handler_mod, HandlerState#handler_state.ledger, self()), + blockchain_state_channels_server:handle_packet(Packet, Time, HandlerState#handler_state.handler_mod, Ledger, self()), lager:warning("packet failed to validate ~p against offer ~p", [Packet, PendingOffer]), stop; {error, Reason} -> @@ -235,7 +201,7 @@ handle_server_msg( stop; true -> lager:debug("sc_handler server got packet: ~p", [Packet]), - blockchain_state_channels_server:handle_packet(Packet, PendingOfferTime, HandlerState#handler_state.handler_mod, HandlerState#handler_state.ledger, self()), + blockchain_state_channels_server:handle_packet(Packet, PendingOfferTime, HandlerState#handler_state.handler_mod, Ledger, self()), handle_next_offer(HandlerState#handler_state{pending_packet_offers=maps:remove(PacketHash, PendingOffers)}) end end; @@ -276,17 +242,8 @@ handle_server_msg( end. handle_client_msg(Msg, HandlerState) -> - %% get ledger if we don't yet have one - Ledger = case HandlerState#handler_state.ledger of - undefined -> - case blockchain_worker:blockchain() of - undefined -> - undefined; - Chain -> - blockchain:ledger(Chain) - end; - L -> L - end, + Chain = blockchain_worker:cached_blockchain(), + Ledger = blockchain:ledger(Chain), case Msg of {banner, Banner} -> case blockchain_state_channel_banner_v1:sc(Banner) of @@ -322,7 +279,7 @@ handle_client_msg(Msg, HandlerState) -> lager:debug("sc_handler client got response: ~p", [Resp]), blockchain_state_channels_client:response(Resp) end, - HandlerState#handler_state{ledger=Ledger}. + HandlerState. -spec handle_next_offer(State) -> {ok, State} | {ok, State, Msg :: any()} when @@ -346,7 +303,6 @@ handle_offer( Offer, Time, #handler_state{ - ledger=Ledger, pending_packet_offers=PendingOffers, handler_mod=Mod, encode_pb=MaybeEncodeMsg, @@ -354,6 +310,8 @@ handle_offer( }=HandlerState0 ) -> lager:debug("sc_handler server got offer: ~p", [Offer]), + Chain = blockchain_worker:cached_blockchain(), + Ledger = blockchain:ledger(Chain), case blockchain_state_channels_server:handle_offer(Offer, Mod, Ledger, self()) of ok -> ReqDiff = blockchain_state_channel_offer_v1:req_diff(Offer), diff --git a/src/state_channel/blockchain_state_channel_handler.erl b/src/state_channel/blockchain_state_channel_handler.erl index b1a6c55d4a..dd5ab68e50 100644 --- a/src/state_channel/blockchain_state_channel_handler.erl +++ b/src/state_channel/blockchain_state_channel_handler.erl @@ -60,8 +60,8 @@ init(server, _Conn, [_Path, Blockchain]) -> Ledger = blockchain:ledger(Blockchain), HandlerMod = application:get_env(blockchain, sc_packet_handler, undefined), OfferLimit = application:get_env(blockchain, sc_pending_offer_limit, 5), - HandlerState = blockchain_state_channel_common:new_handler_state(Blockchain, Ledger, #{}, [], HandlerMod, OfferLimit, true), - case blockchain:config(?sc_version, Ledger) of + HandlerState = blockchain_state_channel_common:new_handler_state(#{}, [], HandlerMod, OfferLimit, true), + case blockchain_ledger_v1:config(?sc_version, Ledger) of %% In this case only sc_version=2 is handling banners %% version 1 never had them and banner will be removed form future versions {ok, 2} -> @@ -99,18 +99,9 @@ init(server, _Conn, [_Path, Blockchain]) -> HandlerState :: any() ) -> libp2p_framed_stream:handle_data_result(). handle_data(client, Data, HandlerState) -> + Chain = blockchain_worker:cached_blockchain(), + Ledger = blockchain:ledger(Chain), %% get ledger if we don't yet have one - Ledger = - case blockchain_state_channel_common:ledger(HandlerState) of - undefined -> - case blockchain_worker:blockchain() of - undefined -> - undefined; - Chain -> - blockchain:ledger(Chain) - end; - L -> L - end, case blockchain_state_channel_message_v1:decode(Data) of {banner, Banner} -> case blockchain_state_channel_banner_v1:sc(Banner) of @@ -146,9 +137,10 @@ handle_data(client, Data, HandlerState) -> lager:debug("sc_handler client got response: ~p", [Resp]), blockchain_state_channels_client:response(Resp) end, - NewHandlerState = blockchain_state_channel_common:ledger(Ledger, HandlerState), - {noreply, NewHandlerState}; + {noreply, HandlerState}; handle_data(server, Data, HandlerState) -> + Chain = blockchain_worker:cached_blockchain(), + Ledger = blockchain:ledger(Chain), PendingOffers = blockchain_state_channel_common:pending_packet_offers(HandlerState), PendingOfferLimit = blockchain_state_channel_common:pending_offer_limit(HandlerState), Time = erlang:system_time(millisecond), @@ -165,7 +157,6 @@ handle_data(server, Data, HandlerState) -> NewHandlerState = blockchain_state_channel_common:offer_queue(CurOfferQueue ++ [{Offer, Time}], HandlerState), {noreply, NewHandlerState}; {packet, Packet} -> - Ledger = blockchain_state_channel_common:ledger(HandlerState), PacketHash = blockchain_helium_packet_v1:packet_hash(blockchain_state_channel_packet_v1:packet(Packet)), case maps:get(PacketHash, PendingOffers, undefined) of undefined -> From 1610f861f7243bce3931f29e4c3cd89d44a2d602 Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Fri, 24 Jun 2022 13:03:25 +0100 Subject: [PATCH 34/48] add read concurrency to cache table --- src/blockchain_worker.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index 173146330f..c723c9771b 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -148,6 +148,7 @@ make_ets_table() -> ets:new(?CACHE, [named_table, public, %% public as ?MODULE:init needs to write chain to the table. TODO: move chain load out of init and make this table protected + {read_concurrency, true}, {heir, self(), undefined}]). %%-------------------------------------------------------------------- From a28ab793675e972390e01fca0caacd7e5f90c374 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Tue, 17 May 2022 15:30:17 -0700 Subject: [PATCH 35/48] Add support for new ledger entries - Simple tests and cleanup - Update for proto compliance - Fix coinbase token test --- include/blockchain_vars.hrl | 5 + rebar.config | 2 +- rebar.lock | 2 +- src/blockchain_token_type_v1.erl | 16 ++ src/ledger/v1/blockchain_ledger_v1.erl | 73 +++++++- src/ledger/v1/blockchain_ledger_v1.hrl | 3 +- src/ledger/v2/blockchain_ledger_entry_v2.erl | 142 +++++++++++++++ .../v1/blockchain_txn_coinbase_v1.erl | 44 ++++- .../v1/blockchain_txn_vars_v1.erl | 8 + src/transactions/v2/blockchain_payment_v2.erl | 63 +++++-- .../v2/blockchain_txn_payment_v2.erl | 37 ++++ test/blockchain_token_SUITE.erl | 170 ++++++++++++++++++ test/test_utils.erl | 19 +- 13 files changed, 559 insertions(+), 25 deletions(-) create mode 100644 src/blockchain_token_type_v1.erl create mode 100644 src/ledger/v2/blockchain_ledger_entry_v2.erl create mode 100644 test/blockchain_token_SUITE.erl diff --git a/include/blockchain_vars.hrl b/include/blockchain_vars.hrl index 03701cd0a8..86a857b481 100644 --- a/include/blockchain_vars.hrl +++ b/include/blockchain_vars.hrl @@ -614,3 +614,8 @@ %% Block size limit variable (in bytes). Set to 25 * 1024 * 1024. -define(block_size_limit, block_size_limit). + +%% ------------------------------------------------------------------ +%% Protocol version (aka support multiple tokens) +%% Testing for now, set to 2 +-define(protocol_version, protocol_version). diff --git a/rebar.config b/rebar.config index 4894b8190d..d3673fc215 100644 --- a/rebar.config +++ b/rebar.config @@ -40,7 +40,7 @@ {erlang_stats, ".*", {git, "https://github.com/helium/erlang-stats.git", {branch, "master"}}}, {e2qc, ".*", {git, "https://github.com/helium/e2qc", {branch, "master"}}}, {vincenty, ".*", {git, "https://github.com/helium/vincenty", {branch, "master"}}}, - {helium_proto, {git, "https://github.com/helium/proto.git", {branch, "master"}}}, + {helium_proto, {git, "https://github.com/helium/proto.git", {branch, "rg/hip-xx-experiments"}}}, {merkerl, ".*", {git, "https://github.com/helium/merkerl.git", {branch, "master"}}}, {xxhash, {git, "https://github.com/pierreis/erlang-xxhash", {branch, "master"}}}, {exor_filter, ".*", {git, "https://github.com/mpope9/exor_filter", {branch, "master"}}}, diff --git a/rebar.lock b/rebar.lock index 1f241846a4..e07295597d 100644 --- a/rebar.lock +++ b/rebar.lock @@ -77,7 +77,7 @@ {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},0}, {<<"helium_proto">>, {git,"https://github.com/helium/proto.git", - {ref,"7c90dde32ff582a856aeb6c8926e52abfcc8611f"}}, + {ref,"71dee1604fe1e126d0ef3bc8dd3e168d6ea8da79"}}, 0}, {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1}, diff --git a/src/blockchain_token_type_v1.erl b/src/blockchain_token_type_v1.erl new file mode 100644 index 0000000000..0465af2723 --- /dev/null +++ b/src/blockchain_token_type_v1.erl @@ -0,0 +1,16 @@ +%%%------------------------------------------------------------------- +%% @doc +%% == Blockchain Token Type V1 == +%% @end +%%%------------------------------------------------------------------- +-module(blockchain_token_type_v1). + +-type token_type() :: hnt | hst | hlt | hgt. +-type token_types() :: [token_type()]. + +-export_type([token_type/0, token_types/0]). +-export([supported_tokens/0]). + +-spec supported_tokens() -> token_types(). +supported_tokens() -> + [hnt, hst, hlt, hgt]. diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index 1a754c4a44..c322f17a74 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -40,6 +40,7 @@ active_gateways/1, snapshot_gateways/1, load_gateways/2, entries/1, + entries_v2/1, htlcs/1, master_key/1, master_key/2, @@ -102,7 +103,9 @@ upgrade_pocs/1, find_entry/2, + find_entry_v2/2, credit_account/3, debit_account/4, debit_fee_from_account/5, + credit_account/4, check_balance/3, dc_entries/1, @@ -285,6 +288,7 @@ -endif. -type entries() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_entry_v1:entry()}. +-type entries_v2() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_entry_v2:entry()}. -type dc_entries() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_data_credits_entry_v1:data_credits_entry()}. -type active_gateways() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_gateway_v2:gateway()}. -type htlcs() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_htlc_v1:htlc()}. @@ -344,11 +348,11 @@ new(Dir, ReadOnly, Options) -> [DefaultCF, AGwsCF, EntriesCF, DCEntriesCF, HTLCsCF, PoCsCF, ProposedPoCsCF, SecuritiesCF, RoutingCF, - SubnetsCF, SCsCF, H3DexCF, GwDenormCF, ValidatorsCF, + SubnetsCF, SCsCF, H3DexCF, GwDenormCF, ValidatorsCF, EntriesV2CF, DelayedDefaultCF, DelayedAGwsCF, DelayedEntriesCF, DelayedDCEntriesCF, DelayedHTLCsCF, DelayedPoCsCF, DelayedProposedPoCsCF, DelayedSecuritiesCF, DelayedRoutingCF, DelayedSubnetsCF, DelayedSCsCF, - DelayedH3DexCF, DelayedGwDenormCF, DelayedValidatorsCF] = CFs, + DelayedH3DexCF, DelayedGwDenormCF, DelayedValidatorsCF, DelayedEntriesV2CF] = CFs, Ledger = #ledger_v1{ dir=Dir, @@ -369,7 +373,8 @@ new(Dir, ReadOnly, Options) -> subnets=SubnetsCF, state_channels=SCsCF, h3dex=H3DexCF, - validators=ValidatorsCF + validators=ValidatorsCF, + entries_v2=EntriesV2CF }, delayed= #sub_ledger_v1{ default=DelayedDefaultCF, @@ -385,7 +390,8 @@ new(Dir, ReadOnly, Options) -> subnets=DelayedSubnetsCF, state_channels=DelayedSCsCF, h3dex=DelayedH3DexCF, - validators=DelayedValidatorsCF + validators=DelayedValidatorsCF, + entries_v2=DelayedEntriesV2CF } }, Ledger. @@ -902,7 +908,8 @@ atom_to_cf(Atom, Ledger) -> subnets -> SL#sub_ledger_v1.subnets; state_channels -> SL#sub_ledger_v1.state_channels; h3dex -> SL#sub_ledger_v1.h3dex; - validators -> SL#sub_ledger_v1.validators + validators -> SL#sub_ledger_v1.validators; + entries_v2 -> SL#sub_ledger_v1.entries_v2 end. apply_raw_changes(Changes, #ledger_v1{db = DB} = Ledger) -> @@ -1341,6 +1348,19 @@ entries(Ledger) -> #{} ). +-spec entries_v2(ledger()) -> entries_v2(). +entries_v2(Ledger) -> + EntriesV2CF = entries_v2_cf(Ledger), + cache_fold( + Ledger, + EntriesV2CF, + fun({Address, Binary}, Acc) -> + Entry = blockchain_ledger_entry_v2:deserialize(Binary), + maps:put(Address, Entry, Acc) + end, + #{} + ). + -spec dc_entries(ledger()) -> dc_entries(). dc_entries(Ledger) -> DCEntriesCF = dc_entries_cf(Ledger), @@ -2989,6 +3009,19 @@ find_entry(Address, Ledger) -> Error end. +-spec find_entry_v2(Addres :: libp2p_crypto:pubkey_bin(), Ledger :: ledger()) -> + {ok, blockchain_ledger_entry_v2:entry()} | {error, any()}. +find_entry_v2(Address, Ledger) -> + EntriesV2CF = entries_v2_cf(Ledger), + case cache_get(Ledger, EntriesV2CF, Address, []) of + {ok, BinEntry} -> + {ok, blockchain_ledger_entry_v2:deserialize(BinEntry)}; + not_found -> + {error, address_entry_not_found}; + Error -> + Error + end. + -spec credit_account(libp2p_crypto:pubkey_bin(), integer(), ledger()) -> ok | {error, any()}. credit_account(Address, Amount, Ledger) -> EntriesCF = entries_cf(Ledger), @@ -3008,6 +3041,29 @@ credit_account(Address, Amount, Ledger) -> Error end. +-spec credit_account(Address :: libp2p_crypto:pubkey_bin(), + Amount :: integer(), + TT :: blockchain_token_type_v1:token(), + Ledger :: ledger()) -> ok | {error, any()}. +credit_account(Address, Amount, TT, Ledger) -> + EntriesCF = entries_v2_cf(Ledger), + case ?MODULE:find_entry_v2(Address, Ledger) of + {error, address_entry_not_found} -> + %% create blank entry to fill all the token types + Entry0 = blockchain_ledger_entry_v2:new(), + %% credit this particular token amount + Entry1 = blockchain_ledger_entry_v2:credit(Entry0, Amount, TT), + Bin = blockchain_ledger_entry_v2:serialize(Entry1), + cache_put(Ledger, EntriesCF, Address, Bin); + {ok, Entry} -> + %% credit this particular token amount + Entry1 = blockchain_ledger_entry_v2:credit(Entry, Amount, TT), + Bin = blockchain_ledger_entry_v2:serialize(Entry1), + cache_put(Ledger, EntriesCF, Address, Bin); + {error, _}=Error -> + Error + end. + -spec debit_account(libp2p_crypto:pubkey_bin(), integer(), integer(), ledger()) -> ok | {error, any()}. debit_account(Address, Amount, Nonce, Ledger) -> case ?MODULE:find_entry(Address, Ledger) of @@ -4138,6 +4194,11 @@ entries_cf(Ledger) -> SL = subledger(Ledger), {entries, db(Ledger), SL#sub_ledger_v1.entries}. +-spec entries_v2_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +entries_v2_cf(Ledger) -> + SL = subledger(Ledger), + {entries_v2, db(Ledger), SL#sub_ledger_v1.entries_v2}. + -spec dc_entries_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. dc_entries_cf(Ledger) -> SL = subledger(Ledger), @@ -4486,7 +4547,7 @@ open_db_(DBDir, DBOptions, DefaultCFs, CFOpts, ReadOnly, Retry) -> default_cfs() -> ["default", "active_gateways", "entries", "dc_entries", "htlcs", "pocs", "proposed_pocs", "securities", "routing", "subnets", - "state_channels", "h3dex", "gw_denorm", "validators"]. + "state_channels", "h3dex", "gw_denorm", "validators", "entries_v2"]. -spec delayed_cfs() -> list(). delayed_cfs() -> diff --git a/src/ledger/v1/blockchain_ledger_v1.hrl b/src/ledger/v1/blockchain_ledger_v1.hrl index 6943fc799a..7a0514abcc 100644 --- a/src/ledger/v1/blockchain_ledger_v1.hrl +++ b/src/ledger/v1/blockchain_ledger_v1.hrl @@ -40,7 +40,8 @@ h3dex :: rocksdb:cf_handle(), validators :: rocksdb:cf_handle(), cache :: undefined | direct | ets:tid(), - gateway_cache :: undefined | ets:tid() + gateway_cache :: undefined | ets:tid(), + entries_v2 :: rocksdb:cf_handle() }). -record(aux_ledger_v1, { diff --git a/src/ledger/v2/blockchain_ledger_entry_v2.erl b/src/ledger/v2/blockchain_ledger_entry_v2.erl new file mode 100644 index 0000000000..b4a7a7a4af --- /dev/null +++ b/src/ledger/v2/blockchain_ledger_entry_v2.erl @@ -0,0 +1,142 @@ +%%%------------------------------------------------------------------- +%% @doc +%% == Blockchain Ledger Entry V2 == +%% @end +%%%------------------------------------------------------------------- +-module(blockchain_ledger_entry_v2). + +-export([ + new/0, + nonce/1, + balance/2, + credit/3, + debit/3, + serialize/1, + deserialize/1 +]). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-include_lib("helium_proto/include/blockchain_ledger_entry_v2_pb.hrl"). + +-type entry() :: #blockchain_ledger_entry_v2_pb{}. + +-export_type([entry/0]). + +%% ================================================================== +%% API Functions +%% ================================================================== + +-spec new() -> entry(). +new() -> + #blockchain_ledger_entry_v2_pb{ + nonce = 0, + hnt_balance = 0, + hst_balance = 0, + hgt_balance = 0, + hlt_balance = 0 + }. + +-spec nonce(Entry :: entry()) -> non_neg_integer(). +nonce(#blockchain_ledger_entry_v2_pb{nonce = Nonce}) -> + Nonce. + +-spec balance(Entry :: entry(), TT :: blockchain_token_type_v1:token_type()) -> non_neg_integer(). +balance(Entry, hnt) -> + hnt_balance(Entry); +balance(Entry, hst) -> + hst_balance(Entry); +balance(Entry, hgt) -> + hgt_balance(Entry); +balance(Entry, hlt) -> + hlt_balance(Entry). + +-spec credit( + Entry :: entry(), + Amount :: non_neg_integer(), + TT :: blockchain_token_type_v1:token_type() +) -> entry(). +credit(Entry, Amount, hnt) -> + credit_hnt(Entry, Amount); +credit(Entry, Amount, hst) -> + credit_hst(Entry, Amount); +credit(Entry, Amount, hgt) -> + credit_hgt(Entry, Amount); +credit(Entry, Amount, hlt) -> + credit_hlt(Entry, Amount). + +-spec debit( + Entry :: entry(), + Amount :: non_neg_integer(), + TT :: blockchain_token_type_v1:token_type() +) -> entry(). +debit(Entry, Amount, hnt) -> + debit_hnt(Entry, Amount); +debit(Entry, Amount, hst) -> + debit_hst(Entry, Amount); +debit(Entry, Amount, hgt) -> + debit_hgt(Entry, Amount); +debit(Entry, Amount, hlt) -> + debit_hlt(Entry, Amount). + +-spec serialize(Entry :: entry()) -> binary(). +serialize(Entry) -> + blockchain_ledger_entry_v2_pb:encode_msg(Entry). + +-spec deserialize(EntryBin :: binary()) -> entry(). +deserialize(EntryBin) -> + blockchain_ledger_entry_v2_pb:decode_msg(EntryBin, blockchain_ledger_entry_v2_pb). + +%% ================================================================== +%% Internal Functions +%% ================================================================== + +-spec credit_hnt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +credit_hnt(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{hnt_balance = hnt_balance(Entry) + Amount}. + +-spec credit_hst(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +credit_hst(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{hst_balance = hst_balance(Entry) + Amount}. + +-spec credit_hlt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +credit_hlt(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{hlt_balance = hlt_balance(Entry) + Amount}. + +-spec credit_hgt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +credit_hgt(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{hgt_balance = hgt_balance(Entry) + Amount}. + +-spec debit_hnt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +debit_hnt(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{hnt_balance = hnt_balance(Entry) - Amount}. + +-spec debit_hst(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +debit_hst(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{hst_balance = hst_balance(Entry) - Amount}. + +-spec debit_hgt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +debit_hgt(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{hgt_balance = hgt_balance(Entry) - Amount}. + +-spec debit_hlt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +debit_hlt(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{hlt_balance = hlt_balance(Entry) - Amount}. + +-spec hnt_balance(Entry :: entry()) -> non_neg_integer(). +hnt_balance(#blockchain_ledger_entry_v2_pb{hnt_balance = Balance}) -> + Balance. + +-spec hst_balance(Entry :: entry()) -> non_neg_integer(). +hst_balance(#blockchain_ledger_entry_v2_pb{hst_balance = Balance}) -> + Balance. + +-spec hgt_balance(Entry :: entry()) -> non_neg_integer(). +hgt_balance(#blockchain_ledger_entry_v2_pb{hgt_balance = Balance}) -> + Balance. + +-spec hlt_balance(Entry :: entry()) -> non_neg_integer(). +hlt_balance(#blockchain_ledger_entry_v2_pb{hlt_balance = Balance}) -> + Balance. diff --git a/src/transactions/v1/blockchain_txn_coinbase_v1.erl b/src/transactions/v1/blockchain_txn_coinbase_v1.erl index e1495e9f5f..d8b6064ba1 100644 --- a/src/transactions/v1/blockchain_txn_coinbase_v1.erl +++ b/src/transactions/v1/blockchain_txn_coinbase_v1.erl @@ -9,17 +9,18 @@ -behavior(blockchain_json). -include("blockchain_json.hrl"). - +-include("blockchain_vars.hrl"). -include("blockchain_utils.hrl"). -include_lib("helium_proto/include/blockchain_txn_coinbase_v1_pb.hrl"). -export([ - new/2, + new/2, new/3, hash/1, payee/1, amount/1, fee/1, fee_payer/2, + token_type/1, is_valid/2, absorb/2, sign/2, @@ -37,12 +38,24 @@ %%-------------------------------------------------------------------- %% @doc +%% Construct new coinbase_v1 transaction with default token_type = hnt %% @end %%-------------------------------------------------------------------- -spec new(libp2p_crypto:pubkey_bin(), non_neg_integer()) -> txn_coinbase(). new(Payee, Amount) -> #blockchain_txn_coinbase_v1_pb{payee=Payee, amount=Amount}. +%%-------------------------------------------------------------------- +%% @doc +%% Construct new coinbase_v1 transaction with specific token type +%% @end +%%-------------------------------------------------------------------- +-spec new(Payee :: libp2p_crypto:pubkey_bin(), + Amount :: non_neg_integer(), + TokenType :: blockchain_token_type_v1:token_type()) -> txn_coinbase(). +new(Payee, Amount, TokenType) -> + #blockchain_txn_coinbase_v1_pb{payee=Payee, amount=Amount, token_type=TokenType}. + %%-------------------------------------------------------------------- %% @doc %% @end @@ -88,6 +101,15 @@ fee(_Txn) -> fee_payer(_Txn, _Ledger) -> undefined. +%%-------------------------------------------------------------------- +%% @doc +%% Get token_type associated with coinbase txn +%% @end +%%-------------------------------------------------------------------- +-spec token_type(txn_coinbase()) -> blockchain_token_type_v1:token_type(). +token_type(Txn) -> + Txn#blockchain_txn_coinbase_v1_pb.token_type. + %%-------------------------------------------------------------------- %% @doc %% This transaction is only allowed in the genesis block @@ -118,7 +140,15 @@ absorb(Txn, Chain) -> Ledger = blockchain:ledger(Chain), Payee = ?MODULE:payee(Txn), Amount = ?MODULE:amount(Txn), - blockchain_ledger_v1:credit_account(Payee, Amount, Ledger). + + case blockchain:config(?protocol_version, Ledger) of + {ok, 2} -> + TokenType = ?MODULE:token_type(Txn), + blockchain_ledger_v1:credit_account(Payee, Amount, TokenType, Ledger); + _ -> + blockchain_ledger_v1:credit_account(Payee, Amount, Ledger) + end. + %%-------------------------------------------------------------------- %% @doc @@ -150,7 +180,13 @@ to_json(Txn, _Opts) -> new_test() -> Tx = #blockchain_txn_coinbase_v1_pb{payee= <<"payee">>, amount=666}, - ?assertEqual(Tx, new(<<"payee">>, 666)). + ?assertEqual(Tx, new(<<"payee">>, 666)), + ?assertEqual(token_type(Tx), hnt). + +new2_test() -> + Tx = #blockchain_txn_coinbase_v1_pb{payee= <<"payee">>, amount=666, token_type=hst}, + ?assertEqual(Tx, new(<<"payee">>, 666, hst)), + ?assertEqual(token_type(Tx), hst). payee_test() -> Tx = new(<<"payee">>, 666), diff --git a/src/transactions/v1/blockchain_txn_vars_v1.erl b/src/transactions/v1/blockchain_txn_vars_v1.erl index dc1d70402c..0fa1929bab 100644 --- a/src/transactions/v1/blockchain_txn_vars_v1.erl +++ b/src/transactions/v1/blockchain_txn_vars_v1.erl @@ -1520,6 +1520,14 @@ validate_var(?discard_zero_freq_witness, Value) -> validate_var(?block_size_limit, Value) -> validate_int(Value, "block_size_limit", 1*1024*1024, 512*1024*1024, false); +validate_var(?protocol_version, Value) -> + case Value of + undefined -> ok; + 2 -> ok; %% Add support for multiple tokens + _ -> + throw({error, {invalid_protocol_version, Value}}) + end; + validate_var(Var, Value) -> %% check if these are dynamic region vars case atom_to_list(Var) of diff --git a/src/transactions/v2/blockchain_payment_v2.erl b/src/transactions/v2/blockchain_payment_v2.erl index 74c1630ed3..d9e3a7ff3c 100644 --- a/src/transactions/v2/blockchain_payment_v2.erl +++ b/src/transactions/v2/blockchain_payment_v2.erl @@ -11,13 +11,15 @@ -include_lib("helium_proto/include/blockchain_txn_payment_v2_pb.hrl"). -export([ - new/2, new/3, + new/2, new/3, new/4, payee/1, amount/1, memo/1, memo/2, max/1, is_valid_memo/1, is_valid_max/1, + token_type/1, token_type/2, + is_valid_token_type/1, print/1, json_type/0, to_json/2 @@ -52,19 +54,33 @@ new(Payee, max) -> -spec new(Payee :: libp2p_crypto:pubkey_bin(), Amount :: non_neg_integer() | max , Memo :: non_neg_integer()) -> payment(). -new(Payee, Amount, Memo) when is_integer(Amount) -> +new(Payee, Amount, Memo) when is_integer(Memo) andalso is_integer(Amount) -> #payment_pb{ payee=Payee, amount=Amount, memo=Memo, max=false }; -new(Payee, max, Memo) -> +new(Payee, Amount, TT) when is_integer(Amount) -> #payment_pb{ payee=Payee, - amount=0, + amount=Amount, + memo=0, + max=false, + token_type=TT + }. + +-spec new(Payee :: libp2p_crypto:pubkey_bin(), + Amount :: non_neg_integer(), + Memo :: non_neg_integer(), + TT :: blockchain_token_type_v1:token_type()) -> payment(). +new(Payee, Amount, Memo, TT) -> + #payment_pb{ + payee=Payee, + amount=Amount, memo=Memo, - max=true + token_type=TT +>>>>>>> a0aa395e (Another rework for proto compliance) }. @@ -88,6 +104,14 @@ memo(Payment, Memo) -> max(Payment) -> Payment#payment_pb.max. +-spec token_type(Payment :: payment()) -> blockchain_token_type_v1:token_type(). +token_type(Payment) -> + Payment#payment_pb.token_type. + +-spec token_type(Payment :: payment(), TT :: blockchain_token_type_v1:token_type()) -> payment(). +token_type(Payment, TT) -> + Payment#payment_pb{token_type=TT}. + -spec is_valid_memo(Payment :: payment()) -> boolean(). is_valid_memo(#payment_pb{memo = Memo}) -> try @@ -105,8 +129,17 @@ is_valid_max(_) -> false. print(undefined) -> <<"type=payment undefined">>; -print(#payment_pb{payee=Payee, amount=Amount, memo=Memo, max=Max}) -> - io_lib:format("type=payment payee: ~p amount: ~p, memo: ~p, max: ~p", [?TO_B58(Payee), Amount, Memo, Max]). +print(#payment_pb{payee=Payee, amount=Amount, memo=Memo, max=Max, token_type=TT}) -> + io_lib:format("type=payment payee: ~p amount: ~p, memo: ~p, max: ~p, token_type: ~p", [?TO_B58(Payee), Amount, Memo, Max, TT]). + +-spec is_valid_token_type(Payment :: payment()) -> boolean(). +is_valid_token_type(#payment_pb{token_type = TT}) -> + try + lists:member(TT, blockchain_token_type_v1:supported_tokens()) + catch _:_ -> + %% we can't do this, invalid + false + end. json_type() -> undefined. @@ -117,7 +150,8 @@ to_json(Payment, _Opts) -> payee => ?BIN_TO_B58(payee(Payment)), amount => amount(Payment), memo => ?MAYBE_FN(fun (V) -> base64:encode(<<(V):64/unsigned-little-integer>>) end, memo(Payment)), - max => ?MODULE:max(Payment) + max => ?MODULE:max(Payment), + token_type => atom_to_list(token_type(Payment)) }. %% ------------------------------------------------------------------ @@ -149,10 +183,19 @@ is_valid_max_test() -> Payment2 = #payment_pb{payee= <<"payee2">>, amount= 100, max= true}, ?assertEqual(false, ?MODULE:is_valid_max(Payment2)). +memo_test() -> + Payment = new(<<"payee">>, 100, 3), + ?assertEqual(3, ?MODULE:memo(Payment)). + +token_type_test() -> + P1 = new(<<"payee">>, 100, 3), + ?assertEqual(hnt, ?MODULE:token_type(P1)), + P2 = new(<<"payee">>, 100, hst), + ?assertEqual(hst, ?MODULE:token_type(P2)). + to_json_test() -> Payment = new(<<"payee">>, 100), Json = to_json(Payment, []), ?assert(lists:all(fun(K) -> maps:is_key(K, Json) end, - [payee, amount, memo, max])). - + [payee, amount, memo, max, token_type])). -endif. diff --git a/src/transactions/v2/blockchain_txn_payment_v2.erl b/src/transactions/v2/blockchain_txn_payment_v2.erl index bc44e83ac9..d7faae8346 100644 --- a/src/transactions/v2/blockchain_txn_payment_v2.erl +++ b/src/transactions/v2/blockchain_txn_payment_v2.erl @@ -460,6 +460,43 @@ has_default_memos(Payments) -> Payments ). +-spec token_check(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> ok | {error, any()}. +token_check(Txn, Ledger) -> + Payments = ?MODULE:payments(Txn), + case blockchain:config(?protocol_version, Ledger) of + {ok, 2} -> + %% check that the memos are valid + case has_valid_tokens(Payments) of + true -> ok; + false -> {error, invalid_tokens} + end; + _ -> + %% old behavior before var, allow only if token_type=hnt (default) + case has_default_tokens(Payments) of + true -> ok; + false -> {error, invalid_token_before_var} + end + end. + +-spec has_valid_tokens(Payments :: blockchain_payment_v2:payments()) -> boolean(). +has_valid_tokens(Payments) -> + lists:all( + fun(Payment) -> + %% TODO: check that the memo field is valid + blockchain_payment_v2:is_valid_token_type(Payment) + end, + Payments + ). + +-spec has_default_tokens(Payments :: blockchain_payment_v2:payments()) -> boolean(). +has_default_tokens(Payments) -> + lists:all( + fun(Payment) -> + hnt == blockchain_payment_v2:token_type(Payment) + end, + Payments + ). + %% ------------------------------------------------------------------ %% EUNIT Tests %% ------------------------------------------------------------------ diff --git a/test/blockchain_token_SUITE.erl b/test/blockchain_token_SUITE.erl new file mode 100644 index 0000000000..586de4c99c --- /dev/null +++ b/test/blockchain_token_SUITE.erl @@ -0,0 +1,170 @@ +-module(blockchain_token_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include("blockchain_vars.hrl"). + +-export([all/0, init_per_testcase/2, end_per_testcase/2]). + +-export([ + coinbase_test/1 +]). + +%%-------------------------------------------------------------------- +%% COMMON TEST CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Running tests for this suite +%% @end +%%-------------------------------------------------------------------- +all() -> + [ + coinbase_test + ]. + +%%-------------------------------------------------------------------- +%% TEST CASE SETUP +%%-------------------------------------------------------------------- + +init_per_testcase(TestCase, Config) -> + Config0 = blockchain_ct_utils:init_base_dir_config(?MODULE, TestCase, Config), + + HNTBal = 5000, + HGTBal = 1000, + HSTBal = 100, + HLTBal = 10, + + {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config0)), + + ExtraVars = #{?protocol_version => 2}, + + {ok, GenesisMembers, _GenesisBlock, ConsensusMembers, Keys} = + test_utils:init_chain_with_opts( + #{ + balance => + HNTBal, + keys => + {PrivKey, PubKey}, + in_consensus => + false, + have_init_dc => + true, + extra_vars => + ExtraVars, + token_allocations => + #{hnt => HNTBal, hst => HSTBal, hgt => HGTBal, hlt => HLTBal} + } + ), + + Chain = blockchain_worker:blockchain(), + Swarm = blockchain_swarm:tid(), + N = length(ConsensusMembers), + + [ + {hnt_bal, HNTBal}, + {hst_bal, HSTBal}, + {hgt_bal, HGTBal}, + {hlt_bal, HLTBal}, + {sup, Sup}, + {pubkey, PubKey}, + {privkey, PrivKey}, + {opts, Opts}, + {chain, Chain}, + {swarm, Swarm}, + {n, N}, + {consensus_members, ConsensusMembers}, + {genesis_members, GenesisMembers}, + Keys + | Config0 + ]. + +%%-------------------------------------------------------------------- +%% TEST CASE TEARDOWN +%%-------------------------------------------------------------------- +end_per_testcase(_, Config) -> + Sup = ?config(sup, Config), + meck:unload(), + % Make sure blockchain saved on file = in memory + case erlang:is_process_alive(Sup) of + true -> + true = erlang:exit(Sup, normal), + ok = test_utils:wait_until(fun() -> false =:= erlang:is_process_alive(Sup) end); + false -> + ok + end, + test_utils:cleanup_tmp_dir(?config(base_dir, Config)), + {comment, done}. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +coinbase_test(Config) -> + Chain = ?config(chain, Config), + HNTBal = ?config(hnt_bal, Config), + HSTBal = ?config(hst_bal, Config), + HGTBal = ?config(hgt_bal, Config), + HLTBal = ?config(hlt_bal, Config), + + % Check ledger to make sure everyone has the right balance + Ledger = blockchain:ledger(Chain), + Entries = blockchain_ledger_v1:entries_v2(Ledger), + _ = lists:foreach( + fun(Entry) -> + HNTBal = blockchain_ledger_entry_v2:balance(Entry, hnt), + 0 = blockchain_ledger_entry_v2:nonce(Entry), + HSTBal = blockchain_ledger_entry_v2:balance(Entry, hst), + 0 = blockchain_ledger_entry_v2:nonce(Entry), + HGTBal = blockchain_ledger_entry_v2:balance(Entry, hgt), + 0 = blockchain_ledger_entry_v2:nonce(Entry), + HLTBal = blockchain_ledger_entry_v2:balance(Entry, hlt), + 0 = blockchain_ledger_entry_v2:nonce(Entry) + end, + maps:values(Entries) + ), + + %% TODO: test payment txn + + %% ConsensusMembers = ?config(consensus_members, Config), + %% Balance = ?config(balance, Config), + + %% %% Test a payment transaction, add a block and check balances + %% [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + + %% %% Create a payment to a single payee + %% Recipient = blockchain_swarm:pubkey_bin(), + %% HNTAmt = 1000, + %% HSTAmt = 100, + %% HGTAmt = 10, + %% HLTAmt = 1, + %% P1 = blockchain_payment_v2:new(Recipient, HNTAmt, hnt), + %% P2 = blockchain_payment_v2:new(Recipient, HSTAmt, hst), + %% P3 = blockchain_payment_v2:new(Recipient, HGTAmt, hgt), + %% P4 = blockchain_payment_v2:new(Recipient, HLTAmt, hlt), + + %% Tx = blockchain_txn_payment_v2:new(Payer, [P1, P2, P3, P4], 1), + %% SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), + %% SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), + + %% ct:pal("~s", [blockchain_txn:print(SignedTx)]), + + %% {ok, Block} = test_utils:create_block(ConsensusMembers, [SignedTx]), + %% _ = blockchain_gossip_handler:add_block(Block, Chain, self(), blockchain_swarm:tid()), + + %% ?assertEqual({ok, blockchain_block:hash_block(Block)}, blockchain:head_hash(Chain)), + %% ?assertEqual({ok, Block}, blockchain:head_block(Chain)), + %% ?assertEqual({ok, 2}, blockchain:height(Chain)), + + %% ?assertEqual({ok, Block}, blockchain:get_block(2, Chain)), + + %% Ledger = blockchain:ledger(Chain), + + %% {ok, NewEntry0} = blockchain_ledger_v1:find_entry(Recipient, Ledger), + %% ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(NewEntry0)), + + %% {ok, NewEntry1} = blockchain_ledger_v1:find_entry(Payer, Ledger), + %% ?assertEqual(Balance - Amount, blockchain_ledger_entry_v1:balance(NewEntry1)), + ok. diff --git a/test/test_utils.erl b/test/test_utils.erl index 067aa27486..f5fbe89fe0 100644 --- a/test/test_utils.erl +++ b/test/test_utils.erl @@ -88,6 +88,7 @@ init_chain(Balance, Keys, InConsensus) when is_tuple(Keys), is_boolean(InConsens init_chain_with_opts(Opts) when is_map(Opts) -> Balance = maps:get(balance, Opts, 5000), ExtraVars = maps:get(extra_vars, Opts, #{}), + TokenAllocations = maps:get(token_allocations, Opts, undefined), GenesisMembers = case maps:find(genesis_members, Opts) of {ok, ConsensusMembers0} -> @@ -101,8 +102,22 @@ init_chain_with_opts(Opts) when is_map(Opts) -> % Create genesis block {InitialVars, Keys} = blockchain_ct_utils:create_vars(ExtraVars), - GenPaymentTxs = [blockchain_txn_coinbase_v1:new(Addr, Balance) - || {Addr, _} <- GenesisMembers], + GenPaymentTxs = + case TokenAllocations of + undefined -> + %% Do old style + [blockchain_txn_coinbase_v1:new(Addr, Balance) + || {Addr, _} <- GenesisMembers]; + TokenMap -> + lists:flatten( + maps:fold( + fun(TT, Bal, Acc) -> + X = [blockchain_txn_coinbase_v1:new(Addr, Bal, TT) || {Addr, _} <- GenesisMembers], + [X | Acc] + end, [], TokenMap)) + + end, + GenDCsTxs = [ blockchain_txn_dc_coinbase_v1:new(Addr, Balance) From add33238f450835a000932111e58d78b767eb5bb Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Thu, 19 May 2022 17:34:46 -0700 Subject: [PATCH 36/48] Add support for multi token payments - Review cmt, rename to total_amounts - Fix and extend multi token payment test - Extend payment_v2 SUITE --- src/ledger/v1/blockchain_ledger_v1.erl | 118 +++-- src/ledger/v2/blockchain_ledger_entry_v2.erl | 20 +- src/transactions/v2/blockchain_payment_v2.erl | 2 - .../v2/blockchain_txn_payment_v2.erl | 225 ++++++++-- test/blockchain_payment_v2_SUITE.erl | 424 ++++++++++++------ test/blockchain_token_SUITE.erl | 163 +++++-- 6 files changed, 698 insertions(+), 254 deletions(-) diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index c322f17a74..be9299eb19 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -39,6 +39,7 @@ process_delayed_actions/3, active_gateways/1, snapshot_gateways/1, load_gateways/2, + versioned_entry_mod_and_entries_cf/1, entries/1, entries_v2/1, htlcs/1, @@ -103,7 +104,6 @@ upgrade_pocs/1, find_entry/2, - find_entry_v2/2, credit_account/3, debit_account/4, debit_fee_from_account/5, credit_account/4, check_balance/3, @@ -300,6 +300,8 @@ blockchain_ledger_state_channel_v1:state_channel() | blockchain_ledger_state_channel_v2:state_channel_v2()}. -type h3dex() :: #{h3:h3_index() => [libp2p_crypto:pubkey_bin()]}. %% these keys are gateway addresses +-type tagged_cf() :: tagged_cf(). + -export_type([ledger/0]). -spec new(file:filename_all()) -> ledger(). @@ -2996,30 +2998,39 @@ trim_price_list(LastTime, PriceEntries) -> %% @doc %% @end %%-------------------------------------------------------------------- --spec find_entry(libp2p_crypto:pubkey_bin(), ledger()) -> {ok, blockchain_ledger_entry_v1:entry()} - | {error, any()}. +-spec find_entry(Address :: libp2p_crypto:pubkey_bin(), + Ledger :: ledger()) -> + {ok, blockchain_ledger_entry_v1:entry()} + | {ok, blockchain_ledger_entry_v2:entry()} + | {error, any()}. find_entry(Address, Ledger) -> - EntriesCF = entries_cf(Ledger), + {EntryMod, EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), + find_entry_(Address, EntryMod, EntriesCF, Ledger). + +-spec find_entry_(Address :: libp2p_crypto:pubkey_bin(), + EntryMod :: atom(), + EntriesCF :: tagged_cf(), + Ledger :: ledger()) -> + {ok, blockchain_ledger_entry_v1:entry()} + | {ok, blockchain_ledger_entry_v2:entry()} + | {error, any()}. +find_entry_(Address, EntryMod, EntriesCF, Ledger) -> case cache_get(Ledger, EntriesCF, Address, []) of {ok, BinEntry} -> - {ok, blockchain_ledger_entry_v1:deserialize(BinEntry)}; + {ok, EntryMod:deserialize(BinEntry)}; not_found -> {error, address_entry_not_found}; Error -> Error end. --spec find_entry_v2(Addres :: libp2p_crypto:pubkey_bin(), Ledger :: ledger()) -> - {ok, blockchain_ledger_entry_v2:entry()} | {error, any()}. -find_entry_v2(Address, Ledger) -> - EntriesV2CF = entries_v2_cf(Ledger), - case cache_get(Ledger, EntriesV2CF, Address, []) of - {ok, BinEntry} -> - {ok, blockchain_ledger_entry_v2:deserialize(BinEntry)}; - not_found -> - {error, address_entry_not_found}; - Error -> - Error +-spec versioned_entry_mod_and_entries_cf(Ledger :: ledger()) -> {atom(), tagged_cf()}. +versioned_entry_mod_and_entries_cf(Ledger) -> + case ?MODULE:config(?protocol_version, Ledger) of + {ok, 2} -> + {blockchain_ledger_entry_v2, entries_v2_cf(Ledger)}; + _ -> + {blockchain_ledger_entry_v1, entries_cf(Ledger)} end. -spec credit_account(libp2p_crypto:pubkey_bin(), integer(), ledger()) -> ok | {error, any()}. @@ -3047,7 +3058,7 @@ credit_account(Address, Amount, Ledger) -> Ledger :: ledger()) -> ok | {error, any()}. credit_account(Address, Amount, TT, Ledger) -> EntriesCF = entries_v2_cf(Ledger), - case ?MODULE:find_entry_v2(Address, Ledger) of + case ?MODULE:find_entry(Address, Ledger) of {error, address_entry_not_found} -> %% create blank entry to fill all the token types Entry0 = blockchain_ledger_entry_v2:new(), @@ -3064,8 +3075,11 @@ credit_account(Address, Amount, TT, Ledger) -> Error end. --spec debit_account(libp2p_crypto:pubkey_bin(), integer(), integer(), ledger()) -> ok | {error, any()}. -debit_account(Address, Amount, Nonce, Ledger) -> +-spec debit_account(Address :: libp2p_crypto:pubkey_bin(), + AmountOrAmounts :: integer() | blockchain_txn_payment_v2:total_amounts(), + Nonce :: integer(), + Ledger :: ledger()) -> ok | {error, any()}. +debit_account(Address, AmountOrAmounts, Nonce, Ledger) when is_integer(AmountOrAmounts) -> case ?MODULE:find_entry(Address, Ledger) of {error, _}=Error -> Error; @@ -3073,23 +3087,55 @@ debit_account(Address, Amount, Nonce, Ledger) -> case Nonce =:= blockchain_ledger_entry_v1:nonce(Entry) + 1 of true -> Balance = blockchain_ledger_entry_v1:balance(Entry), - case (Balance - Amount) >= 0 of + case (Balance - AmountOrAmounts) >= 0 of true -> Entry1 = blockchain_ledger_entry_v1:new( Nonce, - (Balance - Amount) + (Balance - AmountOrAmounts) ), Bin = blockchain_ledger_entry_v1:serialize(Entry1), EntriesCF = entries_cf(Ledger), cache_put(Ledger, EntriesCF, Address, Bin); false -> - {error, {insufficient_balance, {Amount, Balance}}} + {error, {insufficient_balance, {AmountOrAmounts, Balance}}} end; false -> {error, {bad_nonce, {payment, Nonce, blockchain_ledger_entry_v1:nonce(Entry)}}} end + end; +debit_account(Address, AmountOrAmounts, Nonce, Ledger) when is_map(AmountOrAmounts) -> + %% TODO: Maybe also check that protocol_version = 2 is set here? Although amounts being + %% a map only ever should occur with the multi token payment txn, so maybe it's okay? + case ?MODULE:find_entry(Address, Ledger) of + {error, _}=Error -> + Error; + {ok, Entry} -> + case Nonce =:= blockchain_ledger_entry_v2:nonce(Entry) + 1 of + true -> + case lists:all( + fun(TT) -> + blockchain_ledger_entry_v2:balance(Entry, TT) >= maps:get(TT, AmountOrAmounts, 0) + end, + blockchain_token_type_v1:supported_tokens()) + of + true -> + Entry0 = maps:fold( + fun(TT, Amt, Acc) -> + blockchain_ledger_entry_v2:debit(Acc, Amt, TT) + end, Entry, AmountOrAmounts), + Entry1 = blockchain_ledger_entry_v2:nonce(Entry0, Nonce), + Bin = blockchain_ledger_entry_v2:serialize(Entry1), + EntriesCF = entries_v2_cf(Ledger), + cache_put(Ledger, EntriesCF, Address, Bin); + false -> + {error, {insufficient_balance, {libp2p_crypto:bin_to_b58(Address), AmountOrAmounts}}} + end; + false -> + {error, {bad_nonce, {payment_v2, Nonce, blockchain_ledger_entry_v2:nonce(Entry)}}} + end end. + -spec debit_fee_from_account(libp2p_crypto:pubkey_bin(), integer(), ledger(), blockchain_txn:hash(), blockchain:blockchain()) -> ok | {error, any()}. debit_fee_from_account(Address, Fee, Ledger, TxnHash, Chain) -> case ?MODULE:find_entry(Address, Ledger) of @@ -4174,73 +4220,73 @@ get_block_info(Height, #ledger_v1{blocks_db = DB, end end. --spec default_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec default_cf(ledger()) -> tagged_cf(). default_cf(Ledger) -> SL = subledger(Ledger), {default, db(Ledger), SL#sub_ledger_v1.default}. --spec active_gateways_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec active_gateways_cf(ledger()) -> tagged_cf(). active_gateways_cf(Ledger) -> SL = subledger(Ledger), {active_gateways, db(Ledger), SL#sub_ledger_v1.active_gateways}. --spec gw_denorm_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec gw_denorm_cf(ledger()) -> tagged_cf(). gw_denorm_cf(Ledger) -> SL = subledger(Ledger), {gw_denorm, db(Ledger), SL#sub_ledger_v1.gw_denorm}. --spec entries_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec entries_cf(ledger()) -> tagged_cf(). entries_cf(Ledger) -> SL = subledger(Ledger), {entries, db(Ledger), SL#sub_ledger_v1.entries}. --spec entries_v2_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec entries_v2_cf(ledger()) -> tagged_cf(). entries_v2_cf(Ledger) -> SL = subledger(Ledger), {entries_v2, db(Ledger), SL#sub_ledger_v1.entries_v2}. --spec dc_entries_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec dc_entries_cf(ledger()) -> tagged_cf(). dc_entries_cf(Ledger) -> SL = subledger(Ledger), {dc_entries, db(Ledger), SL#sub_ledger_v1.dc_entries}. --spec htlcs_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec htlcs_cf(ledger()) -> tagged_cf(). htlcs_cf(Ledger) -> SL = subledger(Ledger), {htlcs, db(Ledger), SL#sub_ledger_v1.htlcs}. --spec pocs_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec pocs_cf(ledger()) -> tagged_cf(). pocs_cf(Ledger) -> SL = subledger(Ledger), {pocs, db(Ledger), SL#sub_ledger_v1.pocs}. --spec proposed_pocs_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec proposed_pocs_cf(ledger()) -> tagged_cf(). proposed_pocs_cf(Ledger) -> SL = subledger(Ledger), {proposed_pocs, db(Ledger), SL#sub_ledger_v1.proposed_pocs}. --spec securities_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec securities_cf(ledger()) -> tagged_cf(). securities_cf(Ledger) -> SL = subledger(Ledger), {securities, db(Ledger), SL#sub_ledger_v1.securities}. --spec routing_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec routing_cf(ledger()) -> tagged_cf(). routing_cf(Ledger) -> SL = subledger(Ledger), {routing, db(Ledger), SL#sub_ledger_v1.routing}. --spec subnets_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec subnets_cf(ledger()) -> tagged_cf(). subnets_cf(Ledger) -> SL = subledger(Ledger), {subnets, db(Ledger), SL#sub_ledger_v1.subnets}. --spec state_channels_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec state_channels_cf(ledger()) -> tagged_cf(). state_channels_cf(Ledger) -> SL = subledger(Ledger), {state_channels, db(Ledger), SL#sub_ledger_v1.state_channels}. --spec h3dex_cf(ledger()) -> {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. +-spec h3dex_cf(ledger()) -> tagged_cf(). h3dex_cf(Ledger) -> SL = subledger(Ledger), {h3dex, db(Ledger), SL#sub_ledger_v1.h3dex}. diff --git a/src/ledger/v2/blockchain_ledger_entry_v2.erl b/src/ledger/v2/blockchain_ledger_entry_v2.erl index b4a7a7a4af..5fe2210738 100644 --- a/src/ledger/v2/blockchain_ledger_entry_v2.erl +++ b/src/ledger/v2/blockchain_ledger_entry_v2.erl @@ -7,8 +7,8 @@ -export([ new/0, - nonce/1, - balance/2, + nonce/1, nonce/2, + balance/1, balance/2, credit/3, debit/3, serialize/1, @@ -43,6 +43,22 @@ new() -> nonce(#blockchain_ledger_entry_v2_pb{nonce = Nonce}) -> Nonce. +-spec nonce(Entry :: entry(), Nonce :: non_neg_integer()) -> entry(). +nonce(Entry, Nonce) -> + Entry#blockchain_ledger_entry_v2_pb{nonce = Nonce}. + +%%-------------------------------------------------------------------- +%% @doc Return hnt balance as default. +%% @end +%%-------------------------------------------------------------------- +-spec balance(Entry :: entry()) -> non_neg_integer(). +balance(Entry) -> + hnt_balance(Entry). + +%%-------------------------------------------------------------------- +%% @doc Return requested token balance. +%% @end +%%-------------------------------------------------------------------- -spec balance(Entry :: entry(), TT :: blockchain_token_type_v1:token_type()) -> non_neg_integer(). balance(Entry, hnt) -> hnt_balance(Entry); diff --git a/src/transactions/v2/blockchain_payment_v2.erl b/src/transactions/v2/blockchain_payment_v2.erl index d9e3a7ff3c..f46d8e2e80 100644 --- a/src/transactions/v2/blockchain_payment_v2.erl +++ b/src/transactions/v2/blockchain_payment_v2.erl @@ -80,10 +80,8 @@ new(Payee, Amount, Memo, TT) -> amount=Amount, memo=Memo, token_type=TT ->>>>>>> a0aa395e (Another rework for proto compliance) }. - -spec payee(Payment :: payment()) -> libp2p_crypto:pubkey_bin(). payee(Payment) -> Payment#payment_pb.payee. diff --git a/src/transactions/v2/blockchain_txn_payment_v2.erl b/src/transactions/v2/blockchain_txn_payment_v2.erl index d7faae8346..075f1b918d 100644 --- a/src/transactions/v2/blockchain_txn_payment_v2.erl +++ b/src/transactions/v2/blockchain_txn_payment_v2.erl @@ -18,33 +18,34 @@ -include_lib("helium_proto/include/blockchain_txn_payment_v2_pb.hrl"). -export([ - new/3, - hash/1, - payer/1, - payments/1, - payees/1, - amounts/2, - total_amount/2, - fee/1, fee/2, - fee_payer/2, - calculate_fee/2, calculate_fee/5, - nonce/1, - signature/1, - sign/2, - is_valid/2, - absorb/2, - print/1, - json_type/0, - to_json/2 -]). + new/3, + hash/1, + payer/1, + payments/1, + payees/1, + amounts/2, + total_amount/2, total_amounts/1, + fee/1, fee/2, + fee_payer/2, + calculate_fee/2, calculate_fee/5, + nonce/1, + signature/1, + sign/2, + is_valid/2, + absorb/2, + print/1, + json_type/0, + to_json/2 + ]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. +-type total_amounts() :: #{blockchain_token_type_v1:token_type() => non_neg_integer()}. -type txn_payment_v2() :: #blockchain_txn_payment_v2_pb{}. --export_type([txn_payment_v2/0]). +-export_type([txn_payment_v2/0, total_amounts/0]). -spec new( Payer :: libp2p_crypto:pubkey_bin(), @@ -89,6 +90,16 @@ amounts(Txn, Ledger) -> total_amount(Txn, Ledger) -> lists:sum(?MODULE:amounts(Txn, Ledger)). +-spec total_amounts(txn_payment_v2()) -> total_amounts(). +total_amounts(Txn) -> + lists:foldl( + fun(Payment, Acc) -> + TT = blockchain_payment_v2:token_type(Payment), + Amt = blockchain_payment_v2:amount(Payment), + Fun = fun(V) -> V + Amt end, + maps:update_with(TT, Fun, Amt, Acc) + end, #{}, ?MODULE:payments(Txn)). + -spec fee(txn_payment_v2()) -> non_neg_integer(). fee(Txn) -> Txn#blockchain_txn_payment_v2_pb.fee. @@ -150,7 +161,12 @@ is_valid(Txn, Chain) -> ]) of ok -> - do_is_valid_checks(Txn, Chain, M); + case blockchain:config(?protocol_version, Ledger) of + {ok, 2} -> + do_is_valid_checks_v2(Txn, Chain, M); + _ -> + do_is_valid_checks(Txn, Chain, M) + end; Error -> Error end; @@ -161,8 +177,46 @@ is_valid(Txn, Chain) -> -spec absorb(txn_payment_v2(), blockchain:blockchain()) -> ok | {error, any()}. absorb(Txn, Chain) -> Ledger = blockchain:ledger(Chain), - SpecifiedAmount = lists:sum([blockchain_payment_v2:amount(Payment) || Payment <- ?MODULE:payments(Txn)]), + case blockchain:config(?protocol_version, Ledger) of + {ok, 2} -> + absorb_v2_(Txn, Ledger, Chain); + _ -> + absorb_(Txn, Ledger, Chain) + end. + +-spec absorb_v2_(txn_payment_v2(), blockchain_ledger_v1:ledger(), blockchain:blockchain()) -> ok | {error, any()}. +absorb_v2_(Txn, Ledger, Chain) -> + Fee = ?MODULE:fee(Txn), + Hash = ?MODULE:hash(Txn), + Payer = ?MODULE:payer(Txn), + Nonce = ?MODULE:nonce(Txn), + TotalAmounts = ?MODULE:total_amounts(Txn), + AreFeesEnabled = blockchain_ledger_v1:txn_fees_active(Ledger), + case blockchain_ledger_v1:debit_fee(Payer, Fee, Ledger, AreFeesEnabled, Hash, Chain) of + {error, _Reason}=Error -> + Error; + ok -> + case blockchain_ledger_v1:debit_account(Payer, TotalAmounts, Nonce, Ledger) of + {error, _Reason} = Error -> + Error; + ok -> + Payments = ?MODULE:payments(Txn), + ok = lists:foreach( + fun(Payment) -> + PayeePubkeyBin = blockchain_payment_v2:payee(Payment), + PayeeAmount = blockchain_payment_v2:amount(Payment), + TT = blockchain_payment_v2:token_type(Payment), + blockchain_ledger_v1:credit_account(PayeePubkeyBin, PayeeAmount, TT, Ledger) + end, + Payments + ) + end + end. + +-spec absorb_(txn_payment_v2(), blockchain_ledger_v1:ledger(), blockchain:blockchain()) -> ok | {error, any()}. +absorb_(Txn, Ledger, Chain) -> TotAmount = ?MODULE:total_amount(Txn, Ledger), + SpecifiedAmount = lists:sum([blockchain_payment_v2:amount(Payment) || Payment <- ?MODULE:payments(Txn)]), MaxPayment = TotAmount - SpecifiedAmount, Fee = ?MODULE:fee(Txn), Hash = ?MODULE:hash(Txn), @@ -204,17 +258,10 @@ print( signature = S } ) -> - {MaxPayment, SpecifiedPayments} = split_max_payment(Payments), - SpecifiedTotal = lists:sum([blockchain_payment_v2:amount(Payment) || Payment <- SpecifiedPayments]), - TotalAmount = case length(MaxPayment) > 1 of - true -> erlang:integer_to_list(SpecifiedTotal) ++ " + a balance clearing payment"; - false -> SpecifiedTotal - end, io_lib:format( - "type=payment_v2, payer=~p, total_amount: ~p, fee=~p, nonce=~p, signature=~s~n payments: ~s", + "type=payment_v2, payer=~p, fee=~p, nonce=~p, signature=~s~n payments: ~s", [ ?TO_B58(Payer), - TotalAmount, Fee, Nonce, ?TO_B58(S), @@ -317,6 +364,75 @@ do_is_valid_checks(Txn, Chain, MaxPayments) -> end end. +-spec do_is_valid_checks_v2( + Txn :: txn_payment_v2(), + Chain :: blockchain:blockchain(), + MaxPayments :: pos_integer()) -> ok | {error, any()}. +do_is_valid_checks_v2(Txn, Chain, MaxPayments) -> + Ledger = blockchain:ledger(Chain), + Payer = ?MODULE:payer(Txn), + Signature = ?MODULE:signature(Txn), + Payments = ?MODULE:payments(Txn), + PubKey = libp2p_crypto:bin_to_pubkey(Payer), + BaseTxn = Txn#blockchain_txn_payment_v2_pb{signature = <<>>}, + EncodedTxn = blockchain_txn_payment_v2_pb:encode_msg(BaseTxn), + + case libp2p_crypto:verify(EncodedTxn, Signature, PubKey) of + false -> + {error, bad_signature}; + true -> + LengthPayments = length(Payments), + case LengthPayments == 0 of + true -> + %% Check that there are payments + {error, zero_payees}; + false -> + case blockchain_ledger_v1:find_entry(Payer, Ledger) of + {error, _}=Error0 -> + Error0; + {ok, Entry} -> + TxnNonce = ?MODULE:nonce(Txn), + LedgerNonce = blockchain_ledger_entry_v2:nonce(Entry), + case TxnNonce =:= LedgerNonce + 1 of + false -> + {error, {bad_nonce, {payment_v2, TxnNonce, LedgerNonce}}}; + true -> + case LengthPayments > MaxPayments of + %% Check that we don't exceed max payments + true -> + {error, {exceeded_max_payments, {LengthPayments, MaxPayments}}}; + false -> + case lists:member(Payer, ?MODULE:payees(Txn)) of + false -> + %% check that every payee is unique + case has_unique_payees_v2(Payments) of + false -> + {error, duplicate_payees}; + true -> + TokenCheck = token_check(Txn, Ledger), + AmountCheck = amount_check_v2(Txn, Ledger), + MemoCheck = memo_check(Txn, Ledger), + + case {AmountCheck, MemoCheck, TokenCheck} of + {ok, ok, ok} -> + %% Everthing looks good so far, do the fee check last + fee_check(Txn, Chain, Ledger); + _ -> + {error, {invalid_transaction, + {amount_check, AmountCheck}, + {memo_check, MemoCheck}, + {token_check, TokenCheck}}} + end + end; + true -> + {error, self_payment} + end + end + end + end + end + end. + %% ------------------------------------------------------------------ %% Internal functions %% ------------------------------------------------------------------ @@ -357,6 +473,39 @@ memo_check(Txn, Ledger) -> end end. +-spec amount_check_v2(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> ok | {error, any()}. +amount_check_v2(Txn, Ledger) -> + TotAmounts = ?MODULE:total_amounts(Txn), + Payer = ?MODULE:payer(Txn), + Payments = ?MODULE:payments(Txn), + + {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), + + PayerHasEnoughTTBalance = + lists:all( + fun(TT) -> + PayerTTBalance = blockchain_ledger_entry_v2:balance(PayerEntry, TT), + PayerTTBalance >= maps:get(TT, TotAmounts, 0) + end, + blockchain_token_type_v1:supported_tokens()), + + case blockchain:config(?allow_zero_amount, Ledger) of + {ok, false} -> + %% check that none of the payments have a zero amount + case has_non_zero_amounts(Payments) of + false -> false; + true -> + case PayerHasEnoughTTBalance of + false -> + {error, amount_check_v2_failed}; + true -> + ok + end + end; + _ -> + {error, allow_zero_amount_not_set} + end. + -spec amount_check(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> boolean(). amount_check(Txn, Ledger) -> Payments = ?MODULE:payments(Txn), @@ -403,6 +552,19 @@ has_unique_payees(Payments) -> Payees = [blockchain_payment_v2:payee(P) || P <- Payments], length(lists:usort(Payees)) == length(Payees). +-spec has_unique_payees_v2(Payments :: blockchain_payment_v2:payments()) -> boolean(). +has_unique_payees_v2(Payments) -> + %% Check that {payee, token} is unique across payments + PayeesAndTokens = + lists:foldl( + fun(Payment, Acc) -> + TT = blockchain_payment_v2:token_type(Payment), + Payee = blockchain_payment_v2:payee(Payment), + [{Payee, TT} | Acc] + end, [], Payments), + length(lists:usort(PayeesAndTokens)) == length(PayeesAndTokens). + + -spec has_non_zero_amounts(Payments :: blockchain_payment_v2:payments()) -> boolean(). has_non_zero_amounts(Payments) -> Amounts = [blockchain_payment_v2:amount(P) || P <- Payments], @@ -465,7 +627,7 @@ token_check(Txn, Ledger) -> Payments = ?MODULE:payments(Txn), case blockchain:config(?protocol_version, Ledger) of {ok, 2} -> - %% check that the memos are valid + %% check that the tokens are valid case has_valid_tokens(Payments) of true -> ok; false -> {error, invalid_tokens} @@ -482,7 +644,6 @@ token_check(Txn, Ledger) -> has_valid_tokens(Payments) -> lists:all( fun(Payment) -> - %% TODO: check that the memo field is valid blockchain_payment_v2:is_valid_token_type(Payment) end, Payments diff --git a/test/blockchain_payment_v2_SUITE.erl b/test/blockchain_payment_v2_SUITE.erl index f38fc2df1c..4d9ec48b14 100644 --- a/test/blockchain_payment_v2_SUITE.erl +++ b/test/blockchain_payment_v2_SUITE.erl @@ -4,56 +4,96 @@ -include_lib("eunit/include/eunit.hrl"). -include("blockchain_vars.hrl"). --export([all/0, init_per_testcase/2, end_per_testcase/2]). +-export([ + all/0, + groups/0, + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2 +]). -export([ - multisig_test/1, - single_payee_test/1, - same_payees_test/1, - different_payees_test/1, - empty_payees_test/1, - self_payment_test/1, - max_payments_test/1, - balance_clearing_test/1, - invalid_balance_clearing_test/1, - balance_clearing_disabled_test/1, - signature_test/1, - zero_amount_test/1, - negative_amount_test/1, - valid_memo_test/1, - negative_memo_test/1, - valid_memo_not_set_test/1, - invalid_memo_not_set_test/1, - big_memo_valid_test/1, - big_memo_invalid_test/1 - ]). + multisig_test/1, + single_payee_test/1, + same_payees_test/1, + different_payees_test/1, + empty_payees_test/1, + self_payment_test/1, + max_payments_test/1, + balance_clearing_test/1, + invalid_balance_clearing_test/1, + balance_clearing_disabled_test/1, + signature_test/1, + zero_amount_test/1, + negative_amount_test/1, + valid_memo_test/1, + negative_memo_test/1, + valid_memo_not_set_test/1, + invalid_memo_not_set_test/1, + big_memo_valid_test/1, + big_memo_invalid_test/1 +]). + +groups() -> + [ + {without_protocol_version, [], without_protocol_version_tests()}, + {with_protocol_version, [], with_protocol_version_tests()} + ]. + +without_protocol_version_tests() -> + test_cases(). + +with_protocol_version_tests() -> + test_cases(). + +init_per_group(with_protocol_version, Config) -> + [ + {group_vars, #{?protocol_version => 2}}, + {balance, 5000}, + {token_allocations, #{hnt => 5000, hst => 1000, hgt => 100, hlt => 10}} + | Config + ]; +init_per_group(_, Config) -> + [{balance, 5000}, {group_vars, #{}}, {token_allocations, undefined} | Config]. + +end_per_group(_, _Config) -> + ok. all() -> [ - multisig_test, - single_payee_test, - same_payees_test, - different_payees_test, - empty_payees_test, - self_payment_test, - max_payments_test, - balance_clearing_test, - invalid_balance_clearing_test, - balance_clearing_disabled_test, - signature_test, - zero_amount_test, - negative_amount_test, - valid_memo_test, - negative_memo_test, - valid_memo_not_set_test, - invalid_memo_not_set_test, - big_memo_valid_test, - big_memo_invalid_test + {group, without_protocol_version}, + {group, with_protocol_version} + ]. + +test_cases() -> + [ + multisig_test, + single_payee_test, + same_payees_test, + different_payees_test, + empty_payees_test, + self_payment_test, + max_payments_test, + balance_clearing_test, + invalid_balance_clearing_test, + balance_clearing_disabled_test, + signature_test, + zero_amount_test, + negative_amount_test, + valid_memo_test, + negative_memo_test, + valid_memo_not_set_test, + invalid_memo_not_set_test, + big_memo_valid_test, + big_memo_invalid_test ]. -define(MAX_PAYMENTS, 20). --define(VALID_GIANT_MEMO, 18446744073709551615). %% max 64 bit number --define(INVALID_GIANT_MEMO, 18446744073709551616). %% max 64 bit number + 1, takes 72 bits +%% max 64 bit number +-define(VALID_GIANT_MEMO, 18446744073709551615). +%% max 64 bit number + 1, takes 72 bits +-define(INVALID_GIANT_MEMO, 18446744073709551616). %%-------------------------------------------------------------------- %% TEST CASE SETUP @@ -61,13 +101,36 @@ all() -> init_per_testcase(TestCase, Config) -> Config0 = blockchain_ct_utils:init_base_dir_config(?MODULE, TestCase, Config), - Balance = 5000, + + Balance = ?config(balance, Config0), + {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config0)), - ExtraVars = extra_vars(TestCase), + GroupVars = ?config(group_vars, Config0), + ct:pal("group_vars: ~p", [GroupVars]), + TestCaseVars = test_case_vars(TestCase), + ExtraVars = maps:merge(GroupVars, TestCaseVars), + ct:pal("extra vars: ~p", [ExtraVars]), + TokenAllocations = ?config(token_allocations, Config0), + ct:pal("token_allocations: ~p", [TokenAllocations]), {ok, GenesisMembers, _GenesisBlock, ConsensusMembers, Keys} = - test_utils:init_chain(Balance, {PrivKey, PubKey}, true, ExtraVars), + test_utils:init_chain_with_opts( + #{ + balance => + Balance, + keys => + {PrivKey, PubKey}, + in_consensus => + true, + have_init_dc => + true, + extra_vars => + ExtraVars, + token_allocations => + TokenAllocations + } + ), Chain = blockchain_worker:blockchain(), Swarm = blockchain_swarm:swarm(), @@ -75,25 +138,35 @@ init_per_testcase(TestCase, Config) -> % Check ledger to make sure everyone has the right balance Ledger = blockchain:ledger(Chain), + + %% NOTE: Get the current ledger entry module depending on the Ledger + %% For testing (chain var: protocol_version = 2) + {EntryMod, _} = blockchain_ledger_v1:versioned_entry_mod_and_entries_cf(Ledger), + ct:pal("EntryMod: ~p", [EntryMod]), + Entries = blockchain_ledger_v1:entries(Ledger), - _ = lists:foreach(fun(Entry) -> - Balance = blockchain_ledger_entry_v1:balance(Entry), - 0 = blockchain_ledger_entry_v1:nonce(Entry) - end, maps:values(Entries)), + _ = lists:foreach( + fun(Entry) -> + Balance = EntryMod:balance(Entry), + 0 = EntryMod:nonce(Entry) + end, + maps:values(Entries) + ), [ - {balance, Balance}, - {sup, Sup}, - {pubkey, PubKey}, - {privkey, PrivKey}, - {opts, Opts}, - {chain, Chain}, - {swarm, Swarm}, - {n, N}, - {consensus_members, ConsensusMembers}, - {genesis_members, GenesisMembers}, - Keys - | Config0 + {entry_mod, EntryMod}, + {balance, Balance}, + {sup, Sup}, + {pubkey, PubKey}, + {privkey, PrivKey}, + {opts, Opts}, + {chain, Chain}, + {swarm, Swarm}, + {n, N}, + {consensus_members, ConsensusMembers}, + {genesis_members, GenesisMembers}, + Keys + | Config0 ]. %%-------------------------------------------------------------------- @@ -119,6 +192,7 @@ end_per_testcase(_, Config) -> multisig_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Chain = ?config(chain, Config), + EntryMod = ?config(entry_mod, Config), Amount = 10, InitialHeight = 2, @@ -129,29 +203,37 @@ multisig_test(Config) -> %% +------> D[i] %% All funds will come from A, so only it needs a starting balance, %% the rest can be freshly created. - [_, {A, {_, _, A_SigFun}} | _ ] = ConsensusMembers, + [_, {A, {_, _, A_SigFun}} | _] = ConsensusMembers, lists:foldl( - fun ({M, N}, H) -> + fun({M, N}, H) -> {B, B_SigFun} = make_multisig_addr(M, N), {C, _} = make_multisig_addr(M, N), {D, D_SigFun} = make_multisig_addr(M, N), %% * 2 because B will later pay twice (to C and D) - ?assertEqual(ok, transfer(Amount * 2, {A, A_SigFun}, B, H, Chain, ConsensusMembers)), - ?assertEqual(ok, transfer(Amount, {B, B_SigFun}, C, H + 1, Chain, ConsensusMembers)), + ?assertEqual( + ok, + transfer(Amount * 2, {A, A_SigFun}, B, H, Chain, ConsensusMembers, EntryMod) + ), + ?assertEqual( + ok, + transfer(Amount, {B, B_SigFun}, C, H + 1, Chain, ConsensusMembers, EntryMod) + ), %% B->D wrong SigFun ?assertError( {badmatch, {error, {invalid_txns, [{_, bad_signature}]}}}, - transfer(1, {B, D_SigFun}, D, H + 2, Chain, ConsensusMembers) + transfer(1, {B, D_SigFun}, D, H + 2, Chain, ConsensusMembers, EntryMod) ), %% B->D correct SigFun - ?assertEqual(ok, transfer(Amount, {B, B_SigFun}, D, H + 2, Chain, ConsensusMembers)), + ?assertEqual( + ok, + transfer(Amount, {B, B_SigFun}, D, H + 2, Chain, ConsensusMembers, EntryMod) + ), H + 3 end, InitialHeight, [ {M, N} - || - N <- lists:seq(1, 10), + || N <- lists:seq(1, 10), M <- lists:seq(1, N) ] ). @@ -160,9 +242,10 @@ single_payee_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Balance = ?config(balance, Config), Chain = ?config(chain, Config), + EntryMod = ?config(entry_mod, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee Recipient = blockchain_swarm:pubkey_bin(), @@ -187,10 +270,10 @@ single_payee_test(Config) -> Ledger = blockchain:ledger(Chain), {ok, NewEntry0} = blockchain_ledger_v1:find_entry(Recipient, Ledger), - ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(NewEntry0)), + ?assertEqual(Balance + Amount, EntryMod:balance(NewEntry0)), {ok, NewEntry1} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(Balance - Amount, blockchain_ledger_entry_v1:balance(NewEntry1)), + ?assertEqual(Balance - Amount, EntryMod:balance(NewEntry1)), ok. same_payees_test(Config) -> @@ -200,7 +283,7 @@ same_payees_test(Config) -> _Swarm = ?config(swarm, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee TWICE Recipient = blockchain_swarm:pubkey_bin(), @@ -220,9 +303,10 @@ different_payees_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Balance = ?config(balance, Config), Chain = ?config(chain, Config), + EntryMod = ?config(entry_mod, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}, {Recipient2, _} |_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}}, {Recipient2, _} | _] = ConsensusMembers, %% Create a payment to a payee1 Recipient1 = blockchain_swarm:pubkey_bin(), @@ -251,13 +335,13 @@ different_payees_test(Config) -> Ledger = blockchain:ledger(Chain), {ok, NewEntry1} = blockchain_ledger_v1:find_entry(Recipient1, Ledger), - ?assertEqual(Balance + Amount1, blockchain_ledger_entry_v1:balance(NewEntry1)), + ?assertEqual(Balance + Amount1, EntryMod:balance(NewEntry1)), {ok, NewEntry2} = blockchain_ledger_v1:find_entry(Recipient2, Ledger), - ?assertEqual(Balance + Amount2, blockchain_ledger_entry_v1:balance(NewEntry2)), + ?assertEqual(Balance + Amount2, EntryMod:balance(NewEntry2)), {ok, NewEntry0} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(Balance - Amount1 - Amount2, blockchain_ledger_entry_v1:balance(NewEntry0)), + ?assertEqual(Balance - Amount1 - Amount2, EntryMod:balance(NewEntry0)), ok. empty_payees_test(Config) -> @@ -265,7 +349,7 @@ empty_payees_test(Config) -> Chain = ?config(chain, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, Tx = blockchain_txn_payment_v2:new(Payer, [], 1), SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), @@ -281,7 +365,7 @@ self_payment_test(Config) -> Chain = ?config(chain, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee TWICE Recipient = blockchain_swarm:pubkey_bin(), @@ -302,24 +386,28 @@ max_payments_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Chain = ?config(chain, Config), - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create 1+max_payments Payees = [P || {P, _} <- test_utils:generate_keys(?MAX_PAYMENTS + 1, ed25519)], - Payments = lists:foldl(fun(PayeePubkeyBin, Acc) -> - Amount = rand:uniform(100), - [blockchain_payment_v2:new(PayeePubkeyBin, Amount) | Acc] - end, - [], - Payees), + Payments = lists:foldl( + fun(PayeePubkeyBin, Acc) -> + Amount = rand:uniform(100), + [blockchain_payment_v2:new(PayeePubkeyBin, Amount) | Acc] + end, + [], + Payees + ), Tx = blockchain_txn_payment_v2:new(Payer, Payments, 1), SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), - ?assertEqual({error, {exceeded_max_payments, {length(Payments), ?MAX_PAYMENTS}}}, - blockchain_txn_payment_v2:is_valid(SignedTx, Chain)), + ?assertEqual( + {error, {exceeded_max_payments, {length(Payments), ?MAX_PAYMENTS}}}, + blockchain_txn_payment_v2:is_valid(SignedTx, Chain) + ), ok. @@ -327,9 +415,11 @@ balance_clearing_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Balance = ?config(balance, Config), Chain = ?config(chain, Config), + EntryMod = ?config(entry_mod, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}, {Recipient2, {_, Recipient2PrivKey, _}}, {Recipient3, _} | _] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}}, {Recipient2, {_, Recipient2PrivKey, _}}, {Recipient3, _} | _] = + ConsensusMembers, %% Create a payment to payee1 Recipient1 = blockchain_swarm:pubkey_bin(), @@ -357,13 +447,13 @@ balance_clearing_test(Config) -> Ledger = blockchain:ledger(Chain), {ok, RecipientEntry1} = blockchain_ledger_v1:find_entry(Recipient1, Ledger), - ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(RecipientEntry1)), + ?assertEqual(Balance + Amount, EntryMod:balance(RecipientEntry1)), {ok, RecipientEntry2_1} = blockchain_ledger_v1:find_entry(Recipient2, Ledger), - ?assertEqual(Balance + (Balance - Amount), blockchain_ledger_entry_v1:balance(RecipientEntry2_1)), + ?assertEqual(Balance + (Balance - Amount), EntryMod:balance(RecipientEntry2_1)), {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(0, blockchain_ledger_entry_v1:balance(PayerEntry)), + ?assertEqual(0, EntryMod:balance(PayerEntry)), %% Normal txn with an explicit amount is still processed normally Payment3 = blockchain_payment_v2:new(Recipient3, Amount), @@ -383,10 +473,10 @@ balance_clearing_test(Config) -> Ledger2 = blockchain:ledger(Chain), {ok, RecipientEntry2_2} = blockchain_ledger_v1:find_entry(Recipient2, Ledger2), - ?assertEqual((Balance + (Balance - Amount)) - Amount, blockchain_ledger_entry_v1:balance(RecipientEntry2_2)), + ?assertEqual((Balance + (Balance - Amount)) - Amount, EntryMod:balance(RecipientEntry2_2)), {ok, RecipientEntry3_1} = blockchain_ledger_v1:find_entry(Recipient3, Ledger2), - ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(RecipientEntry3_1)), + ?assertEqual(Balance + Amount, EntryMod:balance(RecipientEntry3_1)), %% Balance-clearing `max' txn processed successfully in isolation Payment4 = blockchain_payment_v2:new(Recipient3, max), @@ -405,16 +495,17 @@ balance_clearing_test(Config) -> Ledger3 = blockchain:ledger(Chain), {ok, RecipientEntry2_3} = blockchain_ledger_v1:find_entry(Recipient2, Ledger3), - ?assertEqual(0, blockchain_ledger_entry_v1:balance(RecipientEntry2_3)), + ?assertEqual(0, EntryMod:balance(RecipientEntry2_3)), {ok, RecipientEntry3_2} = blockchain_ledger_v1:find_entry(Recipient3, Ledger3), - ?assertEqual(Balance + Balance + (Balance - Amount), blockchain_ledger_entry_v1:balance(RecipientEntry3_2)), + ?assertEqual(Balance + Balance + (Balance - Amount), EntryMod:balance(RecipientEntry3_2)), ok. invalid_balance_clearing_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Balance = ?config(balance, Config), Chain = ?config(chain, Config), + EntryMod = ?config(entry_mod, Config), %% Test a payment transaction, add a block and check balances [_, {Payer, {_, PayerPrivKey, _}}, {Recipient2, _}, {Recipient3, _} | _] = ConsensusMembers, @@ -437,19 +528,22 @@ invalid_balance_clearing_test(Config) -> ct:pal("~s", [blockchain_txn:print(SignedTx)]), - {error, {invalid_txns, [{BadTx, invalid_transaction}]}} = test_utils:create_block(ConsensusMembers, [SignedTx]), + {error, {invalid_txns, [{BadTx, invalid_transaction}]}} = test_utils:create_block( + ConsensusMembers, + [SignedTx] + ), ?assertEqual(SignedTx, BadTx), Ledger = blockchain:ledger(Chain), {ok, RecipientEntry1} = blockchain_ledger_v1:find_entry(Recipient1, Ledger), - ?assertEqual(Balance, blockchain_ledger_entry_v1:balance(RecipientEntry1)), + ?assertEqual(Balance, EntryMod:balance(RecipientEntry1)), {ok, RecipientEntry2} = blockchain_ledger_v1:find_entry(Recipient2, Ledger), - ?assertEqual(Balance, blockchain_ledger_entry_v1:balance(RecipientEntry2)), + ?assertEqual(Balance, EntryMod:balance(RecipientEntry2)), {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(Balance, blockchain_ledger_entry_v1:balance(PayerEntry)), + ?assertEqual(Balance, EntryMod:balance(PayerEntry)), ok. balance_clearing_disabled_test(Config) -> @@ -472,7 +566,10 @@ balance_clearing_disabled_test(Config) -> ct:pal("~s", [blockchain_txn:print(SignedTx)]), - {error, {invalid_txns, [{BadTx, invalid_transaction}]}} = test_utils:create_block(ConsensusMembers, [SignedTx]), + {error, {invalid_txns, [{BadTx, invalid_transaction}]}} = test_utils:create_block( + ConsensusMembers, + [SignedTx] + ), ?assertEqual(SignedTx, BadTx), ok. @@ -483,7 +580,7 @@ signature_test(Config) -> _Swarm = ?config(swarm, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, _PayerPrivKey, _}}, {_Other, {_, OtherPrivKey, _}} |_] = ConsensusMembers, + [_, {Payer, {_, _PayerPrivKey, _}}, {_Other, {_, OtherPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee Recipient = blockchain_swarm:pubkey_bin(), @@ -506,7 +603,7 @@ zero_amount_test(Config) -> Chain = ?config(chain, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee Recipient = blockchain_swarm:pubkey_bin(), @@ -525,7 +622,7 @@ negative_amount_test(Config) -> Chain = ?config(chain, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee Recipient = blockchain_swarm:pubkey_bin(), @@ -543,9 +640,10 @@ valid_memo_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Balance = ?config(balance, Config), Chain = ?config(chain, Config), + EntryMod = ?config(entry_mod, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}, {OtherRecipient, _} |_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}}, {OtherRecipient, _} | _] = ConsensusMembers, %% Create a payment to a single payee Recipient = blockchain_swarm:pubkey_bin(), @@ -573,13 +671,13 @@ valid_memo_test(Config) -> Ledger = blockchain:ledger(Chain), {ok, NewEntry0} = blockchain_ledger_v1:find_entry(Recipient, Ledger), - ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(NewEntry0)), + ?assertEqual(Balance + Amount, EntryMod:balance(NewEntry0)), {ok, NewEntry1} = blockchain_ledger_v1:find_entry(OtherRecipient, Ledger), - ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(NewEntry1)), + ?assertEqual(Balance + Amount, EntryMod:balance(NewEntry1)), {ok, NewEntry2} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(Balance - 2*Amount, blockchain_ledger_entry_v1:balance(NewEntry2)), + ?assertEqual(Balance - 2 * Amount, EntryMod:balance(NewEntry2)), ok. negative_memo_test(Config) -> @@ -587,7 +685,7 @@ negative_memo_test(Config) -> Chain = ?config(chain, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee Recipient = blockchain_swarm:pubkey_bin(), @@ -599,17 +697,28 @@ negative_memo_test(Config) -> SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), - {[], [{SignedTx, InvalidReason}]} = blockchain_txn:validate([SignedTx], Chain), - ?assertEqual(invalid_memo, InvalidReason), + {error, InvalidReason} = blockchain_txn:is_valid(SignedTx, Chain), + ct:pal("InvalidReason: ~p", [InvalidReason]), + + case ?config(group_vars, Config) of + #{?protocol_version := 2} -> + {invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo}}, + {token_check, ok}} = InvalidReason, + ok; + #{} -> + ?assertEqual(invalid_memo, InvalidReason) + end, + ok. valid_memo_not_set_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Balance = ?config(balance, Config), Chain = ?config(chain, Config), + EntryMod = ?config(entry_mod, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}, {Recipient2, _} |_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}}, {Recipient2, _} | _] = ConsensusMembers, Amount = 100, @@ -637,13 +746,13 @@ valid_memo_not_set_test(Config) -> Ledger = blockchain:ledger(Chain), {ok, NewEntry0} = blockchain_ledger_v1:find_entry(Recipient1, Ledger), - ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(NewEntry0)), + ?assertEqual(Balance + Amount, EntryMod:balance(NewEntry0)), {ok, NewEntry1} = blockchain_ledger_v1:find_entry(Recipient2, Ledger), - ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(NewEntry1)), + ?assertEqual(Balance + Amount, EntryMod:balance(NewEntry1)), {ok, NewEntry2} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(Balance - Amount * 2, blockchain_ledger_entry_v1:balance(NewEntry2)), + ?assertEqual(Balance - Amount * 2, EntryMod:balance(NewEntry2)), ok. @@ -652,7 +761,7 @@ invalid_memo_not_set_test(Config) -> Chain = ?config(chain, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee Recipient = blockchain_swarm:pubkey_bin(), @@ -664,14 +773,25 @@ invalid_memo_not_set_test(Config) -> SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), - {[], [{SignedTx, InvalidReason}]} = blockchain_txn:validate([SignedTx], Chain), - ?assertEqual(invalid_memo_before_var, InvalidReason), + {error, InvalidReason} = blockchain_txn:is_valid(SignedTx, Chain), + ct:pal("InvalidReason: ~p", [InvalidReason]), + + case ?config(group_vars, Config) of + #{?protocol_version := 2} -> + {invalid_transaction, {amount_check, ok}, + {memo_check, {error, invalid_memo_before_var}}, {token_check, ok}} = InvalidReason, + ok; + #{} -> + ?assertEqual(invalid_memo_before_var, InvalidReason) + end, + ok. big_memo_valid_test(Config) -> ConsensusMembers = ?config(consensus_members, Config), Balance = ?config(balance, Config), Chain = ?config(chain, Config), + EntryMod = ?config(entry_mod, Config), %% Test a payment transaction, add a block and check balances [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, @@ -697,10 +817,10 @@ big_memo_valid_test(Config) -> Ledger = blockchain:ledger(Chain), {ok, NewEntry0} = blockchain_ledger_v1:find_entry(Recipient, Ledger), - ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(NewEntry0)), + ?assertEqual(Balance + Amount, EntryMod:balance(NewEntry0)), {ok, NewEntry1} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(Balance - Amount, blockchain_ledger_entry_v1:balance(NewEntry1)), + ?assertEqual(Balance - Amount, EntryMod:balance(NewEntry1)), ok. big_memo_invalid_test(Config) -> @@ -708,7 +828,7 @@ big_memo_invalid_test(Config) -> Chain = ?config(chain, Config), %% Test a payment transaction, add a block and check balances - [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, %% Create a payment to a single payee Recipient = blockchain_swarm:pubkey_bin(), @@ -719,26 +839,40 @@ big_memo_invalid_test(Config) -> SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), - {[], [{SignedTx, InvalidReason}]} = blockchain_txn:validate([SignedTx], Chain), + {error, InvalidReason} = blockchain_txn:is_valid(SignedTx, Chain), ct:pal("InvalidReason: ~p", [InvalidReason]), - ?assertEqual(invalid_memo, InvalidReason), + + case ?config(group_vars, Config) of + #{?protocol_version := 2} -> + {invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo}}, + {token_check, ok}} = InvalidReason, + ok; + #{} -> + ?assertEqual(invalid_memo, InvalidReason) + end, ok. %%-------------------------------------------------------------------- %% Internal Functions %%-------------------------------------------------------------------- -extra_vars(big_memo_valid_test) -> +test_case_vars(big_memo_valid_test) -> #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?allow_payment_v2_memos => true}; -extra_vars(big_memo_invalid_test) -> +test_case_vars(big_memo_invalid_test) -> #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?allow_payment_v2_memos => true}; -extra_vars(valid_memo_test) -> +test_case_vars(valid_memo_test) -> #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?allow_payment_v2_memos => true}; -extra_vars(negative_memo_test) -> +test_case_vars(negative_memo_test) -> #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?allow_payment_v2_memos => true}; -extra_vars(BCEnabled) when BCEnabled == balance_clearing_test orelse BCEnabled == invalid_balance_clearing_test -> - #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?enable_balance_clearing => true}; -extra_vars(_) -> +test_case_vars(BCEnabled) when + BCEnabled == balance_clearing_test orelse BCEnabled == invalid_balance_clearing_test +-> + #{ + ?max_payments => ?MAX_PAYMENTS, + ?allow_zero_amount => false, + ?enable_balance_clearing => true + }; +test_case_vars(_) -> #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false}. %% Helpers -------------------------------------------------------------------- @@ -752,13 +886,12 @@ make_multisig_addr(M, N) -> IFuns = [ {libp2p_crypto:multisig_member_key_index(P, MemberKeys), MakeFun(S)} - || - #{secret := S, public := P} <- KeyMaps + || #{secret := S, public := P} <- KeyMaps ], {ok, MultisigPK} = libp2p_crypto:make_multisig_pubkey(Network, M, N, MemberKeys), MultisigSign = - fun (Msg) -> - ISigs = [{I, F(Msg)}|| {I, F} <- IFuns], + fun(Msg) -> + ISigs = [{I, F(Msg)} || {I, F} <- IFuns], {ok, Sig} = libp2p_crypto:make_multisig_signature(Network, Msg, MultisigPK, MemberKeys, ISigs), Sig @@ -766,14 +899,15 @@ make_multisig_addr(M, N) -> {libp2p_crypto:pubkey_to_bin(Network, MultisigPK), MultisigSign}. % TODO Use in the rest of cases? -transfer(Amount, {Src, SrcSigFun}, Dst, ExpectHeight, Chain, ConsensusMembers) -> - SrcBalance0 = balance(Src, Chain), - DstBalance0 = balance(Dst, Chain), +transfer(Amount, {Src, SrcSigFun}, Dst, ExpectHeight, Chain, ConsensusMembers, EntryMod) -> + SrcBalance0 = balance(Src, Chain, EntryMod), + DstBalance0 = balance(Dst, Chain, EntryMod), Ledger = blockchain:ledger(Chain), - Nonce = case blockchain_ledger_v1:find_entry(Src, Ledger) of - {error, address_entry_not_found} -> 1; - {ok, Entry} -> blockchain_ledger_entry_v1:nonce(Entry) + 1 - end, + Nonce = + case blockchain_ledger_v1:find_entry(Src, Ledger) of + {error, address_entry_not_found} -> 1; + {ok, Entry} -> EntryMod:nonce(Entry) + 1 + end, Tx = blockchain_txn_payment_v2:new(Src, [blockchain_payment_v2:new(Dst, Amount)], Nonce), TxSigned = blockchain_txn_payment_v2:sign(Tx, SrcSigFun), {ok, Block} = test_utils:create_block(ConsensusMembers, [TxSigned]), @@ -782,18 +916,18 @@ transfer(Amount, {Src, SrcSigFun}, Dst, ExpectHeight, Chain, ConsensusMembers) - ?assertEqual({ok, Block}, blockchain:head_block(Chain)), ?assertEqual({ok, ExpectHeight}, blockchain:height(Chain)), ?assertEqual({ok, Block}, blockchain:get_block(ExpectHeight, Chain)), - SrcBalance1 = balance(Src, Chain), - DstBalance1 = balance(Dst, Chain), + SrcBalance1 = balance(Src, Chain, EntryMod), + DstBalance1 = balance(Dst, Chain, EntryMod), ?assertEqual(SrcBalance1, SrcBalance0 - Amount), ?assertEqual(DstBalance1, DstBalance0 + Amount), ok. % TODO Use in the rest of cases? -balance(<>, Chain) -> +balance(<>, Chain, EntryMod) -> Ledger = blockchain:ledger(Chain), case blockchain_ledger_v1:find_entry(Addr, Ledger) of {error, address_entry_not_found} -> 0; {ok, Entry} -> - blockchain_ledger_entry_v1:balance(Entry) + EntryMod:balance(Entry) end. diff --git a/test/blockchain_token_SUITE.erl b/test/blockchain_token_SUITE.erl index 586de4c99c..1d32d81fff 100644 --- a/test/blockchain_token_SUITE.erl +++ b/test/blockchain_token_SUITE.erl @@ -7,7 +7,8 @@ -export([all/0, init_per_testcase/2, end_per_testcase/2]). -export([ - coinbase_test/1 + multi_token_coinbase_test/1, + multi_token_payment_test/1 ]). %%-------------------------------------------------------------------- @@ -22,7 +23,8 @@ %%-------------------------------------------------------------------- all() -> [ - coinbase_test + multi_token_coinbase_test, + multi_token_payment_test ]. %%-------------------------------------------------------------------- @@ -32,14 +34,14 @@ all() -> init_per_testcase(TestCase, Config) -> Config0 = blockchain_ct_utils:init_base_dir_config(?MODULE, TestCase, Config), - HNTBal = 5000, + HNTBal = 50000, + HSTBal = 10000, HGTBal = 1000, - HSTBal = 100, - HLTBal = 10, + HLTBal = 100, {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config0)), - ExtraVars = #{?protocol_version => 2}, + ExtraVars = extra_vars(TestCase), {ok, GenesisMembers, _GenesisBlock, ConsensusMembers, Keys} = test_utils:init_chain_with_opts( @@ -84,6 +86,7 @@ init_per_testcase(TestCase, Config) -> %%-------------------------------------------------------------------- %% TEST CASE TEARDOWN %%-------------------------------------------------------------------- + end_per_testcase(_, Config) -> Sup = ?config(sup, Config), meck:unload(), @@ -102,7 +105,7 @@ end_per_testcase(_, Config) -> %% TEST CASES %%-------------------------------------------------------------------- -coinbase_test(Config) -> +multi_token_coinbase_test(Config) -> Chain = ?config(chain, Config), HNTBal = ?config(hnt_bal, Config), HSTBal = ?config(hst_bal, Config), @@ -125,46 +128,132 @@ coinbase_test(Config) -> end, maps:values(Entries) ), + ok. + +multi_token_payment_test(Config) -> + ConsensusMembers = ?config(consensus_members, Config), + Chain = ?config(chain, Config), + HNTBal = ?config(hnt_bal, Config), + HSTBal = ?config(hst_bal, Config), + HGTBal = ?config(hgt_bal, Config), + HLTBal = ?config(hlt_bal, Config), + Ledger = blockchain:ledger(Chain), + + %% Test a payment transaction, add a block and check balances + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, + + %% Generate two random recipients + [{Recipient1, _}, {Recipient2, _}] = test_utils:generate_keys(2), + + HNTAmt1 = 20000, + HSTAmt1 = 2000, + HGTAmt1 = 200, + HLTAmt1 = 20, + HNTAmt2 = 10000, + HSTAmt2 = 1000, + HGTAmt2 = 100, + HLTAmt2 = 10, + + P1 = blockchain_payment_v2:new(Recipient1, HNTAmt1, hnt), + P2 = blockchain_payment_v2:new(Recipient1, HSTAmt1, hst), + P3 = blockchain_payment_v2:new(Recipient1, HGTAmt1, hgt), + P4 = blockchain_payment_v2:new(Recipient1, HLTAmt1, hlt), + + P5 = blockchain_payment_v2:new(Recipient2, HNTAmt2, hnt), + P6 = blockchain_payment_v2:new(Recipient2, HSTAmt2, hst), + P7 = blockchain_payment_v2:new(Recipient2, HGTAmt2, hgt), + P8 = blockchain_payment_v2:new(Recipient2, HLTAmt2, hlt), + + Payments = [P1, P2, P3, P4, P5, P6, P7, P8], - %% TODO: test payment txn + Tx = blockchain_txn_payment_v2:new(Payer, Payments, 1), + SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), + SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), - %% ConsensusMembers = ?config(consensus_members, Config), - %% Balance = ?config(balance, Config), + {ok, Block} = test_utils:create_block(ConsensusMembers, [SignedTx]), + _ = blockchain_gossip_handler:add_block(Block, Chain, self(), blockchain_swarm:tid()), - %% %% Test a payment transaction, add a block and check balances - %% [_, {Payer, {_, PayerPrivKey, _}}|_] = ConsensusMembers, + ?assertEqual({ok, blockchain_block:hash_block(Block)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block}, blockchain:head_block(Chain)), + ?assertEqual({ok, 2}, blockchain:height(Chain)), - %% %% Create a payment to a single payee - %% Recipient = blockchain_swarm:pubkey_bin(), - %% HNTAmt = 1000, - %% HSTAmt = 100, - %% HGTAmt = 10, - %% HLTAmt = 1, - %% P1 = blockchain_payment_v2:new(Recipient, HNTAmt, hnt), - %% P2 = blockchain_payment_v2:new(Recipient, HSTAmt, hst), - %% P3 = blockchain_payment_v2:new(Recipient, HGTAmt, hgt), - %% P4 = blockchain_payment_v2:new(Recipient, HLTAmt, hlt), + ?assertEqual({ok, Block}, blockchain:get_block(2, Chain)), - %% Tx = blockchain_txn_payment_v2:new(Payer, [P1, P2, P3, P4], 1), - %% SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), - %% SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), + {ok, RecipientEntry1} = blockchain_ledger_v1:find_entry(Recipient1, Ledger), + {ok, RecipientEntry2} = blockchain_ledger_v1:find_entry(Recipient2, Ledger), - %% ct:pal("~s", [blockchain_txn:print(SignedTx)]), + ?assertEqual(HNTAmt1, blockchain_ledger_entry_v2:balance(RecipientEntry1, hnt)), + ?assertEqual(HSTAmt1, blockchain_ledger_entry_v2:balance(RecipientEntry1, hst)), + ?assertEqual(HGTAmt1, blockchain_ledger_entry_v2:balance(RecipientEntry1, hgt)), + ?assertEqual(HLTAmt1, blockchain_ledger_entry_v2:balance(RecipientEntry1, hlt)), - %% {ok, Block} = test_utils:create_block(ConsensusMembers, [SignedTx]), - %% _ = blockchain_gossip_handler:add_block(Block, Chain, self(), blockchain_swarm:tid()), + ?assertEqual(HNTAmt2, blockchain_ledger_entry_v2:balance(RecipientEntry2, hnt)), + ?assertEqual(HSTAmt2, blockchain_ledger_entry_v2:balance(RecipientEntry2, hst)), + ?assertEqual(HGTAmt2, blockchain_ledger_entry_v2:balance(RecipientEntry2, hgt)), + ?assertEqual(HLTAmt2, blockchain_ledger_entry_v2:balance(RecipientEntry2, hlt)), - %% ?assertEqual({ok, blockchain_block:hash_block(Block)}, blockchain:head_hash(Chain)), - %% ?assertEqual({ok, Block}, blockchain:head_block(Chain)), - %% ?assertEqual({ok, 2}, blockchain:height(Chain)), + {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), + ?assertEqual(1, blockchain_ledger_entry_v2:nonce(PayerEntry)), + ?assertEqual(HNTBal - (HNTAmt1 + HNTAmt2), blockchain_ledger_entry_v2:balance(PayerEntry, hnt)), + ?assertEqual(HSTBal - (HSTAmt1 + HSTAmt2), blockchain_ledger_entry_v2:balance(PayerEntry, hst)), + ?assertEqual(HGTBal - (HGTAmt1 + HGTAmt2), blockchain_ledger_entry_v2:balance(PayerEntry, hgt)), + ?assertEqual(HLTBal - (HLTAmt1 + HLTAmt2), blockchain_ledger_entry_v2:balance(PayerEntry, hlt)), - %% ?assertEqual({ok, Block}, blockchain:get_block(2, Chain)), + %% Do another payment - %% Ledger = blockchain:ledger(Chain), + [{Recipient3, _}] = test_utils:generate_keys(1), - %% {ok, NewEntry0} = blockchain_ledger_v1:find_entry(Recipient, Ledger), - %% ?assertEqual(Balance + Amount, blockchain_ledger_entry_v1:balance(NewEntry0)), + HNTAmt3 = 2000, + HSTAmt3 = 200, + HGTAmt3 = 20, + HLTAmt3 = 2, + + P9 = blockchain_payment_v2:new(Recipient3, HNTAmt3, hnt), + P10 = blockchain_payment_v2:new(Recipient3, HSTAmt3, hst), + P11 = blockchain_payment_v2:new(Recipient3, HGTAmt3, hgt), + P12 = blockchain_payment_v2:new(Recipient3, HLTAmt3, hlt), + + Payments3 = [P9, P10, P11, P12], + + Tx3 = blockchain_txn_payment_v2:new(Payer, Payments3, 2), + SignedTx3 = blockchain_txn_payment_v2:sign(Tx3, SigFun), + + {ok, Block3} = test_utils:create_block(ConsensusMembers, [SignedTx3]), + _ = blockchain_gossip_handler:add_block(Block3, Chain, self(), blockchain_swarm:tid()), + + ?assertEqual({ok, blockchain_block:hash_block(Block3)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block3}, blockchain:head_block(Chain)), + ?assertEqual({ok, 3}, blockchain:height(Chain)), + + ?assertEqual({ok, Block3}, blockchain:get_block(3, Chain)), + + {ok, RecipientEntry3} = blockchain_ledger_v1:find_entry(Recipient3, Ledger), + + ?assertEqual(HNTAmt3, blockchain_ledger_entry_v2:balance(RecipientEntry3, hnt)), + ?assertEqual(HSTAmt3, blockchain_ledger_entry_v2:balance(RecipientEntry3, hst)), + ?assertEqual(HGTAmt3, blockchain_ledger_entry_v2:balance(RecipientEntry3, hgt)), + ?assertEqual(HLTAmt3, blockchain_ledger_entry_v2:balance(RecipientEntry3, hlt)), + + {ok, PayerEntry3} = blockchain_ledger_v1:find_entry(Payer, Ledger), + ?assertEqual(2, blockchain_ledger_entry_v2:nonce(PayerEntry3)), + ?assertEqual( + HNTBal - (HNTAmt1 + HNTAmt2 + HNTAmt3), + blockchain_ledger_entry_v2:balance(PayerEntry3, hnt) + ), + ?assertEqual( + HSTBal - (HSTAmt1 + HSTAmt2 + HSTAmt3), + blockchain_ledger_entry_v2:balance(PayerEntry3, hst) + ), + ?assertEqual( + HGTBal - (HGTAmt1 + HGTAmt2 + HGTAmt3), + blockchain_ledger_entry_v2:balance(PayerEntry3, hgt) + ), + ?assertEqual( + HLTBal - (HLTAmt1 + HLTAmt2 + HLTAmt3), + blockchain_ledger_entry_v2:balance(PayerEntry3, hlt) + ), - %% {ok, NewEntry1} = blockchain_ledger_v1:find_entry(Payer, Ledger), - %% ?assertEqual(Balance - Amount, blockchain_ledger_entry_v1:balance(NewEntry1)), ok. + +extra_vars(_) -> + #{?protocol_version => 2, ?max_payments => 20, ?allow_zero_amount => false}. From 33db88dea40702c81633a587baccca187f676d12 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Tue, 24 May 2022 14:31:12 -0700 Subject: [PATCH 37/48] Add support for ledger entry migration - Add token_version - Update new payment conditions - Update token SUITE - Deprecate security_exchange_v1 behind var - Add balance-clearing for multi-coin - Add balance clearing for tokens_v2 - Return better failure error on insufficient funds --- include/blockchain_vars.hrl | 10 +- rebar.lock | 2 +- src/blockchain_token_type_v1.erl | 16 - src/blockchain_token_v1.erl | 16 + src/ledger/v1/blockchain_ledger_v1.erl | 124 ++++-- src/ledger/v2/blockchain_ledger_entry_v2.erl | 93 +++-- .../v1/blockchain_txn_coinbase_v1.erl | 6 +- .../blockchain_txn_security_exchange_v1.erl | 57 +-- .../v1/blockchain_txn_vars_v1.erl | 20 +- src/transactions/v2/blockchain_payment_v2.erl | 58 ++- .../v2/blockchain_txn_payment_v2.erl | 248 ++++++------ test/blockchain_payment_v2_SUITE.erl | 54 +-- test/blockchain_token_SUITE.erl | 366 +++++++++++++++--- test/test_utils.erl | 3 +- 14 files changed, 744 insertions(+), 329 deletions(-) delete mode 100644 src/blockchain_token_type_v1.erl create mode 100644 src/blockchain_token_v1.erl diff --git a/include/blockchain_vars.hrl b/include/blockchain_vars.hrl index 86a857b481..b811f61368 100644 --- a/include/blockchain_vars.hrl +++ b/include/blockchain_vars.hrl @@ -616,6 +616,12 @@ -define(block_size_limit, block_size_limit). %% ------------------------------------------------------------------ -%% Protocol version (aka support multiple tokens) +%% Token version (aka support multiple tokens) %% Testing for now, set to 2 --define(protocol_version, protocol_version). +-define(token_version, token_version). +%% Ledger entry migration variable +%% Will be used to hook and switch the old style ledger entries +%% to new style ledger entries +-define(ledger_entry_version, ledger_entry_version). +%% Var to switch off legacy security_exchange txn +-define(deprecate_security_exchange_v1, deprecate_security_exchange_v1). diff --git a/rebar.lock b/rebar.lock index e07295597d..572e504765 100644 --- a/rebar.lock +++ b/rebar.lock @@ -77,7 +77,7 @@ {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},0}, {<<"helium_proto">>, {git,"https://github.com/helium/proto.git", - {ref,"71dee1604fe1e126d0ef3bc8dd3e168d6ea8da79"}}, + {ref,"732899336e07b798028d30e496fe6e22c39b7987"}}, 0}, {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1}, diff --git a/src/blockchain_token_type_v1.erl b/src/blockchain_token_type_v1.erl deleted file mode 100644 index 0465af2723..0000000000 --- a/src/blockchain_token_type_v1.erl +++ /dev/null @@ -1,16 +0,0 @@ -%%%------------------------------------------------------------------- -%% @doc -%% == Blockchain Token Type V1 == -%% @end -%%%------------------------------------------------------------------- --module(blockchain_token_type_v1). - --type token_type() :: hnt | hst | hlt | hgt. --type token_types() :: [token_type()]. - --export_type([token_type/0, token_types/0]). --export([supported_tokens/0]). - --spec supported_tokens() -> token_types(). -supported_tokens() -> - [hnt, hst, hlt, hgt]. diff --git a/src/blockchain_token_v1.erl b/src/blockchain_token_v1.erl new file mode 100644 index 0000000000..3a9ff1ef57 --- /dev/null +++ b/src/blockchain_token_v1.erl @@ -0,0 +1,16 @@ +%%%------------------------------------------------------------------- +%% @doc +%% == Blockchain Token V1 == +%% @end +%%%------------------------------------------------------------------- +-module(blockchain_token_v1). + +-type type() :: hnt | hst | mobile | iot. +-type types() :: [type()]. + +-export_type([type/0, types/0]). +-export([supported_tokens/0]). + +-spec supported_tokens() -> types(). +supported_tokens() -> + [hnt, hst, mobile, iot]. diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index be9299eb19..c38f5660b9 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -41,7 +41,6 @@ active_gateways/1, snapshot_gateways/1, load_gateways/2, versioned_entry_mod_and_entries_cf/1, entries/1, - entries_v2/1, htlcs/1, master_key/1, master_key/2, @@ -270,7 +269,9 @@ txn_fee_multiplier/1, dc_to_hnt/2, - hnt_to_dc/2 + hnt_to_dc/2, + + migrate_entries/1 ]). @@ -300,7 +301,7 @@ blockchain_ledger_state_channel_v1:state_channel() | blockchain_ledger_state_channel_v2:state_channel_v2()}. -type h3dex() :: #{h3:h3_index() => [libp2p_crypto:pubkey_bin()]}. %% these keys are gateway addresses --type tagged_cf() :: tagged_cf(). +-type tagged_cf() :: {atom(), rocksdb:db_handle(), rocksdb:cf_handle()}. -export_type([ledger/0]). @@ -1337,27 +1338,20 @@ write_gw_denorm_values(Address, Old, Gw, Ledger) -> _ -> cache_put(Ledger, GwDenormCF, <
>, term_to_binary(Gain)) end. --spec entries(ledger()) -> entries(). +-spec entries(ledger()) -> entries() | entries_v2(). entries(Ledger) -> - EntriesCF = entries_cf(Ledger), - cache_fold( - Ledger, - EntriesCF, - fun({Address, Binary}, Acc) -> - Entry = blockchain_ledger_entry_v1:deserialize(Binary), - maps:put(Address, Entry, Acc) - end, - #{} - ). + {EntryMod, EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), + entries_(EntryMod, EntriesCF, Ledger). --spec entries_v2(ledger()) -> entries_v2(). -entries_v2(Ledger) -> - EntriesV2CF = entries_v2_cf(Ledger), +-spec entries_(EntryMod :: blockchain_ledger_entry_v1 | blockchain_ledger_entry_v2, + EntriesCF :: tagged_cf(), + Ledger :: ledger()) -> entries() | entries_v2(). +entries_(EntryMod, EntriesCF, Ledger) -> cache_fold( Ledger, - EntriesV2CF, + EntriesCF, fun({Address, Binary}, Acc) -> - Entry = blockchain_ledger_entry_v2:deserialize(Binary), + Entry = EntryMod:deserialize(Binary), maps:put(Address, Entry, Acc) end, #{} @@ -2884,6 +2878,90 @@ hnt_to_dc(HNTAmount, Ledger)-> hnt_to_dc(HNTAmount, OracleHNTPrice) end. +%%-------------------------------------------------------------------- +%% @doc +%% Migrate ledger entries +%% @end +%%-------------------------------------------------------------------- +-spec migrate_entries(Ledger :: ledger()) -> ok | {error, any()}. +migrate_entries(Ledger) -> + ok = migrate_reg_entries(Ledger), + ok = migrate_sec_entries(Ledger), + ok. + +-spec migrate_reg_entries(Ledger :: ledger()) -> ok | {error, any()}. +migrate_reg_entries(Ledger) -> + EntriesCF = entries_cf(Ledger), + EntriesV2CF = entries_v2_cf(Ledger), + cache_fold( + Ledger, + EntriesCF, + fun({Address, Binary}, ok) -> + EntryV1 = blockchain_ledger_entry_v1:deserialize(Binary), + case ?MODULE:find_entry(Address, Ledger) of + {error, address_entry_not_found} -> + %% This should _always_ occur as we assume no entries_v2 exist + %% on ledger yet + EntryV1Balance = blockchain_ledger_entry_v1:balance(EntryV1), + EntryV1Nonce = blockchain_ledger_entry_v1:nonce(EntryV1), + NewEntryV2 = blockchain_ledger_entry_v2:nonce( + blockchain_ledger_entry_v2:credit( + blockchain_ledger_entry_v2:new(), EntryV1Balance, hnt), + EntryV1Nonce), + Bin = blockchain_ledger_entry_v2:serialize(NewEntryV2), + cache_put(Ledger, EntriesV2CF, Address, Bin); + _ -> + %% NOTE: This should never happen due to the order + %% in which the migration is applied + {error, entry_migration_aborted} + end + + end, + ok + ). + +-spec migrate_sec_entries(Ledger :: ledger()) -> ok | {error, any()}. +migrate_sec_entries(Ledger) -> + SecuritiesCF = securities_cf(Ledger), + EntriesV2CF = entries_v2_cf(Ledger), + cache_fold( + Ledger, + SecuritiesCF, + fun({Address, Binary}, ok) -> + EntryV1 = blockchain_ledger_security_entry_v1:deserialize(Binary), + case ?MODULE:find_entry(Address, Ledger) of + {ok, EntryV2} -> + %% There's already an EntryV2 possibly from doing the entry migration, + %% To consolidate: + %% - Add the two nonces (sec_nonce_v1 + nonce_entry_v2) + %% - Update hst balance in EntryV2 + SecEntryV1Balance = blockchain_ledger_security_entry_v1:balance(EntryV1), + SecEntryV1Nonce = blockchain_ledger_security_entry_v1:nonce(EntryV1), + EntryV2Nonce = blockchain_ledger_entry_v2:nonce(EntryV2), + NewEntryV2 = blockchain_ledger_entry_v2:nonce( + blockchain_ledger_entry_v2:credit( + EntryV2, SecEntryV1Balance, hst), + EntryV2Nonce + SecEntryV1Nonce), + Bin = blockchain_ledger_entry_v2:serialize(NewEntryV2), + cache_put(Ledger, EntriesV2CF, Address, Bin); + {error, address_entry_not_found} -> + %% This address only has security tokens, credit accordingly + SecEntryV1Balance = blockchain_ledger_security_entry_v1:balance(EntryV1), + SecEntryV1Nonce = blockchain_ledger_security_entry_v1:nonce(EntryV1), + NewEntryV2 = blockchain_ledger_entry_v2:nonce( + blockchain_ledger_entry_v2:credit( + blockchain_ledger_entry_v2:new(), SecEntryV1Balance, hst), + SecEntryV1Nonce), + Bin = blockchain_ledger_entry_v2:serialize(NewEntryV2), + cache_put(Ledger, EntriesV2CF, Address, Bin); + _ -> + %% Unexpected, abort! + {error, sec_entry_migration_aborted} + end + end, + ok + ). + %%-------------------------------------------------------------------- %% @doc @@ -3026,7 +3104,7 @@ find_entry_(Address, EntryMod, EntriesCF, Ledger) -> -spec versioned_entry_mod_and_entries_cf(Ledger :: ledger()) -> {atom(), tagged_cf()}. versioned_entry_mod_and_entries_cf(Ledger) -> - case ?MODULE:config(?protocol_version, Ledger) of + case ?MODULE:config(?token_version, Ledger) of {ok, 2} -> {blockchain_ledger_entry_v2, entries_v2_cf(Ledger)}; _ -> @@ -3054,7 +3132,7 @@ credit_account(Address, Amount, Ledger) -> -spec credit_account(Address :: libp2p_crypto:pubkey_bin(), Amount :: integer(), - TT :: blockchain_token_type_v1:token(), + TT :: blockchain_token_v1:type(), Ledger :: ledger()) -> ok | {error, any()}. credit_account(Address, Amount, TT, Ledger) -> EntriesCF = entries_v2_cf(Ledger), @@ -3104,7 +3182,7 @@ debit_account(Address, AmountOrAmounts, Nonce, Ledger) when is_integer(AmountOrA end end; debit_account(Address, AmountOrAmounts, Nonce, Ledger) when is_map(AmountOrAmounts) -> - %% TODO: Maybe also check that protocol_version = 2 is set here? Although amounts being + %% TODO: Maybe also check that token_version = 2 is set here? Although amounts being %% a map only ever should occur with the multi token payment txn, so maybe it's okay? case ?MODULE:find_entry(Address, Ledger) of {error, _}=Error -> @@ -3116,7 +3194,7 @@ debit_account(Address, AmountOrAmounts, Nonce, Ledger) when is_map(AmountOrAmoun fun(TT) -> blockchain_ledger_entry_v2:balance(Entry, TT) >= maps:get(TT, AmountOrAmounts, 0) end, - blockchain_token_type_v1:supported_tokens()) + blockchain_token_v1:supported_tokens()) of true -> Entry0 = maps:fold( diff --git a/src/ledger/v2/blockchain_ledger_entry_v2.erl b/src/ledger/v2/blockchain_ledger_entry_v2.erl index 5fe2210738..4c71a2c6f7 100644 --- a/src/ledger/v2/blockchain_ledger_entry_v2.erl +++ b/src/ledger/v2/blockchain_ledger_entry_v2.erl @@ -12,7 +12,9 @@ credit/3, debit/3, serialize/1, - deserialize/1 + deserialize/1, + + from_v1/2 ]). -ifdef(TEST). @@ -35,8 +37,8 @@ new() -> nonce = 0, hnt_balance = 0, hst_balance = 0, - hgt_balance = 0, - hlt_balance = 0 + mobile_balance = 0, + iot_balance = 0 }. -spec nonce(Entry :: entry()) -> non_neg_integer(). @@ -59,43 +61,43 @@ balance(Entry) -> %% @doc Return requested token balance. %% @end %%-------------------------------------------------------------------- --spec balance(Entry :: entry(), TT :: blockchain_token_type_v1:token_type()) -> non_neg_integer(). +-spec balance(Entry :: entry(), TT :: blockchain_token_v1:type()) -> non_neg_integer(). balance(Entry, hnt) -> hnt_balance(Entry); balance(Entry, hst) -> hst_balance(Entry); -balance(Entry, hgt) -> - hgt_balance(Entry); -balance(Entry, hlt) -> - hlt_balance(Entry). +balance(Entry, mobile) -> + mobile_balance(Entry); +balance(Entry, iot) -> + iot_balance(Entry). -spec credit( Entry :: entry(), Amount :: non_neg_integer(), - TT :: blockchain_token_type_v1:token_type() + TT :: blockchain_token_v1:type() ) -> entry(). credit(Entry, Amount, hnt) -> credit_hnt(Entry, Amount); credit(Entry, Amount, hst) -> credit_hst(Entry, Amount); -credit(Entry, Amount, hgt) -> - credit_hgt(Entry, Amount); -credit(Entry, Amount, hlt) -> - credit_hlt(Entry, Amount). +credit(Entry, Amount, mobile) -> + credit_mobile(Entry, Amount); +credit(Entry, Amount, iot) -> + credit_iot(Entry, Amount). -spec debit( Entry :: entry(), Amount :: non_neg_integer(), - TT :: blockchain_token_type_v1:token_type() + TT :: blockchain_token_v1:type() ) -> entry(). debit(Entry, Amount, hnt) -> debit_hnt(Entry, Amount); debit(Entry, Amount, hst) -> debit_hst(Entry, Amount); -debit(Entry, Amount, hgt) -> - debit_hgt(Entry, Amount); -debit(Entry, Amount, hlt) -> - debit_hlt(Entry, Amount). +debit(Entry, Amount, mobile) -> + debit_mobile(Entry, Amount); +debit(Entry, Amount, iot) -> + debit_iot(Entry, Amount). -spec serialize(Entry :: entry()) -> binary(). serialize(Entry) -> @@ -105,6 +107,29 @@ serialize(Entry) -> deserialize(EntryBin) -> blockchain_ledger_entry_v2_pb:decode_msg(EntryBin, blockchain_ledger_entry_v2_pb). +-spec from_v1( + V1 :: blockchain_ledger_entry_v1:entry() | blockchain_ledger_security_entry_v1:entry(), + Type :: entry | security +) -> entry(). +from_v1(V1, entry) -> + from_entry_v1(V1); +from_v1(V1, security) -> + from_sec_v1(V1). + +-spec from_entry_v1(EntryV1 :: blockchain_ledger_entry_v1:entry()) -> entry(). +from_entry_v1(EntryV1) -> + Balance = blockchain_ledger_entry_v1:balance(EntryV1), + Nonce = blockchain_ledger_entry_v1:nonce(EntryV1), + Entry0 = new(), + nonce(credit(Entry0, Balance, hnt), Nonce). + +-spec from_sec_v1(SecEntryV1 :: blockchain_ledger_security_entry_v1:entry()) -> entry(). +from_sec_v1(SecEntryV1) -> + Balance = blockchain_ledger_security_entry_v1:balance(SecEntryV1), + Nonce = blockchain_ledger_security_entry_v1:nonce(SecEntryV1), + Entry0 = new(), + nonce(credit(Entry0, Balance, hst), Nonce). + %% ================================================================== %% Internal Functions %% ================================================================== @@ -117,13 +142,13 @@ credit_hnt(Entry, Amount) -> credit_hst(Entry, Amount) -> Entry#blockchain_ledger_entry_v2_pb{hst_balance = hst_balance(Entry) + Amount}. --spec credit_hlt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -credit_hlt(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{hlt_balance = hlt_balance(Entry) + Amount}. +-spec credit_iot(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +credit_iot(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{iot_balance = iot_balance(Entry) + Amount}. --spec credit_hgt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -credit_hgt(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{hgt_balance = hgt_balance(Entry) + Amount}. +-spec credit_mobile(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +credit_mobile(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{mobile_balance = mobile_balance(Entry) + Amount}. -spec debit_hnt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). debit_hnt(Entry, Amount) -> @@ -133,13 +158,13 @@ debit_hnt(Entry, Amount) -> debit_hst(Entry, Amount) -> Entry#blockchain_ledger_entry_v2_pb{hst_balance = hst_balance(Entry) - Amount}. --spec debit_hgt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -debit_hgt(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{hgt_balance = hgt_balance(Entry) - Amount}. +-spec debit_mobile(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +debit_mobile(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{mobile_balance = mobile_balance(Entry) - Amount}. --spec debit_hlt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -debit_hlt(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{hlt_balance = hlt_balance(Entry) - Amount}. +-spec debit_iot(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). +debit_iot(Entry, Amount) -> + Entry#blockchain_ledger_entry_v2_pb{iot_balance = iot_balance(Entry) - Amount}. -spec hnt_balance(Entry :: entry()) -> non_neg_integer(). hnt_balance(#blockchain_ledger_entry_v2_pb{hnt_balance = Balance}) -> @@ -149,10 +174,10 @@ hnt_balance(#blockchain_ledger_entry_v2_pb{hnt_balance = Balance}) -> hst_balance(#blockchain_ledger_entry_v2_pb{hst_balance = Balance}) -> Balance. --spec hgt_balance(Entry :: entry()) -> non_neg_integer(). -hgt_balance(#blockchain_ledger_entry_v2_pb{hgt_balance = Balance}) -> +-spec mobile_balance(Entry :: entry()) -> non_neg_integer(). +mobile_balance(#blockchain_ledger_entry_v2_pb{mobile_balance = Balance}) -> Balance. --spec hlt_balance(Entry :: entry()) -> non_neg_integer(). -hlt_balance(#blockchain_ledger_entry_v2_pb{hlt_balance = Balance}) -> +-spec iot_balance(Entry :: entry()) -> non_neg_integer(). +iot_balance(#blockchain_ledger_entry_v2_pb{iot_balance = Balance}) -> Balance. diff --git a/src/transactions/v1/blockchain_txn_coinbase_v1.erl b/src/transactions/v1/blockchain_txn_coinbase_v1.erl index d8b6064ba1..77869c8387 100644 --- a/src/transactions/v1/blockchain_txn_coinbase_v1.erl +++ b/src/transactions/v1/blockchain_txn_coinbase_v1.erl @@ -52,7 +52,7 @@ new(Payee, Amount) -> %%-------------------------------------------------------------------- -spec new(Payee :: libp2p_crypto:pubkey_bin(), Amount :: non_neg_integer(), - TokenType :: blockchain_token_type_v1:token_type()) -> txn_coinbase(). + TokenType :: blockchain_token_v1:type()) -> txn_coinbase(). new(Payee, Amount, TokenType) -> #blockchain_txn_coinbase_v1_pb{payee=Payee, amount=Amount, token_type=TokenType}. @@ -106,7 +106,7 @@ fee_payer(_Txn, _Ledger) -> %% Get token_type associated with coinbase txn %% @end %%-------------------------------------------------------------------- --spec token_type(txn_coinbase()) -> blockchain_token_type_v1:token_type(). +-spec token_type(txn_coinbase()) -> blockchain_token_v1:type(). token_type(Txn) -> Txn#blockchain_txn_coinbase_v1_pb.token_type. @@ -141,7 +141,7 @@ absorb(Txn, Chain) -> Payee = ?MODULE:payee(Txn), Amount = ?MODULE:amount(Txn), - case blockchain:config(?protocol_version, Ledger) of + case blockchain:config(?token_version, Ledger) of {ok, 2} -> TokenType = ?MODULE:token_type(Txn), blockchain_ledger_v1:credit_account(Payee, Amount, TokenType, Ledger); diff --git a/src/transactions/v1/blockchain_txn_security_exchange_v1.erl b/src/transactions/v1/blockchain_txn_security_exchange_v1.erl index c792e55168..a4a79cec9a 100644 --- a/src/transactions/v1/blockchain_txn_security_exchange_v1.erl +++ b/src/transactions/v1/blockchain_txn_security_exchange_v1.erl @@ -190,35 +190,42 @@ is_valid(Txn, Chain) -> PubKey = libp2p_crypto:bin_to_pubkey(Payer), BaseTxn = Txn#blockchain_txn_security_exchange_v1_pb{signature = <<>>}, EncodedTxn = blockchain_txn_security_exchange_v1_pb:encode_msg(BaseTxn), - case blockchain_txn:validate_fields([{{payee, Payee}, {address, libp2p}}]) of - ok -> - case libp2p_crypto:verify(EncodedTxn, Signature, PubKey) of - false -> - {error, bad_signature}; - true -> - case Payer == Payee of + + case blockchain:config(?deprecate_security_exchange_v1, Ledger) of + {ok, true} -> + {error, security_exchange_v1_deprecated}; + _ -> + case blockchain_txn:validate_fields([{{payee, Payee}, {address, libp2p}}]) of + ok -> + case libp2p_crypto:verify(EncodedTxn, Signature, PubKey) of false -> - Amount = ?MODULE:amount(Txn), - case blockchain_ledger_v1:check_security_balance(Payer, Amount, Ledger) of - ok -> - 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 -> - Error - end; + {error, bad_signature}; true -> - {error, invalid_transaction_self_payment} - end - end; - Error -> Error + case Payer == Payee of + false -> + Amount = ?MODULE:amount(Txn), + case blockchain_ledger_v1:check_security_balance(Payer, Amount, Ledger) of + ok -> + 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 -> + Error + end; + true -> + {error, invalid_transaction_self_payment} + end + end; + Error -> Error + end end. + %%-------------------------------------------------------------------- %% @doc %% @end diff --git a/src/transactions/v1/blockchain_txn_vars_v1.erl b/src/transactions/v1/blockchain_txn_vars_v1.erl index 0fa1929bab..8307086eeb 100644 --- a/src/transactions/v1/blockchain_txn_vars_v1.erl +++ b/src/transactions/v1/blockchain_txn_vars_v1.erl @@ -745,6 +745,10 @@ var_hook(?poc_challenger_type, Type, Ledger) -> end, purge_pocs(Ledger), ok; +var_hook(?ledger_entry_version, 2, Ledger) -> + %% Migrate to new style ledger entries + blockchain_ledger_v1:migrate_entries(Ledger), + ok; var_hook(_Var, _Value, _Ledger) -> ok. @@ -1206,6 +1210,11 @@ validate_var(?deprecate_payment_v1, Value) -> Val when is_boolean(Val) -> ok; _ -> throw({error, {invalidate_deprecate_payment_v1, Value}}) end; +validate_var(?deprecate_security_exchange_v1, Value) -> + case Value of + Val when is_boolean(Val) -> ok; + _ -> throw({error, {invalidate_deprecate_security_exchange_v1, Value}}) + end; validate_var(?allow_payment_v2_memos, Value) -> case Value of @@ -1520,12 +1529,19 @@ validate_var(?discard_zero_freq_witness, Value) -> validate_var(?block_size_limit, Value) -> validate_int(Value, "block_size_limit", 1*1024*1024, 512*1024*1024, false); -validate_var(?protocol_version, Value) -> +validate_var(?token_version, Value) -> case Value of undefined -> ok; 2 -> ok; %% Add support for multiple tokens _ -> - throw({error, {invalid_protocol_version, Value}}) + throw({error, {invalid_token_version, Value}}) + end; +validate_var(?ledger_entry_version, Value) -> + case Value of + undefined -> ok; + 2 -> ok; + _ -> + throw({error, {invalid_ledger_entry_version, Value}}) end; validate_var(Var, Value) -> diff --git a/src/transactions/v2/blockchain_payment_v2.erl b/src/transactions/v2/blockchain_payment_v2.erl index f46d8e2e80..67526c2d56 100644 --- a/src/transactions/v2/blockchain_payment_v2.erl +++ b/src/transactions/v2/blockchain_payment_v2.erl @@ -52,8 +52,8 @@ new(Payee, max) -> }. -spec new(Payee :: libp2p_crypto:pubkey_bin(), - Amount :: non_neg_integer() | max , - Memo :: non_neg_integer()) -> payment(). + Amount :: non_neg_integer() | max, + MemoOrTT :: non_neg_integer() | blockchain_token_v1:type()) -> payment(). new(Payee, Amount, Memo) when is_integer(Memo) andalso is_integer(Amount) -> #payment_pb{ payee=Payee, @@ -61,24 +61,48 @@ new(Payee, Amount, Memo) when is_integer(Memo) andalso is_integer(Amount) -> memo=Memo, max=false }; -new(Payee, Amount, TT) when is_integer(Amount) -> +new(Payee, max, Memo) when is_integer(Memo) -> + #payment_pb{ + payee=Payee, + amount=0, + memo=Memo, + max=true + }; +new(Payee, Amount, TT) when is_integer(Amount) andalso is_atom(TT) -> #payment_pb{ payee=Payee, amount=Amount, memo=0, max=false, token_type=TT + }; +new(Payee, max, TT) when is_atom(TT) -> + #payment_pb{ + payee=Payee, + amount=0, + memo=0, + max=true, + token_type=TT }. -spec new(Payee :: libp2p_crypto:pubkey_bin(), - Amount :: non_neg_integer(), + Amount :: non_neg_integer() | max, Memo :: non_neg_integer(), - TT :: blockchain_token_type_v1:token_type()) -> payment(). -new(Payee, Amount, Memo, TT) -> + TT :: blockchain_token_v1:type()) -> payment(). +new(Payee, Amount, Memo, TT) when is_integer(Amount) -> #payment_pb{ payee=Payee, amount=Amount, memo=Memo, + max=false, + token_type=TT + }; +new(Payee, max, Memo, TT) -> + #payment_pb{ + payee=Payee, + amount=0, + memo=Memo, + max=true, token_type=TT }. @@ -102,11 +126,11 @@ memo(Payment, Memo) -> max(Payment) -> Payment#payment_pb.max. --spec token_type(Payment :: payment()) -> blockchain_token_type_v1:token_type(). +-spec token_type(Payment :: payment()) -> blockchain_token_v1:type(). token_type(Payment) -> Payment#payment_pb.token_type. --spec token_type(Payment :: payment(), TT :: blockchain_token_type_v1:token_type()) -> payment(). +-spec token_type(Payment :: payment(), TT :: blockchain_token_v1:type()) -> payment(). token_type(Payment, TT) -> Payment#payment_pb{token_type=TT}. @@ -125,20 +149,20 @@ is_valid_max(#payment_pb{amount=Amount, max=false}) when Amount > 0 -> true; is_valid_max(#payment_pb{amount=0, max=true}) -> true; is_valid_max(_) -> false. -print(undefined) -> - <<"type=payment undefined">>; -print(#payment_pb{payee=Payee, amount=Amount, memo=Memo, max=Max, token_type=TT}) -> - io_lib:format("type=payment payee: ~p amount: ~p, memo: ~p, max: ~p, token_type: ~p", [?TO_B58(Payee), Amount, Memo, Max, TT]). - -spec is_valid_token_type(Payment :: payment()) -> boolean(). is_valid_token_type(#payment_pb{token_type = TT}) -> try - lists:member(TT, blockchain_token_type_v1:supported_tokens()) + lists:member(TT, blockchain_token_v1:supported_tokens()) catch _:_ -> %% we can't do this, invalid false end. +print(undefined) -> + <<"type=payment undefined">>; +print(#payment_pb{payee=Payee, amount=Amount, memo=Memo, max=Max, token_type=TT}) -> + io_lib:format("type=payment payee: ~p amount: ~p, memo: ~p, max: ~p, token_type: ~p", [?TO_B58(Payee), Amount, Memo, Max, TT]). + json_type() -> undefined. @@ -191,6 +215,12 @@ token_type_test() -> P2 = new(<<"payee">>, 100, hst), ?assertEqual(hst, ?MODULE:token_type(P2)). +is_valid_token_type_test() -> + P1 = new(<<"payee">>, 100, mobile), + ?assertEqual(true, ?MODULE:is_valid_token_type(P1)), + P2 = new(<<"payee">>, 100, doge), + ?assertEqual(false, ?MODULE:is_valid_token_type(P2)). + to_json_test() -> Payment = new(<<"payee">>, 100), Json = to_json(Payment, []), diff --git a/src/transactions/v2/blockchain_txn_payment_v2.erl b/src/transactions/v2/blockchain_txn_payment_v2.erl index 075f1b918d..a4a339fcd2 100644 --- a/src/transactions/v2/blockchain_txn_payment_v2.erl +++ b/src/transactions/v2/blockchain_txn_payment_v2.erl @@ -24,7 +24,7 @@ payments/1, payees/1, amounts/2, - total_amount/2, total_amounts/1, + total_amounts/2, fee/1, fee/2, fee_payer/2, calculate_fee/2, calculate_fee/5, @@ -42,7 +42,7 @@ -include_lib("eunit/include/eunit.hrl"). -endif. --type total_amounts() :: #{blockchain_token_type_v1:token_type() => non_neg_integer()}. +-type total_amounts() :: #{blockchain_token_v1:type() => non_neg_integer()}. -type txn_payment_v2() :: #blockchain_txn_payment_v2_pb{}. -export_type([txn_payment_v2/0, total_amounts/0]). @@ -79,26 +79,17 @@ payments(Txn) -> payees(Txn) -> [blockchain_payment_v2:payee(Payment) || Payment <- ?MODULE:payments(Txn)]. --spec amounts(txn_payment_v2(), blockchain_ledger_v1:ledger()) -> [pos_integer()]. +-spec amounts(txn_payment_v2(), blockchain_ledger_v1:ledger()) -> [{blockchain_token_v1:type(), pos_integer()}]. amounts(Txn, Ledger) -> - case split_payment_amounts(Txn, Ledger) of - {undefined, Payments} -> Payments; - {MaxPayment, Payments} -> [MaxPayment | Payments] - end. - --spec total_amount(txn_payment_v2(), blockchain_ledger_v1:ledger()) -> pos_integer(). -total_amount(Txn, Ledger) -> - lists:sum(?MODULE:amounts(Txn, Ledger)). + {MaxPayments, Payments} = split_payment_amounts(Txn, Ledger), + Payments ++ MaxPayments. --spec total_amounts(txn_payment_v2()) -> total_amounts(). -total_amounts(Txn) -> +-spec total_amounts(txn_payment_v2(), blockchain_ledger_v1:ledger()) -> total_amounts(). +total_amounts(Txn, Ledger) -> lists:foldl( - fun(Payment, Acc) -> - TT = blockchain_payment_v2:token_type(Payment), - Amt = blockchain_payment_v2:amount(Payment), - Fun = fun(V) -> V + Amt end, - maps:update_with(TT, Fun, Amt, Acc) - end, #{}, ?MODULE:payments(Txn)). + fun({TT, Amt}, Acc) -> + maps:update_with(TT, fun(V) -> V + Amt end, Amt, Acc) + end, #{}, ?MODULE:amounts(Txn, Ledger)). -spec fee(txn_payment_v2()) -> non_neg_integer(). fee(Txn) -> @@ -161,7 +152,7 @@ is_valid(Txn, Chain) -> ]) of ok -> - case blockchain:config(?protocol_version, Ledger) of + case blockchain:config(?token_version, Ledger) of {ok, 2} -> do_is_valid_checks_v2(Txn, Chain, M); _ -> @@ -177,7 +168,7 @@ is_valid(Txn, Chain) -> -spec absorb(txn_payment_v2(), blockchain:blockchain()) -> ok | {error, any()}. absorb(Txn, Chain) -> Ledger = blockchain:ledger(Chain), - case blockchain:config(?protocol_version, Ledger) of + case blockchain:config(?token_version, Ledger) of {ok, 2} -> absorb_v2_(Txn, Ledger, Chain); _ -> @@ -190,22 +181,27 @@ absorb_v2_(Txn, Ledger, Chain) -> Hash = ?MODULE:hash(Txn), Payer = ?MODULE:payer(Txn), Nonce = ?MODULE:nonce(Txn), - TotalAmounts = ?MODULE:total_amounts(Txn), + TotalAmounts = ?MODULE:total_amounts(Txn, Ledger), AreFeesEnabled = blockchain_ledger_v1:txn_fees_active(Ledger), case blockchain_ledger_v1:debit_fee(Payer, Fee, Ledger, AreFeesEnabled, Hash, Chain) of {error, _Reason}=Error -> Error; ok -> + {MaxAmounts, _} = split_payment_amounts(Txn, Ledger), case blockchain_ledger_v1:debit_account(Payer, TotalAmounts, Nonce, Ledger) of {error, _Reason} = Error -> Error; ok -> Payments = ?MODULE:payments(Txn), + MaxPaymentsMap = maps:from_list(MaxAmounts), ok = lists:foreach( fun(Payment) -> PayeePubkeyBin = blockchain_payment_v2:payee(Payment), - PayeeAmount = blockchain_payment_v2:amount(Payment), TT = blockchain_payment_v2:token_type(Payment), + PayeeAmount = case blockchain_payment_v2:amount(Payment) of + 0 -> maps:get(TT, MaxPaymentsMap, 0); + Amount when Amount > 0 -> Amount + end, blockchain_ledger_v1:credit_account(PayeePubkeyBin, PayeeAmount, TT, Ledger) end, Payments @@ -215,9 +211,10 @@ absorb_v2_(Txn, Ledger, Chain) -> -spec absorb_(txn_payment_v2(), blockchain_ledger_v1:ledger(), blockchain:blockchain()) -> ok | {error, any()}. absorb_(Txn, Ledger, Chain) -> - TotAmount = ?MODULE:total_amount(Txn, Ledger), - SpecifiedAmount = lists:sum([blockchain_payment_v2:amount(Payment) || Payment <- ?MODULE:payments(Txn)]), - MaxPayment = TotAmount - SpecifiedAmount, + {_, SpecifiedAmounts} = split_payment_amounts(Txn, Ledger), + TotalAmount = lists:foldl(fun({_, TAmt}, Acc) -> TAmt + Acc end, 0, ?MODULE:amounts(Txn, Ledger)), + SpecifiedSubTotal = lists:foldl(fun({_, SAmt}, Acc) -> SAmt + Acc end, 0, SpecifiedAmounts), + MaxPayment = TotalAmount - SpecifiedSubTotal, Fee = ?MODULE:fee(Txn), Hash = ?MODULE:hash(Txn), Payer = ?MODULE:payer(Txn), @@ -227,7 +224,7 @@ absorb_(Txn, Ledger, Chain) -> {error, _Reason} = Error -> Error; ok -> - case blockchain_ledger_v1:debit_account(Payer, TotAmount, Nonce, Ledger) of + case blockchain_ledger_v1:debit_account(Payer, TotalAmount, Nonce, Ledger) of {error, _Reason} = Error -> Error; ok -> @@ -347,12 +344,11 @@ do_is_valid_checks(Txn, Chain, MaxPayments) -> MemoCheck = memo_check(Txn, Ledger), case {AmountCheck, MemoCheck} of - {false, _} -> - {error, invalid_transaction}; - {_, {error, _} = E} -> - E; - {true, ok} -> - fee_check(Txn, Chain, Ledger) + {ok, ok} -> fee_check(Txn, Chain, Ledger); + _ -> + {error, {invalid_transaction, + {amount_check, AmountCheck}, + {memo_check, MemoCheck}}} end end; true -> @@ -392,10 +388,10 @@ do_is_valid_checks_v2(Txn, Chain, MaxPayments) -> Error0; {ok, Entry} -> TxnNonce = ?MODULE:nonce(Txn), - LedgerNonce = blockchain_ledger_entry_v2:nonce(Entry), - case TxnNonce =:= LedgerNonce + 1 of + LedgerEntryNonce = blockchain_ledger_entry_v2:nonce(Entry), + case TxnNonce =:= LedgerEntryNonce + 1 of false -> - {error, {bad_nonce, {payment_v2, TxnNonce, LedgerNonce}}}; + {error, {bad_nonce, {payment_v2, TxnNonce, LedgerEntryNonce}}}; true -> case LengthPayments > MaxPayments of %% Check that we don't exceed max payments @@ -475,76 +471,93 @@ memo_check(Txn, Ledger) -> -spec amount_check_v2(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> ok | {error, any()}. amount_check_v2(Txn, Ledger) -> - TotAmounts = ?MODULE:total_amounts(Txn), + TotAmounts = ?MODULE:total_amounts(Txn, Ledger), Payer = ?MODULE:payer(Txn), - Payments = ?MODULE:payments(Txn), {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), - PayerHasEnoughTTBalance = - lists:all( - fun(TT) -> + BalanceList = lists:foldl( + fun(TT, Acc) -> PayerTTBalance = blockchain_ledger_entry_v2:balance(PayerEntry, TT), - PayerTTBalance >= maps:get(TT, TotAmounts, 0) + Amount = maps:get(TT, TotAmounts, 0), + IsSufficient = PayerTTBalance >= Amount, + [{TT, IsSufficient, PayerTTBalance, Amount} | Acc] end, - blockchain_token_type_v1:supported_tokens()), - - case blockchain:config(?allow_zero_amount, Ledger) of - {ok, false} -> - %% check that none of the payments have a zero amount - case has_non_zero_amounts(Payments) of - false -> false; - true -> - case PayerHasEnoughTTBalance of - false -> - {error, amount_check_v2_failed}; - true -> - ok - end - end; - _ -> - {error, allow_zero_amount_not_set} + [], + blockchain_token_v1:supported_tokens()), + + case lists:filter( + fun({_TT, IsSufficient, _PayerTTBalance, _Amount}) -> + IsSufficient == false + end, + BalanceList) of + [ ] -> + %% If the txn amounts have successfully validated to this point + %% perform the same checks previously done prior to token_version 2 + amount_check(Txn, Ledger); + Failure -> + {error, {amount_check_v2_failed, Failure}} end. --spec amount_check(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> boolean(). +-spec amount_check(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> ok | {error, amount_check_failed}. amount_check(Txn, Ledger) -> Payments = ?MODULE:payments(Txn), - case blockchain:config(?enable_balance_clearing, Ledger) of - {ok, true} -> - %% balance clearing txns should be mutually exclusive with allowing zero amount txns - %% or else we can't tell whether or not a payment with a 0 amount is one or the other; - %% no more than one payment has a 0 amount and only if `max' is `true' - case has_single_max_payment(Payments) of - true -> - case split_payment_amounts(Txn, Ledger) of - {MaxPmt, _OtherPmts} when MaxPmt > 0 -> true; - _ -> false - end; - false -> false - end; - _ -> - 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 - ?MODULE:total_amount(Txn, Ledger) >= 0 - end + AmtCheck = case blockchain:config(?enable_balance_clearing, Ledger) of + {ok, true} -> + %% See above note in amount_check_v2 + {MaxPayments, _OtherPayments} = split_payment_amounts(Txn, Ledger), + lists:all(fun({_, Amt}) -> Amt > 0 end, MaxPayments); + _ -> + 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 + lists:sum(maps:values(?MODULE:total_amounts(Txn, Ledger))) >= 0 + end + end, + case AmtCheck of + true -> ok; + false -> {error, amount_check_failed} end. --spec split_payment_amounts(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> {pos_integer() | undefined, [pos_integer()]}. +-spec split_payment_amounts(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> {[{TT, Amt}], [{TT, Amt}]} when TT :: blockchain_token_v1:type(), Amt :: pos_integer(). split_payment_amounts(#blockchain_txn_payment_v2_pb{payer=Payer, fee=Fee}=Txn, Ledger) -> - {MaxPayment, SpecifiedPayments} = split_max_payment(?MODULE:payments(Txn)), - SpecifiedAmounts = [blockchain_payment_v2:amount(Payment) || Payment <- SpecifiedPayments], - case length(MaxPayment) == 1 of - true -> - {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), - Balance = blockchain_ledger_entry_v1:balance(PayerEntry), - MaxPaymentAmount = Balance - lists:sum(SpecifiedAmounts) - Fee, - {MaxPaymentAmount, SpecifiedAmounts}; - false -> - {undefined, SpecifiedAmounts} + {MaxPayments, SpecifiedPayments} = split_max_payments(?MODULE:payments(Txn)), + SpecifiedPayments1 = lists:map(fun(Pmt) -> {blockchain_payment_v2:token_type(Pmt), blockchain_payment_v2:amount(Pmt)} end, SpecifiedPayments), + case length(MaxPayments) of + 0 -> + {MaxPayments, SpecifiedPayments1}; + _ -> + case blockchain:config(?token_version, Ledger) of + {ok, 2} -> + {ok, PayerEntry2} = blockchain_ledger_v1:find_entry(Payer, Ledger), + MaxPayments1 = lists:map(fun(MaxPayment) -> + TokenType = blockchain_payment_v2:token_type(MaxPayment), + TypeBalance = blockchain_ledger_entry_v2:balance(PayerEntry2, TokenType), + TypeSpecifiedAmt = lists:foldl(fun({Type, Val}, Acc) -> + case Type =:= TokenType of + true -> Val + Acc; + false -> Acc + end + end, 0, SpecifiedPayments1), + MaxPaymentAmt = case TokenType of + hnt -> TypeBalance - TypeSpecifiedAmt - Fee; + _ -> TypeBalance - TypeSpecifiedAmt + end, + {TokenType, MaxPaymentAmt} + end, MaxPayments), + {MaxPayments1, SpecifiedPayments1}; + _ -> + %% Prior to token_version 2 there can only be a single balance-clearing + %% `max` payment per txn + {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), + Balance = blockchain_ledger_entry_v1:balance(PayerEntry), + SpecifiedAmts = lists:foldl(fun({_, Val}, Acc) -> Val + Acc end, 0, SpecifiedPayments1), + MaxPaymentAmt = Balance - SpecifiedAmts - Fee, + {[{hnt, MaxPaymentAmt}], SpecifiedPayments1} + end end. -spec has_unique_payees(Payments :: blockchain_payment_v2:payments()) -> boolean(). @@ -570,12 +583,6 @@ has_non_zero_amounts(Payments) -> Amounts = [blockchain_payment_v2:amount(P) || P <- Payments], lists:all(fun(A) -> A > 0 end, Amounts). --spec has_single_max_payment(Payments :: blockchain_payment_v2:payments()) -> boolean(). -has_single_max_payment(Payments) -> - {MaxTrue, MaxFalse} = split_max_payment(Payments), - length(MaxTrue) =< 1 andalso - lists:all(fun(Payment) -> blockchain_payment_v2:amount(Payment) > 0 end, MaxFalse). - -spec has_valid_memos(Payments :: blockchain_payment_v2:payments()) -> boolean(). has_valid_memos(Payments) -> lists:all( @@ -595,21 +602,22 @@ has_valid_memos(Payments) -> Payments ). --spec split_max_payment(Payments) -> {Payments, Payments} when - Payments :: blockchain_payment_v2:payments(). -split_max_payment(Payments) -> - {MaxPayment, _OtherPayments} = SplitPayments = lists:partition( - fun(Payment) -> - blockchain_payment_v2:max(Payment) andalso - blockchain_payment_v2:is_valid_max(Payment) - end, - Payments - ), - case length(MaxPayment) > 1 of +-spec split_max_payments(Payments) -> {Payments, Payments} when + Payments :: [blockchain_payment_v2:payments()]. +split_max_payments(Payments) -> + case lists:all(fun blockchain_payment_v2:is_valid_max/1, Payments) of true -> - throw({error, invalid_payment_txn}); - false -> - SplitPayments + {MaxPayments, _OtherPayments} = SplitPayments = lists:partition(fun blockchain_payment_v2:max/1, Payments), + %% This usort should be converted to `lists:uniq/2` once OTP 25 is fully supported as it's clearer and preserves + %% the list order so a single `=:=` comparison of input and output lists should suffice + UniqMaxPayments = lists:usort(fun(P1, P2) -> + blockchain_payment_v2:token_type(P1) =< blockchain_payment_v2:token_type(P2) + end, MaxPayments), + case length(UniqMaxPayments) =:= length(MaxPayments) of + true -> SplitPayments; + false -> throw({error, invalid_payment_txn}) + end; + false -> throw({error, invalid_payment_txn}) end. -spec has_default_memos(Payments :: blockchain_payment_v2:payments()) -> boolean(). @@ -625,7 +633,7 @@ has_default_memos(Payments) -> -spec token_check(Txn :: txn_payment_v2(), Ledger :: blockchain_ledger_v1:ledger()) -> ok | {error, any()}. token_check(Txn, Ledger) -> Payments = ?MODULE:payments(Txn), - case blockchain:config(?protocol_version, Ledger) of + case blockchain:config(?token_version, Ledger) of {ok, 2} -> %% check that the tokens are valid case has_valid_tokens(Payments) of @@ -713,10 +721,10 @@ amounts_test() -> Payments = [ blockchain_payment_v2:new(<<"x">>, 10), blockchain_payment_v2:new(<<"y">>, 20), - blockchain_payment_v2:new(<<"z">>, 30) + blockchain_payment_v2:new(<<"z">>, 30, mobile) ], Tx = new(<<"payer">>, Payments, 1), - ?assertEqual([10, 20, 30], ?MODULE:amounts(Tx, Ledger)). + ?assertEqual([{hnt, 10}, {hnt, 20}, {mobile, 30}], ?MODULE:amounts(Tx, Ledger)). total_amount_test() -> BaseDir = test_utils:tmp_dir("total_amount_test"), @@ -724,10 +732,10 @@ total_amount_test() -> Payments = [ blockchain_payment_v2:new(<<"x">>, 10), blockchain_payment_v2:new(<<"y">>, 20), - blockchain_payment_v2:new(<<"z">>, 30) + blockchain_payment_v2:new(<<"z">>, 30, iot) ], Tx = new(<<"payer">>, Payments, 1), - ?assertEqual(60, total_amount(Tx, Ledger)). + ?assertEqual(#{hnt => 30, iot => 30}, total_amounts(Tx, Ledger)). total_amount_with_max_test() -> BaseDir = test_utils:tmp_dir("total_amount_with_max_test"), @@ -741,7 +749,7 @@ total_amount_with_max_test() -> blockchain_payment_v2:new(<<"z">>, 40) ], Tx = new(<<"payer">>, Payments, 1), - ?assertEqual(100, total_amount(Tx, Ledger)). + ?assertEqual(#{hnt => 100}, total_amounts(Tx, Ledger)). reject_multi_max_test() -> BaseDir = test_utils:tmp_dir("reject_multi_max_test"), @@ -755,7 +763,7 @@ reject_multi_max_test() -> blockchain_payment_v2:new(<<"z">>, max) ], Tx = new(<<"payer">>, Payments, 1), - ?assertThrow({error, invalid_payment_txn}, total_amount(Tx, Ledger)). + ?assertThrow({error, invalid_payment_txn}, total_amounts(Tx, Ledger)). fee_test() -> Payments = [ diff --git a/test/blockchain_payment_v2_SUITE.erl b/test/blockchain_payment_v2_SUITE.erl index 4d9ec48b14..57e9e230c8 100644 --- a/test/blockchain_payment_v2_SUITE.erl +++ b/test/blockchain_payment_v2_SUITE.erl @@ -37,21 +37,21 @@ groups() -> [ - {without_protocol_version, [], without_protocol_version_tests()}, - {with_protocol_version, [], with_protocol_version_tests()} + {without_token_version, [], without_token_version_tests()}, + {with_token_version, [], with_token_version_tests()} ]. -without_protocol_version_tests() -> +without_token_version_tests() -> test_cases(). -with_protocol_version_tests() -> +with_token_version_tests() -> test_cases(). -init_per_group(with_protocol_version, Config) -> +init_per_group(with_token_version, Config) -> [ - {group_vars, #{?protocol_version => 2}}, + {group_vars, #{?token_version => 2}}, {balance, 5000}, - {token_allocations, #{hnt => 5000, hst => 1000, hgt => 100, hlt => 10}} + {token_allocations, #{hnt => 5000, hst => 1000, mobile => 100, iot => 10}} | Config ]; init_per_group(_, Config) -> @@ -62,8 +62,8 @@ end_per_group(_, _Config) -> all() -> [ - {group, without_protocol_version}, - {group, with_protocol_version} + {group, without_token_version}, + {group, with_token_version} ]. test_cases() -> @@ -140,7 +140,7 @@ init_per_testcase(TestCase, Config) -> Ledger = blockchain:ledger(Chain), %% NOTE: Get the current ledger entry module depending on the Ledger - %% For testing (chain var: protocol_version = 2) + %% For testing (chain var: token_version = 2) {EntryMod, _} = blockchain_ledger_v1:versioned_entry_mod_and_entries_cf(Ledger), ct:pal("EntryMod: ~p", [EntryMod]), @@ -528,7 +528,7 @@ invalid_balance_clearing_test(Config) -> ct:pal("~s", [blockchain_txn:print(SignedTx)]), - {error, {invalid_txns, [{BadTx, invalid_transaction}]}} = test_utils:create_block( + {error, {invalid_txns, [{BadTx, validation_failed}]}} = test_utils:create_block( ConsensusMembers, [SignedTx] ), @@ -566,7 +566,7 @@ balance_clearing_disabled_test(Config) -> ct:pal("~s", [blockchain_txn:print(SignedTx)]), - {error, {invalid_txns, [{BadTx, invalid_transaction}]}} = test_utils:create_block( + {error, {invalid_txns, [{BadTx, validation_failed}]}} = test_utils:create_block( ConsensusMembers, [SignedTx] ), @@ -701,12 +701,12 @@ negative_memo_test(Config) -> ct:pal("InvalidReason: ~p", [InvalidReason]), case ?config(group_vars, Config) of - #{?protocol_version := 2} -> - {invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo}}, - {token_check, ok}} = InvalidReason, - ok; + #{?token_version := 2} -> + ?assertEqual({invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo}}, + {token_check, ok}}, InvalidReason); #{} -> - ?assertEqual(invalid_memo, InvalidReason) + ?assertEqual({invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo}}}, + InvalidReason) end, ok. @@ -777,12 +777,12 @@ invalid_memo_not_set_test(Config) -> ct:pal("InvalidReason: ~p", [InvalidReason]), case ?config(group_vars, Config) of - #{?protocol_version := 2} -> - {invalid_transaction, {amount_check, ok}, - {memo_check, {error, invalid_memo_before_var}}, {token_check, ok}} = InvalidReason, - ok; + #{?token_version := 2} -> + ?assertEqual({invalid_transaction, {amount_check, ok}, + {memo_check, {error, invalid_memo_before_var}}, {token_check, ok}}, InvalidReason); #{} -> - ?assertEqual(invalid_memo_before_var, InvalidReason) + ?assertEqual({invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo_before_var}}}, + InvalidReason) end, ok. @@ -843,12 +843,12 @@ big_memo_invalid_test(Config) -> ct:pal("InvalidReason: ~p", [InvalidReason]), case ?config(group_vars, Config) of - #{?protocol_version := 2} -> - {invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo}}, - {token_check, ok}} = InvalidReason, - ok; + #{?token_version := 2} -> + ?assertEqual({invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo}}, + {token_check, ok}}, InvalidReason); #{} -> - ?assertEqual(invalid_memo, InvalidReason) + ?assertEqual({invalid_transaction, {amount_check, ok}, {memo_check, {error, invalid_memo}}}, + InvalidReason) end, ok. diff --git a/test/blockchain_token_SUITE.erl b/test/blockchain_token_SUITE.erl index 1d32d81fff..d86d012538 100644 --- a/test/blockchain_token_SUITE.erl +++ b/test/blockchain_token_SUITE.erl @@ -8,7 +8,10 @@ -export([ multi_token_coinbase_test/1, - multi_token_payment_test/1 + multi_token_payment_test/1, + multi_token_payment_failure_test/1, + entry_migration_test/1, + entry_migration_with_payment_test/1 ]). %%-------------------------------------------------------------------- @@ -24,7 +27,10 @@ all() -> [ multi_token_coinbase_test, - multi_token_payment_test + multi_token_payment_test, + multi_token_payment_failure_test, + entry_migration_test, + entry_migration_with_payment_test ]. %%-------------------------------------------------------------------- @@ -36,18 +42,29 @@ init_per_testcase(TestCase, Config) -> HNTBal = 50000, HSTBal = 10000, - HGTBal = 1000, - HLTBal = 100, + MobileBal = 1000, + IOTBal = 100, - {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config0)), + Config1 = [ + {hnt_bal, HNTBal}, + {hst_bal, HSTBal}, + {mobile_bal, MobileBal}, + {iot_bal, IOTBal} + | Config0 + ], + + {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config1)), ExtraVars = extra_vars(TestCase), + TokenAllocations = token_allocations(TestCase, Config1), {ok, GenesisMembers, _GenesisBlock, ConsensusMembers, Keys} = test_utils:init_chain_with_opts( #{ balance => HNTBal, + sec_balance => + HSTBal, keys => {PrivKey, PubKey}, in_consensus => @@ -57,19 +74,23 @@ init_per_testcase(TestCase, Config) -> extra_vars => ExtraVars, token_allocations => - #{hnt => HNTBal, hst => HSTBal, hgt => HGTBal, hlt => HLTBal} + TokenAllocations } ), Chain = blockchain_worker:blockchain(), + Ledger = blockchain:ledger(Chain), Swarm = blockchain_swarm:tid(), N = length(ConsensusMembers), + {EntryMod, _} = blockchain_ledger_v1:versioned_entry_mod_and_entries_cf(Ledger), + [ {hnt_bal, HNTBal}, {hst_bal, HSTBal}, - {hgt_bal, HGTBal}, - {hlt_bal, HLTBal}, + {mobile_bal, MobileBal}, + {iot_bal, IOTBal}, + {entry_mod, EntryMod}, {sup, Sup}, {pubkey, PubKey}, {privkey, PrivKey}, @@ -80,7 +101,7 @@ init_per_testcase(TestCase, Config) -> {consensus_members, ConsensusMembers}, {genesis_members, GenesisMembers}, Keys - | Config0 + | Config1 ]. %%-------------------------------------------------------------------- @@ -109,22 +130,25 @@ multi_token_coinbase_test(Config) -> Chain = ?config(chain, Config), HNTBal = ?config(hnt_bal, Config), HSTBal = ?config(hst_bal, Config), - HGTBal = ?config(hgt_bal, Config), - HLTBal = ?config(hlt_bal, Config), + MobileBal = ?config(mobile_bal, Config), + IOTBal = ?config(iot_bal, Config), + EntryMod = ?config(entry_mod, Config), % Check ledger to make sure everyone has the right balance Ledger = blockchain:ledger(Chain), - Entries = blockchain_ledger_v1:entries_v2(Ledger), + + Entries = blockchain_ledger_v1:entries(Ledger), + _ = lists:foreach( fun(Entry) -> - HNTBal = blockchain_ledger_entry_v2:balance(Entry, hnt), - 0 = blockchain_ledger_entry_v2:nonce(Entry), - HSTBal = blockchain_ledger_entry_v2:balance(Entry, hst), - 0 = blockchain_ledger_entry_v2:nonce(Entry), - HGTBal = blockchain_ledger_entry_v2:balance(Entry, hgt), - 0 = blockchain_ledger_entry_v2:nonce(Entry), - HLTBal = blockchain_ledger_entry_v2:balance(Entry, hlt), - 0 = blockchain_ledger_entry_v2:nonce(Entry) + HNTBal = EntryMod:balance(Entry), + 0 = EntryMod:nonce(Entry), + HSTBal = EntryMod:balance(Entry, hst), + 0 = EntryMod:nonce(Entry), + MobileBal = EntryMod:balance(Entry, mobile), + 0 = EntryMod:nonce(Entry), + IOTBal = EntryMod:balance(Entry, iot), + 0 = EntryMod:nonce(Entry) end, maps:values(Entries) ), @@ -135,8 +159,9 @@ multi_token_payment_test(Config) -> Chain = ?config(chain, Config), HNTBal = ?config(hnt_bal, Config), HSTBal = ?config(hst_bal, Config), - HGTBal = ?config(hgt_bal, Config), - HLTBal = ?config(hlt_bal, Config), + MobileBal = ?config(mobile_bal, Config), + IOTBal = ?config(iot_bal, Config), + EntryMod = ?config(entry_mod, Config), Ledger = blockchain:ledger(Chain), %% Test a payment transaction, add a block and check balances @@ -147,22 +172,22 @@ multi_token_payment_test(Config) -> HNTAmt1 = 20000, HSTAmt1 = 2000, - HGTAmt1 = 200, - HLTAmt1 = 20, + MobileAmt1 = 200, + IOTAmt1 = 20, HNTAmt2 = 10000, HSTAmt2 = 1000, - HGTAmt2 = 100, - HLTAmt2 = 10, + MobileAmt2 = 100, + IOTAmt2 = 10, - P1 = blockchain_payment_v2:new(Recipient1, HNTAmt1, hnt), + P1 = blockchain_payment_v2:new(Recipient1, HNTAmt1), P2 = blockchain_payment_v2:new(Recipient1, HSTAmt1, hst), - P3 = blockchain_payment_v2:new(Recipient1, HGTAmt1, hgt), - P4 = blockchain_payment_v2:new(Recipient1, HLTAmt1, hlt), + P3 = blockchain_payment_v2:new(Recipient1, MobileAmt1, mobile), + P4 = blockchain_payment_v2:new(Recipient1, IOTAmt1, iot), - P5 = blockchain_payment_v2:new(Recipient2, HNTAmt2, hnt), + P5 = blockchain_payment_v2:new(Recipient2, HNTAmt2), P6 = blockchain_payment_v2:new(Recipient2, HSTAmt2, hst), - P7 = blockchain_payment_v2:new(Recipient2, HGTAmt2, hgt), - P8 = blockchain_payment_v2:new(Recipient2, HLTAmt2, hlt), + P7 = blockchain_payment_v2:new(Recipient2, MobileAmt2, mobile), + P8 = blockchain_payment_v2:new(Recipient2, IOTAmt2, iot), Payments = [P1, P2, P3, P4, P5, P6, P7, P8], @@ -182,22 +207,22 @@ multi_token_payment_test(Config) -> {ok, RecipientEntry1} = blockchain_ledger_v1:find_entry(Recipient1, Ledger), {ok, RecipientEntry2} = blockchain_ledger_v1:find_entry(Recipient2, Ledger), - ?assertEqual(HNTAmt1, blockchain_ledger_entry_v2:balance(RecipientEntry1, hnt)), - ?assertEqual(HSTAmt1, blockchain_ledger_entry_v2:balance(RecipientEntry1, hst)), - ?assertEqual(HGTAmt1, blockchain_ledger_entry_v2:balance(RecipientEntry1, hgt)), - ?assertEqual(HLTAmt1, blockchain_ledger_entry_v2:balance(RecipientEntry1, hlt)), + ?assertEqual(HNTAmt1, EntryMod:balance(RecipientEntry1)), + ?assertEqual(HSTAmt1, EntryMod:balance(RecipientEntry1, hst)), + ?assertEqual(MobileAmt1, EntryMod:balance(RecipientEntry1, mobile)), + ?assertEqual(IOTAmt1, EntryMod:balance(RecipientEntry1, iot)), - ?assertEqual(HNTAmt2, blockchain_ledger_entry_v2:balance(RecipientEntry2, hnt)), - ?assertEqual(HSTAmt2, blockchain_ledger_entry_v2:balance(RecipientEntry2, hst)), - ?assertEqual(HGTAmt2, blockchain_ledger_entry_v2:balance(RecipientEntry2, hgt)), - ?assertEqual(HLTAmt2, blockchain_ledger_entry_v2:balance(RecipientEntry2, hlt)), + ?assertEqual(HNTAmt2, EntryMod:balance(RecipientEntry2)), + ?assertEqual(HSTAmt2, EntryMod:balance(RecipientEntry2, hst)), + ?assertEqual(MobileAmt2, EntryMod:balance(RecipientEntry2, mobile)), + ?assertEqual(IOTAmt2, EntryMod:balance(RecipientEntry2, iot)), {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(1, blockchain_ledger_entry_v2:nonce(PayerEntry)), - ?assertEqual(HNTBal - (HNTAmt1 + HNTAmt2), blockchain_ledger_entry_v2:balance(PayerEntry, hnt)), - ?assertEqual(HSTBal - (HSTAmt1 + HSTAmt2), blockchain_ledger_entry_v2:balance(PayerEntry, hst)), - ?assertEqual(HGTBal - (HGTAmt1 + HGTAmt2), blockchain_ledger_entry_v2:balance(PayerEntry, hgt)), - ?assertEqual(HLTBal - (HLTAmt1 + HLTAmt2), blockchain_ledger_entry_v2:balance(PayerEntry, hlt)), + ?assertEqual(1, EntryMod:nonce(PayerEntry)), + ?assertEqual(HNTBal - (HNTAmt1 + HNTAmt2), EntryMod:balance(PayerEntry)), + ?assertEqual(HSTBal - (HSTAmt1 + HSTAmt2), EntryMod:balance(PayerEntry, hst)), + ?assertEqual(MobileBal - (MobileAmt1 + MobileAmt2), EntryMod:balance(PayerEntry, mobile)), + ?assertEqual(IOTBal - (IOTAmt1 + IOTAmt2), EntryMod:balance(PayerEntry, iot)), %% Do another payment @@ -205,13 +230,13 @@ multi_token_payment_test(Config) -> HNTAmt3 = 2000, HSTAmt3 = 200, - HGTAmt3 = 20, - HLTAmt3 = 2, + MobileAmt3 = 20, + IOTAmt3 = 2, P9 = blockchain_payment_v2:new(Recipient3, HNTAmt3, hnt), P10 = blockchain_payment_v2:new(Recipient3, HSTAmt3, hst), - P11 = blockchain_payment_v2:new(Recipient3, HGTAmt3, hgt), - P12 = blockchain_payment_v2:new(Recipient3, HLTAmt3, hlt), + P11 = blockchain_payment_v2:new(Recipient3, MobileAmt3, mobile), + P12 = blockchain_payment_v2:new(Recipient3, IOTAmt3, iot), Payments3 = [P9, P10, P11, P12], @@ -229,31 +254,250 @@ multi_token_payment_test(Config) -> {ok, RecipientEntry3} = blockchain_ledger_v1:find_entry(Recipient3, Ledger), - ?assertEqual(HNTAmt3, blockchain_ledger_entry_v2:balance(RecipientEntry3, hnt)), - ?assertEqual(HSTAmt3, blockchain_ledger_entry_v2:balance(RecipientEntry3, hst)), - ?assertEqual(HGTAmt3, blockchain_ledger_entry_v2:balance(RecipientEntry3, hgt)), - ?assertEqual(HLTAmt3, blockchain_ledger_entry_v2:balance(RecipientEntry3, hlt)), + ?assertEqual(HNTAmt3, EntryMod:balance(RecipientEntry3)), + ?assertEqual(HSTAmt3, EntryMod:balance(RecipientEntry3, hst)), + ?assertEqual(MobileAmt3, EntryMod:balance(RecipientEntry3, mobile)), + ?assertEqual(IOTAmt3, EntryMod:balance(RecipientEntry3, iot)), {ok, PayerEntry3} = blockchain_ledger_v1:find_entry(Payer, Ledger), - ?assertEqual(2, blockchain_ledger_entry_v2:nonce(PayerEntry3)), + ?assertEqual(2, EntryMod:nonce(PayerEntry3)), ?assertEqual( HNTBal - (HNTAmt1 + HNTAmt2 + HNTAmt3), - blockchain_ledger_entry_v2:balance(PayerEntry3, hnt) + EntryMod:balance(PayerEntry3) ), ?assertEqual( HSTBal - (HSTAmt1 + HSTAmt2 + HSTAmt3), - blockchain_ledger_entry_v2:balance(PayerEntry3, hst) + EntryMod:balance(PayerEntry3, hst) + ), + ?assertEqual( + MobileBal - (MobileAmt1 + MobileAmt2 + MobileAmt3), + EntryMod:balance(PayerEntry3, mobile) ), ?assertEqual( - HGTBal - (HGTAmt1 + HGTAmt2 + HGTAmt3), - blockchain_ledger_entry_v2:balance(PayerEntry3, hgt) + IOTBal - (IOTAmt1 + IOTAmt2 + IOTAmt3), + EntryMod:balance(PayerEntry3, iot) ), + + ok. + +multi_token_payment_failure_test(Config) -> + ConsensusMembers = ?config(consensus_members, Config), + MobileBal = ?config(mobile_bal, Config), + Chain = ?config(chain, Config), + + %% Test a payment transaction, add a block and check balances + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, + + %% Generate two random recipients + [{Recipient1, _}] = test_utils:generate_keys(1), + + MobileAmt = 200 * 10000, + + P1 = blockchain_payment_v2:new(Recipient1, MobileAmt, mobile), + + Payments = [P1], + + Tx = blockchain_txn_payment_v2:new(Payer, Payments, 1), + SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), + SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), + + {error, InvalidReason} = blockchain_txn:is_valid(SignedTx, Chain), + ct:pal("InvalidReason: ~p", [InvalidReason]), + ?assertEqual( - HLTBal - (HLTAmt1 + HLTAmt2 + HLTAmt3), - blockchain_ledger_entry_v2:balance(PayerEntry3, hlt) + {invalid_transaction, + {amount_check, + {error, {amount_check_v2_failed, [{mobile, false, MobileBal, MobileAmt}]}}}, + {memo_check, ok}, {token_check, ok}}, + InvalidReason + ), + + ok. + +entry_migration_test(Config) -> + Chain = ?config(chain, Config), + HNTBal = ?config(hnt_bal, Config), + HSTBal = ?config(hst_bal, Config), + EntryMod = ?config(entry_mod, Config), + {Priv, _} = ?config(master_key, Config), + ConsensusMembers = ?config(consensus_members, Config), + + % Check ledger to make sure everyone has the right balance + Ledger = blockchain:ledger(Chain), + Entries = blockchain_ledger_v1:entries(Ledger), + ct:pal("BEFORE Entries: ~p", [Entries]), + + Securities = blockchain_ledger_v1:securities(Ledger), + ct:pal("BEFORE Securities: ~p", [Securities]), + + %% Ensure we are on v1 style entries + ?assertEqual(blockchain_ledger_entry_v1, EntryMod), + + ok = lists:foreach( + fun(Entry) -> + HNTBal = EntryMod:balance(Entry), + 0 = EntryMod:nonce(Entry) + end, + maps:values(Entries) + ), + + ok = lists:foreach( + fun(Entry) -> + HSTBal = blockchain_ledger_security_entry_v1:balance(Entry), + 0 = blockchain_ledger_security_entry_v1:nonce(Entry) + end, + maps:values(Securities) + ), + + %% Send var txn with ledger_entry_version = 2 and token_version = 2 + %% to trigger ledger entry migration + + Vars = #{ledger_entry_version => 2, token_version => 2}, + VarTxn = blockchain_txn_vars_v1:new(Vars, 3), + Proof = blockchain_txn_vars_v1:create_proof(Priv, VarTxn), + VarTxn1 = blockchain_txn_vars_v1:proof(VarTxn, Proof), + + {ok, Block2} = test_utils:create_block(ConsensusMembers, [VarTxn1]), + _ = blockchain_gossip_handler:add_block(Block2, Chain, self(), blockchain_swarm:tid()), + + ?assertEqual({ok, blockchain_block:hash_block(Block2)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block2}, blockchain:head_block(Chain)), + ?assertEqual({ok, 2}, blockchain:height(Chain)), + ?assertEqual({ok, Block2}, blockchain:get_block(2, Chain)), + + %% At this point we should switch to v2 style entries + + AfterEntries = blockchain_ledger_v1:entries(Ledger), + ct:pal("AFTER Entries: ~p", [AfterEntries]), + + %% Ensure we are on v2 style entries after the var + {NewEntryMod, _} = blockchain_ledger_v1:versioned_entry_mod_and_entries_cf(Ledger), + ?assertEqual(blockchain_ledger_entry_v2, NewEntryMod), + + ok = lists:foreach( + fun(Entry) -> + HNTBal = NewEntryMod:balance(Entry), + 0 = NewEntryMod:nonce(Entry), + HSTBal = NewEntryMod:balance(Entry, hst) + end, + maps:values(AfterEntries) + ), + + ok. + +entry_migration_with_payment_test(Config) -> + Chain = ?config(chain, Config), + HNTBal = ?config(hnt_bal, Config), + HSTBal = ?config(hst_bal, Config), + {Priv, _} = ?config(master_key, Config), + ConsensusMembers = ?config(consensus_members, Config), + + Ledger = blockchain:ledger(Chain), + + BeforeEntries = blockchain_ledger_v1:entries(Ledger), + ct:pal("BEFORE Entries: ~p", [BeforeEntries]), + + %% Test a payment transaction, add a block and check balances + [_, {Payer, {_, PayerPrivKey, _}} | _] = ConsensusMembers, + + %% Create a payment to a single payee + Recipient = blockchain_swarm:pubkey_bin(), + Amount = 2500, + Payment1 = blockchain_payment_v2:new(Recipient, Amount), + + Tx = blockchain_txn_payment_v2:new(Payer, [Payment1], 1), + SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), + SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), + + ct:pal("~s", [blockchain_txn:print(SignedTx)]), + + {ok, Block} = test_utils:create_block(ConsensusMembers, [SignedTx]), + _ = blockchain_gossip_handler:add_block(Block, Chain, self(), blockchain_swarm:tid()), + + ?assertEqual({ok, blockchain_block:hash_block(Block)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block}, blockchain:head_block(Chain)), + ?assertEqual({ok, 2}, blockchain:height(Chain)), + + ?assertEqual({ok, Block}, blockchain:get_block(2, Chain)), + + %% Create a security exchange transaction + + SecAmount = 100, + STx = blockchain_txn_security_exchange_v1:new( + Payer, + Recipient, + SecAmount, + 1 ), + SignedSTx = blockchain_txn_security_exchange_v1:sign(STx, SigFun), + ct:pal("~s", [blockchain_txn:print(SignedSTx)]), + + {ok, Block3} = test_utils:create_block(ConsensusMembers, [SignedSTx]), + _ = blockchain_gossip_handler:add_block(Block3, Chain, self(), blockchain_swarm:tid()), + + ?assertEqual({ok, blockchain_block:hash_block(Block3)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block3}, blockchain:head_block(Chain)), + ?assertEqual({ok, 3}, blockchain:height(Chain)), + ?assertEqual({ok, Block3}, blockchain:get_block(3, Chain)), + + %% Send var txn with ledger_entry_version = 2 and token_version = 2 + %% to trigger ledger entry migration + + Vars = #{ledger_entry_version => 2, token_version => 2}, + VarTxn = blockchain_txn_vars_v1:new(Vars, 3), + Proof = blockchain_txn_vars_v1:create_proof(Priv, VarTxn), + VarTxn1 = blockchain_txn_vars_v1:proof(VarTxn, Proof), + + {ok, Block4} = test_utils:create_block(ConsensusMembers, [VarTxn1]), + _ = blockchain_gossip_handler:add_block(Block4, Chain, self(), blockchain_swarm:tid()), + + ?assertEqual({ok, blockchain_block:hash_block(Block4)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block4}, blockchain:head_block(Chain)), + ?assertEqual({ok, 4}, blockchain:height(Chain)), + ?assertEqual({ok, Block4}, blockchain:get_block(4, Chain)), + + %% At this point we should switch to v2 style entries + AfterEntries = blockchain_ledger_v1:entries(Ledger), + ct:pal("AFTER Entries: ~p", [AfterEntries]), + + %% Ensure we are on v2 style entries after the var + {NewEntryMod, _} = blockchain_ledger_v1:versioned_entry_mod_and_entries_cf(Ledger), + ?assertEqual(blockchain_ledger_entry_v2, NewEntryMod), + + {ok, RecipientEntry} = blockchain_ledger_v1:find_entry(Recipient, Ledger), + ct:pal("RecipientEntry: ~p", [RecipientEntry]), + ?assertEqual(0, NewEntryMod:nonce(RecipientEntry)), + ?assertEqual(Amount, NewEntryMod:balance(RecipientEntry)), + ?assertEqual(SecAmount, NewEntryMod:balance(RecipientEntry, hst)), + + {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), + ct:pal("RecipientEntry: ~p", [RecipientEntry]), + + %% +1 from hnt payment +1 from security payment + ?assertEqual(2, NewEntryMod:nonce(PayerEntry)), + ?assertEqual(HNTBal - Amount, NewEntryMod:balance(PayerEntry)), + ?assertEqual(HSTBal - SecAmount, NewEntryMod:balance(PayerEntry, hst)), ok. +%%-------------------------------------------------------------------- +%% Helper functions +%%-------------------------------------------------------------------- + +extra_vars(entry_migration_with_payment_test) -> + #{?max_payments => 20, ?allow_zero_amount => false}; +extra_vars(entry_migration_test) -> + #{?max_payments => 20, ?allow_zero_amount => false}; extra_vars(_) -> - #{?protocol_version => 2, ?max_payments => 20, ?allow_zero_amount => false}. + #{?token_version => 2, ?max_payments => 20, ?allow_zero_amount => false}. + +token_allocations(entry_migration_with_payment_test, _Config) -> + undefined; +token_allocations(entry_migration_test, _Config) -> + undefined; +token_allocations(_, Config) -> + HNTBal = ?config(hnt_bal, Config), + HSTBal = ?config(hst_bal, Config), + MobileBal = ?config(mobile_bal, Config), + IOTBal = ?config(iot_bal, Config), + #{hnt => HNTBal, hst => HSTBal, mobile => MobileBal, iot => IOTBal}. diff --git a/test/test_utils.erl b/test/test_utils.erl index f5fbe89fe0..ead561b48b 100644 --- a/test/test_utils.erl +++ b/test/test_utils.erl @@ -87,6 +87,7 @@ init_chain(Balance, Keys, InConsensus) when is_tuple(Keys), is_boolean(InConsens init_chain_with_opts(Opts) when is_map(Opts) -> Balance = maps:get(balance, Opts, 5000), + SecBalance = maps:get(sec_balance, Opts, 5000), ExtraVars = maps:get(extra_vars, Opts, #{}), TokenAllocations = maps:get(token_allocations, Opts, undefined), GenesisMembers = @@ -126,7 +127,7 @@ init_chain_with_opts(Opts) when is_map(Opts) -> maps:get(have_init_dc, Opts, false) ], - GenSecPaymentTxs = [blockchain_txn_security_coinbase_v1:new(Addr, Balance) + GenSecPaymentTxs = [blockchain_txn_security_coinbase_v1:new(Addr, SecBalance) || {Addr, _} <- GenesisMembers], Addresses = [Addr || {Addr, _} <- GenesisMembers], From 6185bbfac17b5bbba310cc2411586e00ae3b8874 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Thu, 9 Jun 2022 11:02:36 -0700 Subject: [PATCH 38/48] Add support for aux ledger - Handle credit_account for new style entries - Add more verbose var comments - Add txn support for subnetworks - Add network rewards --- include/blockchain_vars.hrl | 7 +- rebar.config | 2 +- rebar.lock | 2 +- src/ledger/v1/blockchain_aux_ledger_v1.erl | 4 +- src/ledger/v1/blockchain_ledger_v1.erl | 124 +- src/ledger/v2/blockchain_ledger_entry_v2.erl | 55 +- src/transactions/blockchain_txn.erl | 33 +- .../blockchain_txn_subnetwork_rewards_v1.erl | 2193 +++++++++++++++++ .../v1/blockchain_txn_vars_v1.erl | 2 - .../v2/blockchain_txn_payment_v2.erl | 31 +- 10 files changed, 2328 insertions(+), 125 deletions(-) create mode 100644 src/transactions/v1/blockchain_txn_subnetwork_rewards_v1.erl diff --git a/include/blockchain_vars.hrl b/include/blockchain_vars.hrl index b811f61368..5c7c54c22d 100644 --- a/include/blockchain_vars.hrl +++ b/include/blockchain_vars.hrl @@ -616,12 +616,11 @@ -define(block_size_limit, block_size_limit). %% ------------------------------------------------------------------ -%% Token version (aka support multiple tokens) -%% Testing for now, set to 2 +%% Token version (aka support multiple tokens). Set to 2 (pos_integer). -define(token_version, token_version). %% Ledger entry migration variable %% Will be used to hook and switch the old style ledger entries -%% to new style ledger entries +%% to new style ledger entries. Set to 2 (pos_integer). -define(ledger_entry_version, ledger_entry_version). -%% Var to switch off legacy security_exchange txn +%% Var to switch off legacy security_exchange txn. Boolean. -define(deprecate_security_exchange_v1, deprecate_security_exchange_v1). diff --git a/rebar.config b/rebar.config index d3673fc215..4894b8190d 100644 --- a/rebar.config +++ b/rebar.config @@ -40,7 +40,7 @@ {erlang_stats, ".*", {git, "https://github.com/helium/erlang-stats.git", {branch, "master"}}}, {e2qc, ".*", {git, "https://github.com/helium/e2qc", {branch, "master"}}}, {vincenty, ".*", {git, "https://github.com/helium/vincenty", {branch, "master"}}}, - {helium_proto, {git, "https://github.com/helium/proto.git", {branch, "rg/hip-xx-experiments"}}}, + {helium_proto, {git, "https://github.com/helium/proto.git", {branch, "master"}}}, {merkerl, ".*", {git, "https://github.com/helium/merkerl.git", {branch, "master"}}}, {xxhash, {git, "https://github.com/pierreis/erlang-xxhash", {branch, "master"}}}, {exor_filter, ".*", {git, "https://github.com/mpope9/exor_filter", {branch, "master"}}}, diff --git a/rebar.lock b/rebar.lock index 572e504765..f92601b4cd 100644 --- a/rebar.lock +++ b/rebar.lock @@ -77,7 +77,7 @@ {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},0}, {<<"helium_proto">>, {git,"https://github.com/helium/proto.git", - {ref,"732899336e07b798028d30e496fe6e22c39b7987"}}, + {ref,"9f443381496f8579c867647538df06f10e605b73"}}, 0}, {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1}, diff --git a/src/ledger/v1/blockchain_aux_ledger_v1.erl b/src/ledger/v1/blockchain_aux_ledger_v1.erl index 5c6495abba..ed1324fb3b 100644 --- a/src/ledger/v1/blockchain_aux_ledger_v1.erl +++ b/src/ledger/v1/blockchain_aux_ledger_v1.erl @@ -100,6 +100,7 @@ new(Path, Ledger) -> H3DexCF, GwDenormCF, ValidatorsCF, + EntriesV2CF, AuxHeightsCF, AuxHeightsMDCF, AuxHeightsDiffCF, @@ -127,7 +128,8 @@ new(Path, Ledger) -> subnets = SubnetsCF, state_channels = SCsCF, h3dex = H3DexCF, - validators = ValidatorsCF + validators = ValidatorsCF, + entries_v2 = EntriesV2CF } } }. diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index c38f5660b9..aefd56b17f 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -2902,12 +2902,7 @@ migrate_reg_entries(Ledger) -> {error, address_entry_not_found} -> %% This should _always_ occur as we assume no entries_v2 exist %% on ledger yet - EntryV1Balance = blockchain_ledger_entry_v1:balance(EntryV1), - EntryV1Nonce = blockchain_ledger_entry_v1:nonce(EntryV1), - NewEntryV2 = blockchain_ledger_entry_v2:nonce( - blockchain_ledger_entry_v2:credit( - blockchain_ledger_entry_v2:new(), EntryV1Balance, hnt), - EntryV1Nonce), + NewEntryV2 = blockchain_ledger_entry_v2:from_v1(EntryV1, entry), Bin = blockchain_ledger_entry_v2:serialize(NewEntryV2), cache_put(Ledger, EntriesV2CF, Address, Bin); _ -> @@ -2928,31 +2923,26 @@ migrate_sec_entries(Ledger) -> Ledger, SecuritiesCF, fun({Address, Binary}, ok) -> - EntryV1 = blockchain_ledger_security_entry_v1:deserialize(Binary), + SecEntryV1 = blockchain_ledger_security_entry_v1:deserialize(Binary), case ?MODULE:find_entry(Address, Ledger) of {ok, EntryV2} -> %% There's already an EntryV2 possibly from doing the entry migration, %% To consolidate: %% - Add the two nonces (sec_nonce_v1 + nonce_entry_v2) %% - Update hst balance in EntryV2 - SecEntryV1Balance = blockchain_ledger_security_entry_v1:balance(EntryV1), - SecEntryV1Nonce = blockchain_ledger_security_entry_v1:nonce(EntryV1), + SecEntryV1Balance = blockchain_ledger_security_entry_v1:balance(SecEntryV1), + SecEntryV1Nonce = blockchain_ledger_security_entry_v1:nonce(SecEntryV1), EntryV2Nonce = blockchain_ledger_entry_v2:nonce(EntryV2), - NewEntryV2 = blockchain_ledger_entry_v2:nonce( + NewSecEntryV2 = blockchain_ledger_entry_v2:nonce( blockchain_ledger_entry_v2:credit( EntryV2, SecEntryV1Balance, hst), EntryV2Nonce + SecEntryV1Nonce), - Bin = blockchain_ledger_entry_v2:serialize(NewEntryV2), + Bin = blockchain_ledger_entry_v2:serialize(NewSecEntryV2), cache_put(Ledger, EntriesV2CF, Address, Bin); {error, address_entry_not_found} -> %% This address only has security tokens, credit accordingly - SecEntryV1Balance = blockchain_ledger_security_entry_v1:balance(EntryV1), - SecEntryV1Nonce = blockchain_ledger_security_entry_v1:nonce(EntryV1), - NewEntryV2 = blockchain_ledger_entry_v2:nonce( - blockchain_ledger_entry_v2:credit( - blockchain_ledger_entry_v2:new(), SecEntryV1Balance, hst), - SecEntryV1Nonce), - Bin = blockchain_ledger_entry_v2:serialize(NewEntryV2), + NewSecEntryV2 = blockchain_ledger_entry_v2:from_v1(SecEntryV1, security), + Bin = blockchain_ledger_entry_v2:serialize(NewSecEntryV2), cache_put(Ledger, EntriesV2CF, Address, Bin); _ -> %% Unexpected, abort! @@ -3113,21 +3103,26 @@ versioned_entry_mod_and_entries_cf(Ledger) -> -spec credit_account(libp2p_crypto:pubkey_bin(), integer(), ledger()) -> ok | {error, any()}. credit_account(Address, Amount, Ledger) -> - EntriesCF = entries_cf(Ledger), - case ?MODULE:find_entry(Address, Ledger) of - {error, address_entry_not_found} -> - Entry = blockchain_ledger_entry_v1:new(0, Amount), - Bin = blockchain_ledger_entry_v1:serialize(Entry), - cache_put(Ledger, EntriesCF, Address, Bin); - {ok, Entry} -> - Entry1 = blockchain_ledger_entry_v1:new( - blockchain_ledger_entry_v1:nonce(Entry), - blockchain_ledger_entry_v1:balance(Entry) + Amount - ), - Bin = blockchain_ledger_entry_v1:serialize(Entry1), - cache_put(Ledger, EntriesCF, Address, Bin); - {error, _}=Error -> - Error + case versioned_entry_mod_and_entries_cf(Ledger) of + {blockchain_ledger_entry_v2, _V2CF} -> + %% Just credit HNT + credit_account(Address, Amount, hnt, Ledger); + {blockchain_ledger_entry_v1, V1CF} -> + case ?MODULE:find_entry(Address, Ledger) of + {error, address_entry_not_found} -> + Entry = blockchain_ledger_entry_v1:new(0, Amount), + Bin = blockchain_ledger_entry_v1:serialize(Entry), + cache_put(Ledger, V1CF, Address, Bin); + {ok, Entry} -> + Entry1 = blockchain_ledger_entry_v1:new( + blockchain_ledger_entry_v1:nonce(Entry), + blockchain_ledger_entry_v1:balance(Entry) + Amount + ), + Bin = blockchain_ledger_entry_v1:serialize(Entry1), + cache_put(Ledger, V1CF, Address, Bin); + {error, _}=Error -> + Error + end end. -spec credit_account(Address :: libp2p_crypto:pubkey_bin(), @@ -3158,69 +3153,74 @@ credit_account(Address, Amount, TT, Ledger) -> Nonce :: integer(), Ledger :: ledger()) -> ok | {error, any()}. debit_account(Address, AmountOrAmounts, Nonce, Ledger) when is_integer(AmountOrAmounts) -> + %% debit_account being called with an Amount can occur in two situations: + %% - token_version is not set + %% - token_version is set but for aux ledger + %% - we're in some weird transitionary state + %% - payment_v1 is active on chain (and those only work with HNT token) + %% It's best to make sure that the right ledger entry mod is being invoked + + {EntryMod, EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), case ?MODULE:find_entry(Address, Ledger) of {error, _}=Error -> Error; {ok, Entry} -> - case Nonce =:= blockchain_ledger_entry_v1:nonce(Entry) + 1 of + case Nonce =:= EntryMod:nonce(Entry) + 1 of true -> - Balance = blockchain_ledger_entry_v1:balance(Entry), + Balance = EntryMod:balance(Entry), case (Balance - AmountOrAmounts) >= 0 of true -> - Entry1 = blockchain_ledger_entry_v1:new( - Nonce, - (Balance - AmountOrAmounts) - ), - Bin = blockchain_ledger_entry_v1:serialize(Entry1), - EntriesCF = entries_cf(Ledger), + Entry1 = EntryMod:new( Nonce, (Balance - AmountOrAmounts)), + Bin = EntryMod:serialize(Entry1), cache_put(Ledger, EntriesCF, Address, Bin); false -> {error, {insufficient_balance, {AmountOrAmounts, Balance}}} end; false -> - {error, {bad_nonce, {payment, Nonce, blockchain_ledger_entry_v1:nonce(Entry)}}} + {error, {bad_nonce, {payment, Nonce, EntryMod:nonce(Entry)}}} end end; debit_account(Address, AmountOrAmounts, Nonce, Ledger) when is_map(AmountOrAmounts) -> - %% TODO: Maybe also check that token_version = 2 is set here? Although amounts being - %% a map only ever should occur with the multi token payment txn, so maybe it's okay? + %% For consistency sake we'll get the entry mod here as well (it should ideally always be v2) + {EntryMod, EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), case ?MODULE:find_entry(Address, Ledger) of {error, _}=Error -> Error; {ok, Entry} -> - case Nonce =:= blockchain_ledger_entry_v2:nonce(Entry) + 1 of + case Nonce =:= EntryMod:nonce(Entry) + 1 of true -> case lists:all( fun(TT) -> - blockchain_ledger_entry_v2:balance(Entry, TT) >= maps:get(TT, AmountOrAmounts, 0) + EntryMod:balance(Entry, TT) >= maps:get(TT, AmountOrAmounts, 0) end, blockchain_token_v1:supported_tokens()) of true -> Entry0 = maps:fold( fun(TT, Amt, Acc) -> - blockchain_ledger_entry_v2:debit(Acc, Amt, TT) + EntryMod:debit(Acc, Amt, TT) end, Entry, AmountOrAmounts), - Entry1 = blockchain_ledger_entry_v2:nonce(Entry0, Nonce), - Bin = blockchain_ledger_entry_v2:serialize(Entry1), - EntriesCF = entries_v2_cf(Ledger), + Entry1 = EntryMod:nonce(Entry0, Nonce), + Bin = EntryMod:serialize(Entry1), cache_put(Ledger, EntriesCF, Address, Bin); false -> {error, {insufficient_balance, {libp2p_crypto:bin_to_b58(Address), AmountOrAmounts}}} end; false -> - {error, {bad_nonce, {payment_v2, Nonce, blockchain_ledger_entry_v2:nonce(Entry)}}} + {error, {bad_nonce, {payment_v2, Nonce, EntryMod:nonce(Entry)}}} end end. -spec debit_fee_from_account(libp2p_crypto:pubkey_bin(), integer(), ledger(), blockchain_txn:hash(), blockchain:blockchain()) -> ok | {error, any()}. debit_fee_from_account(Address, Fee, Ledger, TxnHash, Chain) -> + {EntryMod, EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), + case ?MODULE:find_entry(Address, Ledger) of {error, _}=Error -> Error; {ok, Entry} -> - Balance = blockchain_ledger_entry_v1:balance(Entry), + Balance = EntryMod:balance(Entry), case (Balance - Fee) >= 0 of true -> case application:get_env(blockchain, store_implicit_burns, false) of @@ -3233,12 +3233,8 @@ debit_fee_from_account(Address, Fee, Ledger, TxnHash, Chain) -> false -> ok end, - Entry1 = blockchain_ledger_entry_v1:new( - blockchain_ledger_entry_v1:nonce(Entry), - (Balance - Fee) - ), - EntryBin = blockchain_ledger_entry_v1:serialize(Entry1), - EntriesCF = entries_cf(Ledger), + Entry1 = EntryMod:new(EntryMod:nonce(Entry), (Balance - Fee)), + EntryBin = EntryMod:serialize(Entry1), cache_put(Ledger, EntriesCF, Address, EntryBin); false -> {error, {insufficient_balance_for_fee, {Fee, Balance}}} @@ -3247,11 +3243,12 @@ debit_fee_from_account(Address, Fee, Ledger, TxnHash, Chain) -> -spec check_balance(Address :: libp2p_crypto:pubkey_bin(), Amount :: non_neg_integer(), Ledger :: ledger()) -> ok | {error, any()}. check_balance(Address, Amount, Ledger) -> + {EntryMod, _EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), case ?MODULE:find_entry(Address, Ledger) of {error, _}=Error -> Error; {ok, Entry} -> - Balance = blockchain_ledger_entry_v1:balance(Entry), + Balance = EntryMod:balance(Entry), case (Balance - Amount) >= 0 of false -> {error, {insufficient_balance, {Amount, Balance}}}; @@ -5315,12 +5312,13 @@ get_cooldown_stake(Val, Ledger) -> -spec query_circulating_hnt(Ledger :: ledger()) -> non_neg_integer(). query_circulating_hnt(Ledger) -> + {EntryMod, _EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), cache_fold( Ledger, entries_cf(Ledger), fun({_Addr, BinEnt}, Acc) -> - Ent = blockchain_ledger_entry_v1:deserialize(BinEnt), - Acc + blockchain_ledger_entry_v1:balance(Ent) + Ent = EntryMod:deserialize(BinEnt), + Acc + EntryMod:balance(Ent) end, 0 ). @@ -5786,10 +5784,10 @@ snapshot_accounts(Ledger) -> lists:sort(maps:to_list(entries(Ledger))). load_accounts(Accounts, Ledger) -> - EntriesCF = entries_cf(Ledger), + {EntryMod, EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), maps:map( fun(Address, Entry) -> - BEntry = blockchain_ledger_entry_v1:serialize(Entry), + BEntry = EntryMod:serialize(Entry), cache_put(Ledger, EntriesCF, Address, BEntry) end, maps:from_list(Accounts)), diff --git a/src/ledger/v2/blockchain_ledger_entry_v2.erl b/src/ledger/v2/blockchain_ledger_entry_v2.erl index 4c71a2c6f7..de5bcc18f8 100644 --- a/src/ledger/v2/blockchain_ledger_entry_v2.erl +++ b/src/ledger/v2/blockchain_ledger_entry_v2.erl @@ -6,7 +6,7 @@ -module(blockchain_ledger_entry_v2). -export([ - new/0, + new/0, new/2, nonce/1, nonce/2, balance/1, balance/2, credit/3, @@ -41,6 +41,11 @@ new() -> iot_balance = 0 }. +%% NOTE: This function is for 1:1 correspondence entry_v1 +-spec new(Nonce :: non_neg_integer(), HNTBalance :: non_neg_integer()) -> entry(). +new(Nonce, HNTBalance) when Nonce /= undefined andalso HNTBalance /= undefined -> + #blockchain_ledger_entry_v2_pb{nonce = Nonce, hnt_balance = HNTBalance}. + -spec nonce(Entry :: entry()) -> non_neg_integer(). nonce(#blockchain_ledger_entry_v2_pb{nonce = Nonce}) -> Nonce. @@ -77,13 +82,13 @@ balance(Entry, iot) -> TT :: blockchain_token_v1:type() ) -> entry(). credit(Entry, Amount, hnt) -> - credit_hnt(Entry, Amount); + Entry#blockchain_ledger_entry_v2_pb{hnt_balance = hnt_balance(Entry) + Amount}; credit(Entry, Amount, hst) -> - credit_hst(Entry, Amount); + Entry#blockchain_ledger_entry_v2_pb{hst_balance = hst_balance(Entry) + Amount}; credit(Entry, Amount, mobile) -> - credit_mobile(Entry, Amount); + Entry#blockchain_ledger_entry_v2_pb{mobile_balance = mobile_balance(Entry) + Amount}; credit(Entry, Amount, iot) -> - credit_iot(Entry, Amount). + Entry#blockchain_ledger_entry_v2_pb{iot_balance = iot_balance(Entry) + Amount}. -spec debit( Entry :: entry(), @@ -91,13 +96,13 @@ credit(Entry, Amount, iot) -> TT :: blockchain_token_v1:type() ) -> entry(). debit(Entry, Amount, hnt) -> - debit_hnt(Entry, Amount); + Entry#blockchain_ledger_entry_v2_pb{hnt_balance = hnt_balance(Entry) - Amount}; debit(Entry, Amount, hst) -> - debit_hst(Entry, Amount); + Entry#blockchain_ledger_entry_v2_pb{hst_balance = hst_balance(Entry) - Amount}; debit(Entry, Amount, mobile) -> - debit_mobile(Entry, Amount); + Entry#blockchain_ledger_entry_v2_pb{mobile_balance = mobile_balance(Entry) - Amount}; debit(Entry, Amount, iot) -> - debit_iot(Entry, Amount). + Entry#blockchain_ledger_entry_v2_pb{iot_balance = iot_balance(Entry) - Amount}. -spec serialize(Entry :: entry()) -> binary(). serialize(Entry) -> @@ -134,38 +139,6 @@ from_sec_v1(SecEntryV1) -> %% Internal Functions %% ================================================================== --spec credit_hnt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -credit_hnt(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{hnt_balance = hnt_balance(Entry) + Amount}. - --spec credit_hst(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -credit_hst(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{hst_balance = hst_balance(Entry) + Amount}. - --spec credit_iot(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -credit_iot(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{iot_balance = iot_balance(Entry) + Amount}. - --spec credit_mobile(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -credit_mobile(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{mobile_balance = mobile_balance(Entry) + Amount}. - --spec debit_hnt(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -debit_hnt(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{hnt_balance = hnt_balance(Entry) - Amount}. - --spec debit_hst(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -debit_hst(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{hst_balance = hst_balance(Entry) - Amount}. - --spec debit_mobile(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -debit_mobile(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{mobile_balance = mobile_balance(Entry) - Amount}. - --spec debit_iot(Entry :: entry(), Amount :: non_neg_integer()) -> entry(). -debit_iot(Entry, Amount) -> - Entry#blockchain_ledger_entry_v2_pb{iot_balance = iot_balance(Entry) - Amount}. - -spec hnt_balance(Entry :: entry()) -> non_neg_integer(). hnt_balance(#blockchain_ledger_entry_v2_pb{hnt_balance = Balance}) -> Balance. diff --git a/src/transactions/blockchain_txn.erl b/src/transactions/blockchain_txn.erl index f663f3a87d..1eff2438cb 100644 --- a/src/transactions/blockchain_txn.erl +++ b/src/transactions/blockchain_txn.erl @@ -47,7 +47,11 @@ | blockchain_txn_unstake_validator_v1:txn_unstake_validator() | blockchain_txn_unstake_validator_v1:txn_validator_heartbeat() | blockchain_txn_transfer_hotspot_v2:txn_transfer_hotspot_v2() - | blockchain_txn_poc_receipts_v2:txn_poc_receipts_v2(). + | blockchain_txn_poc_receipts_v2:txn_poc_receipts_v2() + | blockchain_txn_add_subnetwork_v1:txn_add_subnetwork_v1() + | blockchain_txn_update_subnetwork_v1:txn_update_subnetwork_v1() + | blockchain_txn_subnetwork_rewards_v1:txn_subnetwork_rewards_v1() + | blockchain_txn_token_convert_v1:txn_token_convert_v1(). -type before_commit_callback() :: fun((blockchain:blockchain(), blockchain_block:hash()) -> ok | {error, any()}). -type txns() :: [txn()]. @@ -139,7 +143,11 @@ {blockchain_txn_gen_price_oracle_v1, 34}, {blockchain_txn_consensus_group_failure_v1, 35}, {blockchain_txn_transfer_hotspot_v2, 36}, - {blockchain_txn_poc_receipts_v2, 37} + {blockchain_txn_poc_receipts_v2, 37}, + {blockchain_txn_add_subnetwork_v1, 38}, + {blockchain_txn_update_subnetwork_v1, 39}, + {blockchain_txn_subnetwork_rewards_v1, 40}, + {blockchain_txn_token_convert_v1, 41} ]). block_delay() -> @@ -250,8 +258,15 @@ wrap_txn(#blockchain_txn_consensus_group_failure_v1_pb{} = Txn) -> wrap_txn(#blockchain_txn_transfer_hotspot_v2_pb{}=Txn) -> #blockchain_txn_pb{txn={transfer_hotspot_v2, Txn}}; wrap_txn(#blockchain_txn_poc_receipts_v2_pb{}=Txn) -> - #blockchain_txn_pb{txn={poc_receipts_v2, Txn}}. - + #blockchain_txn_pb{txn={poc_receipts_v2, Txn}}; +wrap_txn(#blockchain_txn_add_subnetwork_v1_pb{}=Txn) -> + #blockchain_txn_pb{txn={add_subnetwork, Txn}}; +wrap_txn(#blockchain_txn_update_subnetwork_v1_pb{}=Txn) -> + #blockchain_txn_pb{txn={update_subnetwork, Txn}}; +wrap_txn(#blockchain_txn_subnetwork_rewards_v1_pb{}=Txn) -> + #blockchain_txn_pb{txn={subnetwork_rewards, Txn}}; +wrap_txn(#blockchain_txn_token_convert_v1_pb{}=Txn) -> + #blockchain_txn_pb{txn={token_convert, Txn}}. -spec unwrap_txn(#blockchain_txn_pb{}) -> blockchain_txn:txn(). unwrap_txn(#blockchain_txn_pb{txn={bundle, #blockchain_txn_bundle_v1_pb{transactions=Txns} = Bundle}}) -> @@ -710,7 +725,15 @@ type(#blockchain_txn_validator_heartbeat_v1_pb{}) -> type(#blockchain_txn_transfer_hotspot_v2_pb{}) -> blockchain_txn_transfer_hotspot_v2; type(#blockchain_txn_poc_receipts_v2_pb{}) -> - blockchain_txn_poc_receipts_v2. + blockchain_txn_poc_receipts_v2; +type(#blockchain_txn_add_subnetwork_v1_pb{}) -> + blockchain_txn_add_subnetwork_v1; +type(#blockchain_txn_update_subnetwork_v1_pb{}) -> + blockchain_txn_update_subnetwork_v1; +type(#blockchain_txn_subnetwork_rewards_v1_pb{}) -> + blockchain_txn_subnetwork_rewards_v1; +type(#blockchain_txn_token_convert_v1_pb{}) -> + blockchain_txn_token_convert_v1. -spec validate_fields([{{atom(), iodata() | undefined}, {binary, pos_integer()} | diff --git a/src/transactions/v1/blockchain_txn_subnetwork_rewards_v1.erl b/src/transactions/v1/blockchain_txn_subnetwork_rewards_v1.erl new file mode 100644 index 0000000000..bd9dd9bb2e --- /dev/null +++ b/src/transactions/v1/blockchain_txn_subnetwork_rewards_v1.erl @@ -0,0 +1,2193 @@ +%%------------------------------------------------------------------- +%% @doc +%% This module implements rewards v2 which track only an account and +%% an amount. The breakdowns of rewards by type and associated gateway +%% are not kept here. This was done to streamline the increasing +%% size of rewards as the network grows. +%% +%% In the future, we will need to work on further ways to streamline +%% the size of this transaction. One proposal was to use the ledger +%% as an encoding dictionary and use ledger offsets to mark the +%% account instead of using the full sized account id. +%% @end +%%%------------------------------------------------------------------- +-module(blockchain_txn_subnetwork_rewards_v1). + +-behavior(blockchain_txn). + +-behavior(blockchain_json). +-include("blockchain_json.hrl"). + +-include("blockchain_vars.hrl"). +-include_lib("helium_proto/include/blockchain_txn_subnetwork_rewards_v1_pb.hrl"). + +-export([ + new/3, + hash/1, + start_epoch/1, + end_epoch/1, + rewards/1, + + %% reward v2 accessors + reward_account/1, + reward_amount/1, + + sign/2, + fee/1, + fee_payer/2, + is_valid/2, + absorb/2, + print/1, + json_type/0, + to_json/2 +]). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-type txn_subnetwork_rewards_v1() :: #blockchain_txn_subnetwork_rewards_v1_pb{}. +-type subnetwork_reward_v1() :: #blockchain_txn_subnetwork_reward_v1_pb{}. +-type rewards() :: [subnetwork_reward_v1()]. +-type reward_vars() :: map(). + +-export_type([txn_subnetwork_rewards_v1/0]). + +%% ------------------------------------------------------------------ +%% Public API +%% ------------------------------------------------------------------ + +-spec new(non_neg_integer(), non_neg_integer(), rewards()) -> txn_subnetwork_rewards_v1(). +new(Start, End, Rewards) -> + SortedRewards = lists:sort(Rewards), + #blockchain_txn_rewards_v2_pb{start_epoch=Start, end_epoch=End, rewards=SortedRewards}. + +-spec hash(txn_subnetwork_rewards_v1()|reward_v2()) -> blockchain_txn:hash(). +hash(Txn) -> + EncodedTxn = blockchain_txn_rewards_v2_pb:encode_msg(Txn), + crypto:hash(sha256, EncodedTxn). + +-spec start_epoch(txn_subnetwork_rewards_v1()) -> non_neg_integer(). +start_epoch(#blockchain_txn_rewards_v2_pb{start_epoch=Start}) -> + Start. + +-spec end_epoch(txn_subnetwork_rewards_v1()) -> non_neg_integer(). +end_epoch(#blockchain_txn_rewards_v2_pb{end_epoch=End}) -> + End. + +-spec rewards(txn_subnetwork_rewards_v1()) -> rewards(). +rewards(#blockchain_txn_rewards_v2_pb{rewards=Rewards}) -> + Rewards. + +-spec reward_account(reward_v2()) -> binary(). +reward_account(#blockchain_txn_reward_v2_pb{account = Account}) -> + Account. + +-spec reward_amount(reward_v2()) -> non_neg_integer(). +reward_amount(#blockchain_txn_reward_v2_pb{amount = Amount}) -> + Amount. + +-spec sign(txn_subnetwork_rewards_v1(), libp2p_crypto:sig_fun()) -> txn_subnetwork_rewards_v1(). +sign(Txn, SigFun) -> + Txn. + +-spec fee(txn_subnetwork_rewards_v1()) -> 0. +fee(_Txn) -> + 0. + +-spec fee_payer(txn_subnetwork_rewards_v1(), blockchain_ledger_v1:ledger()) -> libp2p_crypto:pubkey_bin() | undefined. +fee_payer(_Txn, _Ledger) -> + undefined. + +-spec is_valid(txn_subnetwork_rewards_v1(), blockchain:blockchain()) -> ok | {error, atom()} | {error, {atom(), any()}}. +is_valid(Txn, Chain) -> + Start = ?MODULE:start_epoch(Txn), + End = ?MODULE:end_epoch(Txn), + TxnRewards = ?MODULE:rewards(Txn), + %% TODO: REMOVE THIS ENTIRE CASE STATEMENT AT NEXT RESTART + case TxnRewards of + [] -> + ok; + _ -> + case ?MODULE:calculate_rewards(Start, End, Chain) of + {error, _Reason}=Error -> + Error; + {ok, CalRewards} -> + CalRewardsHashes = [hash(R)|| R <- CalRewards], + TxnRewardsHashes = [hash(R)|| R <- TxnRewards], + case CalRewardsHashes == TxnRewardsHashes of + false -> {error, invalid_rewards_v2}; + true -> ok + end + end + end. + +-spec absorb(txn_subnetwork_rewards_v1(), blockchain:blockchain()) -> ok | {error, atom()} | {error, {atom(), any()}}. +absorb(Txn, Chain) -> + Ledger = blockchain:ledger(Chain), + + case blockchain:config(?net_emissions_enabled, Ledger) of + {ok, true} -> + %% initial proposed max 34.24 + {ok, Max} = blockchain:config(?net_emissions_max_rate, Ledger), + {ok, Burned} = blockchain_ledger_v1:hnt_burned(Ledger), + {ok, Overage} = blockchain_ledger_v1:net_overage(Ledger), + + %% clear this since we have it already + ok = blockchain_ledger_v1:clear_hnt_burned(Ledger), + + case Burned > Max of + %% if burned > max, then add (burned - max) to overage + true -> + Overage1 = Overage + (Burned - Max), + ok = blockchain_ledger_v1:net_overage(Overage1, Ledger); + %% else we may have pulled from overage to the tune of + %% max - burned + _ -> + %% here we pulled from overage up to max + case (Max - Burned) < Overage of + %% emitted max, pulled from overage + true -> + Overage1 = Overage - (Max - Burned), + ok = blockchain_ledger_v1:net_overage(Overage1, Ledger); + %% not enough overage to emit up to max, 0 overage + _ -> + ok = blockchain_ledger_v1:net_overage(0, Ledger) + end + end; + _ -> + ok + end, + + case blockchain_ledger_v1:mode(Ledger) == aux of + false -> + %% only absorb in the main ledger + absorb_(Txn, Ledger); + true -> + aux_absorb(Txn, Ledger, Chain) + end. + +-spec absorb_(Txn :: txn_subnetwork_rewards_v1(), Ledger :: blockchain_ledger_v1:ledger()) -> ok. +absorb_(Txn, Ledger) -> + Rewards = ?MODULE:rewards(Txn), + absorb_rewards(Rewards, Ledger). + +-spec absorb_rewards(Rewards :: rewards(), + Ledger :: blockchain_ledger_v1:ledger()) -> ok. +absorb_rewards(Rewards, Ledger) -> + lists:foreach( + fun(#blockchain_txn_reward_v2_pb{account=Account, amount=Amount}) -> + ok = blockchain_ledger_v1:credit_account(Account, Amount, Ledger) + end, + Rewards + ). + +-spec aux_absorb(Txn :: txn_subnetwork_rewards_v1(), + AuxLedger :: blockchain_ledger_v1:ledger(), + Chain :: blockchain:blockchain()) -> ok | {error, any()}. +aux_absorb(Txn, AuxLedger, Chain) -> + Start = ?MODULE:start_epoch(Txn), + End = ?MODULE:end_epoch(Txn), + %% NOTE: This is an aux ledger, we don't use rewards(txn) here, instead we calculate them manually + %% and do 0 verification for absorption + case calculate_rewards_(Start, End, AuxLedger, Chain, true) of + {error, _}=E -> E; + {ok, AuxRewards, AuxMD} -> + TxnRewards = rewards(Txn), + %% absorb the rewards attached to the txn (real) + absorb_rewards(TxnRewards, AuxLedger), + %% set auxiliary rewards in the aux ledger also + lager:info("are aux rewards equal?: ~p", [lists:sort(TxnRewards) == lists:sort(AuxRewards)]), + %% rewards appear in (End + 1) block + blockchain_aux_ledger_v1:set_rewards(End + 1, TxnRewards, AuxRewards, AuxLedger), + case calculate_rewards_(Start, End, blockchain_ledger_v1:mode(active, AuxLedger), Chain, true) of + {error, _}=E -> E; + {ok, _, OrigMD} -> + blockchain_aux_ledger_v1:set_rewards_md(End + 1, OrigMD, AuxMD, AuxLedger) + end + end. + + +-spec calculate_rewards(non_neg_integer(), non_neg_integer(), blockchain:blockchain()) -> + {ok, rewards()} | {error, any()}. +%% @doc Calculate and return an ordered list (as ordered by lists:sort/1) of +%% rewards for use in a rewards_v2 transaction. Given how lists:sort/1 works, +%% ordering will depend on (binary) account information. +calculate_rewards(Start, End, Chain) -> + {ok, Ledger} = blockchain:ledger_at(End, Chain), + calculate_rewards_(Start, End, Ledger, Chain, false). + +-spec calculate_rewards_( + Start :: non_neg_integer(), + End :: non_neg_integer(), + Ledger :: blockchain_ledger_v1:ledger(), + Chain :: blockchain:blockchain(), + ReturnMD :: boolean() + ) -> {error, any()} | {ok, rewards()} | {ok, rewards(), rewards_metadata()}. +calculate_rewards_(Start, End, Ledger, Chain, ReturnMD) -> + {ok, Results} = calculate_rewards_metadata(Start, End, blockchain:ledger(Ledger, Chain)), + try + case ReturnMD of + false -> + {ok, prepare_rewards_v2_txns(Results, Ledger)}; + true -> + {ok, prepare_rewards_v2_txns(Results, Ledger), Results} + end + catch + C:Error:Stack -> + lager:error("Caught ~p; couldn't prepare rewards txn because: ~p~n~p", [C, Error, Stack]), + Error + end. + +-spec calculate_rewards_metadata( + Start :: non_neg_integer(), + End :: non_neg_integer(), + Chain :: blockchain:blockchain() ) -> + {ok, Metadata :: rewards_metadata()} | {error, Error :: term()}. +%% @doc Calculate only rewards metadata (do not return v2 reward records +%% to the caller.) +%% +%% Keys that exist in the rewards metadata map include: +%%
    +%%
  • poc_challenger
  • +%%
  • poc_witness
  • +%%
  • poc_challengee
  • +%%
  • dc_rewards
  • +%%
  • consensus_rewards
  • +%%
  • securities_rewards
  • +%%
+%% +%% Each of the keys is itself a map which has the shape of `#{ Entry => Amount }' +%% where Entry is defined as a tuple of `{gateway, reward_type, Gateway}' or +%% `{owner, reward_type, Owner}' +%% +%% There is an additional key `overages' which may or may not have an integer +%% value. It represents the amount of excess fees paid in the given epoch which +%% were used as bonus HNT rewards for the consensus members. +%% @end +calculate_rewards_metadata(Start, End, Chain) -> + {ok, Ledger} = blockchain:ledger_at(End, Chain), + Vars0 = get_reward_vars(Start, End, Ledger), + VarMap = case blockchain_hex:var_map(Ledger) of + {error, _Reason} -> #{}; + {ok, VM} -> VM + end, + + RegionVars = blockchain_region_v1:get_all_region_bins(Ledger), + + Vars = Vars0#{ var_map => VarMap, region_vars => RegionVars}, + + %% Previously, if a state_channel closed in the grace blocks before an + %% epoch ended, then it wouldn't ever get rewarded. + {ok, PreviousGraceBlockDCRewards} = collect_dc_rewards_from_previous_epoch_grace(Start, End, + Chain, Vars, + Ledger), + + %% Initialize our reward accumulator. We are going to build up a map which + %% will be in the shape of + %% #{ reward_type => #{ Entry => Amount } } + %% + %% where Entry is of the the shape + %% {owner, reward_type, Owner} or + %% {gateway, reward_type, Gateway} + AccInit = #{ dc_rewards => PreviousGraceBlockDCRewards, + poc_challenger => #{}, + poc_challengee => #{}, + poc_witness => #{} }, + + try + %% We only want to fold over the blocks and transaction in an epoch once, + %% so we will do that top level work here. If we get a thrown error while + %% we are folding, we will abort reward calculation. + Me = self(), + PerfTab = + case application:get_env(blockchain, print_rewards_perf, false) of + true -> + PT = ets:new(rwd_perf, []), + erlang:put({Me, '$rwd_perf'}, PT), + PT; + _ -> ok + end, + Results0 = fold_blocks_for_rewards(Start, End, Chain, + Vars, Ledger, AccInit), + + %% Prior to HIP 28 (reward_version <6), force EpochReward amount for the CG to always + %% be around ElectionInterval (30 blocks) so that there is less incentive + %% to stay in the consensus group. With HIP 28, relax that to be up to election_interval + + %% election_retry_interval to allow for time for election to complete. + ConsensusEpochReward = + case maps:get(reward_version, Vars) of + RewardVersion when RewardVersion >= 6 -> + calculate_consensus_epoch_reward(Start, End, Vars, Ledger); + _ -> + calculate_epoch_reward(1, Start, End, Ledger) + end, + + Vars1 = Vars#{ consensus_epoch_reward => ConsensusEpochReward }, + + Results = finalize_reward_calculations(Results0, Ledger, Vars1), + %% we are only keeping hex density calculations memoized for a single + %% rewards transaction calculation, then we discard that work and avoid + %% cache invalidation issues. + case application:get_env(blockchain, destroy_memo, true) of + true -> + true = blockchain_hex:destroy_memoization(); + _ -> ok + end, + perf_report(PerfTab), + catch ets:delete(PerfTab), + erlang:erase({Me, '$rwd_perf'}), + {ok, Results} + catch + C:Error:Stack -> + lager:error("Caught ~p; couldn't calculate rewards metadata because: ~p~n~p", [C, Error, Stack]), + Error + end. + +perf(Tag, Time) -> + Me = self(), + case erlang:get({Me, '$rwd_perf'}) of + undefined -> + ok; + Tab -> + catch ets:update_counter(Tab, Tag, Time, {Tag, Time}) + end. + +perf_report(Tab) -> + case application:get_env(blockchain, print_rewards_perf, false) of + true -> + Measurements = lists:reverse(lists:keysort(2, ets:tab2list(Tab))), + lager:info("perf report:"), + lists:foreach( + fun({K, V}) -> + lager:info("txn ~p: ~pms", [K, V]) + end, + Measurements); + false -> + ok + end. + +-spec print(txn_subnetwork_rewards_v1()) -> iodata(). +print(undefined) -> <<"type=rewards_v2 undefined">>; +print(#blockchain_txn_rewards_v2_pb{start_epoch=Start, + end_epoch=End}) -> + io_lib:format("type=rewards_v2 start_epoch=~p end_epoch=~p", + [Start, End]). + +json_type() -> + <<"subnetwork_rewards_v1">>. + +-spec to_json(txn_subnetwork_rewards_v1(), blockchain_json:opts()) -> blockchain_json:json_object(). +to_json(Txn, Opts) -> + RewardToJson = + fun + ({gateway, Type, G}, Amount, Ledger, Acc) -> + case blockchain_ledger_v1:find_gateway_owner(G, Ledger) of + {error, _Error} -> + Acc; + {ok, GwOwner} -> + [#{account => ?BIN_TO_B58(GwOwner), + gateway => ?BIN_TO_B58(G), + amount => Amount, + type => Type} | Acc] + end; + ({validator, Type, V}, Amount, Ledger, Acc) -> + case blockchain_ledger_v1:get_validator(V, Ledger) of + {error, _Error} -> + Acc; + {ok, Val} -> + Owner = blockchain_ledger_validator_v1:owner_address(Val), + [#{account => ?BIN_TO_B58(Owner), + gateway => ?BIN_TO_B58(V), + amount => Amount, + type => Type} | Acc] + end; + ({owner, Type, O}, Amount, _Ledger, Acc) -> + [#{account => ?BIN_TO_B58(O), + gateway => undefined, + amount => Amount, + type => Type} | Acc] + end, + Rewards = case lists:keyfind(chain, 1, Opts) of + {chain, Chain} -> + Start = blockchain_txn_rewards_v2:start_epoch(Txn), + End = ?MODULE:end_epoch(Txn), + {ok, Ledger} = blockchain:ledger_at(End, Chain), + {ok, Metadata} = case lists:keyfind(rewards_metadata, 1, Opts) of + {rewards_metadata, M} -> {ok, M}; + _ -> ?MODULE:calculate_rewards_metadata(Start, End, Chain) + end, + maps:fold( + fun(overages, Amount, Acc) -> + [#{amount => Amount, + type => overages} | Acc]; + (_RewardCategory, Rewards, Acc0) -> + maps:fold( + fun(Entry, Amount, Acc) -> + RewardToJson(Entry, Amount, Ledger, Acc) + end, Acc0, Rewards) + end, [], Metadata); + _ -> [ reward_to_json(R, []) || R <- rewards(Txn)] + end, + + #{ + type => ?MODULE:json_type(), + hash => ?BIN_TO_B64(hash(Txn)), + start_epoch => start_epoch(Txn), + end_epoch => end_epoch(Txn), + rewards => Rewards + }. + +%% ------------------------------------------------------------------ +%% Internal Function Definitions +%% ------------------------------------------------------------------ + + +-spec reward_to_json( Reward :: reward_v2(), + Opts :: blockchain_json:opts() ) -> blockchain_json:json_object(). +reward_to_json(#blockchain_txn_reward_v2_pb{account = Account, amount = Amt}, _Opts) -> + #{ + type => <<"reward_v2">>, + account => ?BIN_TO_B58(Account), + amount => Amt + }. + +-spec new_reward( Account :: libp2p_crypto:pubkey_bin(), + Amount :: non_neg_integer() ) -> reward_v2(). +new_reward(Account, Amount) -> + #blockchain_txn_reward_v2_pb{account=Account, amount=Amount}. + +-spec fold_blocks_for_rewards( Current :: pos_integer(), + End :: pos_integer(), + Chain :: blockchain:blockchain(), + Vars :: reward_vars(), + Ledger :: blockchain_ledger_v1:ledger(), + Acc :: rewards_share_metadata() ) -> rewards_share_metadata(). +fold_blocks_for_rewards(Current, End, _Chain, _Vars, _Ledger, Acc) when Current == End + 1 -> Acc; +fold_blocks_for_rewards(910360, End, Chain, Vars, Ledger, Acc) -> + fold_blocks_for_rewards(910361, End, Chain, Vars, Ledger, Acc); +fold_blocks_for_rewards(Current, End, Chain, Vars, Ledger, Acc) -> + case blockchain:get_block(Current, Chain) of + {error, _Reason} = Error -> throw(Error); + {ok, Block} -> + Txns = blockchain_block:transactions(Block), + NewAcc = lists:foldl(fun(T, A) -> + Type = blockchain_txn:type(T), + Start = erlang:monotonic_time(microsecond), + A1 = calculate_reward_for_txn(Type, T, End, + A, Chain, Ledger, Vars), + perf(Type, erlang:monotonic_time(microsecond) - Start), + A1 + end, + Acc, Txns), + fold_blocks_for_rewards(Current+1, End, Chain, Vars, Ledger, NewAcc) + end. + +-spec calculate_reward_for_txn( Type :: atom(), + Txn :: blockchain_txn:txn(), + End :: pos_integer(), + Acc :: rewards_share_metadata(), + Chain :: blockchain:blockchain(), + Ledger :: blockchain_ledger_v1:ledger(), + Vars :: reward_vars() ) -> rewards_share_metadata(). +calculate_reward_for_txn(?MODULE, _Txn, _End, _Acc, _Chain, + _Ledger, _Vars) -> throw({error, already_existing_rewards_v2}); +calculate_reward_for_txn(blockchain_txn_rewards_v1, _Txn, _End, _Acc, _Chain, + _Ledger, _Vars) -> throw({error, already_existing_rewards_v1}); +calculate_reward_for_txn(blockchain_txn_poc_receipts_v1 = T, Txn, _End, + #{ poc_challenger := Challenger } = Acc, Chain, Ledger, Vars) -> + Start = erlang:monotonic_time(microsecond), + Acc0 = poc_challenger_reward(Txn, Challenger, Vars), + Start1 = erlang:monotonic_time(microsecond), + perf({T, challenger}, Start1 - Start), + Acc1 = calculate_poc_challengee_rewards(Txn, Acc#{ poc_challenger => Acc0 }, Chain, Ledger, Vars), + Start2 = erlang:monotonic_time(microsecond), + perf({T, challengee}, Start2 - Start1), + Acc2 = calculate_poc_witness_rewards(Txn, Acc1, Chain, Ledger, Vars), + WitnessTime = erlang:monotonic_time(microsecond) - Start2, + perf({T, witnesses}, WitnessTime), + Acc2; +calculate_reward_for_txn(blockchain_txn_poc_receipts_v2, Txn, _End, + #{ poc_challenger := Challenger } = Acc, Chain, Ledger, Vars) -> + Acc0 = poc_challenger_reward(Txn, Challenger, Vars), + Acc1 = calculate_poc_challengee_rewards(Txn, Acc#{ poc_challenger => Acc0 }, Chain, Ledger, Vars), + calculate_poc_witness_rewards(Txn, Acc1, Chain, Ledger, Vars); + +calculate_reward_for_txn(blockchain_txn_state_channel_close_v1, Txn, End, Acc, Chain, Ledger, Vars) -> + calculate_dc_rewards(Txn, End, Acc, Chain, Ledger, Vars); +calculate_reward_for_txn(Type, Txn, _End, Acc, _Chain, Ledger, _Vars) -> + consider_overage(Type, Txn, Acc, Ledger). + +-spec consider_overage( Type :: atom(), + Txn :: blockchain_txn:txn(), + Acc :: rewards_share_metadata(), + Ledger :: blockchain_ledger_v1:ledger() ) -> rewards_share_metadata(). +consider_overage(Type, Txn, Acc, Ledger) -> + %% calculate any fee paid in excess which we will distribute as bonus HNT + %% to consensus members + try + Type:calculate_fee(Txn, Ledger) - Type:fee(Txn) of + Overage when Overage > 0 -> + maps:update_with(overages, fun(Overages) -> Overages + Overage end, Overage, Acc); + _ -> + Acc + catch + _:_ -> + Acc + end. + +-spec calculate_poc_challengee_rewards( Txn :: blockchain_txn:txn(), + Acc :: rewards_share_metadata(), + Chain :: blockchain:blockchain(), + Ledger :: blockchain_ledger_v1:ledger(), + Vars :: reward_vars() ) -> rewards_share_metadata(). +calculate_poc_challengee_rewards(Txn, #{ poc_challengee := ChallengeeMap } = Acc, + Chain, Ledger, #{ var_map := VarMap } = Vars) -> + TxnType = blockchain_txn:type(Txn), + Path = TxnType:path(Txn), + NewCM = poc_challengees_rewards_(Vars, Path, Path, Txn, Chain, Ledger, true, VarMap, ChallengeeMap), + Acc#{ poc_challengee => NewCM }. + +-spec calculate_poc_witness_rewards( Txn :: blockchain_txn:txn(), + Acc :: rewards_share_metadata(), + Chain :: blockchain:blockchain(), + Ledger :: blockchain_ledger_v1:ledger(), + Vars :: reward_vars() ) -> rewards_share_metadata(). +calculate_poc_witness_rewards(Txn, #{ poc_witness := WitnessMap } = Acc, Chain, Ledger, Vars) -> + NewWM = poc_witness_reward(Txn, WitnessMap, Chain, Ledger, Vars), + Acc#{ poc_witness => NewWM }. + +-spec calculate_dc_rewards( Txn :: blockchain_txn:txn(), + End :: pos_integer(), + Acc :: rewards_share_metadata(), + Chain :: blockchain:blockchain(), + Ledger :: blockchain_ledger_v1:ledger(), + Vars :: reward_vars() ) -> rewards_share_metadata(). +calculate_dc_rewards(Txn, End, #{ dc_rewards := DCRewardMap } = Acc, _Chain, Ledger, Vars) -> + NewDCM = dc_reward(Txn, End, DCRewardMap, Ledger, Vars), + Acc#{ dc_rewards => NewDCM }. + +-spec finalize_reward_calculations( AccIn :: rewards_share_metadata(), + Ledger :: blockchain_ledger_v1:ledger(), + Vars :: reward_vars() ) -> rewards_metadata(). +finalize_reward_calculations(#{ dc_rewards := DCShares, + poc_witness := WitnessShares, + poc_challenger := ChallengerShares, + poc_challengee := ChallengeeShares } = AccIn, Ledger, Vars) -> + SecuritiesRewards = securities_rewards(Ledger, Vars), + Overages = maps:get(overages, AccIn, 0), + ConsensusRewards = consensus_members_rewards(Ledger, Vars, Overages), + {DCRemainder, DCRewards} = normalize_dc_rewards(DCShares, Vars), + Vars0 = maps:put(dc_remainder, DCRemainder, Vars), + + %% apply the DC remainder, if any to the other PoC categories pro rata + %% + %% these normalize functions take reward "shares" and convert them + %% into HNT payouts + + #{ poc_witness => normalize_witness_rewards(WitnessShares, Vars0), + poc_challenger => normalize_challenger_rewards(ChallengerShares, Vars0), + poc_challengee => normalize_challengee_rewards(ChallengeeShares, Vars0), + dc_rewards => DCRewards, + consensus_rewards => ConsensusRewards, + securities_rewards => SecuritiesRewards, + overages => Overages }. + +-spec prepare_rewards_v2_txns( Results :: rewards_metadata(), + Ledger :: blockchain_ledger_v1:ledger() ) -> rewards(). +prepare_rewards_v2_txns(Results, Ledger) -> + %% we are going to fold over a list of keys in the rewards map (Results) + %% and generate a new map which has _all_ the owners and the sum of + %% _all_ rewards types in a new map... + AllRewards = lists:foldl( + fun(RewardCategory, Rewards) -> + R = maps:get(RewardCategory, Results), + %% R is our map of rewards of the given type + %% and now we are going to do a maps:fold/3 + %% over this reward category and either + %% add the owner and amount for the first + %% time or add an amount to an existing owner + %% in the Rewards accumulator + + maps:fold(fun(Entry, Amt, Acc) -> + case Entry of + {owner, _Type, O} -> + maps:update_with(O, + fun(Balance) -> Balance + Amt end, + Amt, + Acc); + {gateway, _Type, G} -> + case blockchain_ledger_v1:find_gateway_owner(G, Ledger) of + {error, _Error} -> Acc; + {ok, GwOwner} -> + maps:update_with(GwOwner, + fun(Balance) -> Balance + Amt end, + Amt, + Acc) + end; % gw case + {validator, _Type, V} -> + case blockchain_ledger_v1:get_validator(V, Ledger) of + {error, _} -> Acc; + {ok, Val} -> + Owner = blockchain_ledger_validator_v1:owner_address(Val), + maps:update_with(Owner, + fun(Balance) -> Balance + Amt end, + Amt, + Acc) + end + end % Entry case + end, % function + Rewards, + maps:iterator(R)) %% bound memory size no matter size of map + end, + #{}, + [poc_challenger, poc_challengee, poc_witness, + dc_rewards, consensus_rewards, securities_rewards]), + + %% now we are going to fold over all rewards and construct our + %% transaction for the blockchain + + Rewards = maps:fold(fun(Owner, 0, Acc) -> + lager:debug("Dropping reward for ~p because the amount is 0", + [?BIN_TO_B58(Owner)]), + Acc; + (Owner, Amount, Acc) -> + [ new_reward(Owner, Amount) | Acc ] + end, + [], + maps:iterator(AllRewards)), %% again, bound memory no matter size of map + + %% sort the rewards list before it gets returned so list ordering is deterministic + %% (map keys can be enumerated in any arbitrary order) + lists:sort(Rewards). + +-spec get_reward_vars(pos_integer(), pos_integer(), blockchain_ledger_v1:ledger()) -> reward_vars(). +get_reward_vars(Start, End, Ledger) -> + {ok, MonthlyReward} = blockchain:config(?monthly_reward, Ledger), + {ok, SecuritiesPercent} = blockchain:config(?securities_percent, Ledger), + {ok, PocChallengeesPercent} = blockchain:config(?poc_challengees_percent, Ledger), + {ok, PocChallengersPercent} = blockchain:config(?poc_challengers_percent, Ledger), + {ok, PocWitnessesPercent} = blockchain:config(?poc_witnesses_percent, Ledger), + {ok, ConsensusPercent} = blockchain:config(?consensus_percent, Ledger), + {ok, OraclePrice} = blockchain_ledger_v1:current_oracle_price(Ledger), + DCPercent = case blockchain:config(?dc_percent, Ledger) of + {ok, R1} -> + R1; + _ -> + 0 + end, + SCGrace = case blockchain:config(?sc_grace_blocks, Ledger) of + {ok, R2} -> + R2; + _ -> + 0 + end, + SCVersion = case blockchain:config(?sc_version, Ledger) of + {ok, R3} -> + R3; + _ -> + 1 + end, + SCDisputeStrategyVersion = case blockchain:config(?sc_dispute_strategy_version, Ledger) of + {ok, SCDV} -> SCDV; + _ -> 0 + end, + POCVersion = case blockchain:config(?poc_version, Ledger) of + {ok, V} -> V; + _ -> 1 + end, + RewardVersion = case blockchain:config(?reward_version, Ledger) of + {ok, R4} -> R4; + _ -> 1 + end, + + WitnessRedundancy = case blockchain:config(?witness_redundancy, Ledger) of + {ok, WR} -> WR; + _ -> undefined + end, + + DecayRate = case blockchain:config(?poc_reward_decay_rate, Ledger) of + {ok, R} -> R; + _ -> undefined + end, + + DensityTgtRes = case blockchain:config(?density_tgt_res, Ledger) of + {ok, D} -> D; + _ -> undefined + end, + + HIP15TxRewardUnitCap = case blockchain:config(?hip15_tx_reward_unit_cap, Ledger) of + {ok, Val} -> Val; + _ -> undefined + end, + + {ok, ElectionInterval} = blockchain:config(?election_interval, Ledger), + {ok, ElectionRestartInterval} = blockchain:config(?election_restart_interval, Ledger), + {ok, BlockTime} = blockchain:config(?block_time, Ledger), + + WitnessRewardDecayRate = case blockchain:config(?witness_reward_decay_rate, Ledger) of + {ok, Dec} -> Dec; + _ -> undefined + end, + + WitnessRewardDecayExclusion = + case blockchain:config(?witness_reward_decay_exclusion, Ledger) of + {ok, Exc} -> Exc; + _ -> undefined + end, + + PocChallengerType = + case blockchain:config(?poc_challenger_type, Ledger) of + {ok, validator} -> validator; + _ -> gateway + end, + + EpochReward = calculate_epoch_reward(Start, End, Ledger), + #{ + monthly_reward => MonthlyReward, + epoch_reward => EpochReward, + oracle_price => OraclePrice, + securities_percent => SecuritiesPercent, + poc_challengees_percent => PocChallengeesPercent, + poc_challengers_percent => PocChallengersPercent, + poc_witnesses_percent => PocWitnessesPercent, + consensus_percent => ConsensusPercent, + dc_percent => DCPercent, + poc_challenger_type => PocChallengerType, + sc_grace_blocks => SCGrace, + sc_version => SCVersion, + sc_dispute_strategy_version => SCDisputeStrategyVersion, + poc_version => POCVersion, + reward_version => RewardVersion, + witness_redundancy => WitnessRedundancy, + poc_reward_decay_rate => DecayRate, + density_tgt_res => DensityTgtRes, + hip15_tx_reward_unit_cap => HIP15TxRewardUnitCap, + election_interval => ElectionInterval, + election_restart_interval => ElectionRestartInterval, + block_time => BlockTime, + witness_reward_decay_rate => WitnessRewardDecayRate, + witness_reward_decay_exclusion => WitnessRewardDecayExclusion + }. + +-spec calculate_epoch_reward(pos_integer(), pos_integer(), blockchain_ledger_v1:ledger()) -> float(). +calculate_epoch_reward(Start, End, Ledger) -> + Version = case blockchain:config(?reward_version, Ledger) of + {ok, V} -> V; + _ -> 1 + end, + calculate_epoch_reward(Version, Start, End, Ledger). + +-spec calculate_epoch_reward(pos_integer(), pos_integer(), pos_integer(), blockchain_ledger_v1:ledger()) -> float(). +calculate_epoch_reward(Version, Start, End, Ledger) -> + {ok, ElectionInterval} = blockchain:config(?election_interval, Ledger), + {ok, BlockTime0} = blockchain:config(?block_time, Ledger), + {ok, MonthlyReward} = blockchain:config(?monthly_reward, Ledger), + calculate_epoch_reward(Version, Start, End, BlockTime0, + ElectionInterval, MonthlyReward, Ledger). + +calculate_net_emissions_reward(Ledger) -> + case blockchain:config(?net_emissions_enabled, Ledger) of + {ok, true} -> + %% initial proposed max 34.24 + {ok, Max} = blockchain:config(?net_emissions_max_rate, Ledger), + {ok, Burned} = blockchain_ledger_v1:hnt_burned(Ledger), + {ok, Overage} = blockchain_ledger_v1:net_overage(Ledger), + min(Max, Burned + Overage); + _ -> + 0 + end. + +-spec calculate_epoch_reward(pos_integer(), pos_integer(), pos_integer(), + pos_integer(), pos_integer(), pos_integer(), + blockchain_ledger_v1:ledger()) -> float(). +calculate_epoch_reward(Version, Start, End, BlockTime0, _ElectionInterval, MonthlyReward, Ledger) when Version >= 6 -> + BlockTime1 = (BlockTime0/1000), + % Convert to blocks per min + BlockPerMin = 60/BlockTime1, + % Convert to blocks per hour + BlockPerHour = BlockPerMin*60, + % Calculate election interval in blocks + ElectionInterval = End - Start + 1, % epoch is inclusive of start and end + ElectionPerHour = BlockPerHour/ElectionInterval, + Reward = MonthlyReward/30/24/ElectionPerHour, + Extra = calculate_net_emissions_reward(Ledger), + Reward + Extra; +calculate_epoch_reward(Version, Start, End, BlockTime0, _ElectionInterval, MonthlyReward, Ledger) when Version >= 2 -> + BlockTime1 = (BlockTime0/1000), + % Convert to blocks per min + BlockPerMin = 60/BlockTime1, + % Convert to blocks per hour + BlockPerHour = BlockPerMin*60, + % Calculate election interval in blocks + ElectionInterval = End - Start, + ElectionPerHour = BlockPerHour/ElectionInterval, + Reward = MonthlyReward/30/24/ElectionPerHour, + Extra = calculate_net_emissions_reward(Ledger), + Reward + Extra; +calculate_epoch_reward(_Version, _Start, _End, BlockTime0, ElectionInterval, MonthlyReward, Ledger) -> + BlockTime1 = (BlockTime0/1000), + % Convert to blocks per min + BlockPerMin = 60/BlockTime1, + % Convert to blocks per hour + BlockPerHour = BlockPerMin*60, + % Calculate number of elections per hour + ElectionPerHour = BlockPerHour/ElectionInterval, + Reward = MonthlyReward/30/24/ElectionPerHour, + Extra = calculate_net_emissions_reward(Ledger), + Reward + Extra. + + + +-spec calculate_consensus_epoch_reward(pos_integer(), pos_integer(), + map(), blockchain_ledger_v1:ledger()) -> float(). +calculate_consensus_epoch_reward(Start, End, Vars, Ledger) -> + + #{ block_time := BlockTime0, + election_interval := ElectionInterval, + election_restart_interval := ElectionRestartInterval, + monthly_reward := MonthlyReward } = Vars, + BlockTime1 = (BlockTime0/1000), + % Convert to blocks per min + BlockPerMin = 60/BlockTime1, + % Convert to blocks per month + BlockPerMonth = BlockPerMin*60*24*30, + % Calculate epoch length in blocks, cap at election interval + grace period + EpochLength = erlang:min(End - Start + 1, ElectionInterval + ElectionRestartInterval), + Reward = (MonthlyReward/BlockPerMonth) * EpochLength, + Extra = calculate_net_emissions_reward(Ledger), + Reward + Extra. + +-spec consensus_members_rewards(blockchain_ledger_v1:ledger(), + reward_vars(), + non_neg_integer()) -> rewards_map(). +consensus_members_rewards(Ledger, #{consensus_epoch_reward := EpochReward, + consensus_percent := ConsensusPercent}, OverageTotal) -> + GwOrVal = + case blockchain:config(?election_version, Ledger) of + {ok, N} when N >= 5 -> + validator; + _ -> + gateway + end, + {ok, Members} = blockchain_ledger_v1:consensus_members(Ledger), + Count = erlang:length(Members), + OveragePerMember = OverageTotal div Count, + ConsensusReward = EpochReward * ConsensusPercent, + lists:foldl( + fun(Member, Acc) -> + PercentofReward = 100/Count/100, + Amount = erlang:round(PercentofReward*ConsensusReward), + %% in transitional blocks and in the last reward block of v5 it's possible to still + %% have gateways in who need to be rewarded, so make sure that everyone gets tagged + %% correctly with the proper code path + Actual = + case GwOrVal of + validator -> + case blockchain_ledger_v1:get_validator(Member, Ledger) of + {ok, _} -> validator; + {error, not_found} -> gateway + end; + gateway -> gateway + end, + maps:put({Actual, consensus, Member}, Amount+OveragePerMember, Acc) + end, + #{}, + Members). + +-spec securities_rewards(blockchain_ledger_v1:ledger(), + reward_vars()) -> rewards_map(). +securities_rewards(Ledger, #{epoch_reward := EpochReward, + securities_percent := SecuritiesPercent}) -> + Securities = blockchain_ledger_v1:securities(Ledger), + TotalSecurities = maps:fold( + fun(_, Entry, Acc) -> + Acc + blockchain_ledger_security_entry_v1:balance(Entry) + end, + 0, + Securities + ), + SecuritiesReward = EpochReward * SecuritiesPercent, + maps:fold( + fun(Key, Entry, Acc) -> + Balance = blockchain_ledger_security_entry_v1:balance(Entry), + PercentofReward = (Balance*100/TotalSecurities)/100, + Amount = erlang:round(PercentofReward*SecuritiesReward), + maps:put({owner, securities, Key}, Amount, Acc) + end, + #{}, + Securities + ). + +-spec poc_challenger_reward( Txn :: blockchain_txn:txn(), + Acc :: rewards_share_map(), + Vars :: reward_vars() ) -> rewards_share_map(). +poc_challenger_reward(Txn, ChallengerRewards, #{poc_version := Version}) -> + TxnType = blockchain_txn:type(Txn), + Challenger = TxnType:challenger(Txn), + I = maps:get(Challenger, ChallengerRewards, 0), + case TxnType:check_path_continuation( + TxnType:path(Txn)) of + true when is_integer(Version) andalso Version > 4 -> + maps:put(Challenger, I+2, ChallengerRewards); + _ -> + maps:put(Challenger, I+1, ChallengerRewards) + end. + +-spec normalize_challenger_rewards( ChallengerRewards :: rewards_share_map(), + Vars :: reward_vars() ) -> rewards_map(). +normalize_challenger_rewards(ChallengerRewards, + #{epoch_reward := EpochReward, + poc_challengers_percent := PocChallengersPercent, + poc_challenger_type := PocChallengerType}=Vars) -> + TotalChallenged = lists:sum(maps:values(ChallengerRewards)), + ShareOfDCRemainder = + case PocChallengerType of + validator -> + 0; + _ -> + share_of_dc_rewards(poc_challengers_percent, Vars) + end, + ChallengersReward = (EpochReward * PocChallengersPercent) + ShareOfDCRemainder, + maps:fold( + fun(Challenger, Challenged, Acc) -> + PercentofReward = (Challenged*100/TotalChallenged)/100, + Amount = erlang:round(PercentofReward * ChallengersReward), + maps:put({PocChallengerType, poc_challengers, Challenger}, Amount, Acc) + end, + #{}, + ChallengerRewards + ). + +-spec normalize_challengee_rewards( ChallengeeRewards :: rewards_share_map(), + Vars :: reward_vars() ) -> rewards_map(). +normalize_challengee_rewards(ChallengeeRewards, #{epoch_reward := EpochReward, + poc_challengees_percent := PocChallengeesPercent}=Vars) -> + TotalChallenged = lists:sum(maps:values(ChallengeeRewards)), + ShareOfDCRemainder = share_of_dc_rewards(poc_challengees_percent, Vars), + ChallengeesReward = (EpochReward * PocChallengeesPercent) + ShareOfDCRemainder, + maps:fold( + fun(Challengee, Challenged, Acc) -> + PercentofReward = (Challenged*100/TotalChallenged)/100, + % TODO: Not sure about the all round thing... + Amount = erlang:round(PercentofReward*ChallengeesReward), + maps:put({gateway, poc_challengees, Challengee}, Amount, Acc) + end, + #{}, + ChallengeeRewards + ). + +-spec poc_challengees_rewards_( Vars :: reward_vars(), + Paths :: blockchain_poc_path_element_v1:poc_path(), + StaticPath :: blockchain_poc_path_element_v1:poc_path(), + Txn :: blockchain_poc_receipts_v1:txn_poc_receipts(), + Chain :: blockchain:blockchain(), + Ledger :: blockchain_ledger_v1:ledger(), + IsFirst :: boolean(), + VarMap :: blockchain_hex:var_map(), + Acc0 :: rewards_share_map() ) -> rewards_share_map(). +poc_challengees_rewards_(_Vars, [], _StaticPath, _Txn, _Chain, _Ledger, _, _, Acc) -> + Acc; +poc_challengees_rewards_(#{poc_version := Version}=Vars, + [Elem|Path], + StaticPath, + Txn, + Chain, + Ledger, + IsFirst, + VarMap, + Acc0) when Version >= 2 -> + RegionVars = maps:get(region_vars, Vars), % explode on purpose + TxnType = blockchain_txn:type(Txn), + WitnessRedundancy = maps:get(witness_redundancy, Vars, undefined), + DecayRate = maps:get(poc_reward_decay_rate, Vars, undefined), + DensityTgtRes = maps:get(density_tgt_res, Vars, undefined), + HIP15TxRewardUnitCap = maps:get(hip15_tx_reward_unit_cap, Vars, undefined), + %% check if there were any legitimate witnesses + WitStart = erlang:monotonic_time(microsecond), + Witnesses = legit_witnesses(Txn, Chain, Ledger, Elem, StaticPath, RegionVars, Version), + WitEnd = erlang:monotonic_time(microsecond), + perf({challengee, witness}, WitEnd - WitStart), + Challengee = blockchain_poc_path_element_v1:challengee(Elem), + ChallengeeLoc = case blockchain_ledger_v1:find_gateway_location(Challengee, Ledger) of + {ok, CLoc} -> + CLoc; + _ -> + undefined + end, + I = maps:get(Challengee, Acc0, 0), + case blockchain_poc_path_element_v1:receipt(Elem) of + undefined -> + Acc1 = case + Witnesses /= [] orelse + TxnType:check_path_continuation(Path) + of + true when is_integer(Version), Version > 4, IsFirst == true -> + %% while we don't have a receipt for this node, we do know + %% there were witnesses or the path continued which means + %% the challengee transmitted + case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of + {error, _} -> + %% Old behavior + maps:put(Challengee, I+1, Acc0); + {ok, ToAdd} -> + TxScale = maybe_calc_tx_scale(Challengee, + DensityTgtRes, + ChallengeeLoc, + VarMap, + Ledger), + maps:put(Challengee, I+(ToAdd * TxScale), Acc0) + end; + true when is_integer(Version), Version > 4, IsFirst == false -> + %% while we don't have a receipt for this node, we do know + %% there were witnesses or the path continued which means + %% the challengee transmitted + %% Additionally, we know this layer came in over radio so + %% there's an implicit rx as well + case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of + {error, _} -> + %% Old behavior + maps:put(Challengee, I+2, Acc0); + {ok, ToAdd} -> + TxScale = maybe_calc_tx_scale(Challengee, + DensityTgtRes, + ChallengeeLoc, + VarMap, + Ledger), + maps:put(Challengee, I+(ToAdd * TxScale), Acc0) + end; + _ -> + Acc0 + end, + poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc1); + Receipt -> + case blockchain_poc_receipt_v1:origin(Receipt) of + radio -> + Acc1 = case + Witnesses /= [] orelse + TxnType:check_path_continuation(Path) + of + true when is_integer(Version), Version > 4 -> + %% this challengee both rx'd and tx'd over radio + %% AND sent a receipt + %% so give them 3 payouts + case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of + {error, _} -> + %% Old behavior + maps:put(Challengee, I+3, Acc0); + {ok, ToAdd} -> + TxScale = maybe_calc_tx_scale(Challengee, + DensityTgtRes, + ChallengeeLoc, + VarMap, + Ledger), + maps:put(Challengee, I+(ToAdd * TxScale), Acc0) + end; + false when is_integer(Version), Version > 4 -> + %% this challengee rx'd and sent a receipt + case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of + {error, _} -> + %% Old behavior + maps:put(Challengee, I+2, Acc0); + {ok, ToAdd} -> + TxScale = maybe_calc_tx_scale(Challengee, + DensityTgtRes, + ChallengeeLoc, + VarMap, + Ledger), + maps:put(Challengee, I+(ToAdd * TxScale), Acc0) + end; + _ -> + %% Old behavior + maps:put(Challengee, I+1, Acc0) + end, + poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc1); + p2p -> + %% if there are legitimate witnesses or the path continues + %% the challengee did their job + Acc1 = case + Witnesses /= [] orelse + TxnType:check_path_continuation(Path) + of + false -> + %% path did not continue, this is an 'all gray' path + Acc0; + true when is_integer(Version), Version > 4 -> + %% Sent a receipt and the path continued on + case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of + {error, _} -> + %% Old behavior + maps:put(Challengee, I+2, Acc0); + {ok, ToAdd} -> + TxScale = maybe_calc_tx_scale(Challengee, + DensityTgtRes, + ChallengeeLoc, + VarMap, + Ledger), + maps:put(Challengee, I+(ToAdd * TxScale), Acc0) + end; + true -> + maps:put(Challengee, I+1, Acc0) + end, + poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc1) + end + end; +poc_challengees_rewards_(Vars, [Elem|Path], StaticPath, Txn, Chain, Ledger, _IsFirst, VarMap, Acc0) -> + case blockchain_poc_path_element_v1:receipt(Elem) of + undefined -> + poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc0); + _Receipt -> + Challengee = blockchain_poc_path_element_v1:challengee(Elem), + I = maps:get(Challengee, Acc0, 0), + Acc1 = maps:put(Challengee, I+1, Acc0), + poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc1) + end. + +-spec poc_challengee_reward_unit(WitnessRedundancy :: undefined | pos_integer(), + DecayRate :: undefined | float(), + HIP15TxRewardUnitCap :: undefined | float(), + Witnesses :: blockchain_poc_witness_v1:poc_witnesses()) -> {error, any()} | {ok, float()}. +poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) -> + case {WitnessRedundancy, DecayRate} of + {undefined, _} -> {error, witness_redundancy_undefined}; + {_, undefined} -> {error, poc_reward_decay_rate_undefined}; + {N, R} -> + W = length(Witnesses), + Unit = poc_reward_tx_unit(R, W, N), + NUnit = normalize_reward_unit(HIP15TxRewardUnitCap, Unit), + {ok, NUnit} + end. + +-spec normalize_reward_unit(HIP15TxRewardUnitCap :: undefined | float(), Unit :: float()) -> float(). +normalize_reward_unit(undefined, Unit) when Unit > 1.0 -> 1.0; +normalize_reward_unit(undefined, Unit) -> Unit; +normalize_reward_unit(HIP15TxRewardUnitCap, Unit) when Unit >= HIP15TxRewardUnitCap -> HIP15TxRewardUnitCap; +normalize_reward_unit(_TxRewardUnitCap, Unit) -> Unit. + +-spec normalize_reward_unit(Unit :: float()) -> float(). +normalize_reward_unit(Unit) when Unit > 1.0 -> 1.0; +normalize_reward_unit(Unit) -> Unit. + +-spec poc_witness_reward( Txn :: blockchain_txn_poc_receipts_v1:txn_poc_receipts() | + blockchain_txn_poc_receipts_v2:txn_poc_receipts(), + AccIn :: rewards_share_map(), + Chain :: blockchain:blockchain(), + Ledger :: blockchain_ledger_v1:ledger(), + Vars :: reward_vars() ) -> rewards_share_map(). +poc_witness_reward(Txn, AccIn, + Chain, Ledger, + #{ poc_version := POCVersion, + var_map := VarMap } = Vars) when is_integer(POCVersion) + andalso POCVersion >= 9 -> + TxnType = blockchain_txn:type(Txn), + WitnessRedundancy = maps:get(witness_redundancy, Vars, undefined), + DecayRate = maps:get(poc_reward_decay_rate, Vars, undefined), + DensityTgtRes = maps:get(density_tgt_res, Vars, undefined), + RegionVars = maps:get(region_vars, Vars), % explode on purpose + KeyHash = TxnType:onion_key_hash(Txn), + + try + %% Get channels without validation + {ok, Channels} = TxnType:get_channels(Txn, POCVersion, RegionVars, Chain), + Path = TxnType:path(Txn), + + %% Do the new thing for witness filtering + lists:foldl( + fun(Elem, Acc1) -> + ElemPos = blockchain_utils:index_of(Elem, Path), + WitnessChannel = lists:nth(ElemPos, Channels), + WitStart = erlang:monotonic_time(microsecond), + ElemHash = erlang:phash2(Elem), + ValidWitnesses = + case get({KeyHash, ElemHash}) of + undefined -> + VW = TxnType:valid_witnesses(Elem, WitnessChannel, RegionVars, Ledger), + put({KeyHash, ElemHash}, VW), + VW; + VW -> VW + end, + case ValidWitnesses of + [] -> Acc1; + [_|_] -> + %% lager:info("witness witness ~p", [erlang:phash2(ValidWitnesses)]), + WitEnd = erlang:monotonic_time(microsecond), + perf({witnesses, witness}, WitEnd - WitStart), + %% We found some valid witnesses, we only apply + %% the witness_redundancy and decay_rate if + %% BOTH are set as chain variables, otherwise + %% we default to the old behavior and set + %% ToAdd=1 + %% + %% If both witness_redundancy and decay_rate + %% are set, we calculate a scaled rx unit (the + %% value ToAdd) + %% + %% This is determined using the formulae + %% mentioned in hip15 + ToAdd = case {WitnessRedundancy, DecayRate} of + {undefined, _} -> 1; + {_, undefined} -> 1; + {N, R} -> + W = length(ValidWitnesses), + poc_witness_reward_unit(R, W, N) + end, + + case DensityTgtRes of + undefined -> + %% old (HIP15) + lists:foldl( + fun(WitnessRecord, Acc2) -> + Witness = blockchain_poc_witness_v1:gateway(WitnessRecord), + {C, I} = maps:get(Witness, Acc2, {0, 0}), + maps:put(Witness, {C+1, I+(ToAdd * witness_decay(C, Vars))}, Acc2) + end, + Acc1, + ValidWitnesses); + D -> + %% new (HIP17) + lists:foldl( + fun(WitnessRecord, Acc2) -> + Challengee = blockchain_poc_path_element_v1:challengee(Elem), + %% This must always be {ok, ...} + %% Challengee must have a location + {ok, ChallengeeLoc} = + blockchain_ledger_v1:find_gateway_location(Challengee, Ledger), + Witness = + blockchain_poc_witness_v1:gateway(WitnessRecord), + %% The witnesses get scaled by the value of their transmitters + RxScale = blockchain_utils:normalize_float( + blockchain_hex:scale(ChallengeeLoc, + VarMap, + D, + Ledger)), + Value = blockchain_utils:normalize_float(ToAdd * RxScale), + {C, I} = maps:get(Witness, Acc2, {0, 0}), + maps:put(Witness, {C+1, I+(Value * witness_decay(C, Vars))}, Acc2) + end, + Acc1, + ValidWitnesses) + end + end + end, + AccIn, + Path) + catch + throw:{error, {unknown_region, Region}}:_ST -> + lager:error("Reported unknown_region: ~p", [Region]), + AccIn; + What:Why:ST -> + lager:error("failed to calculate poc_witness_rewards, error ~p:~p:~p", [What, Why, ST]), + AccIn + end; +poc_witness_reward(Txn, AccIn, _Chain, Ledger, + #{ poc_version := POCVersion } = Vars) when is_integer(POCVersion) + andalso POCVersion > 4 -> + TxnType = blockchain_txn:type(Txn), + lists:foldl( + fun(Elem, A) -> + case TxnType:good_quality_witnesses(Elem, Ledger) of + [] -> + A; + GoodQualityWitnesses -> + lists:foldl( + fun(WitnessRecord, Map) -> + Witness = blockchain_poc_witness_v1:gateway(WitnessRecord), + {C, I} = maps:get(Witness, Map, {0, 0}), + maps:put(Witness, {C+1, I+(1 * witness_decay(C, Vars))}, Map) + end, + A, + GoodQualityWitnesses) + end + end, + AccIn, + TxnType:path(Txn) + ); +poc_witness_reward(Txn, AccIn, _Chain, _Ledger, Vars) -> + TxnType = blockchain_txn:type(Txn), + lists:foldl( + fun(Elem, A) -> + lists:foldl( + fun(WitnessRecord, Map) -> + Witness = blockchain_poc_witness_v1:gateway(WitnessRecord), + {C, I} = maps:get(Witness, Map, {0, 0}), + maps:put(Witness, {C+1, I+(1 * witness_decay(C, Vars))}, Map) + end, + A, + blockchain_poc_path_element_v1:witnesses(Elem)) + end, + AccIn, + TxnType:path(Txn)). + +-spec normalize_witness_rewards( WitnessRewards :: rewards_share_map(), + Vars :: reward_vars() ) -> rewards_map(). +normalize_witness_rewards(WitnessRewards, #{epoch_reward := EpochReward, + poc_witnesses_percent := PocWitnessesPercent}=Vars) -> + TotalWitnesses = lists:sum(element(2, lists:unzip(maps:values(WitnessRewards)))), + ShareOfDCRemainder = share_of_dc_rewards(poc_witnesses_percent, Vars), + WitnessesReward = (EpochReward * PocWitnessesPercent) + ShareOfDCRemainder, + maps:fold( + fun(Witness, {_Count, Witnessed}, Acc) -> + PercentofReward = (Witnessed*100/TotalWitnesses)/100, + Amount = erlang:round(PercentofReward*WitnessesReward), + maps:put({gateway, poc_witnesses, Witness}, Amount, Acc) + end, + #{}, + WitnessRewards + ). + +-spec collect_dc_rewards_from_previous_epoch_grace( + Start :: non_neg_integer(), + End :: non_neg_integer(), + Chain :: blockchain:blockchain(), + Vars :: reward_vars(), + Ledger :: blockchain_ledger_v1:ledger()) -> {ok, dc_rewards_share_map()} | {error, any()}. +collect_dc_rewards_from_previous_epoch_grace(Start, End, Chain, + #{sc_grace_blocks := Grace, + reward_version := RV} = Vars, + Ledger) when RV > 4 -> + scan_grace_block(max(1, Start - Grace), Start, End, Vars, Chain, Ledger, #{}); +collect_dc_rewards_from_previous_epoch_grace(_Start, _End, _Chain, _Vars, _Ledger) -> {ok, #{}}. + +-spec scan_grace_block( Current :: pos_integer(), + Start :: pos_integer(), + End :: pos_integer(), + Vars :: reward_vars(), + Chain :: blockchain:blockchain(), + Ledger :: blockchain_ledger_v1:ledger(), + Acc :: dc_rewards_share_map() ) -> {ok, dc_rewards_share_map()} | {error, term()}. +scan_grace_block(Current, Start, _End, _Vars, _Chain, _Ledger, Acc) + when Current == Start + 1 -> {ok, Acc}; +scan_grace_block(Current, Start, End, Vars, Chain, Ledger, Acc) -> + case blockchain:get_block(Current, Chain) of + {error, _Error} = Err -> + lager:error("failed to get grace block ~p ~p", [_Error, Current]), + Err; + {ok, Block} -> + Txns = blockchain_block:transactions(Block), + NewAcc = lists:foldl(fun(T, A) -> + case blockchain_txn:type(T) of + blockchain_txn_state_channel_close_v1 -> + dc_reward(T, End, A, Ledger, Vars); + _ -> A + end + end, + Acc, + Txns), + scan_grace_block(Current+1, Start, End, Vars, Chain, Ledger, NewAcc) + end. + +-spec dc_reward( Txn :: blockchain_txn_state_channel_close_v1:txn_state_channel_close(), + End :: pos_integer(), + AccIn :: dc_rewards_share_map(), + Ledger :: blockchain_ledger_v1:ledger(), + Vars :: reward_vars() ) -> dc_rewards_share_map(). +dc_reward(Txn, End, AccIn, Ledger, #{ sc_grace_blocks := GraceBlocks, + sc_version := 2} = Vars) -> + case blockchain_txn_state_channel_close_v1:state_channel_expire_at(Txn) + GraceBlocks < End of + true -> + SCID = blockchain_txn_state_channel_close_v1:state_channel_id(Txn), + case lists:member(SCID, maps:get(seen, AccIn, [])) of + false -> + %% haven't seen this state channel yet, pull the final result from the ledger + case blockchain_ledger_v1:find_state_channel( + blockchain_txn_state_channel_close_v1:state_channel_id(Txn), + blockchain_txn_state_channel_close_v1:state_channel_owner(Txn), + Ledger) of + {ok, SC} -> + %% check for a holdover v1 channel + case blockchain_ledger_state_channel_v2:is_v2(SC) of + true -> + %% pull out the final version of the state channel + FinalSC = blockchain_ledger_state_channel_v2:state_channel(SC), + RewardVersion = maps:get(reward_version, Vars, 1), + CloseState = blockchain_ledger_state_channel_v2:close_state(SC), + SCDisputeStrategy = maps:get(?sc_dispute_strategy_version, Vars, 0), + {Summaries, Bonus} = + case {SCDisputeStrategy, CloseState} of + {Ver, dispute} when Ver >= 1 -> + %% When sc_dispute_strategy_version is 1 we want to zero out as much as possible. + %% No Bonuses, no summaries. All slashed. + {[], 0}; + {_, _} -> + + InnerSummaries = case RewardVersion > 3 of + %% reward version 4 normalizes payouts + true -> blockchain_state_channel_v1:summaries(blockchain_state_channel_v1:normalize(FinalSC)); + false -> blockchain_state_channel_v1:summaries(FinalSC) + end, + + %% check the dispute status + InnerBonus = case blockchain_ledger_state_channel_v2:close_state(SC) of + %% Reward version 4 or higher just slashes overcommit + dispute when RewardVersion < 4 -> + %% the owner of the state channel + %% did a naughty thing, divide + %% their overcommit between the + %% participants + + OverCommit = blockchain_ledger_state_channel_v2:amount(SC) + - blockchain_ledger_state_channel_v2:original(SC), + OverCommit div length(InnerSummaries); + _ -> + 0 + end, + {InnerSummaries, InnerBonus} + end, + + lists:foldl(fun(Summary, A) -> + Key = blockchain_state_channel_summary_v1:client_pubkeybin(Summary), + DCs = blockchain_state_channel_summary_v1:num_dcs(Summary) + Bonus, + maps:update_with(Key, + fun(V) -> V + DCs end, + DCs, + A) + end, + maps:update_with(seen, + fun(Seen) -> [SCID|Seen] end, + [SCID], + AccIn), + Summaries); + false -> + %% this is a v1 SC; ignore + AccIn + end; + {error, not_found} -> + ExpireAt = blockchain_txn_state_channel_close_v1:state_channel_expire_at(Txn), + lager:warning("missing scid ~p", [SCID]), + lager:warning("expire ~p + grace ~p > end ~p?", [ExpireAt, GraceBlocks, End]), + AccIn + end; + true -> + %% we have already seen this SCID before; ignore + AccIn + end; + false -> + %% SC did not close in _this_ epoch; skip it + AccIn + end. + +-spec normalize_dc_rewards( DCRewards0 :: dc_rewards_share_map(), + Vars :: reward_vars() ) -> {non_neg_integer(), rewards_map()}. +normalize_dc_rewards(DCRewards0, #{epoch_reward := EpochReward, + dc_percent := DCPercent}=Vars) -> + DCRewards = maps:remove(seen, DCRewards0), + OraclePrice = maps:get(oracle_price, Vars, 0), + RewardVersion = maps:get(reward_version, Vars, 1), + TotalDCs = lists:sum(maps:values(DCRewards)), + MaxDCReward = EpochReward * DCPercent, + %% compute the price HNT equivalent of the DCs burned in this epoch + DCReward = case OraclePrice == 0 orelse RewardVersion < 3 of + true -> + %% no oracle price, or rewards =< 2 + MaxDCReward; + false -> + {ok, DCInThisEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(TotalDCs, OraclePrice), + case DCInThisEpochAsHNT >= MaxDCReward of + true -> + %% we spent enough, just allocate it proportionally + MaxDCReward; + false -> + %% we didn't spend enough this epoch, return the remainder to the pool + DCInThisEpochAsHNT + end + end, + + {max(0, round(MaxDCReward - DCReward)), + maps:fold( + fun(Key, NumDCs, Acc) -> + PercentofReward = (NumDCs*100/TotalDCs)/100, + Amount = erlang:round(PercentofReward*DCReward), + maps:put({gateway, data_credits, Key}, Amount, Acc) + end, + #{}, + DCRewards + )}. + +-spec poc_reward_tx_unit(R :: float(), + W :: pos_integer(), + N :: pos_integer()) -> float(). +poc_reward_tx_unit(_R, W, N) when W =< N -> + blockchain_utils:normalize_float(W / N); +poc_reward_tx_unit(R, W, N) -> + NoNorm = 1 + (1 - math:pow(R, (W - N))), + blockchain_utils:normalize_float(NoNorm). + +-spec poc_witness_reward_unit(R :: float(), + W :: pos_integer(), + N :: pos_integer()) -> float(). +poc_witness_reward_unit(_R, W, N) when W =< N -> + 1.0; +poc_witness_reward_unit(R, W, N) -> + %% It's okay to call the previously broken normalize_reward_unit here because + %% the value does not asympotically tend to 2.0, instead it tends to 0.0 + normalize_reward_unit(blockchain_utils:normalize_float((N - (1 - math:pow(R, (W - N))))/W)). + +-spec legit_witnesses( Txn :: blockchain_txn_poc_receipts_v1:txn_poc_receipts() | + blockchain_txn_poc_receipts_v2:txn_poc_receipts(), + Chain :: blockchain:blockchain(), + Ledger :: blockchain_ledger_v1:ledger(), + Elem :: blockchain_poc_path_element_v1:poc_element(), + StaticPath :: blockchain_poc_path_element_v1:poc_path(), + RegionVars :: {ok, [{atom(), binary() | {error, any()}}]} | {error, any()}, + Version :: pos_integer() + ) -> [blockchain_txn_poc_witnesses_v1:poc_witness()]. +legit_witnesses(Txn, Chain, Ledger, Elem, StaticPath, RegionVars, Version) -> + TxnType = blockchain_txn:type(Txn), + case Version of + V when is_integer(V), V >= 9 -> + try + %% Get channels without validation + {ok, Channels} = TxnType:get_channels(Txn, Version, RegionVars, Chain), + ElemPos = blockchain_utils:index_of(Elem, StaticPath), + WitnessChannel = lists:nth(ElemPos, Channels), + KeyHash = TxnType:onion_key_hash(Txn), + ElemHash = erlang:phash2(Elem), + ValidWitnesses = + case get({KeyHash, ElemHash}) of + undefined -> + VW = TxnType:valid_witnesses(Elem, WitnessChannel, RegionVars, Ledger), + put({KeyHash, ElemHash}, VW), + VW; + VW -> VW + end, + ValidWitnesses + catch + throw:{error, {unknown_region, Region}}:_ST -> + lager:error("Reported unknown_region: ~p", [Region]), + []; + What:Why:ST -> + lager:error("failed to calculate poc_challengees_rewards, error ~p:~p:~p", [What, Why, ST]), + [] + end; + V when is_integer(V), V > 4 -> + TxnType:good_quality_witnesses(Elem, Ledger); + _ -> + blockchain_poc_path_element_v1:witnesses(Elem) + end. + +maybe_calc_tx_scale(_Challengee, + DensityTgtRes, + ChallengeeLoc, + VarMap, + Ledger) -> + case {DensityTgtRes, ChallengeeLoc} of + {undefined, _} -> 1.0; + {_, undefined} -> 1.0; + {D, Loc} -> + TxScale = blockchain_hex:scale(Loc, VarMap, D, Ledger), + %% lager:info("Challengee: ~p, TxScale: ~p", + %% [blockchain_utils:addr2name(Challengee), TxScale]), + blockchain_utils:normalize_float(TxScale) + end. + +share_of_dc_rewards(_Key, #{dc_remainder := 0}) -> + 0; +share_of_dc_rewards(Key, Vars=#{dc_remainder := DCRemainder, + poc_challenger_type := validator}) -> + erlang:round(DCRemainder + * ((maps:get(Key, Vars) / + (maps:get(poc_challengees_percent, Vars) + + maps:get(poc_witnesses_percent, Vars)))) + ); +share_of_dc_rewards(Key, Vars=#{dc_remainder := DCRemainder}) -> + erlang:round(DCRemainder + * ((maps:get(Key, Vars) / + (maps:get(poc_challengers_percent, Vars) + + maps:get(poc_challengees_percent, Vars) + + maps:get(poc_witnesses_percent, Vars)))) + ). + +witness_decay(Count, Vars) -> + case maps:find(witness_reward_decay_rate, Vars) of + {ok, undefined} -> + 1; + {ok, DecayRate} -> + Exclusion = case maps:find(witness_reward_decay_exclusion, Vars) of + {ok, undefined} -> 0; + {ok, ExclusionValue} -> ExclusionValue + end, + case Count < Exclusion of + true -> + 1; + false -> + Scale = math:exp((Count - Exclusion) * -1 * DecayRate), + lager:debug("scaling witness reward by ~p", [Scale]), + Scale + end; + _ -> + 1 + end. + +%% ------------------------------------------------------------------ +%% EUNIT Tests +%% ------------------------------------------------------------------ +-ifdef(TEST). + +%% @doc Given a list of reward_v1 txns, return the equivalent reward_v2 +%% list. +-spec v1_to_v2( RewardsV1 :: [blockchain_txn_reward_v1:rewards()] ) -> rewards(). +v1_to_v2(RewardsV1) -> + R = lists:foldl(fun(R, Acc) -> + Owner = blockchain_txn_reward_v1:account(R), + Amt = blockchain_txn_reward_v1:amount(R), + maps:update_with(Owner, fun(Balance) -> Balance + Amt end, Amt, Acc) + end, + #{}, + RewardsV1), + lists:sort(maps:fold(fun(_O, 0, Acc) -> Acc; %% drop any 0 amount reward, as in v2 + (O, A, Acc) -> [ new_reward(O, A) | Acc ] end, + [], + R)). + +new_test() -> + Tx = #blockchain_txn_rewards_v2_pb{start_epoch=1, end_epoch=30, rewards=[]}, + ?assertEqual(Tx, new(1, 30, [])). + +start_epoch_test() -> + Tx = new(1, 30, []), + ?assertEqual(1, start_epoch(Tx)). + +end_epoch_test() -> + Tx = new(1, 30, []), + ?assertEqual(30, end_epoch(Tx)). + +rewards_test() -> + Tx = new(1, 30, []), + ?assertEqual([], rewards(Tx)). + +poc_challengers_rewards_2_test() -> + ReceiptForA = blockchain_poc_receipt_v1:new(<<"a">>, 1, 1, <<"data">>, radio), + ElemForA = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, []), + + Txns = [ + blockchain_txn_poc_receipts_v2:new(<<"a">>, <<"Secret">>, <<"OnionKeyHash">>, [], <<"BlockHash">>), + blockchain_txn_poc_receipts_v2:new(<<"b">>, <<"Secret">>, <<"OnionKeyHash">>, [], <<"BlockHash">>), + blockchain_txn_poc_receipts_v2:new(<<"c">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForA], <<"BlockHash">>) + ], + Vars = #{ + epoch_reward => 1000, + poc_challengers_percent => 0.15, + poc_witnesses_percent => 0.0, + poc_challengees_percent => 0.0, + dc_remainder => 0, + poc_version => 5, + poc_challenger_type => validator + }, + Rewards = #{ + {validator, poc_challengers, <<"a">>} => 38, + {validator, poc_challengers, <<"b">>} => 38, + {validator, poc_challengers, <<"c">>} => 75 + }, + ChallengerShares = lists:foldl(fun(T, Acc) -> poc_challenger_reward(T, Acc, Vars) end, #{}, Txns), + ?assertEqual(Rewards, normalize_challenger_rewards(ChallengerShares, Vars)), + + AltVars = Vars#{ poc_challenger_type => gateway }, + AltRewards = #{ + {gateway, poc_challengers, <<"a">>} => 38, + {gateway, poc_challengers, <<"b">>} => 38, + {gateway, poc_challengers, <<"c">>} => 75 + }, + ?assertEqual(AltRewards, normalize_challenger_rewards(ChallengerShares, AltVars)). + +poc_challengees_rewards_3_test() -> + BaseDir = test_utils:tmp_dir("poc_challengees_rewards_3_test"), + Block = blockchain_block:new_genesis_block([]), + {ok, Chain} = blockchain:new(BaseDir, Block, undefined, undefined), + Ledger = blockchain:ledger(Chain), + Ledger1 = blockchain_ledger_v1:new_context(Ledger), + + Vars = #{ + epoch_reward => 1000, + poc_challengees_percent => 0.35, + poc_witnesses_percent => 0.0, + poc_challengers_percent => 0.0, + dc_remainder => 0, + poc_version => 5, + region_vars => [] + }, + + LedgerVars = maps:put(?poc_version, 5, common_poc_vars()), + + ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), + + One = 631179381270930431, + Two = 631196173757531135, + Three = 631196173214364159, + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"a">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"a">>, One, 1, Ledger1), + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"b">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"b">>, Two, 1, Ledger1), + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"c">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"c">>, Three, 1, Ledger1), + + ok = blockchain_ledger_v1:commit_context(Ledger1), + + ReceiptForA = blockchain_poc_receipt_v1:new(<<"a">>, 1, -120, <<"data">>, radio), + WitnessForA = blockchain_poc_witness_v1:new(<<"c">>, 1, -120, <<>>), + ReceiptForB = blockchain_poc_receipt_v1:new(<<"b">>, 1, -70, <<"data">>, radio), + WitnessForB = blockchain_poc_witness_v1:new(<<"c">>, 1, -120, <<>>), + ReceiptForC = blockchain_poc_receipt_v1:new(<<"c">>, 1, -120, <<"data">>, radio), + + ElemForA = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, []), + ElemForAWithWitness = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, [WitnessForA]), + ElemForB = blockchain_poc_path_element_v1:new(<<"b">>, undefined, []), + ElemForBWithWitness = blockchain_poc_path_element_v1:new(<<"b">>, ReceiptForB, [WitnessForB]), + ElemForC = blockchain_poc_path_element_v1:new(<<"c">>, ReceiptForC, []), + + Txns = [ + %% No rewards here, Only receipt with no witness or subsequent receipt + blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForB, ElemForA], <<"BlockHash">>), %% 1, 2 + %% Reward because of witness + blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForAWithWitness], <<"BlockHash">>), %% 3 + %% Reward because of next elem has receipt + blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForA, ElemForB, ElemForC], <<"BlockHash">>), %% 3, 2, 2 + %% Reward because of witness (adding to make reward 50/50) + blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForBWithWitness], <<"BlockHash">>) %% 3 + ], + Rewards = #{ + %% a gets 8 shares + {gateway, poc_challengees, <<"a">>} => 175, + %% b gets 6 shares + {gateway, poc_challengees, <<"b">>} => 131, + %% c gets 2 shares + {gateway, poc_challengees, <<"c">>} => 44 + }, + ChallengeeShares = lists:foldl(fun(T, Acc) -> + Path = blockchain_txn_poc_receipts_v2:path(T), + poc_challengees_rewards_(Vars, Path, Path, T, Chain, Ledger, true, #{}, Acc) + end, + #{}, + Txns), + ?assertEqual(Rewards, normalize_challengee_rewards(ChallengeeShares, Vars)), + test_utils:cleanup_tmp_dir(BaseDir). + +poc_witnesses_rewards_test() -> + BaseDir = test_utils:tmp_dir("poc_witnesses_rewards_test"), + Block = blockchain_block:new_genesis_block([]), + {ok, Chain} = blockchain:new(BaseDir, Block, undefined, undefined), + Ledger = blockchain:ledger(Chain), + Ledger1 = blockchain_ledger_v1:new_context(Ledger), + EpochVars = #{ + epoch_reward => 1000, + poc_witnesses_percent => 0.05, + poc_challengees_percent => 0.0, + poc_challengers_percent => 0.0, + dc_remainder => 0, + poc_version => 5 + }, + + LedgerVars = maps:merge(common_poc_vars(), EpochVars), + + ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), + + One = 631179381270930431, + Two = 631196173757531135, + Three = 631196173214364159, + Four = 631179381325720575, + Five = 631179377081096191, + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"a">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"a">>, One, 1, Ledger1), + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"b">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"b">>, Two, 1, Ledger1), + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"c">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"c">>, Three, 1, Ledger1), + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"d">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"d">>, Four, 1, Ledger1), + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"e">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"e">>, Five, 1, Ledger1), + + ok = blockchain_ledger_v1:commit_context(Ledger1), + + Witness1 = blockchain_poc_witness_v1:new(<<"a">>, 1, -80, <<>>), + Witness2 = blockchain_poc_witness_v1:new(<<"b">>, 1, -80, <<>>), + Elem = blockchain_poc_path_element_v1:new(<<"c">>, <<"Receipt not undefined">>, [Witness1, Witness2]), + Txns = [ + blockchain_txn_poc_receipts_v2:new(<<"d">>, <<"Secret">>, <<"OnionKeyHash">>, [Elem, Elem], <<"BlockHash">>), + blockchain_txn_poc_receipts_v2:new(<<"e">>, <<"Secret">>, <<"OnionKeyHash">>, [Elem, Elem], <<"BlockHash">>) + ], + + Rewards = #{{gateway,poc_witnesses,<<"a">>} => 25, + {gateway,poc_witnesses,<<"b">>} => 25}, + + WitnessShares = lists:foldl(fun(T, Acc) -> poc_witness_reward(T, Acc, Chain, Ledger, EpochVars) end, + #{}, Txns), + ?assertEqual(Rewards, normalize_witness_rewards(WitnessShares, EpochVars)), + test_utils:cleanup_tmp_dir(BaseDir). + +dc_rewards_sc_dispute_strategy_test() -> + BaseDir = test_utils:tmp_dir("dc_rewards_dispute_sc_test"), + Ledger = blockchain_ledger_v1:new(BaseDir), + Ledger1 = blockchain_ledger_v1:new_context(Ledger), + + Vars = #{ + epoch_reward => 100000, + dc_percent => 0.3, + consensus_percent => 0.06 + 0.025, + poc_challengees_percent => 0.18, + poc_challengers_percent => 0.0095, + poc_witnesses_percent => 0.0855, + securities_percent => 0.34, + sc_version => 2, + sc_grace_blocks => 5, + reward_version => 4, + oracle_price => 100000000, %% 1 dollar + consensus_members => [<<"c">>, <<"d">>], + %% This is the important part of what's being tested here. + sc_dispute_strategy_version => 1 + }, + + LedgerVars = maps:merge(#{?poc_version => 5, ?sc_version => 2, ?sc_grace_blocks => 5}, common_poc_vars()), + ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), + + {SC0, _} = blockchain_state_channel_v1:new(<<"id">>, <<"owner">>, 100, <<"blockhash">>, 10), + SCValid = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 1, 1), blockchain_state_channel_summary_v1:new(<<"b">>, 2, 2)], SC0), + SCDispute = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 2, 2), blockchain_state_channel_summary_v1:new(<<"b">>, 3, 3)], SC0), + + ok = blockchain_ledger_v1:add_state_channel(<<"id">>, <<"owner">>, 10, 1, 100, 200, Ledger1), + {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), + + ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"owner">>, SCValid, <<"id">>, false, Ledger1), + {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), + + ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"a">>, SCDispute, <<"id">>, true, Ledger1), + + SCClose = blockchain_txn_state_channel_close_v1:new(SCValid, <<"owner">>), + {ok, _DCsInEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(3, 100000000), %% 3 DCs burned at HNT price of 1 dollar + + DCShares = dc_reward(SCClose, 100, #{}, Ledger1, Vars), + + %% We only care that no rewards are generated when sc_dispute_strategy_version is active. + {Res, M} = normalize_dc_rewards(DCShares, Vars), + ?assertEqual(#{}, M, "no summaries in rewards map"), + ?assertEqual(30000, Res), + test_utils:cleanup_tmp_dir(BaseDir). + +dc_rewards_v3_test() -> + BaseDir = test_utils:tmp_dir("dc_rewards_v3_test"), + Ledger = blockchain_ledger_v1:new(BaseDir), + Ledger1 = blockchain_ledger_v1:new_context(Ledger), + + Vars = #{ + epoch_reward => 100000, + dc_percent => 0.3, + consensus_percent => 0.06 + 0.025, + poc_challengees_percent => 0.18, + poc_challengers_percent => 0.0095, + poc_witnesses_percent => 0.0855, + securities_percent => 0.34, + sc_version => 2, + sc_grace_blocks => 5, + sc_dispute_strategy_version => 0, + reward_version => 3, + oracle_price => 100000000, %% 1 dollar + consensus_members => [<<"c">>, <<"d">>] + }, + + LedgerVars = maps:merge(#{?poc_version => 5, ?sc_version => 2, ?sc_grace_blocks => 5}, common_poc_vars()), + + ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), + + {SC0, _} = blockchain_state_channel_v1:new(<<"id">>, <<"owner">>, 100, <<"blockhash">>, 10), + SC = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 1, 1), blockchain_state_channel_summary_v1:new(<<"b">>, 2, 2)], SC0), + + ok = blockchain_ledger_v1:add_state_channel(<<"id">>, <<"owner">>, 10, 1, 100, 200, Ledger1), + + {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), + + ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"owner">>, SC, <<"id">>, false, Ledger1), + + {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), + + + SCClose = blockchain_txn_state_channel_close_v1:new(SC, <<"owner">>), + {ok, DCsInEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(3, 100000000), %% 3 DCs burned at HNT price of 1 dollar + %% NOTE: Rewards are split 33-66% + Rewards = #{ + {gateway, data_credits, <<"a">>} => round(DCsInEpochAsHNT * (1/3)), + {gateway, data_credits, <<"b">>} => round(DCsInEpochAsHNT * (2/3)) + }, + DCShares = dc_reward(SCClose, 100, #{}, Ledger1, Vars), + ?assertEqual({26999, Rewards}, normalize_dc_rewards(DCShares, Vars)), + test_utils:cleanup_tmp_dir(BaseDir). + +dc_rewards_v3_spillover_test() -> + BaseDir = test_utils:tmp_dir("dc_rewards_v3_spillover_test"), + Block = blockchain_block:new_genesis_block([]), + {ok, Chain} = blockchain:new(BaseDir, Block, undefined, undefined), + Ledger = blockchain:ledger(Chain), + Ledger1 = blockchain_ledger_v1:new_context(Ledger), + + Vars = #{ + epoch_reward => 100000, + dc_percent => 0.05, + consensus_percent => 0.1, + poc_challengees_percent => 0.20, + poc_challengers_percent => 0.15, + poc_witnesses_percent => 0.15, + securities_percent => 0.35, + poc_challenger_type => validator, + sc_version => 2, + sc_grace_blocks => 5, + reward_version => 3, + poc_version => 5, + dc_remainder => 0, + oracle_price => 100000000, %% 1 dollar + var_map => undefined, + region_vars => [], + consensus_members => [<<"c">>, <<"d">>] + }, + + LedgerVars = maps:merge(#{?poc_version => 5, ?sc_version => 2, ?sc_grace_blocks => 5}, common_poc_vars()), + + ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), + + One = 631179381270930431, + Two = 631196173757531135, + Three = 631196173214364159, + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"a">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"a">>, One, 1, Ledger1), + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"b">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"b">>, Two, 1, Ledger1), + + ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"c">>, Ledger1), + ok = blockchain_ledger_v1:add_gateway_location(<<"c">>, Three, 1, Ledger1), + + ReceiptForA = blockchain_poc_receipt_v1:new(<<"a">>, 1, -120, <<"data">>, radio), + WitnessForA = blockchain_poc_witness_v1:new(<<"c">>, 1, -120, <<>>), + ReceiptForB = blockchain_poc_receipt_v1:new(<<"b">>, 1, -70, <<"data">>, radio), + WitnessForB = blockchain_poc_witness_v1:new(<<"c">>, 1, -120, <<>>), + ReceiptForC = blockchain_poc_receipt_v1:new(<<"c">>, 1, -120, <<"data">>, radio), + + ElemForA = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, []), + ElemForAWithWitness = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, [WitnessForA]), + ElemForB = blockchain_poc_path_element_v1:new(<<"b">>, undefined, []), + ElemForBWithWitness = blockchain_poc_path_element_v1:new(<<"b">>, ReceiptForB, [WitnessForB]), + ElemForC = blockchain_poc_path_element_v1:new(<<"c">>, ReceiptForC, []), + + Txns = [ + %% No rewards here, Only receipt with no witness or subsequent receipt + blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForB, ElemForA], <<"BlockHash">>), %% 1, 2 + %% Reward because of witness + blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForAWithWitness], <<"BlockHash">>), %% 3 + %% Reward because of next elem has receipt + blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForA, ElemForB, ElemForC], <<"BlockHash">>), %% 3, 2, 2 + %% Reward because of witness (adding to make reward 50/50) + blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForBWithWitness], <<"BlockHash">>) %% 3 + ], + + + {SC0, _} = blockchain_state_channel_v1:new(<<"id">>, <<"owner">>, 100, <<"blockhash">>, 10), + SC = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 1, 1), blockchain_state_channel_summary_v1:new(<<"b">>, 2, 2)], SC0), + + ok = blockchain_ledger_v1:add_state_channel(<<"id">>, <<"owner">>, 10, 1, 100, 200, Ledger1), + + {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), + + ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"owner">>, SC, <<"id">>, false, Ledger1), + + {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), + + ok = blockchain_ledger_v1:commit_context(Ledger1), + + Txns2 = [ + blockchain_txn_state_channel_close_v1:new(SC, <<"owner">>) + ], + AllTxns = Txns ++ Txns2, + {ok, DCsInEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(3, 100000000), %% 3 DCs burned at HNT price of 1 dollar + %% NOTE: Rewards are split 33-66% + Rewards = #{ + {gateway, data_credits, <<"a">>} => round(DCsInEpochAsHNT * (1/3)), + {gateway, data_credits, <<"b">>} => round(DCsInEpochAsHNT * (2/3)) + }, + + RewardSharesInit = #{ + dc_rewards => #{}, + poc_challenger => #{}, + poc_challengee => #{}, + poc_witness => #{} + }, + + NoSpillover = lists:foldl(fun(T, Acc) -> calculate_reward_for_txn( + blockchain_txn:type(T), T, 100, + Acc, Chain, Ledger, Vars) + end, + RewardSharesInit, + AllTxns), + DCShares = maps:get(dc_rewards, NoSpillover), + {DCRemainder, DCRewards} = normalize_dc_rewards(DCShares, Vars), + DCAward = trunc(maps:get(epoch_reward, Vars) * maps:get(dc_percent, Vars)), + ?assertEqual({DCAward - DCsInEpochAsHNT, Rewards}, {DCRemainder, DCRewards}), + + NewVars = maps:put(dc_remainder, DCRemainder, Vars), + + Spillover = lists:foldl(fun(T, Acc) -> calculate_reward_for_txn( + blockchain_txn:type(T), T, 100, + Acc, Chain, Ledger, NewVars) + end, + RewardSharesInit, + AllTxns), + + ChallengerShares = maps:get(poc_challenger, NoSpillover), + ChallengeeShares = maps:get(poc_challengee, NoSpillover), + WitnessShares = maps:get(poc_witness, NoSpillover), + + ChallengerRewards = normalize_challenger_rewards(ChallengerShares, Vars), + ChallengeeRewards = normalize_challengee_rewards(ChallengeeShares, Vars), + WitnessRewards = normalize_witness_rewards(WitnessShares, Vars), + + ChallengersAward = trunc(maps:get(epoch_reward, Vars) * maps:get(poc_challengers_percent, Vars)), + ?assertEqual(#{{validator,poc_challengers,<<"X">>} => ChallengersAward}, ChallengerRewards), %% entire 15% allocation + ChallengeesAward = trunc(maps:get(epoch_reward, Vars) * maps:get(poc_challengees_percent, Vars)), + ?assertEqual(#{{gateway,poc_challengees,<<"a">>} => trunc(ChallengeesAward * 4/8), %% 4 of 8 shares of 20% allocation + {gateway,poc_challengees,<<"b">>} => trunc(ChallengeesAward * 3/8), %% 3 shares + {gateway,poc_challengees,<<"c">>} => trunc(ChallengeesAward * 1/8)}, %% 1 share + ChallengeeRewards), + WitnessesAward = trunc(maps:get(epoch_reward, Vars) * maps:get(poc_witnesses_percent, Vars)), + ?assertEqual(#{{gateway,poc_witnesses,<<"c">>} => WitnessesAward}, %% entire 15% allocation + WitnessRewards), + + + %% apply the DC remainder, if any to the other PoC categories pro rata + SpilloverChallengerShares = maps:get(poc_challenger, Spillover), + SpilloverChallengeeShares = maps:get(poc_challengee, Spillover), + SpilloverWitnessShares = maps:get(poc_witness, Spillover), + + SpilloverChallengerRewards = normalize_challenger_rewards(SpilloverChallengerShares, NewVars), + SpilloverChallengeeRewards = normalize_challengee_rewards(SpilloverChallengeeShares, NewVars), + SpilloverWitnessRewards = normalize_witness_rewards(SpilloverWitnessShares, NewVars), + + ChallengerSpilloverAward = 0, + + ?assertEqual(#{{validator,poc_challengers,<<"X">>} => ChallengersAward + ChallengerSpilloverAward}, SpilloverChallengerRewards), %% entire 15% allocation + ChallengeeSpilloverAward = erlang:round(DCRemainder * ((maps:get(poc_challengees_percent, Vars) / (maps:get(poc_challengees_percent, Vars) + + maps:get(poc_witnesses_percent, Vars))))), + ?assertEqual(#{{gateway,poc_challengees,<<"a">>} => trunc((ChallengeesAward + ChallengeeSpilloverAward) * 4/8), %% 4 of 8 shares of 20% allocation + {gateway,poc_challengees,<<"b">>} => trunc((ChallengeesAward + ChallengeeSpilloverAward) * 3/8), %% 3 shares + {gateway,poc_challengees,<<"c">>} => round((ChallengeesAward + ChallengeeSpilloverAward) * 1/8)}, %% 1 share + SpilloverChallengeeRewards), + WitnessesSpilloverAward = erlang:round(DCRemainder * ((maps:get(poc_witnesses_percent, Vars) / (maps:get(poc_challengees_percent, Vars) + + maps:get(poc_witnesses_percent, Vars))))), + ?assertEqual(#{{gateway,poc_witnesses,<<"c">>} => WitnessesAward + WitnessesSpilloverAward}, %% entire 15% allocation + SpilloverWitnessRewards), + test_utils:cleanup_tmp_dir(BaseDir). + +to_json_test() -> + Tx = #blockchain_txn_rewards_v2_pb{start_epoch=1, end_epoch=30, rewards=[]}, + Json = to_json(Tx, []), + ?assert(lists:all(fun(K) -> maps:is_key(K, Json) end, + [type, start_epoch, end_epoch, rewards])). + + +fixed_normalize_reward_unit_test() -> + Rewards = [0.1, 0.9, 1.0, 1.8, 2.5, 2000, 0], + + %% Expectation: reward should get capped at 2.0 + Correct = lists:foldl( + fun(Reward, Acc) -> + %% Set cap=2.0 + maps:put(Reward, normalize_reward_unit(2.0, Reward), Acc) + end, #{}, Rewards), + + Incorrect = lists:foldl( + fun(Reward, Acc) -> + %% Set cap=undefined (old behavior) + maps:put(Reward, normalize_reward_unit(undefined, Reward), Acc) + end, #{}, Rewards), + + ?assertEqual(1.8, maps:get(1.8, Correct)), %% 1.8 -> 1.8 + ?assertEqual(0.1, maps:get(0.1, Correct)), %% 0.1 -> 0.1 + ?assertEqual(0.9, maps:get(0.9, Correct)), %% 0.9 -> 0.9 + ?assertEqual(2.0, maps:get(2.5, Correct)), %% 2.5 -> 2.0 + ?assertEqual(1.0, maps:get(2.5, Incorrect)), %% 2.5 -> 1.0 (incorrect) + ?assertEqual(1.0, maps:get(1.8, Incorrect)), %% 1.8 -> 1.0 (incorrect) + ?assertEqual(0.1, maps:get(0.1, Incorrect)), %% 0.1 -> 0.1 + ?assertEqual(0.9, maps:get(0.9, Incorrect)), %% 0.9 -> 0.9 + + ok. + +common_poc_vars() -> + #{ + ?poc_v4_exclusion_cells => 10, + ?poc_v4_parent_res => 11, + ?poc_v4_prob_bad_rssi => 0.01, + ?poc_v4_prob_count_wt => 0.3, + ?poc_v4_prob_good_rssi => 1.0, + ?poc_v4_prob_no_rssi => 0.5, + ?poc_v4_prob_rssi_wt => 0.3, + ?poc_v4_prob_time_wt => 0.3, + ?poc_v4_randomness_wt => 0.1, + ?poc_v4_target_challenge_age => 300, + ?poc_v4_target_exclusion_cells => 6000, + ?poc_v4_target_prob_edge_wt => 0.2, + ?poc_v4_target_prob_score_wt => 0.8, + ?poc_v4_target_score_curve => 5, + ?poc_v5_target_prob_randomness_wt => 0.0 + }. + + +hip28_calc_test() -> + {timeout, 30000, + fun() -> + meck:new(blockchain_ledger_v1, [passthrough]), + meck:expect(blockchain_ledger_v1, hnt_burned, + fun(_Ledger) -> + 0 + end), + meck:expect(blockchain_ledger_v1, net_overage, + fun(_Ledger) -> + 0 + end), + meck:expect(blockchain_ledger_v1, config, + fun(_, _Ledger) -> + 0 + end), + + % set test vars such that rewards are 1 per block + Vars = #{ block_time => 60000, + election_interval => 30, + election_restart_interval => 5, + monthly_reward => 43200, + reward_version => 6 }, + ?assertEqual(30.0, calculate_consensus_epoch_reward(1, 30, Vars, ledger)), + ?assertEqual(35.0, calculate_consensus_epoch_reward(1, 50, Vars, ledger)), + meck:unload(blockchain_ledger_v1), + ok + end}. + +consensus_epoch_reward_test() -> + {timeout, 30000, + fun() -> + meck:new(blockchain_ledger_v1, [passthrough]), + meck:expect(blockchain_ledger_v1, hnt_burned, + fun(_Ledger) -> + 0 + end), + meck:expect(blockchain_ledger_v1, net_overage, + fun(_Ledger) -> + 0 + end), + meck:expect(blockchain_ledger_v1, config, + fun(_, _Ledger) -> + 0 + end), + + %% using test values such that reward is 1 per block + %% should always return the election interval as the answer + ?assertEqual(30.0,calculate_epoch_reward(1, 1, 25, 60000, 30, 43200, ledger)), + + %% more than 30 blocks should return 30 + ?assertEqual(30.0,calculate_epoch_reward(1, 1, 50, 60000, 30, 43200, ledger)), + meck:unload(blockchain_ledger_v1) + end}. + +-endif. diff --git a/src/transactions/v1/blockchain_txn_vars_v1.erl b/src/transactions/v1/blockchain_txn_vars_v1.erl index 8307086eeb..5cec778073 100644 --- a/src/transactions/v1/blockchain_txn_vars_v1.erl +++ b/src/transactions/v1/blockchain_txn_vars_v1.erl @@ -1531,14 +1531,12 @@ validate_var(?block_size_limit, Value) -> validate_var(?token_version, Value) -> case Value of - undefined -> ok; 2 -> ok; %% Add support for multiple tokens _ -> throw({error, {invalid_token_version, Value}}) end; validate_var(?ledger_entry_version, Value) -> case Value of - undefined -> ok; 2 -> ok; _ -> throw({error, {invalid_ledger_entry_version, Value}}) diff --git a/src/transactions/v2/blockchain_txn_payment_v2.erl b/src/transactions/v2/blockchain_txn_payment_v2.erl index a4a339fcd2..31d736d3fc 100644 --- a/src/transactions/v2/blockchain_txn_payment_v2.erl +++ b/src/transactions/v2/blockchain_txn_payment_v2.erl @@ -182,17 +182,17 @@ absorb_v2_(Txn, Ledger, Chain) -> Payer = ?MODULE:payer(Txn), Nonce = ?MODULE:nonce(Txn), TotalAmounts = ?MODULE:total_amounts(Txn, Ledger), - AreFeesEnabled = blockchain_ledger_v1:txn_fees_active(Ledger), - case blockchain_ledger_v1:debit_fee(Payer, Fee, Ledger, AreFeesEnabled, Hash, Chain) of + ShouldImplicitBurn = blockchain_ledger_v1:txn_fees_active(Ledger), + case blockchain_ledger_v1:debit_fee(Payer, Fee, Ledger, ShouldImplicitBurn, Hash, Chain) of {error, _Reason}=Error -> Error; ok -> - {MaxAmounts, _} = split_payment_amounts(Txn, Ledger), case blockchain_ledger_v1:debit_account(Payer, TotalAmounts, Nonce, Ledger) of {error, _Reason} = Error -> Error; ok -> Payments = ?MODULE:payments(Txn), + {MaxAmounts, _} = split_payment_amounts(Txn, Ledger), MaxPaymentsMap = maps:from_list(MaxAmounts), ok = lists:foreach( fun(Payment) -> @@ -219,8 +219,8 @@ absorb_(Txn, Ledger, Chain) -> Hash = ?MODULE:hash(Txn), Payer = ?MODULE:payer(Txn), Nonce = ?MODULE:nonce(Txn), - AreFeesEnabled = blockchain_ledger_v1:txn_fees_active(Ledger), - case blockchain_ledger_v1:debit_fee(Payer, Fee, Ledger, AreFeesEnabled, Hash, Chain) of + ShouldImplicitBurn = blockchain_ledger_v1:txn_fees_active(Ledger), + case blockchain_ledger_v1:debit_fee(Payer, Fee, Ledger, ShouldImplicitBurn, Hash, Chain) of {error, _Reason} = Error -> Error; ok -> @@ -543,7 +543,7 @@ split_payment_amounts(#blockchain_txn_payment_v2_pb{payer=Payer, fee=Fee}=Txn, L end end, 0, SpecifiedPayments1), MaxPaymentAmt = case TokenType of - hnt -> TypeBalance - TypeSpecifiedAmt - Fee; + hnt -> TypeBalance - TypeSpecifiedAmt - calculate_hnt_fee(Payer, Fee, Ledger); _ -> TypeBalance - TypeSpecifiedAmt end, {TokenType, MaxPaymentAmt} @@ -555,11 +555,28 @@ split_payment_amounts(#blockchain_txn_payment_v2_pb{payer=Payer, fee=Fee}=Txn, L {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), Balance = blockchain_ledger_entry_v1:balance(PayerEntry), SpecifiedAmts = lists:foldl(fun({_, Val}, Acc) -> Val + Acc end, 0, SpecifiedPayments1), - MaxPaymentAmt = Balance - SpecifiedAmts - Fee, + MaxPaymentAmt = Balance - SpecifiedAmts - calculate_hnt_fee(Payer, Fee, Ledger), {[{hnt, MaxPaymentAmt}], SpecifiedPayments1} end end. +-spec calculate_hnt_fee(Payer :: libp2p_crypto:pubkey_bin(), Fee :: non_neg_integer(), Ledger :: blockchain_ledger_v1:ledger()) -> non_neg_integer(). +calculate_hnt_fee(_, 0, _) -> 0; +calculate_hnt_fee(Payer, Fee, Ledger) when Fee > 0 -> + case blockchain_ledger_v1:find_dc_entry(Payer, Ledger) of + {error, dc_entry_not_found} -> + {ok, FeeInHNT} = blockchain_ledger_v1:dc_to_hnt(Fee, Ledger), + FeeInHNT; + {ok, Entry} -> + DCBalance = blockchain_ledger_data_credits_entry_v1:balance(Entry), + case (DCBalance - Fee >= 0) of + true -> 0; + false -> + {ok, FeeInHNT} = blockchain_ledger_v1:dc_to_hnt(Fee, Ledger), + FeeInHNT + end + end. + -spec has_unique_payees(Payments :: blockchain_payment_v2:payments()) -> boolean(). has_unique_payees(Payments) -> Payees = [blockchain_payment_v2:payee(P) || P <- Payments], From 10bb3d67bde993dd0bc22d009c95b97422026909 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Fri, 3 Jun 2022 17:07:02 -0700 Subject: [PATCH 39/48] Add support for subnetwork transactions - Add some validations for add_subnetwork txn - Add blockchain_ledger_subnetwork_v1 scaffolding - Funs to add subnetworks to ledger - Add more validation logic for add_subnetwork - Follow premine semantics - Add absorb for add subnetwork txn - Add blockchain_subnetwork_SUITE - Add a failing add_subnetwork test - Add more checks to add_subnetwork_test - Update subnetwork rewards - Strip rewards metadata, clean dialyzer - Add test for subnetwork rewards - Another extra validation check for adding subnet --- include/blockchain_vars.hrl | 2 + rebar.lock | 2 +- .../v1/blockchain_ledger_subnetwork_v1.erl | 92 + src/ledger/v1/blockchain_ledger_v1.erl | 87 +- src/ledger/v1/blockchain_ledger_v1.hrl | 3 +- src/transactions/blockchain_txn.erl | 12 +- .../v1/blockchain_txn_add_subnetwork_v1.erl | 216 ++ .../blockchain_txn_subnetwork_rewards_v1.erl | 2236 ++--------------- .../v1/blockchain_txn_vars_v1.erl | 8 + test/blockchain_subnetwork_SUITE.erl | 221 ++ test/blockchain_subnetwork_rewards_SUITE.erl | 259 ++ 11 files changed, 1034 insertions(+), 2104 deletions(-) create mode 100644 src/ledger/v1/blockchain_ledger_subnetwork_v1.erl create mode 100644 src/transactions/v1/blockchain_txn_add_subnetwork_v1.erl create mode 100644 test/blockchain_subnetwork_SUITE.erl create mode 100644 test/blockchain_subnetwork_rewards_SUITE.erl diff --git a/include/blockchain_vars.hrl b/include/blockchain_vars.hrl index 5c7c54c22d..66db519436 100644 --- a/include/blockchain_vars.hrl +++ b/include/blockchain_vars.hrl @@ -624,3 +624,5 @@ -define(ledger_entry_version, ledger_entry_version). %% Var to switch off legacy security_exchange txn. Boolean. -define(deprecate_security_exchange_v1, deprecate_security_exchange_v1). +%% How many reward server keys to allow +-define(allowed_num_reward_server_keys, allowed_num_reward_server_keys). diff --git a/rebar.lock b/rebar.lock index f92601b4cd..0c763d3e84 100644 --- a/rebar.lock +++ b/rebar.lock @@ -77,7 +77,7 @@ {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},0}, {<<"helium_proto">>, {git,"https://github.com/helium/proto.git", - {ref,"9f443381496f8579c867647538df06f10e605b73"}}, + {ref,"d1694d5ddb3801f70461921a33407b04d9df66f3"}}, 0}, {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1}, diff --git a/src/ledger/v1/blockchain_ledger_subnetwork_v1.erl b/src/ledger/v1/blockchain_ledger_subnetwork_v1.erl new file mode 100644 index 0000000000..076d17ea9e --- /dev/null +++ b/src/ledger/v1/blockchain_ledger_subnetwork_v1.erl @@ -0,0 +1,92 @@ +%%%------------------------------------------------------------------- +%% @doc +%% == Blockchain Ledger Subnetwork V1 == +%% @end +%%%------------------------------------------------------------------- +-module(blockchain_ledger_subnetwork_v1). + +-export([ + new/5, + type/1, + token_treasury/1, token_treasury/2, + hnt_treasury/1, + reward_server_keys/1, + nonce/1, nonce/2, + last_rewarded_block/1, last_rewarded_block/2, + serialize/1, + deserialize/1 +]). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-include_lib("helium_proto/include/blockchain_ledger_subnetwork_v1_pb.hrl"). + +-type subnetwork_v1() :: #blockchain_ledger_subnetwork_v1_pb{}. + +-export_type([subnetwork_v1/0]). + +%% ================================================================== +%% API Functions +%% ================================================================== + +-spec new( + TT :: blockchain_token_v1:type(), + SNTreasury :: non_neg_integer(), + HNTTreasury :: non_neg_integer(), + SNKey :: libp2p_crypto:pubkey_bin(), + RewardServerKeys :: [libp2p_crypto:pubkey_bin()] +) -> subnetwork_v1(). +new(TT, SNTreasury, HNTTreasury, SNKey, RewardServerKeys) -> + #blockchain_ledger_subnetwork_v1_pb{ + type = TT, + token_treasury = SNTreasury, + hnt_treasury = HNTTreasury, + subnetwork_key = SNKey, + reward_server_keys = lists:sort(RewardServerKeys) + }. + +-spec type(SN :: subnetwork_v1()) -> blockchain_token_v1:type(). +type(#blockchain_ledger_subnetwork_v1_pb{type = Type}) -> + Type. + +-spec token_treasury(SN :: subnetwork_v1()) -> non_neg_integer(). +token_treasury(#blockchain_ledger_subnetwork_v1_pb{token_treasury = SNT}) -> + SNT. + +-spec token_treasury(SN :: subnetwork_v1(), TokenTreasury :: non_neg_integer()) -> subnetwork_v1(). +token_treasury(SN, TokenTreasury) -> + SN#blockchain_ledger_subnetwork_v1_pb{token_treasury = TokenTreasury}. + +-spec hnt_treasury(SN :: subnetwork_v1()) -> non_neg_integer(). +hnt_treasury(#blockchain_ledger_subnetwork_v1_pb{hnt_treasury = SNHT}) -> + SNHT. + +-spec reward_server_keys(SN :: subnetwork_v1()) -> [libp2p_crypto:pubkey_bin()]. +reward_server_keys(#blockchain_ledger_subnetwork_v1_pb{reward_server_keys = Keys}) -> + Keys. + +-spec last_rewarded_block(SN :: subnetwork_v1()) -> non_neg_integer(). +last_rewarded_block(#blockchain_ledger_subnetwork_v1_pb{last_rewarded_block = LRB}) -> + LRB. + +-spec last_rewarded_block(SN :: subnetwork_v1(), LRB :: non_neg_integer()) -> subnetwork_v1(). +last_rewarded_block(SN, LRB) -> + SN#blockchain_ledger_subnetwork_v1_pb{last_rewarded_block = LRB}. + +-spec nonce(SN :: subnetwork_v1()) -> non_neg_integer(). +nonce(#blockchain_ledger_subnetwork_v1_pb{nonce = Nonce}) -> + Nonce. + +-spec nonce(SN :: subnetwork_v1(), Nonce :: non_neg_integer()) -> subnetwork_v1(). +nonce(SN, Nonce) -> + SN#blockchain_ledger_subnetwork_v1_pb{nonce = Nonce}. + +-spec serialize(SN :: subnetwork_v1()) -> binary(). +serialize(SN) -> + blockchain_ledger_subnetwork_v1_pb:encode_msg(SN). + +-spec deserialize(SNBin :: binary()) -> subnetwork_v1(). +deserialize(SNBin) -> + blockchain_ledger_subnetwork_v1_pb:decode_msg(SNBin, blockchain_ledger_subnetwork_v1_pb). diff --git a/src/ledger/v1/blockchain_ledger_v1.erl b/src/ledger/v1/blockchain_ledger_v1.erl index aefd56b17f..12365efecc 100644 --- a/src/ledger/v1/blockchain_ledger_v1.erl +++ b/src/ledger/v1/blockchain_ledger_v1.erl @@ -271,8 +271,12 @@ dc_to_hnt/2, hnt_to_dc/2, - migrate_entries/1 + migrate_entries/1, + add_subnetwork/6, + update_subnetwork/2, + subnetworks_v1/1, + find_subnetwork_v1/2 ]). -include("blockchain.hrl"). @@ -290,6 +294,7 @@ -type entries() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_entry_v1:entry()}. -type entries_v2() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_entry_v2:entry()}. +-type subnetworks_v1() :: #{blockchain_token_v1:type() => blockchain_ledger_subnetwork_v1:subnetwork_v1()}. -type dc_entries() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_data_credits_entry_v1:data_credits_entry()}. -type active_gateways() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_gateway_v2:gateway()}. -type htlcs() :: #{libp2p_crypto:pubkey_bin() => blockchain_ledger_htlc_v1:htlc()}. @@ -351,11 +356,11 @@ new(Dir, ReadOnly, Options) -> [DefaultCF, AGwsCF, EntriesCF, DCEntriesCF, HTLCsCF, PoCsCF, ProposedPoCsCF, SecuritiesCF, RoutingCF, - SubnetsCF, SCsCF, H3DexCF, GwDenormCF, ValidatorsCF, EntriesV2CF, + SubnetsCF, SCsCF, H3DexCF, GwDenormCF, ValidatorsCF, EntriesV2CF, SubnetworksV1CF, DelayedDefaultCF, DelayedAGwsCF, DelayedEntriesCF, DelayedDCEntriesCF, DelayedHTLCsCF, DelayedPoCsCF, DelayedProposedPoCsCF, DelayedSecuritiesCF, DelayedRoutingCF, DelayedSubnetsCF, DelayedSCsCF, - DelayedH3DexCF, DelayedGwDenormCF, DelayedValidatorsCF, DelayedEntriesV2CF] = CFs, + DelayedH3DexCF, DelayedGwDenormCF, DelayedValidatorsCF, DelayedEntriesV2CF, DelayedSubnetworksV1CF] = CFs, Ledger = #ledger_v1{ dir=Dir, @@ -377,7 +382,8 @@ new(Dir, ReadOnly, Options) -> state_channels=SCsCF, h3dex=H3DexCF, validators=ValidatorsCF, - entries_v2=EntriesV2CF + entries_v2=EntriesV2CF, + subnetworks_v1=SubnetworksV1CF }, delayed= #sub_ledger_v1{ default=DelayedDefaultCF, @@ -394,7 +400,8 @@ new(Dir, ReadOnly, Options) -> state_channels=DelayedSCsCF, h3dex=DelayedH3DexCF, validators=DelayedValidatorsCF, - entries_v2=DelayedEntriesV2CF + entries_v2=DelayedEntriesV2CF, + subnetworks_v1=DelayedSubnetworksV1CF } }, Ledger. @@ -912,7 +919,8 @@ atom_to_cf(Atom, Ledger) -> state_channels -> SL#sub_ledger_v1.state_channels; h3dex -> SL#sub_ledger_v1.h3dex; validators -> SL#sub_ledger_v1.validators; - entries_v2 -> SL#sub_ledger_v1.entries_v2 + entries_v2 -> SL#sub_ledger_v1.entries_v2; + subnetworks_v1 -> SL#sub_ledger_v1.subnetworks_v1 end. apply_raw_changes(Changes, #ledger_v1{db = DB} = Ledger) -> @@ -1338,6 +1346,33 @@ write_gw_denorm_values(Address, Old, Gw, Ledger) -> _ -> cache_put(Ledger, GwDenormCF, <
>, term_to_binary(Gain)) end. +-spec subnetworks_v1(ledger()) -> subnetworks_v1(). +subnetworks_v1(Ledger) -> + SubnetworksV1CF = subnetworks_v1_cf(Ledger), + cache_fold( + Ledger, + SubnetworksV1CF, + fun({TT, Binary}, Acc) -> + SN = blockchain_ledger_subnetwork_v1:deserialize(Binary), + maps:put(binary_to_atom(TT, utf8), SN, Acc) + end, + #{} + ). + +-spec find_subnetwork_v1(TT :: blockchain_token_v1:type(), Ledger :: ledger()) -> + {ok, blockchain_ledger_subnetwork_v1:subnetwork_v1()} + | {error, any()}. +find_subnetwork_v1(TT, Ledger) -> + SNCF = subnetworks_v1_cf(Ledger), + case cache_get(Ledger, SNCF, atom_to_binary(TT, utf8), []) of + {ok, BinEntry} -> + {ok, blockchain_ledger_subnetwork_v1:deserialize(BinEntry)}; + not_found -> + {error, subnetwork_not_found}; + Error -> + Error + end. + -spec entries(ledger()) -> entries() | entries_v2(). entries(Ledger) -> {EntryMod, EntriesCF} = versioned_entry_mod_and_entries_cf(Ledger), @@ -2952,6 +2987,39 @@ migrate_sec_entries(Ledger) -> ok ). +-spec add_subnetwork(TT :: blockchain_token_v1:type(), + TokenTreasury :: non_neg_integer(), + HNTTreasury :: non_neg_integer(), + SNKey :: libp2p_crypto:pubkey_bin(), + RewardServerKeys :: [libp2p_crypto:pubkey_bin()], + Ledger :: ledger()) -> ok | {error, any()}. +add_subnetwork(TT, TokenTreasury, HNTTreasury, SNKey, RewardServerKeys, Ledger) -> + SubnetworksV1CF = subnetworks_v1_cf(Ledger), + case ?MODULE:find_subnetwork_v1(TT, Ledger) of + {error, subnetwork_not_found} -> + case lists:member(TT, blockchain_token_v1:supported_tokens()) of + false -> + %% We do not support this token type + {error, {unsupported_subnetwork, TT}}; + true -> + %% Only add subnetwork if it is not found on ledger + SN = blockchain_ledger_subnetwork_v1:new(TT, TokenTreasury, HNTTreasury, SNKey, RewardServerKeys), + Bin = blockchain_ledger_subnetwork_v1:serialize(SN), + cache_put(Ledger, SubnetworksV1CF, atom_to_binary(TT, utf8), Bin) + end; + {ok, _SN} -> + {error, subnetwork_already_exists}; + {error, _}=Error -> + Error + end. + +-spec update_subnetwork(SN :: blockchain_ledger_subnetwork_v1:subnetwork_v1(), + Ledger :: ledger()) -> ok. +update_subnetwork(SN, Ledger) -> + SubnetworksV1CF = subnetworks_v1_cf(Ledger), + TT = blockchain_ledger_subnetwork_v1:type(SN), + Bin = blockchain_ledger_subnetwork_v1:serialize(SN), + cache_put(Ledger, SubnetworksV1CF, atom_to_binary(TT, utf8), Bin). %%-------------------------------------------------------------------- %% @doc @@ -4320,6 +4388,11 @@ entries_v2_cf(Ledger) -> SL = subledger(Ledger), {entries_v2, db(Ledger), SL#sub_ledger_v1.entries_v2}. +-spec subnetworks_v1_cf(ledger()) -> tagged_cf(). +subnetworks_v1_cf(Ledger) -> + SL = subledger(Ledger), + {subnetworks_v1, db(Ledger), SL#sub_ledger_v1.subnetworks_v1}. + -spec dc_entries_cf(ledger()) -> tagged_cf(). dc_entries_cf(Ledger) -> SL = subledger(Ledger), @@ -4668,7 +4741,7 @@ open_db_(DBDir, DBOptions, DefaultCFs, CFOpts, ReadOnly, Retry) -> default_cfs() -> ["default", "active_gateways", "entries", "dc_entries", "htlcs", "pocs", "proposed_pocs", "securities", "routing", "subnets", - "state_channels", "h3dex", "gw_denorm", "validators", "entries_v2"]. + "state_channels", "h3dex", "gw_denorm", "validators", "entries_v2", "subnetworks_v1"]. -spec delayed_cfs() -> list(). delayed_cfs() -> diff --git a/src/ledger/v1/blockchain_ledger_v1.hrl b/src/ledger/v1/blockchain_ledger_v1.hrl index 7a0514abcc..fcb1d79dc6 100644 --- a/src/ledger/v1/blockchain_ledger_v1.hrl +++ b/src/ledger/v1/blockchain_ledger_v1.hrl @@ -41,7 +41,8 @@ validators :: rocksdb:cf_handle(), cache :: undefined | direct | ets:tid(), gateway_cache :: undefined | ets:tid(), - entries_v2 :: rocksdb:cf_handle() + entries_v2 :: rocksdb:cf_handle(), + subnetworks_v1 :: rocksdb:cf_handle() }). -record(aux_ledger_v1, { diff --git a/src/transactions/blockchain_txn.erl b/src/transactions/blockchain_txn.erl index 1eff2438cb..f62dd1b83f 100644 --- a/src/transactions/blockchain_txn.erl +++ b/src/transactions/blockchain_txn.erl @@ -51,7 +51,7 @@ | blockchain_txn_add_subnetwork_v1:txn_add_subnetwork_v1() | blockchain_txn_update_subnetwork_v1:txn_update_subnetwork_v1() | blockchain_txn_subnetwork_rewards_v1:txn_subnetwork_rewards_v1() - | blockchain_txn_token_convert_v1:txn_token_convert_v1(). + | blockchain_txn_token_redeem_v1:txn_token_redeem_v1(). -type before_commit_callback() :: fun((blockchain:blockchain(), blockchain_block:hash()) -> ok | {error, any()}). -type txns() :: [txn()]. @@ -147,7 +147,7 @@ {blockchain_txn_add_subnetwork_v1, 38}, {blockchain_txn_update_subnetwork_v1, 39}, {blockchain_txn_subnetwork_rewards_v1, 40}, - {blockchain_txn_token_convert_v1, 41} + {blockchain_txn_token_redeem_v1, 41} ]). block_delay() -> @@ -265,8 +265,8 @@ wrap_txn(#blockchain_txn_update_subnetwork_v1_pb{}=Txn) -> #blockchain_txn_pb{txn={update_subnetwork, Txn}}; wrap_txn(#blockchain_txn_subnetwork_rewards_v1_pb{}=Txn) -> #blockchain_txn_pb{txn={subnetwork_rewards, Txn}}; -wrap_txn(#blockchain_txn_token_convert_v1_pb{}=Txn) -> - #blockchain_txn_pb{txn={token_convert, Txn}}. +wrap_txn(#blockchain_txn_token_redeem_v1_pb{}=Txn) -> + #blockchain_txn_pb{txn={token_redeem, Txn}}. -spec unwrap_txn(#blockchain_txn_pb{}) -> blockchain_txn:txn(). unwrap_txn(#blockchain_txn_pb{txn={bundle, #blockchain_txn_bundle_v1_pb{transactions=Txns} = Bundle}}) -> @@ -732,8 +732,8 @@ type(#blockchain_txn_update_subnetwork_v1_pb{}) -> blockchain_txn_update_subnetwork_v1; type(#blockchain_txn_subnetwork_rewards_v1_pb{}) -> blockchain_txn_subnetwork_rewards_v1; -type(#blockchain_txn_token_convert_v1_pb{}) -> - blockchain_txn_token_convert_v1. +type(#blockchain_txn_token_redeem_v1_pb{}) -> + blockchain_txn_token_redeem_v1. -spec validate_fields([{{atom(), iodata() | undefined}, {binary, pos_integer()} | diff --git a/src/transactions/v1/blockchain_txn_add_subnetwork_v1.erl b/src/transactions/v1/blockchain_txn_add_subnetwork_v1.erl new file mode 100644 index 0000000000..7b11132553 --- /dev/null +++ b/src/transactions/v1/blockchain_txn_add_subnetwork_v1.erl @@ -0,0 +1,216 @@ +%%%------------------------------------------------------------------- +%% @doc +%% == Blockchain Txn to add a subnetwork token to the network == +%% @end +%%%------------------------------------------------------------------- +-module(blockchain_txn_add_subnetwork_v1). + +-behavior(blockchain_txn). + +-behavior(blockchain_json). +-include("blockchain_json.hrl"). +-include("blockchain_utils.hrl"). +-include("blockchain_vars.hrl"). + +-include_lib("helium_proto/include/blockchain_txn_add_subnetwork_v1_pb.hrl"). + +-export([ + new/4, + token_type/1, + subnetwork_key/1, + reward_server_keys/1, + premine/1, + network_signature/1, + subnetwork_signature/1, + sign/2, + sign_subnetwork/2, + hash/1, + + fee/1, + fee_payer/2, + is_valid/2, + absorb/2, + + print/1, + json_type/0, + to_json/2 +]). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-type txn_add_subnetwork() :: #blockchain_txn_add_subnetwork_v1_pb{}. +-export_type([txn_add_subnetwork/0]). + +-spec new( + TT :: blockchain_token_v1:type(), + SubnetworkKey :: libp2p_crypto:pubkey_bin(), + RewardServerKeys :: [libp2p_crypto:pubkey_bin()], + PremineAmt :: non_neg_integer() +) -> txn_add_subnetwork(). +new(TT, SubnetworkKey, RewardServerKeys, PremineAmt) when PremineAmt >= 0 -> + #blockchain_txn_add_subnetwork_v1_pb{ + token_type = TT, + subnetwork_key = SubnetworkKey, + reward_server_keys = lists:sort(RewardServerKeys), + premine = PremineAmt, + network_signature = <<>>, + subnetwork_signature = <<>> + }. + +-spec token_type(Txn :: txn_add_subnetwork()) -> blockchain_token_v1:token_type(). +token_type(Txn) -> + Txn#blockchain_txn_add_subnetwork_v1_pb.token_type. + +-spec subnetwork_key(Txn :: txn_add_subnetwork()) -> libp2p_crypto:pubkey_bin(). +subnetwork_key(Txn) -> + Txn#blockchain_txn_add_subnetwork_v1_pb.subnetwork_key. + +-spec reward_server_keys(Txn :: txn_add_subnetwork()) -> [libp2p_crypto:pubkey_bin()]. +reward_server_keys(Txn) -> + Txn#blockchain_txn_add_subnetwork_v1_pb.reward_server_keys. + +-spec premine(Txn :: txn_add_subnetwork()) -> non_neg_integer(). +premine(Txn) -> + Txn#blockchain_txn_add_subnetwork_v1_pb.premine. + +-spec network_signature(txn_add_subnetwork()) -> binary(). +network_signature(Txn) -> + Txn#blockchain_txn_add_subnetwork_v1_pb.network_signature. + +-spec subnetwork_signature(txn_add_subnetwork()) -> binary(). +subnetwork_signature(Txn) -> + Txn#blockchain_txn_add_subnetwork_v1_pb.subnetwork_signature. + +-spec sign(txn_add_subnetwork(), libp2p_crypto:sig_fun()) -> txn_add_subnetwork(). +sign(Txn, SigFun) -> + BaseTxn = unset_signatures(Txn), + EncodedTxn = blockchain_txn_add_subnetwork_v1_pb:encode_msg(BaseTxn), + Txn#blockchain_txn_add_subnetwork_v1_pb{network_signature = SigFun(EncodedTxn)}. + +-spec sign_subnetwork(txn_add_subnetwork(), libp2p_crypto:sig_fun()) -> txn_add_subnetwork(). +sign_subnetwork(Txn, SigFun) -> + BaseTxn = unset_signatures(Txn), + EncodedTxn = blockchain_txn_add_subnetwork_v1_pb:encode_msg(BaseTxn), + Txn#blockchain_txn_add_subnetwork_v1_pb{subnetwork_signature = SigFun(EncodedTxn)}. + +-spec hash(txn_add_subnetwork()) -> blockchain_txn:hash(). +hash(Txn) -> + BaseTxn = unset_signatures(Txn), + EncodedTxn = blockchain_txn_add_subnetwork_v1_pb:encode_msg(BaseTxn), + crypto:hash(sha256, EncodedTxn). + +-spec fee(Txn :: txn_add_subnetwork()) -> non_neg_integer(). +fee(_Txn) -> + 0. + +-spec fee_payer(Txn :: txn_add_subnetwork(), Ledger :: blockchain_ledger_v1:ledger()) -> undefined. +fee_payer(_Txn, _Ledger) -> + undefined. + +-spec is_valid(Txn :: txn_add_subnetwork(), Chain :: blockchain:blockchain()) -> + ok | {error, any()}. +is_valid(Txn, Chain) -> + Ledger = blockchain:ledger(Chain), + case token_type(Txn) of + hst -> + {error, invalid_token_hst}; + hnt -> + {error, invalid_token_hnt}; + TT -> + case lists:member(TT, blockchain_token_v1:supported_tokens()) of + false -> + {error, {unsupported_token, TT}}; + true -> + case blockchain:config(?allowed_num_reward_server_keys, Ledger) of + {ok, 1} -> + Artifact = create_artifact(Txn), + SubnetworkKey = ?MODULE:subnetwork_key(Txn), + SubnetworkSig = ?MODULE:subnetwork_signature(Txn), + case verify_key(Artifact, SubnetworkKey, SubnetworkSig) of + true -> + {ok, MasterKey} = blockchain_ledger_v1:master_key(Ledger), + NetworkSig = ?MODULE:network_signature(Txn), + case verify_key(Artifact, MasterKey, NetworkSig) of + true -> + ok; + _ -> + {error, invalid_network_signature} + end; + _ -> + {error, invalid_subnetwork_signature} + end; + _ -> + %% NOTE: Update here when more than one reward server keys are allowed + {error, invalid_reward_server_key_length} + end + end + end. + +-spec absorb(Txn :: txn_add_subnetwork(), Chain :: blockchain:blockchain()) -> ok | {error, any()}. +absorb(Txn, Chain) -> + Ledger = blockchain:ledger(Chain), + TT = ?MODULE:token_type(Txn), + PremineAmt = ?MODULE:premine(Txn), + HNTTreasury = 0, + SubnetworkKey = ?MODULE:subnetwork_key(Txn), + RewardServerKeys = ?MODULE:reward_server_keys(Txn), + blockchain_ledger_v1:add_subnetwork( + TT, + PremineAmt, + HNTTreasury, + SubnetworkKey, + RewardServerKeys, + Ledger + ). + +-spec print(txn_add_subnetwork()) -> iodata(). +print(undefined) -> + <<"type=add_subnetwork_v1, undefined">>; +print( + #blockchain_txn_add_subnetwork_v1_pb{ + token_type = TT, + subnetwork_key = SKey, + reward_server_keys = RKeys, + premine = PremineAmt, + network_signature = NS, + subnetwork_signature = SS + } +) -> + io_lib:format( + "type=add_subnetwork_v1, token_type=~p, premine=~p, subnetwork_key=~p, reward_server_keys=~p, network_signature=~s~n subnetwork_signature: ~s", + [ + atom_to_list(TT), + PremineAmt, + ?TO_B58(SKey), + [?TO_B58(RKey) || RKey <- RKeys], + ?TO_B58(NS), + ?TO_B58(SS) + ] + ). + +json_type() -> + <<"add_subnetwork_v1">>. + +-spec to_json(txn_add_subnetwork(), blockchain_json:opts()) -> blockchain_json:json_object(). +to_json(Txn, _Opts) -> + #{ + type => ?MODULE:json_type(), + hash => ?BIN_TO_B64(hash(Txn)), + token_type => atom_to_binary(token_type(Txn), utf8), + subnetwork_key => ?BIN_TO_B58(subnetwork_key(Txn)), + reward_server_keys => [?BIN_TO_B58(K) || K <- reward_server_keys(Txn)] + }. + +create_artifact(Txn) -> + Txn1 = unset_signatures(Txn), + blockchain_txn_add_subnetwork_v1_pb:encode_msg(Txn1). + +unset_signatures(Txn) -> + Txn#blockchain_txn_add_subnetwork_v1_pb{network_signature = <<>>, subnetwork_signature = <<>>}. + +verify_key(_Artifact, _Key, <<>>) -> + throw({error, no_signature}); +verify_key(Artifact, Key, Signature) -> + libp2p_crypto:verify(Artifact, Signature, libp2p_crypto:bin_to_pubkey(Key)). diff --git a/src/transactions/v1/blockchain_txn_subnetwork_rewards_v1.erl b/src/transactions/v1/blockchain_txn_subnetwork_rewards_v1.erl index bd9dd9bb2e..b14852fe0f 100644 --- a/src/transactions/v1/blockchain_txn_subnetwork_rewards_v1.erl +++ b/src/transactions/v1/blockchain_txn_subnetwork_rewards_v1.erl @@ -1,14 +1,7 @@ %%------------------------------------------------------------------- %% @doc -%% This module implements rewards v2 which track only an account and -%% an amount. The breakdowns of rewards by type and associated gateway -%% are not kept here. This was done to streamline the increasing -%% size of rewards as the network grows. -%% -%% In the future, we will need to work on further ways to streamline -%% the size of this transaction. One proposal was to use the ledger -%% as an encoding dictionary and use ledger offsets to mark the -%% account instead of using the full sized account id. +%% This module implements subnetwork rewards, which will be driven +%% externally via reward server(s). %% @end %%%------------------------------------------------------------------- -module(blockchain_txn_subnetwork_rewards_v1). @@ -22,15 +15,17 @@ -include_lib("helium_proto/include/blockchain_txn_subnetwork_rewards_v1_pb.hrl"). -export([ - new/3, + new/4, hash/1, start_epoch/1, end_epoch/1, rewards/1, + reward_server_signature/1, - %% reward v2 accessors + %% reward accessors reward_account/1, reward_amount/1, + new_reward/2, sign/2, fee/1, @@ -47,9 +42,8 @@ -endif. -type txn_subnetwork_rewards_v1() :: #blockchain_txn_subnetwork_rewards_v1_pb{}. --type subnetwork_reward_v1() :: #blockchain_txn_subnetwork_reward_v1_pb{}. --type rewards() :: [subnetwork_reward_v1()]. --type reward_vars() :: map(). +-type subnetwork_reward() :: #subnetwork_reward_pb{}. +-type rewards() :: [subnetwork_reward()]. -export_type([txn_subnetwork_rewards_v1/0]). @@ -57,2137 +51,201 @@ %% Public API %% ------------------------------------------------------------------ --spec new(non_neg_integer(), non_neg_integer(), rewards()) -> txn_subnetwork_rewards_v1(). -new(Start, End, Rewards) -> +-spec new(blockchain_token_v1:type(), non_neg_integer(), non_neg_integer(), rewards()) -> + txn_subnetwork_rewards_v1(). +new(Type, Start, End, Rewards) -> SortedRewards = lists:sort(Rewards), - #blockchain_txn_rewards_v2_pb{start_epoch=Start, end_epoch=End, rewards=SortedRewards}. + #blockchain_txn_subnetwork_rewards_v1_pb{ + token_type = Type, + start_epoch = Start, + end_epoch = End, + rewards = SortedRewards + }. --spec hash(txn_subnetwork_rewards_v1()|reward_v2()) -> blockchain_txn:hash(). +-spec hash(txn_subnetwork_rewards_v1()) -> blockchain_txn:hash(). hash(Txn) -> - EncodedTxn = blockchain_txn_rewards_v2_pb:encode_msg(Txn), + EncodedTxn = blockchain_txn_subnetwork_rewards_v1_pb:encode_msg(Txn), crypto:hash(sha256, EncodedTxn). +-spec token_type(txn_subnetwork_rewards_v1()) -> blockchain_token_v1:type(). +token_type(#blockchain_txn_subnetwork_rewards_v1_pb{token_type = Type}) -> + Type. + -spec start_epoch(txn_subnetwork_rewards_v1()) -> non_neg_integer(). -start_epoch(#blockchain_txn_rewards_v2_pb{start_epoch=Start}) -> +start_epoch(#blockchain_txn_subnetwork_rewards_v1_pb{start_epoch = Start}) -> Start. -spec end_epoch(txn_subnetwork_rewards_v1()) -> non_neg_integer(). -end_epoch(#blockchain_txn_rewards_v2_pb{end_epoch=End}) -> +end_epoch(#blockchain_txn_subnetwork_rewards_v1_pb{end_epoch = End}) -> End. -spec rewards(txn_subnetwork_rewards_v1()) -> rewards(). -rewards(#blockchain_txn_rewards_v2_pb{rewards=Rewards}) -> +rewards(#blockchain_txn_subnetwork_rewards_v1_pb{rewards = Rewards}) -> Rewards. --spec reward_account(reward_v2()) -> binary(). -reward_account(#blockchain_txn_reward_v2_pb{account = Account}) -> +-spec reward_account(subnetwork_reward()) -> binary(). +reward_account(#subnetwork_reward_pb{account = Account}) -> Account. --spec reward_amount(reward_v2()) -> non_neg_integer(). -reward_amount(#blockchain_txn_reward_v2_pb{amount = Amount}) -> +-spec reward_amount(subnetwork_reward()) -> non_neg_integer(). +reward_amount(#subnetwork_reward_pb{amount = Amount}) -> Amount. +-spec new_reward( + Account :: libp2p_crypto:pubkey_bin(), + Amount :: non_neg_integer() +) -> subnetwork_reward(). +new_reward(Account, Amount) -> + #subnetwork_reward_pb{account = Account, amount = Amount}. + +reward_server_signature(#blockchain_txn_subnetwork_rewards_v1_pb{reward_server_signature = RSS}) -> + RSS. + -spec sign(txn_subnetwork_rewards_v1(), libp2p_crypto:sig_fun()) -> txn_subnetwork_rewards_v1(). sign(Txn, SigFun) -> - Txn. + BaseTxn = Txn#blockchain_txn_subnetwork_rewards_v1_pb{reward_server_signature = <<>>}, + EncodedTxn = blockchain_txn_subnetwork_rewards_v1_pb:encode_msg(BaseTxn), + Txn#blockchain_txn_subnetwork_rewards_v1_pb{reward_server_signature = SigFun(EncodedTxn)}. -spec fee(txn_subnetwork_rewards_v1()) -> 0. fee(_Txn) -> 0. --spec fee_payer(txn_subnetwork_rewards_v1(), blockchain_ledger_v1:ledger()) -> libp2p_crypto:pubkey_bin() | undefined. +-spec fee_payer(txn_subnetwork_rewards_v1(), blockchain_ledger_v1:ledger()) -> + libp2p_crypto:pubkey_bin() | undefined. fee_payer(_Txn, _Ledger) -> undefined. --spec is_valid(txn_subnetwork_rewards_v1(), blockchain:blockchain()) -> ok | {error, atom()} | {error, {atom(), any()}}. +-spec is_valid(txn_subnetwork_rewards_v1(), blockchain:blockchain()) -> + ok | {error, atom()} | {error, {atom(), any()}}. is_valid(Txn, Chain) -> + Ledger = blockchain:ledger(Chain), Start = ?MODULE:start_epoch(Txn), End = ?MODULE:end_epoch(Txn), - TxnRewards = ?MODULE:rewards(Txn), - %% TODO: REMOVE THIS ENTIRE CASE STATEMENT AT NEXT RESTART - case TxnRewards of - [] -> - ok; - _ -> - case ?MODULE:calculate_rewards(Start, End, Chain) of - {error, _Reason}=Error -> - Error; - {ok, CalRewards} -> - CalRewardsHashes = [hash(R)|| R <- CalRewards], - TxnRewardsHashes = [hash(R)|| R <- TxnRewards], - case CalRewardsHashes == TxnRewardsHashes of - false -> {error, invalid_rewards_v2}; - true -> ok - end - end + TokenType = token_type(Txn), + Signature = reward_server_signature(Txn), + %% make sure that the signature is correct + %% make sure that the rewards are less than the amount stored + {ok, Subnet} = blockchain_ledger_v1:find_subnetwork_v1(TokenType, Ledger), + TotalRewards = total_rewards(Txn), + Tokens = blockchain_ledger_subnetwork_v1:token_treasury(Subnet), + LastRewardedBlock = blockchain_ledger_subnetwork_v1:last_rewarded_block(Subnet), + try + %% this needs to somehow limit the mint here? but if there is only premine I don't + %% understand how we do that. + case TotalRewards =< blockchain_ledger_subnetwork_v1:token_treasury(Subnet) of + true -> ok; + false -> throw({insufficient_tokens_to_fulfil_rewards, Tokens, TotalRewards}) + end, + BaseTxn = Txn#blockchain_txn_subnetwork_rewards_v1_pb{reward_server_signature = <<>>}, + Artifact = blockchain_txn_subnetwork_rewards_v1_pb:encode_msg(BaseTxn), + case + lists:any( + fun(Key) -> + libp2p_crypto:verify(Artifact, Signature, libp2p_crypto:bin_to_pubkey(Key)) + end, + blockchain_ledger_subnetwork_v1:reward_server_keys(Subnet) + ) + of + true -> ok; + false -> throw(invalid_signature) + end, + case End > Start andalso Start > LastRewardedBlock of + true -> ok; + false -> throw({invalid_reward_range, Start, End, LastRewardedBlock}) + end + catch + throw:Err -> + {error, Err} end. --spec absorb(txn_subnetwork_rewards_v1(), blockchain:blockchain()) -> ok | {error, atom()} | {error, {atom(), any()}}. +-spec absorb(txn_subnetwork_rewards_v1(), blockchain:blockchain()) -> + ok | {error, atom()} | {error, {atom(), any()}}. absorb(Txn, Chain) -> Ledger = blockchain:ledger(Chain), - - case blockchain:config(?net_emissions_enabled, Ledger) of - {ok, true} -> - %% initial proposed max 34.24 - {ok, Max} = blockchain:config(?net_emissions_max_rate, Ledger), - {ok, Burned} = blockchain_ledger_v1:hnt_burned(Ledger), - {ok, Overage} = blockchain_ledger_v1:net_overage(Ledger), - - %% clear this since we have it already - ok = blockchain_ledger_v1:clear_hnt_burned(Ledger), - - case Burned > Max of - %% if burned > max, then add (burned - max) to overage - true -> - Overage1 = Overage + (Burned - Max), - ok = blockchain_ledger_v1:net_overage(Overage1, Ledger); - %% else we may have pulled from overage to the tune of - %% max - burned - _ -> - %% here we pulled from overage up to max - case (Max - Burned) < Overage of - %% emitted max, pulled from overage - true -> - Overage1 = Overage - (Max - Burned), - ok = blockchain_ledger_v1:net_overage(Overage1, Ledger); - %% not enough overage to emit up to max, 0 overage - _ -> - ok = blockchain_ledger_v1:net_overage(0, Ledger) - end - end; - _ -> - ok - end, - - case blockchain_ledger_v1:mode(Ledger) == aux of - false -> - %% only absorb in the main ledger - absorb_(Txn, Ledger); - true -> - aux_absorb(Txn, Ledger, Chain) - end. - --spec absorb_(Txn :: txn_subnetwork_rewards_v1(), Ledger :: blockchain_ledger_v1:ledger()) -> ok. -absorb_(Txn, Ledger) -> - Rewards = ?MODULE:rewards(Txn), - absorb_rewards(Rewards, Ledger). - --spec absorb_rewards(Rewards :: rewards(), - Ledger :: blockchain_ledger_v1:ledger()) -> ok. -absorb_rewards(Rewards, Ledger) -> + %% these rewards are the same no matter the ledger + TokenType = token_type(Txn), + TotalRewards = total_rewards(Txn), + %% Remove total_rewards from the token treasury + {ok, Subnet} = blockchain_ledger_v1:find_subnetwork_v1(TokenType, Ledger), + TokenTreasury = blockchain_ledger_subnetwork_v1:token_treasury(Subnet), + Subnet1 = blockchain_ledger_subnetwork_v1:token_treasury(Subnet, TokenTreasury - TotalRewards), + %% Absorb the rewards + ok = absorb_rewards(TokenType, rewards(Txn), Ledger), + %% Save the subnetwork + Subnet2 = blockchain_ledger_subnetwork_v1:last_rewarded_block(Subnet1, end_epoch(Txn)), + ok = blockchain_ledger_v1:update_subnetwork(Subnet2, Ledger). + +-spec absorb_rewards( + TokenType :: blockchain_token_v1:type(), + Rewards :: rewards(), + Ledger :: blockchain_ledger_v1:ledger() +) -> ok. +absorb_rewards(TokenType, Rewards, Ledger) -> lists:foreach( - fun(#blockchain_txn_reward_v2_pb{account=Account, amount=Amount}) -> - ok = blockchain_ledger_v1:credit_account(Account, Amount, Ledger) + fun(#subnetwork_reward_pb{account = Account, amount = Amount}) -> + ok = blockchain_ledger_v1:credit_account(Account, Amount, TokenType, Ledger) end, Rewards ). --spec aux_absorb(Txn :: txn_subnetwork_rewards_v1(), - AuxLedger :: blockchain_ledger_v1:ledger(), - Chain :: blockchain:blockchain()) -> ok | {error, any()}. -aux_absorb(Txn, AuxLedger, Chain) -> - Start = ?MODULE:start_epoch(Txn), - End = ?MODULE:end_epoch(Txn), - %% NOTE: This is an aux ledger, we don't use rewards(txn) here, instead we calculate them manually - %% and do 0 verification for absorption - case calculate_rewards_(Start, End, AuxLedger, Chain, true) of - {error, _}=E -> E; - {ok, AuxRewards, AuxMD} -> - TxnRewards = rewards(Txn), - %% absorb the rewards attached to the txn (real) - absorb_rewards(TxnRewards, AuxLedger), - %% set auxiliary rewards in the aux ledger also - lager:info("are aux rewards equal?: ~p", [lists:sort(TxnRewards) == lists:sort(AuxRewards)]), - %% rewards appear in (End + 1) block - blockchain_aux_ledger_v1:set_rewards(End + 1, TxnRewards, AuxRewards, AuxLedger), - case calculate_rewards_(Start, End, blockchain_ledger_v1:mode(active, AuxLedger), Chain, true) of - {error, _}=E -> E; - {ok, _, OrigMD} -> - blockchain_aux_ledger_v1:set_rewards_md(End + 1, OrigMD, AuxMD, AuxLedger) - end - end. - - --spec calculate_rewards(non_neg_integer(), non_neg_integer(), blockchain:blockchain()) -> - {ok, rewards()} | {error, any()}. -%% @doc Calculate and return an ordered list (as ordered by lists:sort/1) of -%% rewards for use in a rewards_v2 transaction. Given how lists:sort/1 works, -%% ordering will depend on (binary) account information. -calculate_rewards(Start, End, Chain) -> - {ok, Ledger} = blockchain:ledger_at(End, Chain), - calculate_rewards_(Start, End, Ledger, Chain, false). - --spec calculate_rewards_( - Start :: non_neg_integer(), - End :: non_neg_integer(), - Ledger :: blockchain_ledger_v1:ledger(), - Chain :: blockchain:blockchain(), - ReturnMD :: boolean() - ) -> {error, any()} | {ok, rewards()} | {ok, rewards(), rewards_metadata()}. -calculate_rewards_(Start, End, Ledger, Chain, ReturnMD) -> - {ok, Results} = calculate_rewards_metadata(Start, End, blockchain:ledger(Ledger, Chain)), - try - case ReturnMD of - false -> - {ok, prepare_rewards_v2_txns(Results, Ledger)}; - true -> - {ok, prepare_rewards_v2_txns(Results, Ledger), Results} - end - catch - C:Error:Stack -> - lager:error("Caught ~p; couldn't prepare rewards txn because: ~p~n~p", [C, Error, Stack]), - Error - end. - --spec calculate_rewards_metadata( - Start :: non_neg_integer(), - End :: non_neg_integer(), - Chain :: blockchain:blockchain() ) -> - {ok, Metadata :: rewards_metadata()} | {error, Error :: term()}. -%% @doc Calculate only rewards metadata (do not return v2 reward records -%% to the caller.) -%% -%% Keys that exist in the rewards metadata map include: -%%
    -%%
  • poc_challenger
  • -%%
  • poc_witness
  • -%%
  • poc_challengee
  • -%%
  • dc_rewards
  • -%%
  • consensus_rewards
  • -%%
  • securities_rewards
  • -%%
-%% -%% Each of the keys is itself a map which has the shape of `#{ Entry => Amount }' -%% where Entry is defined as a tuple of `{gateway, reward_type, Gateway}' or -%% `{owner, reward_type, Owner}' -%% -%% There is an additional key `overages' which may or may not have an integer -%% value. It represents the amount of excess fees paid in the given epoch which -%% were used as bonus HNT rewards for the consensus members. -%% @end -calculate_rewards_metadata(Start, End, Chain) -> - {ok, Ledger} = blockchain:ledger_at(End, Chain), - Vars0 = get_reward_vars(Start, End, Ledger), - VarMap = case blockchain_hex:var_map(Ledger) of - {error, _Reason} -> #{}; - {ok, VM} -> VM - end, - - RegionVars = blockchain_region_v1:get_all_region_bins(Ledger), - - Vars = Vars0#{ var_map => VarMap, region_vars => RegionVars}, - - %% Previously, if a state_channel closed in the grace blocks before an - %% epoch ended, then it wouldn't ever get rewarded. - {ok, PreviousGraceBlockDCRewards} = collect_dc_rewards_from_previous_epoch_grace(Start, End, - Chain, Vars, - Ledger), - - %% Initialize our reward accumulator. We are going to build up a map which - %% will be in the shape of - %% #{ reward_type => #{ Entry => Amount } } - %% - %% where Entry is of the the shape - %% {owner, reward_type, Owner} or - %% {gateway, reward_type, Gateway} - AccInit = #{ dc_rewards => PreviousGraceBlockDCRewards, - poc_challenger => #{}, - poc_challengee => #{}, - poc_witness => #{} }, - - try - %% We only want to fold over the blocks and transaction in an epoch once, - %% so we will do that top level work here. If we get a thrown error while - %% we are folding, we will abort reward calculation. - Me = self(), - PerfTab = - case application:get_env(blockchain, print_rewards_perf, false) of - true -> - PT = ets:new(rwd_perf, []), - erlang:put({Me, '$rwd_perf'}, PT), - PT; - _ -> ok - end, - Results0 = fold_blocks_for_rewards(Start, End, Chain, - Vars, Ledger, AccInit), - - %% Prior to HIP 28 (reward_version <6), force EpochReward amount for the CG to always - %% be around ElectionInterval (30 blocks) so that there is less incentive - %% to stay in the consensus group. With HIP 28, relax that to be up to election_interval + - %% election_retry_interval to allow for time for election to complete. - ConsensusEpochReward = - case maps:get(reward_version, Vars) of - RewardVersion when RewardVersion >= 6 -> - calculate_consensus_epoch_reward(Start, End, Vars, Ledger); - _ -> - calculate_epoch_reward(1, Start, End, Ledger) - end, - - Vars1 = Vars#{ consensus_epoch_reward => ConsensusEpochReward }, - - Results = finalize_reward_calculations(Results0, Ledger, Vars1), - %% we are only keeping hex density calculations memoized for a single - %% rewards transaction calculation, then we discard that work and avoid - %% cache invalidation issues. - case application:get_env(blockchain, destroy_memo, true) of - true -> - true = blockchain_hex:destroy_memoization(); - _ -> ok +-spec total_rewards(Txn :: txn_subnetwork_rewards_v1()) -> non_neg_integer(). +total_rewards(Txn) -> + lists:foldl( + fun(Reward, Acc) -> + Acc + reward_amount(Reward) end, - perf_report(PerfTab), - catch ets:delete(PerfTab), - erlang:erase({Me, '$rwd_perf'}), - {ok, Results} - catch - C:Error:Stack -> - lager:error("Caught ~p; couldn't calculate rewards metadata because: ~p~n~p", [C, Error, Stack]), - Error - end. - -perf(Tag, Time) -> - Me = self(), - case erlang:get({Me, '$rwd_perf'}) of - undefined -> - ok; - Tab -> - catch ets:update_counter(Tab, Tag, Time, {Tag, Time}) - end. - -perf_report(Tab) -> - case application:get_env(blockchain, print_rewards_perf, false) of - true -> - Measurements = lists:reverse(lists:keysort(2, ets:tab2list(Tab))), - lager:info("perf report:"), - lists:foreach( - fun({K, V}) -> - lager:info("txn ~p: ~pms", [K, V]) - end, - Measurements); - false -> - ok - end. + 0, + ?MODULE:rewards(Txn) + ). -spec print(txn_subnetwork_rewards_v1()) -> iodata(). -print(undefined) -> <<"type=rewards_v2 undefined">>; -print(#blockchain_txn_rewards_v2_pb{start_epoch=Start, - end_epoch=End}) -> - io_lib:format("type=rewards_v2 start_epoch=~p end_epoch=~p", - [Start, End]). +print(undefined) -> + <<"type=subnetwork_rewards_v1 undefined">>; +print( + #blockchain_txn_subnetwork_rewards_v1_pb{ + start_epoch = Start, + end_epoch = End, + token_type = TT, + rewards = Rewards + } = Txn +) -> + io_lib:format( + "type=subnetwork_rewards_v1 start_epoch=~p end_epoch=~p token_type=~p rewards_hash=~p total_rewards=~p", + [Start, End, TT, crypto:hash(sha256, term_to_binary(Rewards)), total_rewards(Txn)] + ). json_type() -> <<"subnetwork_rewards_v1">>. -spec to_json(txn_subnetwork_rewards_v1(), blockchain_json:opts()) -> blockchain_json:json_object(). -to_json(Txn, Opts) -> - RewardToJson = - fun - ({gateway, Type, G}, Amount, Ledger, Acc) -> - case blockchain_ledger_v1:find_gateway_owner(G, Ledger) of - {error, _Error} -> - Acc; - {ok, GwOwner} -> - [#{account => ?BIN_TO_B58(GwOwner), - gateway => ?BIN_TO_B58(G), - amount => Amount, - type => Type} | Acc] - end; - ({validator, Type, V}, Amount, Ledger, Acc) -> - case blockchain_ledger_v1:get_validator(V, Ledger) of - {error, _Error} -> - Acc; - {ok, Val} -> - Owner = blockchain_ledger_validator_v1:owner_address(Val), - [#{account => ?BIN_TO_B58(Owner), - gateway => ?BIN_TO_B58(V), - amount => Amount, - type => Type} | Acc] - end; - ({owner, Type, O}, Amount, _Ledger, Acc) -> - [#{account => ?BIN_TO_B58(O), - gateway => undefined, - amount => Amount, - type => Type} | Acc] +to_json(Txn, _Opts) -> + Rewards = lists:foldl( + fun(#subnetwork_reward_pb{account = Account, amount = Amount}, Acc) -> + [ + #{ + type => <<"subnetwork_reward">>, + account => ?BIN_TO_B58(Account), + amount => Amount + } + | Acc + ] end, - Rewards = case lists:keyfind(chain, 1, Opts) of - {chain, Chain} -> - Start = blockchain_txn_rewards_v2:start_epoch(Txn), - End = ?MODULE:end_epoch(Txn), - {ok, Ledger} = blockchain:ledger_at(End, Chain), - {ok, Metadata} = case lists:keyfind(rewards_metadata, 1, Opts) of - {rewards_metadata, M} -> {ok, M}; - _ -> ?MODULE:calculate_rewards_metadata(Start, End, Chain) - end, - maps:fold( - fun(overages, Amount, Acc) -> - [#{amount => Amount, - type => overages} | Acc]; - (_RewardCategory, Rewards, Acc0) -> - maps:fold( - fun(Entry, Amount, Acc) -> - RewardToJson(Entry, Amount, Ledger, Acc) - end, Acc0, Rewards) - end, [], Metadata); - _ -> [ reward_to_json(R, []) || R <- rewards(Txn)] - end, + [], + ?MODULE:rewards(Txn) + ), #{ - type => ?MODULE:json_type(), - hash => ?BIN_TO_B64(hash(Txn)), - start_epoch => start_epoch(Txn), - end_epoch => end_epoch(Txn), - rewards => Rewards + type => ?MODULE:json_type(), + hash => ?BIN_TO_B64(hash(Txn)), + start_epoch => start_epoch(Txn), + end_epoch => end_epoch(Txn), + rewards => Rewards }. -%% ------------------------------------------------------------------ -%% Internal Function Definitions -%% ------------------------------------------------------------------ - - --spec reward_to_json( Reward :: reward_v2(), - Opts :: blockchain_json:opts() ) -> blockchain_json:json_object(). -reward_to_json(#blockchain_txn_reward_v2_pb{account = Account, amount = Amt}, _Opts) -> - #{ - type => <<"reward_v2">>, - account => ?BIN_TO_B58(Account), - amount => Amt - }. - --spec new_reward( Account :: libp2p_crypto:pubkey_bin(), - Amount :: non_neg_integer() ) -> reward_v2(). -new_reward(Account, Amount) -> - #blockchain_txn_reward_v2_pb{account=Account, amount=Amount}. - --spec fold_blocks_for_rewards( Current :: pos_integer(), - End :: pos_integer(), - Chain :: blockchain:blockchain(), - Vars :: reward_vars(), - Ledger :: blockchain_ledger_v1:ledger(), - Acc :: rewards_share_metadata() ) -> rewards_share_metadata(). -fold_blocks_for_rewards(Current, End, _Chain, _Vars, _Ledger, Acc) when Current == End + 1 -> Acc; -fold_blocks_for_rewards(910360, End, Chain, Vars, Ledger, Acc) -> - fold_blocks_for_rewards(910361, End, Chain, Vars, Ledger, Acc); -fold_blocks_for_rewards(Current, End, Chain, Vars, Ledger, Acc) -> - case blockchain:get_block(Current, Chain) of - {error, _Reason} = Error -> throw(Error); - {ok, Block} -> - Txns = blockchain_block:transactions(Block), - NewAcc = lists:foldl(fun(T, A) -> - Type = blockchain_txn:type(T), - Start = erlang:monotonic_time(microsecond), - A1 = calculate_reward_for_txn(Type, T, End, - A, Chain, Ledger, Vars), - perf(Type, erlang:monotonic_time(microsecond) - Start), - A1 - end, - Acc, Txns), - fold_blocks_for_rewards(Current+1, End, Chain, Vars, Ledger, NewAcc) - end. - --spec calculate_reward_for_txn( Type :: atom(), - Txn :: blockchain_txn:txn(), - End :: pos_integer(), - Acc :: rewards_share_metadata(), - Chain :: blockchain:blockchain(), - Ledger :: blockchain_ledger_v1:ledger(), - Vars :: reward_vars() ) -> rewards_share_metadata(). -calculate_reward_for_txn(?MODULE, _Txn, _End, _Acc, _Chain, - _Ledger, _Vars) -> throw({error, already_existing_rewards_v2}); -calculate_reward_for_txn(blockchain_txn_rewards_v1, _Txn, _End, _Acc, _Chain, - _Ledger, _Vars) -> throw({error, already_existing_rewards_v1}); -calculate_reward_for_txn(blockchain_txn_poc_receipts_v1 = T, Txn, _End, - #{ poc_challenger := Challenger } = Acc, Chain, Ledger, Vars) -> - Start = erlang:monotonic_time(microsecond), - Acc0 = poc_challenger_reward(Txn, Challenger, Vars), - Start1 = erlang:monotonic_time(microsecond), - perf({T, challenger}, Start1 - Start), - Acc1 = calculate_poc_challengee_rewards(Txn, Acc#{ poc_challenger => Acc0 }, Chain, Ledger, Vars), - Start2 = erlang:monotonic_time(microsecond), - perf({T, challengee}, Start2 - Start1), - Acc2 = calculate_poc_witness_rewards(Txn, Acc1, Chain, Ledger, Vars), - WitnessTime = erlang:monotonic_time(microsecond) - Start2, - perf({T, witnesses}, WitnessTime), - Acc2; -calculate_reward_for_txn(blockchain_txn_poc_receipts_v2, Txn, _End, - #{ poc_challenger := Challenger } = Acc, Chain, Ledger, Vars) -> - Acc0 = poc_challenger_reward(Txn, Challenger, Vars), - Acc1 = calculate_poc_challengee_rewards(Txn, Acc#{ poc_challenger => Acc0 }, Chain, Ledger, Vars), - calculate_poc_witness_rewards(Txn, Acc1, Chain, Ledger, Vars); - -calculate_reward_for_txn(blockchain_txn_state_channel_close_v1, Txn, End, Acc, Chain, Ledger, Vars) -> - calculate_dc_rewards(Txn, End, Acc, Chain, Ledger, Vars); -calculate_reward_for_txn(Type, Txn, _End, Acc, _Chain, Ledger, _Vars) -> - consider_overage(Type, Txn, Acc, Ledger). - --spec consider_overage( Type :: atom(), - Txn :: blockchain_txn:txn(), - Acc :: rewards_share_metadata(), - Ledger :: blockchain_ledger_v1:ledger() ) -> rewards_share_metadata(). -consider_overage(Type, Txn, Acc, Ledger) -> - %% calculate any fee paid in excess which we will distribute as bonus HNT - %% to consensus members - try - Type:calculate_fee(Txn, Ledger) - Type:fee(Txn) of - Overage when Overage > 0 -> - maps:update_with(overages, fun(Overages) -> Overages + Overage end, Overage, Acc); - _ -> - Acc - catch - _:_ -> - Acc - end. - --spec calculate_poc_challengee_rewards( Txn :: blockchain_txn:txn(), - Acc :: rewards_share_metadata(), - Chain :: blockchain:blockchain(), - Ledger :: blockchain_ledger_v1:ledger(), - Vars :: reward_vars() ) -> rewards_share_metadata(). -calculate_poc_challengee_rewards(Txn, #{ poc_challengee := ChallengeeMap } = Acc, - Chain, Ledger, #{ var_map := VarMap } = Vars) -> - TxnType = blockchain_txn:type(Txn), - Path = TxnType:path(Txn), - NewCM = poc_challengees_rewards_(Vars, Path, Path, Txn, Chain, Ledger, true, VarMap, ChallengeeMap), - Acc#{ poc_challengee => NewCM }. - --spec calculate_poc_witness_rewards( Txn :: blockchain_txn:txn(), - Acc :: rewards_share_metadata(), - Chain :: blockchain:blockchain(), - Ledger :: blockchain_ledger_v1:ledger(), - Vars :: reward_vars() ) -> rewards_share_metadata(). -calculate_poc_witness_rewards(Txn, #{ poc_witness := WitnessMap } = Acc, Chain, Ledger, Vars) -> - NewWM = poc_witness_reward(Txn, WitnessMap, Chain, Ledger, Vars), - Acc#{ poc_witness => NewWM }. - --spec calculate_dc_rewards( Txn :: blockchain_txn:txn(), - End :: pos_integer(), - Acc :: rewards_share_metadata(), - Chain :: blockchain:blockchain(), - Ledger :: blockchain_ledger_v1:ledger(), - Vars :: reward_vars() ) -> rewards_share_metadata(). -calculate_dc_rewards(Txn, End, #{ dc_rewards := DCRewardMap } = Acc, _Chain, Ledger, Vars) -> - NewDCM = dc_reward(Txn, End, DCRewardMap, Ledger, Vars), - Acc#{ dc_rewards => NewDCM }. - --spec finalize_reward_calculations( AccIn :: rewards_share_metadata(), - Ledger :: blockchain_ledger_v1:ledger(), - Vars :: reward_vars() ) -> rewards_metadata(). -finalize_reward_calculations(#{ dc_rewards := DCShares, - poc_witness := WitnessShares, - poc_challenger := ChallengerShares, - poc_challengee := ChallengeeShares } = AccIn, Ledger, Vars) -> - SecuritiesRewards = securities_rewards(Ledger, Vars), - Overages = maps:get(overages, AccIn, 0), - ConsensusRewards = consensus_members_rewards(Ledger, Vars, Overages), - {DCRemainder, DCRewards} = normalize_dc_rewards(DCShares, Vars), - Vars0 = maps:put(dc_remainder, DCRemainder, Vars), - - %% apply the DC remainder, if any to the other PoC categories pro rata - %% - %% these normalize functions take reward "shares" and convert them - %% into HNT payouts - - #{ poc_witness => normalize_witness_rewards(WitnessShares, Vars0), - poc_challenger => normalize_challenger_rewards(ChallengerShares, Vars0), - poc_challengee => normalize_challengee_rewards(ChallengeeShares, Vars0), - dc_rewards => DCRewards, - consensus_rewards => ConsensusRewards, - securities_rewards => SecuritiesRewards, - overages => Overages }. - --spec prepare_rewards_v2_txns( Results :: rewards_metadata(), - Ledger :: blockchain_ledger_v1:ledger() ) -> rewards(). -prepare_rewards_v2_txns(Results, Ledger) -> - %% we are going to fold over a list of keys in the rewards map (Results) - %% and generate a new map which has _all_ the owners and the sum of - %% _all_ rewards types in a new map... - AllRewards = lists:foldl( - fun(RewardCategory, Rewards) -> - R = maps:get(RewardCategory, Results), - %% R is our map of rewards of the given type - %% and now we are going to do a maps:fold/3 - %% over this reward category and either - %% add the owner and amount for the first - %% time or add an amount to an existing owner - %% in the Rewards accumulator - - maps:fold(fun(Entry, Amt, Acc) -> - case Entry of - {owner, _Type, O} -> - maps:update_with(O, - fun(Balance) -> Balance + Amt end, - Amt, - Acc); - {gateway, _Type, G} -> - case blockchain_ledger_v1:find_gateway_owner(G, Ledger) of - {error, _Error} -> Acc; - {ok, GwOwner} -> - maps:update_with(GwOwner, - fun(Balance) -> Balance + Amt end, - Amt, - Acc) - end; % gw case - {validator, _Type, V} -> - case blockchain_ledger_v1:get_validator(V, Ledger) of - {error, _} -> Acc; - {ok, Val} -> - Owner = blockchain_ledger_validator_v1:owner_address(Val), - maps:update_with(Owner, - fun(Balance) -> Balance + Amt end, - Amt, - Acc) - end - end % Entry case - end, % function - Rewards, - maps:iterator(R)) %% bound memory size no matter size of map - end, - #{}, - [poc_challenger, poc_challengee, poc_witness, - dc_rewards, consensus_rewards, securities_rewards]), - - %% now we are going to fold over all rewards and construct our - %% transaction for the blockchain - - Rewards = maps:fold(fun(Owner, 0, Acc) -> - lager:debug("Dropping reward for ~p because the amount is 0", - [?BIN_TO_B58(Owner)]), - Acc; - (Owner, Amount, Acc) -> - [ new_reward(Owner, Amount) | Acc ] - end, - [], - maps:iterator(AllRewards)), %% again, bound memory no matter size of map - - %% sort the rewards list before it gets returned so list ordering is deterministic - %% (map keys can be enumerated in any arbitrary order) - lists:sort(Rewards). - --spec get_reward_vars(pos_integer(), pos_integer(), blockchain_ledger_v1:ledger()) -> reward_vars(). -get_reward_vars(Start, End, Ledger) -> - {ok, MonthlyReward} = blockchain:config(?monthly_reward, Ledger), - {ok, SecuritiesPercent} = blockchain:config(?securities_percent, Ledger), - {ok, PocChallengeesPercent} = blockchain:config(?poc_challengees_percent, Ledger), - {ok, PocChallengersPercent} = blockchain:config(?poc_challengers_percent, Ledger), - {ok, PocWitnessesPercent} = blockchain:config(?poc_witnesses_percent, Ledger), - {ok, ConsensusPercent} = blockchain:config(?consensus_percent, Ledger), - {ok, OraclePrice} = blockchain_ledger_v1:current_oracle_price(Ledger), - DCPercent = case blockchain:config(?dc_percent, Ledger) of - {ok, R1} -> - R1; - _ -> - 0 - end, - SCGrace = case blockchain:config(?sc_grace_blocks, Ledger) of - {ok, R2} -> - R2; - _ -> - 0 - end, - SCVersion = case blockchain:config(?sc_version, Ledger) of - {ok, R3} -> - R3; - _ -> - 1 - end, - SCDisputeStrategyVersion = case blockchain:config(?sc_dispute_strategy_version, Ledger) of - {ok, SCDV} -> SCDV; - _ -> 0 - end, - POCVersion = case blockchain:config(?poc_version, Ledger) of - {ok, V} -> V; - _ -> 1 - end, - RewardVersion = case blockchain:config(?reward_version, Ledger) of - {ok, R4} -> R4; - _ -> 1 - end, - - WitnessRedundancy = case blockchain:config(?witness_redundancy, Ledger) of - {ok, WR} -> WR; - _ -> undefined - end, - - DecayRate = case blockchain:config(?poc_reward_decay_rate, Ledger) of - {ok, R} -> R; - _ -> undefined - end, - - DensityTgtRes = case blockchain:config(?density_tgt_res, Ledger) of - {ok, D} -> D; - _ -> undefined - end, - - HIP15TxRewardUnitCap = case blockchain:config(?hip15_tx_reward_unit_cap, Ledger) of - {ok, Val} -> Val; - _ -> undefined - end, - - {ok, ElectionInterval} = blockchain:config(?election_interval, Ledger), - {ok, ElectionRestartInterval} = blockchain:config(?election_restart_interval, Ledger), - {ok, BlockTime} = blockchain:config(?block_time, Ledger), - - WitnessRewardDecayRate = case blockchain:config(?witness_reward_decay_rate, Ledger) of - {ok, Dec} -> Dec; - _ -> undefined - end, - - WitnessRewardDecayExclusion = - case blockchain:config(?witness_reward_decay_exclusion, Ledger) of - {ok, Exc} -> Exc; - _ -> undefined - end, - - PocChallengerType = - case blockchain:config(?poc_challenger_type, Ledger) of - {ok, validator} -> validator; - _ -> gateway - end, - - EpochReward = calculate_epoch_reward(Start, End, Ledger), - #{ - monthly_reward => MonthlyReward, - epoch_reward => EpochReward, - oracle_price => OraclePrice, - securities_percent => SecuritiesPercent, - poc_challengees_percent => PocChallengeesPercent, - poc_challengers_percent => PocChallengersPercent, - poc_witnesses_percent => PocWitnessesPercent, - consensus_percent => ConsensusPercent, - dc_percent => DCPercent, - poc_challenger_type => PocChallengerType, - sc_grace_blocks => SCGrace, - sc_version => SCVersion, - sc_dispute_strategy_version => SCDisputeStrategyVersion, - poc_version => POCVersion, - reward_version => RewardVersion, - witness_redundancy => WitnessRedundancy, - poc_reward_decay_rate => DecayRate, - density_tgt_res => DensityTgtRes, - hip15_tx_reward_unit_cap => HIP15TxRewardUnitCap, - election_interval => ElectionInterval, - election_restart_interval => ElectionRestartInterval, - block_time => BlockTime, - witness_reward_decay_rate => WitnessRewardDecayRate, - witness_reward_decay_exclusion => WitnessRewardDecayExclusion - }. - --spec calculate_epoch_reward(pos_integer(), pos_integer(), blockchain_ledger_v1:ledger()) -> float(). -calculate_epoch_reward(Start, End, Ledger) -> - Version = case blockchain:config(?reward_version, Ledger) of - {ok, V} -> V; - _ -> 1 - end, - calculate_epoch_reward(Version, Start, End, Ledger). - --spec calculate_epoch_reward(pos_integer(), pos_integer(), pos_integer(), blockchain_ledger_v1:ledger()) -> float(). -calculate_epoch_reward(Version, Start, End, Ledger) -> - {ok, ElectionInterval} = blockchain:config(?election_interval, Ledger), - {ok, BlockTime0} = blockchain:config(?block_time, Ledger), - {ok, MonthlyReward} = blockchain:config(?monthly_reward, Ledger), - calculate_epoch_reward(Version, Start, End, BlockTime0, - ElectionInterval, MonthlyReward, Ledger). - -calculate_net_emissions_reward(Ledger) -> - case blockchain:config(?net_emissions_enabled, Ledger) of - {ok, true} -> - %% initial proposed max 34.24 - {ok, Max} = blockchain:config(?net_emissions_max_rate, Ledger), - {ok, Burned} = blockchain_ledger_v1:hnt_burned(Ledger), - {ok, Overage} = blockchain_ledger_v1:net_overage(Ledger), - min(Max, Burned + Overage); - _ -> - 0 - end. - --spec calculate_epoch_reward(pos_integer(), pos_integer(), pos_integer(), - pos_integer(), pos_integer(), pos_integer(), - blockchain_ledger_v1:ledger()) -> float(). -calculate_epoch_reward(Version, Start, End, BlockTime0, _ElectionInterval, MonthlyReward, Ledger) when Version >= 6 -> - BlockTime1 = (BlockTime0/1000), - % Convert to blocks per min - BlockPerMin = 60/BlockTime1, - % Convert to blocks per hour - BlockPerHour = BlockPerMin*60, - % Calculate election interval in blocks - ElectionInterval = End - Start + 1, % epoch is inclusive of start and end - ElectionPerHour = BlockPerHour/ElectionInterval, - Reward = MonthlyReward/30/24/ElectionPerHour, - Extra = calculate_net_emissions_reward(Ledger), - Reward + Extra; -calculate_epoch_reward(Version, Start, End, BlockTime0, _ElectionInterval, MonthlyReward, Ledger) when Version >= 2 -> - BlockTime1 = (BlockTime0/1000), - % Convert to blocks per min - BlockPerMin = 60/BlockTime1, - % Convert to blocks per hour - BlockPerHour = BlockPerMin*60, - % Calculate election interval in blocks - ElectionInterval = End - Start, - ElectionPerHour = BlockPerHour/ElectionInterval, - Reward = MonthlyReward/30/24/ElectionPerHour, - Extra = calculate_net_emissions_reward(Ledger), - Reward + Extra; -calculate_epoch_reward(_Version, _Start, _End, BlockTime0, ElectionInterval, MonthlyReward, Ledger) -> - BlockTime1 = (BlockTime0/1000), - % Convert to blocks per min - BlockPerMin = 60/BlockTime1, - % Convert to blocks per hour - BlockPerHour = BlockPerMin*60, - % Calculate number of elections per hour - ElectionPerHour = BlockPerHour/ElectionInterval, - Reward = MonthlyReward/30/24/ElectionPerHour, - Extra = calculate_net_emissions_reward(Ledger), - Reward + Extra. - - - --spec calculate_consensus_epoch_reward(pos_integer(), pos_integer(), - map(), blockchain_ledger_v1:ledger()) -> float(). -calculate_consensus_epoch_reward(Start, End, Vars, Ledger) -> - - #{ block_time := BlockTime0, - election_interval := ElectionInterval, - election_restart_interval := ElectionRestartInterval, - monthly_reward := MonthlyReward } = Vars, - BlockTime1 = (BlockTime0/1000), - % Convert to blocks per min - BlockPerMin = 60/BlockTime1, - % Convert to blocks per month - BlockPerMonth = BlockPerMin*60*24*30, - % Calculate epoch length in blocks, cap at election interval + grace period - EpochLength = erlang:min(End - Start + 1, ElectionInterval + ElectionRestartInterval), - Reward = (MonthlyReward/BlockPerMonth) * EpochLength, - Extra = calculate_net_emissions_reward(Ledger), - Reward + Extra. - --spec consensus_members_rewards(blockchain_ledger_v1:ledger(), - reward_vars(), - non_neg_integer()) -> rewards_map(). -consensus_members_rewards(Ledger, #{consensus_epoch_reward := EpochReward, - consensus_percent := ConsensusPercent}, OverageTotal) -> - GwOrVal = - case blockchain:config(?election_version, Ledger) of - {ok, N} when N >= 5 -> - validator; - _ -> - gateway - end, - {ok, Members} = blockchain_ledger_v1:consensus_members(Ledger), - Count = erlang:length(Members), - OveragePerMember = OverageTotal div Count, - ConsensusReward = EpochReward * ConsensusPercent, - lists:foldl( - fun(Member, Acc) -> - PercentofReward = 100/Count/100, - Amount = erlang:round(PercentofReward*ConsensusReward), - %% in transitional blocks and in the last reward block of v5 it's possible to still - %% have gateways in who need to be rewarded, so make sure that everyone gets tagged - %% correctly with the proper code path - Actual = - case GwOrVal of - validator -> - case blockchain_ledger_v1:get_validator(Member, Ledger) of - {ok, _} -> validator; - {error, not_found} -> gateway - end; - gateway -> gateway - end, - maps:put({Actual, consensus, Member}, Amount+OveragePerMember, Acc) - end, - #{}, - Members). - --spec securities_rewards(blockchain_ledger_v1:ledger(), - reward_vars()) -> rewards_map(). -securities_rewards(Ledger, #{epoch_reward := EpochReward, - securities_percent := SecuritiesPercent}) -> - Securities = blockchain_ledger_v1:securities(Ledger), - TotalSecurities = maps:fold( - fun(_, Entry, Acc) -> - Acc + blockchain_ledger_security_entry_v1:balance(Entry) - end, - 0, - Securities - ), - SecuritiesReward = EpochReward * SecuritiesPercent, - maps:fold( - fun(Key, Entry, Acc) -> - Balance = blockchain_ledger_security_entry_v1:balance(Entry), - PercentofReward = (Balance*100/TotalSecurities)/100, - Amount = erlang:round(PercentofReward*SecuritiesReward), - maps:put({owner, securities, Key}, Amount, Acc) - end, - #{}, - Securities - ). - --spec poc_challenger_reward( Txn :: blockchain_txn:txn(), - Acc :: rewards_share_map(), - Vars :: reward_vars() ) -> rewards_share_map(). -poc_challenger_reward(Txn, ChallengerRewards, #{poc_version := Version}) -> - TxnType = blockchain_txn:type(Txn), - Challenger = TxnType:challenger(Txn), - I = maps:get(Challenger, ChallengerRewards, 0), - case TxnType:check_path_continuation( - TxnType:path(Txn)) of - true when is_integer(Version) andalso Version > 4 -> - maps:put(Challenger, I+2, ChallengerRewards); - _ -> - maps:put(Challenger, I+1, ChallengerRewards) - end. - --spec normalize_challenger_rewards( ChallengerRewards :: rewards_share_map(), - Vars :: reward_vars() ) -> rewards_map(). -normalize_challenger_rewards(ChallengerRewards, - #{epoch_reward := EpochReward, - poc_challengers_percent := PocChallengersPercent, - poc_challenger_type := PocChallengerType}=Vars) -> - TotalChallenged = lists:sum(maps:values(ChallengerRewards)), - ShareOfDCRemainder = - case PocChallengerType of - validator -> - 0; - _ -> - share_of_dc_rewards(poc_challengers_percent, Vars) - end, - ChallengersReward = (EpochReward * PocChallengersPercent) + ShareOfDCRemainder, - maps:fold( - fun(Challenger, Challenged, Acc) -> - PercentofReward = (Challenged*100/TotalChallenged)/100, - Amount = erlang:round(PercentofReward * ChallengersReward), - maps:put({PocChallengerType, poc_challengers, Challenger}, Amount, Acc) - end, - #{}, - ChallengerRewards - ). - --spec normalize_challengee_rewards( ChallengeeRewards :: rewards_share_map(), - Vars :: reward_vars() ) -> rewards_map(). -normalize_challengee_rewards(ChallengeeRewards, #{epoch_reward := EpochReward, - poc_challengees_percent := PocChallengeesPercent}=Vars) -> - TotalChallenged = lists:sum(maps:values(ChallengeeRewards)), - ShareOfDCRemainder = share_of_dc_rewards(poc_challengees_percent, Vars), - ChallengeesReward = (EpochReward * PocChallengeesPercent) + ShareOfDCRemainder, - maps:fold( - fun(Challengee, Challenged, Acc) -> - PercentofReward = (Challenged*100/TotalChallenged)/100, - % TODO: Not sure about the all round thing... - Amount = erlang:round(PercentofReward*ChallengeesReward), - maps:put({gateway, poc_challengees, Challengee}, Amount, Acc) - end, - #{}, - ChallengeeRewards - ). - --spec poc_challengees_rewards_( Vars :: reward_vars(), - Paths :: blockchain_poc_path_element_v1:poc_path(), - StaticPath :: blockchain_poc_path_element_v1:poc_path(), - Txn :: blockchain_poc_receipts_v1:txn_poc_receipts(), - Chain :: blockchain:blockchain(), - Ledger :: blockchain_ledger_v1:ledger(), - IsFirst :: boolean(), - VarMap :: blockchain_hex:var_map(), - Acc0 :: rewards_share_map() ) -> rewards_share_map(). -poc_challengees_rewards_(_Vars, [], _StaticPath, _Txn, _Chain, _Ledger, _, _, Acc) -> - Acc; -poc_challengees_rewards_(#{poc_version := Version}=Vars, - [Elem|Path], - StaticPath, - Txn, - Chain, - Ledger, - IsFirst, - VarMap, - Acc0) when Version >= 2 -> - RegionVars = maps:get(region_vars, Vars), % explode on purpose - TxnType = blockchain_txn:type(Txn), - WitnessRedundancy = maps:get(witness_redundancy, Vars, undefined), - DecayRate = maps:get(poc_reward_decay_rate, Vars, undefined), - DensityTgtRes = maps:get(density_tgt_res, Vars, undefined), - HIP15TxRewardUnitCap = maps:get(hip15_tx_reward_unit_cap, Vars, undefined), - %% check if there were any legitimate witnesses - WitStart = erlang:monotonic_time(microsecond), - Witnesses = legit_witnesses(Txn, Chain, Ledger, Elem, StaticPath, RegionVars, Version), - WitEnd = erlang:monotonic_time(microsecond), - perf({challengee, witness}, WitEnd - WitStart), - Challengee = blockchain_poc_path_element_v1:challengee(Elem), - ChallengeeLoc = case blockchain_ledger_v1:find_gateway_location(Challengee, Ledger) of - {ok, CLoc} -> - CLoc; - _ -> - undefined - end, - I = maps:get(Challengee, Acc0, 0), - case blockchain_poc_path_element_v1:receipt(Elem) of - undefined -> - Acc1 = case - Witnesses /= [] orelse - TxnType:check_path_continuation(Path) - of - true when is_integer(Version), Version > 4, IsFirst == true -> - %% while we don't have a receipt for this node, we do know - %% there were witnesses or the path continued which means - %% the challengee transmitted - case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of - {error, _} -> - %% Old behavior - maps:put(Challengee, I+1, Acc0); - {ok, ToAdd} -> - TxScale = maybe_calc_tx_scale(Challengee, - DensityTgtRes, - ChallengeeLoc, - VarMap, - Ledger), - maps:put(Challengee, I+(ToAdd * TxScale), Acc0) - end; - true when is_integer(Version), Version > 4, IsFirst == false -> - %% while we don't have a receipt for this node, we do know - %% there were witnesses or the path continued which means - %% the challengee transmitted - %% Additionally, we know this layer came in over radio so - %% there's an implicit rx as well - case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of - {error, _} -> - %% Old behavior - maps:put(Challengee, I+2, Acc0); - {ok, ToAdd} -> - TxScale = maybe_calc_tx_scale(Challengee, - DensityTgtRes, - ChallengeeLoc, - VarMap, - Ledger), - maps:put(Challengee, I+(ToAdd * TxScale), Acc0) - end; - _ -> - Acc0 - end, - poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc1); - Receipt -> - case blockchain_poc_receipt_v1:origin(Receipt) of - radio -> - Acc1 = case - Witnesses /= [] orelse - TxnType:check_path_continuation(Path) - of - true when is_integer(Version), Version > 4 -> - %% this challengee both rx'd and tx'd over radio - %% AND sent a receipt - %% so give them 3 payouts - case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of - {error, _} -> - %% Old behavior - maps:put(Challengee, I+3, Acc0); - {ok, ToAdd} -> - TxScale = maybe_calc_tx_scale(Challengee, - DensityTgtRes, - ChallengeeLoc, - VarMap, - Ledger), - maps:put(Challengee, I+(ToAdd * TxScale), Acc0) - end; - false when is_integer(Version), Version > 4 -> - %% this challengee rx'd and sent a receipt - case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of - {error, _} -> - %% Old behavior - maps:put(Challengee, I+2, Acc0); - {ok, ToAdd} -> - TxScale = maybe_calc_tx_scale(Challengee, - DensityTgtRes, - ChallengeeLoc, - VarMap, - Ledger), - maps:put(Challengee, I+(ToAdd * TxScale), Acc0) - end; - _ -> - %% Old behavior - maps:put(Challengee, I+1, Acc0) - end, - poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc1); - p2p -> - %% if there are legitimate witnesses or the path continues - %% the challengee did their job - Acc1 = case - Witnesses /= [] orelse - TxnType:check_path_continuation(Path) - of - false -> - %% path did not continue, this is an 'all gray' path - Acc0; - true when is_integer(Version), Version > 4 -> - %% Sent a receipt and the path continued on - case poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) of - {error, _} -> - %% Old behavior - maps:put(Challengee, I+2, Acc0); - {ok, ToAdd} -> - TxScale = maybe_calc_tx_scale(Challengee, - DensityTgtRes, - ChallengeeLoc, - VarMap, - Ledger), - maps:put(Challengee, I+(ToAdd * TxScale), Acc0) - end; - true -> - maps:put(Challengee, I+1, Acc0) - end, - poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc1) - end - end; -poc_challengees_rewards_(Vars, [Elem|Path], StaticPath, Txn, Chain, Ledger, _IsFirst, VarMap, Acc0) -> - case blockchain_poc_path_element_v1:receipt(Elem) of - undefined -> - poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc0); - _Receipt -> - Challengee = blockchain_poc_path_element_v1:challengee(Elem), - I = maps:get(Challengee, Acc0, 0), - Acc1 = maps:put(Challengee, I+1, Acc0), - poc_challengees_rewards_(Vars, Path, StaticPath, Txn, Chain, Ledger, false, VarMap, Acc1) - end. - --spec poc_challengee_reward_unit(WitnessRedundancy :: undefined | pos_integer(), - DecayRate :: undefined | float(), - HIP15TxRewardUnitCap :: undefined | float(), - Witnesses :: blockchain_poc_witness_v1:poc_witnesses()) -> {error, any()} | {ok, float()}. -poc_challengee_reward_unit(WitnessRedundancy, DecayRate, HIP15TxRewardUnitCap, Witnesses) -> - case {WitnessRedundancy, DecayRate} of - {undefined, _} -> {error, witness_redundancy_undefined}; - {_, undefined} -> {error, poc_reward_decay_rate_undefined}; - {N, R} -> - W = length(Witnesses), - Unit = poc_reward_tx_unit(R, W, N), - NUnit = normalize_reward_unit(HIP15TxRewardUnitCap, Unit), - {ok, NUnit} - end. - --spec normalize_reward_unit(HIP15TxRewardUnitCap :: undefined | float(), Unit :: float()) -> float(). -normalize_reward_unit(undefined, Unit) when Unit > 1.0 -> 1.0; -normalize_reward_unit(undefined, Unit) -> Unit; -normalize_reward_unit(HIP15TxRewardUnitCap, Unit) when Unit >= HIP15TxRewardUnitCap -> HIP15TxRewardUnitCap; -normalize_reward_unit(_TxRewardUnitCap, Unit) -> Unit. - --spec normalize_reward_unit(Unit :: float()) -> float(). -normalize_reward_unit(Unit) when Unit > 1.0 -> 1.0; -normalize_reward_unit(Unit) -> Unit. - --spec poc_witness_reward( Txn :: blockchain_txn_poc_receipts_v1:txn_poc_receipts() | - blockchain_txn_poc_receipts_v2:txn_poc_receipts(), - AccIn :: rewards_share_map(), - Chain :: blockchain:blockchain(), - Ledger :: blockchain_ledger_v1:ledger(), - Vars :: reward_vars() ) -> rewards_share_map(). -poc_witness_reward(Txn, AccIn, - Chain, Ledger, - #{ poc_version := POCVersion, - var_map := VarMap } = Vars) when is_integer(POCVersion) - andalso POCVersion >= 9 -> - TxnType = blockchain_txn:type(Txn), - WitnessRedundancy = maps:get(witness_redundancy, Vars, undefined), - DecayRate = maps:get(poc_reward_decay_rate, Vars, undefined), - DensityTgtRes = maps:get(density_tgt_res, Vars, undefined), - RegionVars = maps:get(region_vars, Vars), % explode on purpose - KeyHash = TxnType:onion_key_hash(Txn), - - try - %% Get channels without validation - {ok, Channels} = TxnType:get_channels(Txn, POCVersion, RegionVars, Chain), - Path = TxnType:path(Txn), - - %% Do the new thing for witness filtering - lists:foldl( - fun(Elem, Acc1) -> - ElemPos = blockchain_utils:index_of(Elem, Path), - WitnessChannel = lists:nth(ElemPos, Channels), - WitStart = erlang:monotonic_time(microsecond), - ElemHash = erlang:phash2(Elem), - ValidWitnesses = - case get({KeyHash, ElemHash}) of - undefined -> - VW = TxnType:valid_witnesses(Elem, WitnessChannel, RegionVars, Ledger), - put({KeyHash, ElemHash}, VW), - VW; - VW -> VW - end, - case ValidWitnesses of - [] -> Acc1; - [_|_] -> - %% lager:info("witness witness ~p", [erlang:phash2(ValidWitnesses)]), - WitEnd = erlang:monotonic_time(microsecond), - perf({witnesses, witness}, WitEnd - WitStart), - %% We found some valid witnesses, we only apply - %% the witness_redundancy and decay_rate if - %% BOTH are set as chain variables, otherwise - %% we default to the old behavior and set - %% ToAdd=1 - %% - %% If both witness_redundancy and decay_rate - %% are set, we calculate a scaled rx unit (the - %% value ToAdd) - %% - %% This is determined using the formulae - %% mentioned in hip15 - ToAdd = case {WitnessRedundancy, DecayRate} of - {undefined, _} -> 1; - {_, undefined} -> 1; - {N, R} -> - W = length(ValidWitnesses), - poc_witness_reward_unit(R, W, N) - end, - - case DensityTgtRes of - undefined -> - %% old (HIP15) - lists:foldl( - fun(WitnessRecord, Acc2) -> - Witness = blockchain_poc_witness_v1:gateway(WitnessRecord), - {C, I} = maps:get(Witness, Acc2, {0, 0}), - maps:put(Witness, {C+1, I+(ToAdd * witness_decay(C, Vars))}, Acc2) - end, - Acc1, - ValidWitnesses); - D -> - %% new (HIP17) - lists:foldl( - fun(WitnessRecord, Acc2) -> - Challengee = blockchain_poc_path_element_v1:challengee(Elem), - %% This must always be {ok, ...} - %% Challengee must have a location - {ok, ChallengeeLoc} = - blockchain_ledger_v1:find_gateway_location(Challengee, Ledger), - Witness = - blockchain_poc_witness_v1:gateway(WitnessRecord), - %% The witnesses get scaled by the value of their transmitters - RxScale = blockchain_utils:normalize_float( - blockchain_hex:scale(ChallengeeLoc, - VarMap, - D, - Ledger)), - Value = blockchain_utils:normalize_float(ToAdd * RxScale), - {C, I} = maps:get(Witness, Acc2, {0, 0}), - maps:put(Witness, {C+1, I+(Value * witness_decay(C, Vars))}, Acc2) - end, - Acc1, - ValidWitnesses) - end - end - end, - AccIn, - Path) - catch - throw:{error, {unknown_region, Region}}:_ST -> - lager:error("Reported unknown_region: ~p", [Region]), - AccIn; - What:Why:ST -> - lager:error("failed to calculate poc_witness_rewards, error ~p:~p:~p", [What, Why, ST]), - AccIn - end; -poc_witness_reward(Txn, AccIn, _Chain, Ledger, - #{ poc_version := POCVersion } = Vars) when is_integer(POCVersion) - andalso POCVersion > 4 -> - TxnType = blockchain_txn:type(Txn), - lists:foldl( - fun(Elem, A) -> - case TxnType:good_quality_witnesses(Elem, Ledger) of - [] -> - A; - GoodQualityWitnesses -> - lists:foldl( - fun(WitnessRecord, Map) -> - Witness = blockchain_poc_witness_v1:gateway(WitnessRecord), - {C, I} = maps:get(Witness, Map, {0, 0}), - maps:put(Witness, {C+1, I+(1 * witness_decay(C, Vars))}, Map) - end, - A, - GoodQualityWitnesses) - end - end, - AccIn, - TxnType:path(Txn) - ); -poc_witness_reward(Txn, AccIn, _Chain, _Ledger, Vars) -> - TxnType = blockchain_txn:type(Txn), - lists:foldl( - fun(Elem, A) -> - lists:foldl( - fun(WitnessRecord, Map) -> - Witness = blockchain_poc_witness_v1:gateway(WitnessRecord), - {C, I} = maps:get(Witness, Map, {0, 0}), - maps:put(Witness, {C+1, I+(1 * witness_decay(C, Vars))}, Map) - end, - A, - blockchain_poc_path_element_v1:witnesses(Elem)) - end, - AccIn, - TxnType:path(Txn)). - --spec normalize_witness_rewards( WitnessRewards :: rewards_share_map(), - Vars :: reward_vars() ) -> rewards_map(). -normalize_witness_rewards(WitnessRewards, #{epoch_reward := EpochReward, - poc_witnesses_percent := PocWitnessesPercent}=Vars) -> - TotalWitnesses = lists:sum(element(2, lists:unzip(maps:values(WitnessRewards)))), - ShareOfDCRemainder = share_of_dc_rewards(poc_witnesses_percent, Vars), - WitnessesReward = (EpochReward * PocWitnessesPercent) + ShareOfDCRemainder, - maps:fold( - fun(Witness, {_Count, Witnessed}, Acc) -> - PercentofReward = (Witnessed*100/TotalWitnesses)/100, - Amount = erlang:round(PercentofReward*WitnessesReward), - maps:put({gateway, poc_witnesses, Witness}, Amount, Acc) - end, - #{}, - WitnessRewards - ). - --spec collect_dc_rewards_from_previous_epoch_grace( - Start :: non_neg_integer(), - End :: non_neg_integer(), - Chain :: blockchain:blockchain(), - Vars :: reward_vars(), - Ledger :: blockchain_ledger_v1:ledger()) -> {ok, dc_rewards_share_map()} | {error, any()}. -collect_dc_rewards_from_previous_epoch_grace(Start, End, Chain, - #{sc_grace_blocks := Grace, - reward_version := RV} = Vars, - Ledger) when RV > 4 -> - scan_grace_block(max(1, Start - Grace), Start, End, Vars, Chain, Ledger, #{}); -collect_dc_rewards_from_previous_epoch_grace(_Start, _End, _Chain, _Vars, _Ledger) -> {ok, #{}}. - --spec scan_grace_block( Current :: pos_integer(), - Start :: pos_integer(), - End :: pos_integer(), - Vars :: reward_vars(), - Chain :: blockchain:blockchain(), - Ledger :: blockchain_ledger_v1:ledger(), - Acc :: dc_rewards_share_map() ) -> {ok, dc_rewards_share_map()} | {error, term()}. -scan_grace_block(Current, Start, _End, _Vars, _Chain, _Ledger, Acc) - when Current == Start + 1 -> {ok, Acc}; -scan_grace_block(Current, Start, End, Vars, Chain, Ledger, Acc) -> - case blockchain:get_block(Current, Chain) of - {error, _Error} = Err -> - lager:error("failed to get grace block ~p ~p", [_Error, Current]), - Err; - {ok, Block} -> - Txns = blockchain_block:transactions(Block), - NewAcc = lists:foldl(fun(T, A) -> - case blockchain_txn:type(T) of - blockchain_txn_state_channel_close_v1 -> - dc_reward(T, End, A, Ledger, Vars); - _ -> A - end - end, - Acc, - Txns), - scan_grace_block(Current+1, Start, End, Vars, Chain, Ledger, NewAcc) - end. - --spec dc_reward( Txn :: blockchain_txn_state_channel_close_v1:txn_state_channel_close(), - End :: pos_integer(), - AccIn :: dc_rewards_share_map(), - Ledger :: blockchain_ledger_v1:ledger(), - Vars :: reward_vars() ) -> dc_rewards_share_map(). -dc_reward(Txn, End, AccIn, Ledger, #{ sc_grace_blocks := GraceBlocks, - sc_version := 2} = Vars) -> - case blockchain_txn_state_channel_close_v1:state_channel_expire_at(Txn) + GraceBlocks < End of - true -> - SCID = blockchain_txn_state_channel_close_v1:state_channel_id(Txn), - case lists:member(SCID, maps:get(seen, AccIn, [])) of - false -> - %% haven't seen this state channel yet, pull the final result from the ledger - case blockchain_ledger_v1:find_state_channel( - blockchain_txn_state_channel_close_v1:state_channel_id(Txn), - blockchain_txn_state_channel_close_v1:state_channel_owner(Txn), - Ledger) of - {ok, SC} -> - %% check for a holdover v1 channel - case blockchain_ledger_state_channel_v2:is_v2(SC) of - true -> - %% pull out the final version of the state channel - FinalSC = blockchain_ledger_state_channel_v2:state_channel(SC), - RewardVersion = maps:get(reward_version, Vars, 1), - CloseState = blockchain_ledger_state_channel_v2:close_state(SC), - SCDisputeStrategy = maps:get(?sc_dispute_strategy_version, Vars, 0), - {Summaries, Bonus} = - case {SCDisputeStrategy, CloseState} of - {Ver, dispute} when Ver >= 1 -> - %% When sc_dispute_strategy_version is 1 we want to zero out as much as possible. - %% No Bonuses, no summaries. All slashed. - {[], 0}; - {_, _} -> - - InnerSummaries = case RewardVersion > 3 of - %% reward version 4 normalizes payouts - true -> blockchain_state_channel_v1:summaries(blockchain_state_channel_v1:normalize(FinalSC)); - false -> blockchain_state_channel_v1:summaries(FinalSC) - end, - - %% check the dispute status - InnerBonus = case blockchain_ledger_state_channel_v2:close_state(SC) of - %% Reward version 4 or higher just slashes overcommit - dispute when RewardVersion < 4 -> - %% the owner of the state channel - %% did a naughty thing, divide - %% their overcommit between the - %% participants - - OverCommit = blockchain_ledger_state_channel_v2:amount(SC) - - blockchain_ledger_state_channel_v2:original(SC), - OverCommit div length(InnerSummaries); - _ -> - 0 - end, - {InnerSummaries, InnerBonus} - end, - - lists:foldl(fun(Summary, A) -> - Key = blockchain_state_channel_summary_v1:client_pubkeybin(Summary), - DCs = blockchain_state_channel_summary_v1:num_dcs(Summary) + Bonus, - maps:update_with(Key, - fun(V) -> V + DCs end, - DCs, - A) - end, - maps:update_with(seen, - fun(Seen) -> [SCID|Seen] end, - [SCID], - AccIn), - Summaries); - false -> - %% this is a v1 SC; ignore - AccIn - end; - {error, not_found} -> - ExpireAt = blockchain_txn_state_channel_close_v1:state_channel_expire_at(Txn), - lager:warning("missing scid ~p", [SCID]), - lager:warning("expire ~p + grace ~p > end ~p?", [ExpireAt, GraceBlocks, End]), - AccIn - end; - true -> - %% we have already seen this SCID before; ignore - AccIn - end; - false -> - %% SC did not close in _this_ epoch; skip it - AccIn - end. - --spec normalize_dc_rewards( DCRewards0 :: dc_rewards_share_map(), - Vars :: reward_vars() ) -> {non_neg_integer(), rewards_map()}. -normalize_dc_rewards(DCRewards0, #{epoch_reward := EpochReward, - dc_percent := DCPercent}=Vars) -> - DCRewards = maps:remove(seen, DCRewards0), - OraclePrice = maps:get(oracle_price, Vars, 0), - RewardVersion = maps:get(reward_version, Vars, 1), - TotalDCs = lists:sum(maps:values(DCRewards)), - MaxDCReward = EpochReward * DCPercent, - %% compute the price HNT equivalent of the DCs burned in this epoch - DCReward = case OraclePrice == 0 orelse RewardVersion < 3 of - true -> - %% no oracle price, or rewards =< 2 - MaxDCReward; - false -> - {ok, DCInThisEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(TotalDCs, OraclePrice), - case DCInThisEpochAsHNT >= MaxDCReward of - true -> - %% we spent enough, just allocate it proportionally - MaxDCReward; - false -> - %% we didn't spend enough this epoch, return the remainder to the pool - DCInThisEpochAsHNT - end - end, - - {max(0, round(MaxDCReward - DCReward)), - maps:fold( - fun(Key, NumDCs, Acc) -> - PercentofReward = (NumDCs*100/TotalDCs)/100, - Amount = erlang:round(PercentofReward*DCReward), - maps:put({gateway, data_credits, Key}, Amount, Acc) - end, - #{}, - DCRewards - )}. - --spec poc_reward_tx_unit(R :: float(), - W :: pos_integer(), - N :: pos_integer()) -> float(). -poc_reward_tx_unit(_R, W, N) when W =< N -> - blockchain_utils:normalize_float(W / N); -poc_reward_tx_unit(R, W, N) -> - NoNorm = 1 + (1 - math:pow(R, (W - N))), - blockchain_utils:normalize_float(NoNorm). - --spec poc_witness_reward_unit(R :: float(), - W :: pos_integer(), - N :: pos_integer()) -> float(). -poc_witness_reward_unit(_R, W, N) when W =< N -> - 1.0; -poc_witness_reward_unit(R, W, N) -> - %% It's okay to call the previously broken normalize_reward_unit here because - %% the value does not asympotically tend to 2.0, instead it tends to 0.0 - normalize_reward_unit(blockchain_utils:normalize_float((N - (1 - math:pow(R, (W - N))))/W)). - --spec legit_witnesses( Txn :: blockchain_txn_poc_receipts_v1:txn_poc_receipts() | - blockchain_txn_poc_receipts_v2:txn_poc_receipts(), - Chain :: blockchain:blockchain(), - Ledger :: blockchain_ledger_v1:ledger(), - Elem :: blockchain_poc_path_element_v1:poc_element(), - StaticPath :: blockchain_poc_path_element_v1:poc_path(), - RegionVars :: {ok, [{atom(), binary() | {error, any()}}]} | {error, any()}, - Version :: pos_integer() - ) -> [blockchain_txn_poc_witnesses_v1:poc_witness()]. -legit_witnesses(Txn, Chain, Ledger, Elem, StaticPath, RegionVars, Version) -> - TxnType = blockchain_txn:type(Txn), - case Version of - V when is_integer(V), V >= 9 -> - try - %% Get channels without validation - {ok, Channels} = TxnType:get_channels(Txn, Version, RegionVars, Chain), - ElemPos = blockchain_utils:index_of(Elem, StaticPath), - WitnessChannel = lists:nth(ElemPos, Channels), - KeyHash = TxnType:onion_key_hash(Txn), - ElemHash = erlang:phash2(Elem), - ValidWitnesses = - case get({KeyHash, ElemHash}) of - undefined -> - VW = TxnType:valid_witnesses(Elem, WitnessChannel, RegionVars, Ledger), - put({KeyHash, ElemHash}, VW), - VW; - VW -> VW - end, - ValidWitnesses - catch - throw:{error, {unknown_region, Region}}:_ST -> - lager:error("Reported unknown_region: ~p", [Region]), - []; - What:Why:ST -> - lager:error("failed to calculate poc_challengees_rewards, error ~p:~p:~p", [What, Why, ST]), - [] - end; - V when is_integer(V), V > 4 -> - TxnType:good_quality_witnesses(Elem, Ledger); - _ -> - blockchain_poc_path_element_v1:witnesses(Elem) - end. - -maybe_calc_tx_scale(_Challengee, - DensityTgtRes, - ChallengeeLoc, - VarMap, - Ledger) -> - case {DensityTgtRes, ChallengeeLoc} of - {undefined, _} -> 1.0; - {_, undefined} -> 1.0; - {D, Loc} -> - TxScale = blockchain_hex:scale(Loc, VarMap, D, Ledger), - %% lager:info("Challengee: ~p, TxScale: ~p", - %% [blockchain_utils:addr2name(Challengee), TxScale]), - blockchain_utils:normalize_float(TxScale) - end. - -share_of_dc_rewards(_Key, #{dc_remainder := 0}) -> - 0; -share_of_dc_rewards(Key, Vars=#{dc_remainder := DCRemainder, - poc_challenger_type := validator}) -> - erlang:round(DCRemainder - * ((maps:get(Key, Vars) / - (maps:get(poc_challengees_percent, Vars) - + maps:get(poc_witnesses_percent, Vars)))) - ); -share_of_dc_rewards(Key, Vars=#{dc_remainder := DCRemainder}) -> - erlang:round(DCRemainder - * ((maps:get(Key, Vars) / - (maps:get(poc_challengers_percent, Vars) - + maps:get(poc_challengees_percent, Vars) - + maps:get(poc_witnesses_percent, Vars)))) - ). - -witness_decay(Count, Vars) -> - case maps:find(witness_reward_decay_rate, Vars) of - {ok, undefined} -> - 1; - {ok, DecayRate} -> - Exclusion = case maps:find(witness_reward_decay_exclusion, Vars) of - {ok, undefined} -> 0; - {ok, ExclusionValue} -> ExclusionValue - end, - case Count < Exclusion of - true -> - 1; - false -> - Scale = math:exp((Count - Exclusion) * -1 * DecayRate), - lager:debug("scaling witness reward by ~p", [Scale]), - Scale - end; - _ -> - 1 - end. - %% ------------------------------------------------------------------ %% EUNIT Tests %% ------------------------------------------------------------------ -ifdef(TEST). -%% @doc Given a list of reward_v1 txns, return the equivalent reward_v2 -%% list. --spec v1_to_v2( RewardsV1 :: [blockchain_txn_reward_v1:rewards()] ) -> rewards(). -v1_to_v2(RewardsV1) -> - R = lists:foldl(fun(R, Acc) -> - Owner = blockchain_txn_reward_v1:account(R), - Amt = blockchain_txn_reward_v1:amount(R), - maps:update_with(Owner, fun(Balance) -> Balance + Amt end, Amt, Acc) - end, - #{}, - RewardsV1), - lists:sort(maps:fold(fun(_O, 0, Acc) -> Acc; %% drop any 0 amount reward, as in v2 - (O, A, Acc) -> [ new_reward(O, A) | Acc ] end, - [], - R)). - -new_test() -> - Tx = #blockchain_txn_rewards_v2_pb{start_epoch=1, end_epoch=30, rewards=[]}, - ?assertEqual(Tx, new(1, 30, [])). - -start_epoch_test() -> - Tx = new(1, 30, []), - ?assertEqual(1, start_epoch(Tx)). - -end_epoch_test() -> - Tx = new(1, 30, []), - ?assertEqual(30, end_epoch(Tx)). - -rewards_test() -> - Tx = new(1, 30, []), - ?assertEqual([], rewards(Tx)). - -poc_challengers_rewards_2_test() -> - ReceiptForA = blockchain_poc_receipt_v1:new(<<"a">>, 1, 1, <<"data">>, radio), - ElemForA = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, []), - - Txns = [ - blockchain_txn_poc_receipts_v2:new(<<"a">>, <<"Secret">>, <<"OnionKeyHash">>, [], <<"BlockHash">>), - blockchain_txn_poc_receipts_v2:new(<<"b">>, <<"Secret">>, <<"OnionKeyHash">>, [], <<"BlockHash">>), - blockchain_txn_poc_receipts_v2:new(<<"c">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForA], <<"BlockHash">>) - ], - Vars = #{ - epoch_reward => 1000, - poc_challengers_percent => 0.15, - poc_witnesses_percent => 0.0, - poc_challengees_percent => 0.0, - dc_remainder => 0, - poc_version => 5, - poc_challenger_type => validator - }, - Rewards = #{ - {validator, poc_challengers, <<"a">>} => 38, - {validator, poc_challengers, <<"b">>} => 38, - {validator, poc_challengers, <<"c">>} => 75 - }, - ChallengerShares = lists:foldl(fun(T, Acc) -> poc_challenger_reward(T, Acc, Vars) end, #{}, Txns), - ?assertEqual(Rewards, normalize_challenger_rewards(ChallengerShares, Vars)), - - AltVars = Vars#{ poc_challenger_type => gateway }, - AltRewards = #{ - {gateway, poc_challengers, <<"a">>} => 38, - {gateway, poc_challengers, <<"b">>} => 38, - {gateway, poc_challengers, <<"c">>} => 75 - }, - ?assertEqual(AltRewards, normalize_challenger_rewards(ChallengerShares, AltVars)). - -poc_challengees_rewards_3_test() -> - BaseDir = test_utils:tmp_dir("poc_challengees_rewards_3_test"), - Block = blockchain_block:new_genesis_block([]), - {ok, Chain} = blockchain:new(BaseDir, Block, undefined, undefined), - Ledger = blockchain:ledger(Chain), - Ledger1 = blockchain_ledger_v1:new_context(Ledger), - - Vars = #{ - epoch_reward => 1000, - poc_challengees_percent => 0.35, - poc_witnesses_percent => 0.0, - poc_challengers_percent => 0.0, - dc_remainder => 0, - poc_version => 5, - region_vars => [] - }, - - LedgerVars = maps:put(?poc_version, 5, common_poc_vars()), - - ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), - - One = 631179381270930431, - Two = 631196173757531135, - Three = 631196173214364159, - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"a">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"a">>, One, 1, Ledger1), - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"b">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"b">>, Two, 1, Ledger1), - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"c">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"c">>, Three, 1, Ledger1), - - ok = blockchain_ledger_v1:commit_context(Ledger1), - - ReceiptForA = blockchain_poc_receipt_v1:new(<<"a">>, 1, -120, <<"data">>, radio), - WitnessForA = blockchain_poc_witness_v1:new(<<"c">>, 1, -120, <<>>), - ReceiptForB = blockchain_poc_receipt_v1:new(<<"b">>, 1, -70, <<"data">>, radio), - WitnessForB = blockchain_poc_witness_v1:new(<<"c">>, 1, -120, <<>>), - ReceiptForC = blockchain_poc_receipt_v1:new(<<"c">>, 1, -120, <<"data">>, radio), - - ElemForA = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, []), - ElemForAWithWitness = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, [WitnessForA]), - ElemForB = blockchain_poc_path_element_v1:new(<<"b">>, undefined, []), - ElemForBWithWitness = blockchain_poc_path_element_v1:new(<<"b">>, ReceiptForB, [WitnessForB]), - ElemForC = blockchain_poc_path_element_v1:new(<<"c">>, ReceiptForC, []), - - Txns = [ - %% No rewards here, Only receipt with no witness or subsequent receipt - blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForB, ElemForA], <<"BlockHash">>), %% 1, 2 - %% Reward because of witness - blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForAWithWitness], <<"BlockHash">>), %% 3 - %% Reward because of next elem has receipt - blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForA, ElemForB, ElemForC], <<"BlockHash">>), %% 3, 2, 2 - %% Reward because of witness (adding to make reward 50/50) - blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForBWithWitness], <<"BlockHash">>) %% 3 - ], - Rewards = #{ - %% a gets 8 shares - {gateway, poc_challengees, <<"a">>} => 175, - %% b gets 6 shares - {gateway, poc_challengees, <<"b">>} => 131, - %% c gets 2 shares - {gateway, poc_challengees, <<"c">>} => 44 - }, - ChallengeeShares = lists:foldl(fun(T, Acc) -> - Path = blockchain_txn_poc_receipts_v2:path(T), - poc_challengees_rewards_(Vars, Path, Path, T, Chain, Ledger, true, #{}, Acc) - end, - #{}, - Txns), - ?assertEqual(Rewards, normalize_challengee_rewards(ChallengeeShares, Vars)), - test_utils:cleanup_tmp_dir(BaseDir). - -poc_witnesses_rewards_test() -> - BaseDir = test_utils:tmp_dir("poc_witnesses_rewards_test"), - Block = blockchain_block:new_genesis_block([]), - {ok, Chain} = blockchain:new(BaseDir, Block, undefined, undefined), - Ledger = blockchain:ledger(Chain), - Ledger1 = blockchain_ledger_v1:new_context(Ledger), - EpochVars = #{ - epoch_reward => 1000, - poc_witnesses_percent => 0.05, - poc_challengees_percent => 0.0, - poc_challengers_percent => 0.0, - dc_remainder => 0, - poc_version => 5 - }, - - LedgerVars = maps:merge(common_poc_vars(), EpochVars), - - ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), - - One = 631179381270930431, - Two = 631196173757531135, - Three = 631196173214364159, - Four = 631179381325720575, - Five = 631179377081096191, - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"a">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"a">>, One, 1, Ledger1), - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"b">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"b">>, Two, 1, Ledger1), - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"c">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"c">>, Three, 1, Ledger1), - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"d">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"d">>, Four, 1, Ledger1), - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"e">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"e">>, Five, 1, Ledger1), - - ok = blockchain_ledger_v1:commit_context(Ledger1), - - Witness1 = blockchain_poc_witness_v1:new(<<"a">>, 1, -80, <<>>), - Witness2 = blockchain_poc_witness_v1:new(<<"b">>, 1, -80, <<>>), - Elem = blockchain_poc_path_element_v1:new(<<"c">>, <<"Receipt not undefined">>, [Witness1, Witness2]), - Txns = [ - blockchain_txn_poc_receipts_v2:new(<<"d">>, <<"Secret">>, <<"OnionKeyHash">>, [Elem, Elem], <<"BlockHash">>), - blockchain_txn_poc_receipts_v2:new(<<"e">>, <<"Secret">>, <<"OnionKeyHash">>, [Elem, Elem], <<"BlockHash">>) - ], - - Rewards = #{{gateway,poc_witnesses,<<"a">>} => 25, - {gateway,poc_witnesses,<<"b">>} => 25}, - - WitnessShares = lists:foldl(fun(T, Acc) -> poc_witness_reward(T, Acc, Chain, Ledger, EpochVars) end, - #{}, Txns), - ?assertEqual(Rewards, normalize_witness_rewards(WitnessShares, EpochVars)), - test_utils:cleanup_tmp_dir(BaseDir). - -dc_rewards_sc_dispute_strategy_test() -> - BaseDir = test_utils:tmp_dir("dc_rewards_dispute_sc_test"), - Ledger = blockchain_ledger_v1:new(BaseDir), - Ledger1 = blockchain_ledger_v1:new_context(Ledger), - - Vars = #{ - epoch_reward => 100000, - dc_percent => 0.3, - consensus_percent => 0.06 + 0.025, - poc_challengees_percent => 0.18, - poc_challengers_percent => 0.0095, - poc_witnesses_percent => 0.0855, - securities_percent => 0.34, - sc_version => 2, - sc_grace_blocks => 5, - reward_version => 4, - oracle_price => 100000000, %% 1 dollar - consensus_members => [<<"c">>, <<"d">>], - %% This is the important part of what's being tested here. - sc_dispute_strategy_version => 1 - }, - - LedgerVars = maps:merge(#{?poc_version => 5, ?sc_version => 2, ?sc_grace_blocks => 5}, common_poc_vars()), - ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), - - {SC0, _} = blockchain_state_channel_v1:new(<<"id">>, <<"owner">>, 100, <<"blockhash">>, 10), - SCValid = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 1, 1), blockchain_state_channel_summary_v1:new(<<"b">>, 2, 2)], SC0), - SCDispute = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 2, 2), blockchain_state_channel_summary_v1:new(<<"b">>, 3, 3)], SC0), - - ok = blockchain_ledger_v1:add_state_channel(<<"id">>, <<"owner">>, 10, 1, 100, 200, Ledger1), - {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), - - ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"owner">>, SCValid, <<"id">>, false, Ledger1), - {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), - - ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"a">>, SCDispute, <<"id">>, true, Ledger1), - - SCClose = blockchain_txn_state_channel_close_v1:new(SCValid, <<"owner">>), - {ok, _DCsInEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(3, 100000000), %% 3 DCs burned at HNT price of 1 dollar - - DCShares = dc_reward(SCClose, 100, #{}, Ledger1, Vars), - - %% We only care that no rewards are generated when sc_dispute_strategy_version is active. - {Res, M} = normalize_dc_rewards(DCShares, Vars), - ?assertEqual(#{}, M, "no summaries in rewards map"), - ?assertEqual(30000, Res), - test_utils:cleanup_tmp_dir(BaseDir). - -dc_rewards_v3_test() -> - BaseDir = test_utils:tmp_dir("dc_rewards_v3_test"), - Ledger = blockchain_ledger_v1:new(BaseDir), - Ledger1 = blockchain_ledger_v1:new_context(Ledger), - - Vars = #{ - epoch_reward => 100000, - dc_percent => 0.3, - consensus_percent => 0.06 + 0.025, - poc_challengees_percent => 0.18, - poc_challengers_percent => 0.0095, - poc_witnesses_percent => 0.0855, - securities_percent => 0.34, - sc_version => 2, - sc_grace_blocks => 5, - sc_dispute_strategy_version => 0, - reward_version => 3, - oracle_price => 100000000, %% 1 dollar - consensus_members => [<<"c">>, <<"d">>] - }, - - LedgerVars = maps:merge(#{?poc_version => 5, ?sc_version => 2, ?sc_grace_blocks => 5}, common_poc_vars()), - - ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), - - {SC0, _} = blockchain_state_channel_v1:new(<<"id">>, <<"owner">>, 100, <<"blockhash">>, 10), - SC = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 1, 1), blockchain_state_channel_summary_v1:new(<<"b">>, 2, 2)], SC0), - - ok = blockchain_ledger_v1:add_state_channel(<<"id">>, <<"owner">>, 10, 1, 100, 200, Ledger1), - - {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), - - ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"owner">>, SC, <<"id">>, false, Ledger1), - - {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), - - - SCClose = blockchain_txn_state_channel_close_v1:new(SC, <<"owner">>), - {ok, DCsInEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(3, 100000000), %% 3 DCs burned at HNT price of 1 dollar - %% NOTE: Rewards are split 33-66% - Rewards = #{ - {gateway, data_credits, <<"a">>} => round(DCsInEpochAsHNT * (1/3)), - {gateway, data_credits, <<"b">>} => round(DCsInEpochAsHNT * (2/3)) - }, - DCShares = dc_reward(SCClose, 100, #{}, Ledger1, Vars), - ?assertEqual({26999, Rewards}, normalize_dc_rewards(DCShares, Vars)), - test_utils:cleanup_tmp_dir(BaseDir). - -dc_rewards_v3_spillover_test() -> - BaseDir = test_utils:tmp_dir("dc_rewards_v3_spillover_test"), - Block = blockchain_block:new_genesis_block([]), - {ok, Chain} = blockchain:new(BaseDir, Block, undefined, undefined), - Ledger = blockchain:ledger(Chain), - Ledger1 = blockchain_ledger_v1:new_context(Ledger), - - Vars = #{ - epoch_reward => 100000, - dc_percent => 0.05, - consensus_percent => 0.1, - poc_challengees_percent => 0.20, - poc_challengers_percent => 0.15, - poc_witnesses_percent => 0.15, - securities_percent => 0.35, - poc_challenger_type => validator, - sc_version => 2, - sc_grace_blocks => 5, - reward_version => 3, - poc_version => 5, - dc_remainder => 0, - oracle_price => 100000000, %% 1 dollar - var_map => undefined, - region_vars => [], - consensus_members => [<<"c">>, <<"d">>] - }, - - LedgerVars = maps:merge(#{?poc_version => 5, ?sc_version => 2, ?sc_grace_blocks => 5}, common_poc_vars()), - - ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1), - - One = 631179381270930431, - Two = 631196173757531135, - Three = 631196173214364159, - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"a">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"a">>, One, 1, Ledger1), - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"b">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"b">>, Two, 1, Ledger1), - - ok = blockchain_ledger_v1:add_gateway(<<"o">>, <<"c">>, Ledger1), - ok = blockchain_ledger_v1:add_gateway_location(<<"c">>, Three, 1, Ledger1), - - ReceiptForA = blockchain_poc_receipt_v1:new(<<"a">>, 1, -120, <<"data">>, radio), - WitnessForA = blockchain_poc_witness_v1:new(<<"c">>, 1, -120, <<>>), - ReceiptForB = blockchain_poc_receipt_v1:new(<<"b">>, 1, -70, <<"data">>, radio), - WitnessForB = blockchain_poc_witness_v1:new(<<"c">>, 1, -120, <<>>), - ReceiptForC = blockchain_poc_receipt_v1:new(<<"c">>, 1, -120, <<"data">>, radio), - - ElemForA = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, []), - ElemForAWithWitness = blockchain_poc_path_element_v1:new(<<"a">>, ReceiptForA, [WitnessForA]), - ElemForB = blockchain_poc_path_element_v1:new(<<"b">>, undefined, []), - ElemForBWithWitness = blockchain_poc_path_element_v1:new(<<"b">>, ReceiptForB, [WitnessForB]), - ElemForC = blockchain_poc_path_element_v1:new(<<"c">>, ReceiptForC, []), - - Txns = [ - %% No rewards here, Only receipt with no witness or subsequent receipt - blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForB, ElemForA], <<"BlockHash">>), %% 1, 2 - %% Reward because of witness - blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForAWithWitness], <<"BlockHash">>), %% 3 - %% Reward because of next elem has receipt - blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForA, ElemForB, ElemForC], <<"BlockHash">>), %% 3, 2, 2 - %% Reward because of witness (adding to make reward 50/50) - blockchain_txn_poc_receipts_v2:new(<<"X">>, <<"Secret">>, <<"OnionKeyHash">>, [ElemForBWithWitness], <<"BlockHash">>) %% 3 - ], - - - {SC0, _} = blockchain_state_channel_v1:new(<<"id">>, <<"owner">>, 100, <<"blockhash">>, 10), - SC = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 1, 1), blockchain_state_channel_summary_v1:new(<<"b">>, 2, 2)], SC0), - - ok = blockchain_ledger_v1:add_state_channel(<<"id">>, <<"owner">>, 10, 1, 100, 200, Ledger1), - - {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), - - ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"owner">>, SC, <<"id">>, false, Ledger1), - - {ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1), - - ok = blockchain_ledger_v1:commit_context(Ledger1), - - Txns2 = [ - blockchain_txn_state_channel_close_v1:new(SC, <<"owner">>) - ], - AllTxns = Txns ++ Txns2, - {ok, DCsInEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(3, 100000000), %% 3 DCs burned at HNT price of 1 dollar - %% NOTE: Rewards are split 33-66% - Rewards = #{ - {gateway, data_credits, <<"a">>} => round(DCsInEpochAsHNT * (1/3)), - {gateway, data_credits, <<"b">>} => round(DCsInEpochAsHNT * (2/3)) - }, - - RewardSharesInit = #{ - dc_rewards => #{}, - poc_challenger => #{}, - poc_challengee => #{}, - poc_witness => #{} - }, - - NoSpillover = lists:foldl(fun(T, Acc) -> calculate_reward_for_txn( - blockchain_txn:type(T), T, 100, - Acc, Chain, Ledger, Vars) - end, - RewardSharesInit, - AllTxns), - DCShares = maps:get(dc_rewards, NoSpillover), - {DCRemainder, DCRewards} = normalize_dc_rewards(DCShares, Vars), - DCAward = trunc(maps:get(epoch_reward, Vars) * maps:get(dc_percent, Vars)), - ?assertEqual({DCAward - DCsInEpochAsHNT, Rewards}, {DCRemainder, DCRewards}), - - NewVars = maps:put(dc_remainder, DCRemainder, Vars), - - Spillover = lists:foldl(fun(T, Acc) -> calculate_reward_for_txn( - blockchain_txn:type(T), T, 100, - Acc, Chain, Ledger, NewVars) - end, - RewardSharesInit, - AllTxns), - - ChallengerShares = maps:get(poc_challenger, NoSpillover), - ChallengeeShares = maps:get(poc_challengee, NoSpillover), - WitnessShares = maps:get(poc_witness, NoSpillover), - - ChallengerRewards = normalize_challenger_rewards(ChallengerShares, Vars), - ChallengeeRewards = normalize_challengee_rewards(ChallengeeShares, Vars), - WitnessRewards = normalize_witness_rewards(WitnessShares, Vars), - - ChallengersAward = trunc(maps:get(epoch_reward, Vars) * maps:get(poc_challengers_percent, Vars)), - ?assertEqual(#{{validator,poc_challengers,<<"X">>} => ChallengersAward}, ChallengerRewards), %% entire 15% allocation - ChallengeesAward = trunc(maps:get(epoch_reward, Vars) * maps:get(poc_challengees_percent, Vars)), - ?assertEqual(#{{gateway,poc_challengees,<<"a">>} => trunc(ChallengeesAward * 4/8), %% 4 of 8 shares of 20% allocation - {gateway,poc_challengees,<<"b">>} => trunc(ChallengeesAward * 3/8), %% 3 shares - {gateway,poc_challengees,<<"c">>} => trunc(ChallengeesAward * 1/8)}, %% 1 share - ChallengeeRewards), - WitnessesAward = trunc(maps:get(epoch_reward, Vars) * maps:get(poc_witnesses_percent, Vars)), - ?assertEqual(#{{gateway,poc_witnesses,<<"c">>} => WitnessesAward}, %% entire 15% allocation - WitnessRewards), - - - %% apply the DC remainder, if any to the other PoC categories pro rata - SpilloverChallengerShares = maps:get(poc_challenger, Spillover), - SpilloverChallengeeShares = maps:get(poc_challengee, Spillover), - SpilloverWitnessShares = maps:get(poc_witness, Spillover), - - SpilloverChallengerRewards = normalize_challenger_rewards(SpilloverChallengerShares, NewVars), - SpilloverChallengeeRewards = normalize_challengee_rewards(SpilloverChallengeeShares, NewVars), - SpilloverWitnessRewards = normalize_witness_rewards(SpilloverWitnessShares, NewVars), - - ChallengerSpilloverAward = 0, - - ?assertEqual(#{{validator,poc_challengers,<<"X">>} => ChallengersAward + ChallengerSpilloverAward}, SpilloverChallengerRewards), %% entire 15% allocation - ChallengeeSpilloverAward = erlang:round(DCRemainder * ((maps:get(poc_challengees_percent, Vars) / (maps:get(poc_challengees_percent, Vars) + - maps:get(poc_witnesses_percent, Vars))))), - ?assertEqual(#{{gateway,poc_challengees,<<"a">>} => trunc((ChallengeesAward + ChallengeeSpilloverAward) * 4/8), %% 4 of 8 shares of 20% allocation - {gateway,poc_challengees,<<"b">>} => trunc((ChallengeesAward + ChallengeeSpilloverAward) * 3/8), %% 3 shares - {gateway,poc_challengees,<<"c">>} => round((ChallengeesAward + ChallengeeSpilloverAward) * 1/8)}, %% 1 share - SpilloverChallengeeRewards), - WitnessesSpilloverAward = erlang:round(DCRemainder * ((maps:get(poc_witnesses_percent, Vars) / (maps:get(poc_challengees_percent, Vars) + - maps:get(poc_witnesses_percent, Vars))))), - ?assertEqual(#{{gateway,poc_witnesses,<<"c">>} => WitnessesAward + WitnessesSpilloverAward}, %% entire 15% allocation - SpilloverWitnessRewards), - test_utils:cleanup_tmp_dir(BaseDir). - -to_json_test() -> - Tx = #blockchain_txn_rewards_v2_pb{start_epoch=1, end_epoch=30, rewards=[]}, - Json = to_json(Tx, []), - ?assert(lists:all(fun(K) -> maps:is_key(K, Json) end, - [type, start_epoch, end_epoch, rewards])). - - -fixed_normalize_reward_unit_test() -> - Rewards = [0.1, 0.9, 1.0, 1.8, 2.5, 2000, 0], - - %% Expectation: reward should get capped at 2.0 - Correct = lists:foldl( - fun(Reward, Acc) -> - %% Set cap=2.0 - maps:put(Reward, normalize_reward_unit(2.0, Reward), Acc) - end, #{}, Rewards), - - Incorrect = lists:foldl( - fun(Reward, Acc) -> - %% Set cap=undefined (old behavior) - maps:put(Reward, normalize_reward_unit(undefined, Reward), Acc) - end, #{}, Rewards), - - ?assertEqual(1.8, maps:get(1.8, Correct)), %% 1.8 -> 1.8 - ?assertEqual(0.1, maps:get(0.1, Correct)), %% 0.1 -> 0.1 - ?assertEqual(0.9, maps:get(0.9, Correct)), %% 0.9 -> 0.9 - ?assertEqual(2.0, maps:get(2.5, Correct)), %% 2.5 -> 2.0 - ?assertEqual(1.0, maps:get(2.5, Incorrect)), %% 2.5 -> 1.0 (incorrect) - ?assertEqual(1.0, maps:get(1.8, Incorrect)), %% 1.8 -> 1.0 (incorrect) - ?assertEqual(0.1, maps:get(0.1, Incorrect)), %% 0.1 -> 0.1 - ?assertEqual(0.9, maps:get(0.9, Incorrect)), %% 0.9 -> 0.9 - - ok. - -common_poc_vars() -> - #{ - ?poc_v4_exclusion_cells => 10, - ?poc_v4_parent_res => 11, - ?poc_v4_prob_bad_rssi => 0.01, - ?poc_v4_prob_count_wt => 0.3, - ?poc_v4_prob_good_rssi => 1.0, - ?poc_v4_prob_no_rssi => 0.5, - ?poc_v4_prob_rssi_wt => 0.3, - ?poc_v4_prob_time_wt => 0.3, - ?poc_v4_randomness_wt => 0.1, - ?poc_v4_target_challenge_age => 300, - ?poc_v4_target_exclusion_cells => 6000, - ?poc_v4_target_prob_edge_wt => 0.2, - ?poc_v4_target_prob_score_wt => 0.8, - ?poc_v4_target_score_curve => 5, - ?poc_v5_target_prob_randomness_wt => 0.0 - }. - - -hip28_calc_test() -> - {timeout, 30000, - fun() -> - meck:new(blockchain_ledger_v1, [passthrough]), - meck:expect(blockchain_ledger_v1, hnt_burned, - fun(_Ledger) -> - 0 - end), - meck:expect(blockchain_ledger_v1, net_overage, - fun(_Ledger) -> - 0 - end), - meck:expect(blockchain_ledger_v1, config, - fun(_, _Ledger) -> - 0 - end), - - % set test vars such that rewards are 1 per block - Vars = #{ block_time => 60000, - election_interval => 30, - election_restart_interval => 5, - monthly_reward => 43200, - reward_version => 6 }, - ?assertEqual(30.0, calculate_consensus_epoch_reward(1, 30, Vars, ledger)), - ?assertEqual(35.0, calculate_consensus_epoch_reward(1, 50, Vars, ledger)), - meck:unload(blockchain_ledger_v1), - ok - end}. - -consensus_epoch_reward_test() -> - {timeout, 30000, - fun() -> - meck:new(blockchain_ledger_v1, [passthrough]), - meck:expect(blockchain_ledger_v1, hnt_burned, - fun(_Ledger) -> - 0 - end), - meck:expect(blockchain_ledger_v1, net_overage, - fun(_Ledger) -> - 0 - end), - meck:expect(blockchain_ledger_v1, config, - fun(_, _Ledger) -> - 0 - end), - - %% using test values such that reward is 1 per block - %% should always return the election interval as the answer - ?assertEqual(30.0,calculate_epoch_reward(1, 1, 25, 60000, 30, 43200, ledger)), - - %% more than 30 blocks should return 30 - ?assertEqual(30.0,calculate_epoch_reward(1, 1, 50, 60000, 30, 43200, ledger)), - meck:unload(blockchain_ledger_v1) - end}. - -endif. diff --git a/src/transactions/v1/blockchain_txn_vars_v1.erl b/src/transactions/v1/blockchain_txn_vars_v1.erl index 5cec778073..0924938f5c 100644 --- a/src/transactions/v1/blockchain_txn_vars_v1.erl +++ b/src/transactions/v1/blockchain_txn_vars_v1.erl @@ -1234,6 +1234,14 @@ validate_var(?enable_balance_clearing, Value) -> _ -> throw({error, {invalid_enable_balance_clearing, Value}}) end; +validate_var(?allowed_num_reward_server_keys, Value) -> + case Value of + N when N == 1 -> + %% only supported one reward server for now + ok; + _ -> throw({error, {invalid_allowed_num_reward_server_keys, Value}}) + end; + %% general txn vars validate_var(?txn_field_validation_version, Value) -> diff --git a/test/blockchain_subnetwork_SUITE.erl b/test/blockchain_subnetwork_SUITE.erl new file mode 100644 index 0000000000..393326e2ac --- /dev/null +++ b/test/blockchain_subnetwork_SUITE.erl @@ -0,0 +1,221 @@ +-module(blockchain_subnetwork_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include("blockchain_vars.hrl"). + +-export([all/0, init_per_testcase/2, end_per_testcase/2]). + +-export([ + add_subnetwork_test/1, + failing_subnetwork_test/1 +]). + +%%-------------------------------------------------------------------- +%% COMMON TEST CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Running tests for this suite +%% @end +%%-------------------------------------------------------------------- +all() -> + [ + add_subnetwork_test, + failing_subnetwork_test + ]. + +%%-------------------------------------------------------------------- +%% TEST CASE SETUP +%%-------------------------------------------------------------------- + +init_per_testcase(TestCase, Config) -> + Config0 = blockchain_ct_utils:init_base_dir_config(?MODULE, TestCase, Config), + + HNTBal = 50000, + HSTBal = 10000, + MobileBal = 1000, + IOTBal = 100, + + Config1 = [ + {hnt_bal, HNTBal}, + {hst_bal, HSTBal}, + {mobile_bal, MobileBal}, + {iot_bal, IOTBal} + | Config0 + ], + + {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config1)), + + ExtraVars = extra_vars(TestCase), + TokenAllocations = token_allocations(TestCase, Config1), + + {ok, GenesisMembers, _GenesisBlock, ConsensusMembers, Keys} = + test_utils:init_chain_with_opts( + #{ + balance => + HNTBal, + sec_balance => + HSTBal, + keys => + {PrivKey, PubKey}, + in_consensus => + false, + have_init_dc => + true, + extra_vars => + ExtraVars, + token_allocations => + TokenAllocations + } + ), + + Chain = blockchain_worker:blockchain(), + Ledger = blockchain:ledger(Chain), + Swarm = blockchain_swarm:tid(), + N = length(ConsensusMembers), + + {EntryMod, _} = blockchain_ledger_v1:versioned_entry_mod_and_entries_cf(Ledger), + + [ + {hnt_bal, HNTBal}, + {hst_bal, HSTBal}, + {mobile_bal, MobileBal}, + {iot_bal, IOTBal}, + {entry_mod, EntryMod}, + {sup, Sup}, + {pubkey, PubKey}, + {privkey, PrivKey}, + {opts, Opts}, + {chain, Chain}, + {swarm, Swarm}, + {n, N}, + {consensus_members, ConsensusMembers}, + {genesis_members, GenesisMembers}, + Keys + | Config1 + ]. + +%%-------------------------------------------------------------------- +%% TEST CASE TEARDOWN +%%-------------------------------------------------------------------- + +end_per_testcase(_, Config) -> + Sup = ?config(sup, Config), + meck:unload(), + % Make sure blockchain saved on file = in memory + case erlang:is_process_alive(Sup) of + true -> + true = erlang:exit(Sup, normal), + ok = test_utils:wait_until(fun() -> false =:= erlang:is_process_alive(Sup) end); + false -> + ok + end, + test_utils:cleanup_tmp_dir(?config(base_dir, Config)), + {comment, done}. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +add_subnetwork_test(Config) -> + {NetworkPriv, _} = ?config(master_key, Config), + Chain = ?config(chain, Config), + Ledger = blockchain:ledger(Chain), + ConsensusMembers = ?config(consensus_members, Config), + + %% Generate a random subnetwork signer + [{SubnetworkPubkeyBin, {_SubnetworkPub, _SubnetworkPriv, SubnetworkSigFun}}] = test_utils:generate_keys( + 1 + ), + + %% Generate a random reward server + [{RewardServerPubkeyBin, _}] = test_utils:generate_keys(1), + + NetworkSigfun = libp2p_crypto:mk_sig_fun(NetworkPriv), + + TT = mobile, + Premine = 5000, + T = blockchain_txn_add_subnetwork_v1:new( + TT, SubnetworkPubkeyBin, [RewardServerPubkeyBin], Premine + ), + ST0 = blockchain_txn_add_subnetwork_v1:sign_subnetwork(T, SubnetworkSigFun), + ST = blockchain_txn_add_subnetwork_v1:sign(ST0, NetworkSigfun), + + IsValid = blockchain_txn:is_valid(ST, Chain), + ?assertEqual(ok, IsValid), + + {ok, Block} = test_utils:create_block(ConsensusMembers, [ST]), + _ = blockchain_gossip_handler:add_block(Block, Chain, self(), blockchain_swarm:tid()), + + ?assertEqual({ok, blockchain_block:hash_block(Block)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block}, blockchain:head_block(Chain)), + ?assertEqual({ok, 2}, blockchain:height(Chain)), + + ?assertEqual({ok, Block}, blockchain:get_block(2, Chain)), + + LedgerSubnetworks = blockchain_ledger_v1:subnetworks_v1(Ledger), + LedgerSubnetwork = maps:get(mobile, LedgerSubnetworks), + ?assertEqual(mobile, blockchain_ledger_subnetwork_v1:type(LedgerSubnetwork)), + ?assertEqual(Premine, blockchain_ledger_subnetwork_v1:token_treasury(LedgerSubnetwork)), + ?assertEqual([RewardServerPubkeyBin], blockchain_ledger_subnetwork_v1:reward_server_keys(LedgerSubnetwork)), + ?assertEqual(0, blockchain_ledger_subnetwork_v1:hnt_treasury(LedgerSubnetwork)), + ?assertEqual(0, blockchain_ledger_subnetwork_v1:nonce(LedgerSubnetwork)), + + ok. + +failing_subnetwork_test(Config) -> + {NetworkPriv, _} = ?config(master_key, Config), + Chain = ?config(chain, Config), + + %% Generate a random subnetwork signer + [{SubnetworkPubkeyBin, {_SubnetworkPub, _SubnetworkPriv, SubnetworkSigFun}}] = test_utils:generate_keys( + 1 + ), + + %% Generate a random reward server + [{RewardServerPubkeyBin, _}] = test_utils:generate_keys(1), + + NetworkSigfun = libp2p_crypto:mk_sig_fun(NetworkPriv), + + ct:pal("subnetwork_sigfun: ~p", [SubnetworkSigFun]), + ct:pal("network_sigfun: ~p", [NetworkSigfun]), + + TT = hst, + Premine = 5000, + T = blockchain_txn_add_subnetwork_v1:new( + TT, SubnetworkPubkeyBin, [RewardServerPubkeyBin], Premine + ), + ST0 = blockchain_txn_add_subnetwork_v1:sign_subnetwork(T, SubnetworkSigFun), + ST = blockchain_txn_add_subnetwork_v1:sign(ST0, NetworkSigfun), + + IsValid = blockchain_txn:is_valid(ST, Chain), + ?assertEqual({error, invalid_token_hst}, IsValid), + + TT2 = hnt, + T2 = blockchain_txn_add_subnetwork_v1:new( + TT2, SubnetworkPubkeyBin, [RewardServerPubkeyBin], Premine + ), + ST1 = blockchain_txn_add_subnetwork_v1:sign_subnetwork(T2, SubnetworkSigFun), + ST2 = blockchain_txn_add_subnetwork_v1:sign(ST1, NetworkSigfun), + + IsValid2 = blockchain_txn:is_valid(ST2, Chain), + ?assertEqual({error, invalid_token_hnt}, IsValid2), + + ok. + +%%-------------------------------------------------------------------- +%% Helper functions +%%-------------------------------------------------------------------- + +extra_vars(_) -> + #{?allowed_num_reward_server_keys => 1}. + +token_allocations(_, Config) -> + HNTBal = ?config(hnt_bal, Config), + HSTBal = ?config(hst_bal, Config), + MobileBal = ?config(mobile_bal, Config), + IOTBal = ?config(iot_bal, Config), + #{hnt => HNTBal, hst => HSTBal, mobile => MobileBal, iot => IOTBal}. diff --git a/test/blockchain_subnetwork_rewards_SUITE.erl b/test/blockchain_subnetwork_rewards_SUITE.erl new file mode 100644 index 0000000000..5e3254c10e --- /dev/null +++ b/test/blockchain_subnetwork_rewards_SUITE.erl @@ -0,0 +1,259 @@ +-module(blockchain_subnetwork_rewards_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-include("blockchain_vars.hrl"). + +-export([ + all/0, + init_per_suite/1, + end_per_suite/1, + init_per_testcase/2, + end_per_testcase/2 +]). + +-export([ + basic_test/1 +]). + +all() -> + [ + basic_test + ]. + +%%-------------------------------------------------------------------- +%% TEST SUITE SETUP +%%-------------------------------------------------------------------- + +init_per_suite(Config) -> + {ok, StorePid} = blockchain_test_reward_store:start(), + [{store, StorePid} | Config]. + +%%-------------------------------------------------------------------- +%% TEST SUITE TEARDOWN +%%-------------------------------------------------------------------- + +end_per_suite(_Config) -> + blockchain_test_reward_store:stop(), + ok. + +%%-------------------------------------------------------------------- +%% TEST CASE SETUP +%%-------------------------------------------------------------------- + +init_per_testcase(TestCase, Config) -> + Config0 = blockchain_ct_utils:init_base_dir_config(?MODULE, TestCase, Config), + + HNTBal = 50000, + HSTBal = 10000, + MobileBal = 1000, + IOTBal = 100, + + Config1 = [ + {hnt_bal, HNTBal}, + {hst_bal, HSTBal}, + {mobile_bal, MobileBal}, + {iot_bal, IOTBal} + | Config0 + ], + + {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config1)), + + ExtraVars = extra_vars(TestCase), + TokenAllocations = token_allocations(TestCase, Config1), + + {ok, GenesisMembers, _GenesisBlock, ConsensusMembers, Keys} = + test_utils:init_chain_with_opts( + #{ + balance => + HNTBal, + sec_balance => + HSTBal, + keys => + {PrivKey, PubKey}, + in_consensus => + false, + have_init_dc => + true, + extra_vars => + ExtraVars, + token_allocations => + TokenAllocations + } + ), + + Chain = blockchain_worker:blockchain(), + Ledger = blockchain:ledger(Chain), + Swarm = blockchain_swarm:tid(), + N = length(ConsensusMembers), + + {EntryMod, _} = blockchain_ledger_v1:versioned_entry_mod_and_entries_cf(Ledger), + + [ + {hnt_bal, HNTBal}, + {hst_bal, HSTBal}, + {mobile_bal, MobileBal}, + {iot_bal, IOTBal}, + {entry_mod, EntryMod}, + {sup, Sup}, + {pubkey, PubKey}, + {privkey, PrivKey}, + {opts, Opts}, + {chain, Chain}, + {swarm, Swarm}, + {n, N}, + {consensus_members, ConsensusMembers}, + {genesis_members, GenesisMembers}, + Keys + | Config1 + ]. + +%%-------------------------------------------------------------------- +%% TEST CASE TEARDOWN +%%-------------------------------------------------------------------- + +end_per_testcase(_TestCase, Config) -> + Sup = ?config(sup, Config), + % Make sure blockchain saved on file = in memory + case erlang:is_process_alive(Sup) of + true -> + true = erlang:exit(Sup, normal), + ok = test_utils:wait_until(fun() -> false =:= erlang:is_process_alive(Sup) end); + false -> + ok + end, + ok. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +basic_test(Config) -> + {NetworkPriv, _} = ?config(master_key, Config), + Chain = ?config(chain, Config), + Ledger = blockchain:ledger(Chain), + ConsensusMembers = ?config(consensus_members, Config), + + %% Generate a random subnetwork signer + [{SubnetworkPubkeyBin, {_SubnetworkPub, _SubnetworkPriv, SubnetworkSigFun}}] = test_utils:generate_keys( + 1 + ), + + %% Generate a random reward server + [{RewardServerPubkeyBin, {_, _, RewardServerSigFun}}] = test_utils:generate_keys(1), + + NetworkSigfun = libp2p_crypto:mk_sig_fun(NetworkPriv), + + TT = mobile, + Premine = 5000, + T = blockchain_txn_add_subnetwork_v1:new( + TT, + SubnetworkPubkeyBin, + [RewardServerPubkeyBin], + Premine + ), + ST0 = blockchain_txn_add_subnetwork_v1:sign_subnetwork(T, SubnetworkSigFun), + ST = blockchain_txn_add_subnetwork_v1:sign(ST0, NetworkSigfun), + + IsValid = blockchain_txn:is_valid(ST, Chain), + ?assertEqual(ok, IsValid), + + {ok, Block} = test_utils:create_block(ConsensusMembers, [ST]), + _ = blockchain_gossip_handler:add_block(Block, Chain, self(), blockchain_swarm:tid()), + + ?assertEqual({ok, blockchain_block:hash_block(Block)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block}, blockchain:head_block(Chain)), + ?assertEqual({ok, 2}, blockchain:height(Chain)), + + ?assertEqual({ok, Block}, blockchain:get_block(2, Chain)), + + LedgerSubnetworks = blockchain_ledger_v1:subnetworks_v1(Ledger), + LedgerSubnetwork = maps:get(mobile, LedgerSubnetworks), + ?assertEqual(mobile, blockchain_ledger_subnetwork_v1:type(LedgerSubnetwork)), + ?assertEqual(Premine, blockchain_ledger_subnetwork_v1:token_treasury(LedgerSubnetwork)), + ?assertEqual( + [RewardServerPubkeyBin], + blockchain_ledger_subnetwork_v1:reward_server_keys(LedgerSubnetwork) + ), + ?assertEqual(0, blockchain_ledger_subnetwork_v1:hnt_treasury(LedgerSubnetwork)), + ?assertEqual(0, blockchain_ledger_subnetwork_v1:nonce(LedgerSubnetwork)), + + %% Generate some random addresses to reward to + [{Rewardee1, _}, {Rewardee2, _}] = test_utils:generate_keys(2), + Rewards = [ + blockchain_txn_subnetwork_rewards_v1:new_reward(Rewardee1, 100), + blockchain_txn_subnetwork_rewards_v1:new_reward(Rewardee2, 200) + ], + Start = 10, + End = Start + 30, + + %% Sign with the wrong signature + InvRewardsTxn1 = blockchain_txn_subnetwork_rewards_v1:new(mobile, Start, End, Rewards), + InvRewardsTxn2 = blockchain_txn_subnetwork_rewards_v1:sign(InvRewardsTxn1, SubnetworkSigFun), + {error, invalid_signature} = blockchain_txn:is_valid(InvRewardsTxn2, Chain), + + %% Incorrect start-end range + InvRewardsTxn3 = blockchain_txn_subnetwork_rewards_v1:new( + mobile, + Start, + End - 30 - 11, + Rewards + ), + InvRewardsTxn4 = blockchain_txn_subnetwork_rewards_v1:sign(InvRewardsTxn3, RewardServerSigFun), + {error, {invalid_reward_range, _, _, _}} = blockchain_txn:is_valid(InvRewardsTxn4, Chain), + + %% Rewarding too much + ExcessRewards = [ + blockchain_txn_subnetwork_rewards_v1:new_reward(Rewardee1, 1000), + blockchain_txn_subnetwork_rewards_v1:new_reward(Rewardee2, 4001) + ], + InvRewardsTxn5 = blockchain_txn_subnetwork_rewards_v1:new(mobile, Start, End, ExcessRewards), + InvRewardsTxn6 = blockchain_txn_subnetwork_rewards_v1:sign(InvRewardsTxn5, RewardServerSigFun), + {error, {insufficient_tokens_to_fulfil_rewards, _, _}} = blockchain_txn:is_valid( + InvRewardsTxn6, + Chain + ), + + RewardsTxn = blockchain_txn_subnetwork_rewards_v1:new(mobile, Start, End, Rewards), + SignedRewardsTxn = blockchain_txn_subnetwork_rewards_v1:sign(RewardsTxn, RewardServerSigFun), + %% This one should be valid + ok = blockchain_txn:is_valid(SignedRewardsTxn, Chain), + + {ok, Block3} = test_utils:create_block(ConsensusMembers, [SignedRewardsTxn]), + _ = blockchain_gossip_handler:add_block(Block3, Chain, self(), blockchain_swarm:tid()), + + ?assertEqual({ok, blockchain_block:hash_block(Block3)}, blockchain:head_hash(Chain)), + ?assertEqual({ok, Block3}, blockchain:head_block(Chain)), + ?assertEqual({ok, 3}, blockchain:height(Chain)), + ?assertEqual({ok, Block3}, blockchain:get_block(3, Chain)), + + {ok, E1} = blockchain_ledger_v1:find_entry(Rewardee1, Ledger), + {ok, E2} = blockchain_ledger_v1:find_entry(Rewardee2, Ledger), + ?assertEqual(100, blockchain_ledger_entry_v2:balance(E1, mobile)), + ?assertEqual(200, blockchain_ledger_entry_v2:balance(E2, mobile)), + ?assertEqual(0, blockchain_ledger_entry_v2:balance(E1, hnt)), + ?assertEqual(0, blockchain_ledger_entry_v2:balance(E2, hnt)), + ?assertEqual(0, blockchain_ledger_entry_v2:balance(E1, iot)), + ?assertEqual(0, blockchain_ledger_entry_v2:balance(E2, iot)), + ?assertEqual(0, blockchain_ledger_entry_v2:balance(E1, hst)), + ?assertEqual(0, blockchain_ledger_entry_v2:balance(E2, hst)), + + {ok, MobileLedgerSubnet} = blockchain_ledger_v1:find_subnetwork_v1(mobile, Ledger), + %% 5000 - (100 + 200) + 4700 = blockchain_ledger_subnetwork_v1:token_treasury(MobileLedgerSubnet), + + ok. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +extra_vars(_) -> + #{?allowed_num_reward_server_keys => 1, ?token_version => 2}. + +token_allocations(_, Config) -> + HNTBal = ?config(hnt_bal, Config), + HSTBal = ?config(hst_bal, Config), + MobileBal = ?config(mobile_bal, Config), + IOTBal = ?config(iot_bal, Config), + #{hnt => HNTBal, hst => HSTBal, mobile => MobileBal, iot => IOTBal}. From a3fb4b7f5a527d7fa6ec01a20f41455096b1131a Mon Sep 17 00:00:00 2001 From: Evan Vigil-McClanahan Date: Fri, 24 Jun 2022 15:39:51 -0700 Subject: [PATCH 40/48] dialyzer fix for different ledger fetch path --- src/state_channel/blockchain_state_channel_common.erl | 8 ++++---- src/state_channel/blockchain_state_channel_handler.erl | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/state_channel/blockchain_state_channel_common.erl b/src/state_channel/blockchain_state_channel_common.erl index 1c1ac48321..8684ffeb4c 100644 --- a/src/state_channel/blockchain_state_channel_common.erl +++ b/src/state_channel/blockchain_state_channel_common.erl @@ -214,7 +214,7 @@ handle_server_msg( lager:debug("sc_handler client got banner, sc_id: ~p", [blockchain_state_channel_v1:id(BannerSC)]), %% either we don't have a ledger or we do and the SC is valid - case Ledger == undefined orelse is_active_sc(BannerSC, Ledger) == ok of + case is_active_sc(BannerSC, Ledger) == ok of true -> blockchain_state_channels_client:banner(Banner, self()); false -> @@ -226,7 +226,7 @@ handle_server_msg( lager:debug("sc_handler client got purchase, sc_id: ~p", [blockchain_state_channel_v1:id(PurchaseSC)]), %% either we don't have a ledger or we do and the SC is valid - case Ledger == undefined orelse is_active_sc(PurchaseSC, Ledger) == ok of + case is_active_sc(PurchaseSC, Ledger) == ok of true -> blockchain_state_channels_client:purchase(Purchase, self()); false -> @@ -254,7 +254,7 @@ handle_client_msg(Msg, HandlerState) -> lager:debug("sc_handler client got banner, sc_id: ~p", [blockchain_state_channel_v1:id(BannerSC)]), %% either we don't have a ledger or we do and the SC is valid - case Ledger == undefined orelse is_active_sc(BannerSC, Ledger) == ok of + case is_active_sc(BannerSC, Ledger) == ok of true -> blockchain_state_channels_client:banner(Banner, self()); false -> @@ -266,7 +266,7 @@ handle_client_msg(Msg, HandlerState) -> lager:debug("sc_handler client got purchase, sc_id: ~p", [blockchain_state_channel_v1:id(PurchaseSC)]), %% either we don't have a ledger or we do and the SC is valid - case Ledger == undefined orelse is_active_sc(PurchaseSC, Ledger) == ok of + case is_active_sc(PurchaseSC, Ledger) == ok of true -> blockchain_state_channels_client:purchase(Purchase, self()); false -> diff --git a/src/state_channel/blockchain_state_channel_handler.erl b/src/state_channel/blockchain_state_channel_handler.erl index dd5ab68e50..0160397c8f 100644 --- a/src/state_channel/blockchain_state_channel_handler.erl +++ b/src/state_channel/blockchain_state_channel_handler.erl @@ -112,7 +112,7 @@ handle_data(client, Data, HandlerState) -> lager:debug("sc_handler client got banner, sc_id: ~p", [blockchain_state_channel_v1:id(BannerSC)]), %% either we don't have a ledger or we do and the SC is valid - case Ledger == undefined orelse blockchain_state_channel_common:is_active_sc(BannerSC, Ledger) == ok of + case blockchain_state_channel_common:is_active_sc(BannerSC, Ledger) == ok of true -> blockchain_state_channels_client:banner(Banner, self()); false -> @@ -124,7 +124,7 @@ handle_data(client, Data, HandlerState) -> lager:debug("sc_handler client got purchase, sc_id: ~p", [blockchain_state_channel_v1:id(PurchaseSC)]), %% either we don't have a ledger or we do and the SC is valid - case Ledger == undefined orelse blockchain_state_channel_common:is_active_sc(PurchaseSC, Ledger) == ok of + case blockchain_state_channel_common:is_active_sc(PurchaseSC, Ledger) == ok of true -> blockchain_state_channels_client:purchase(Purchase, self()); false -> From 15d7ca639e5d4f40f0a950a2537f6848a629dd4b Mon Sep 17 00:00:00 2001 From: Evan Vigil-McClanahan Date: Fri, 24 Jun 2022 15:48:36 -0700 Subject: [PATCH 41/48] remove stale comments Co-authored-by: Jeff Grunewald --- src/state_channel/blockchain_state_channel_common.erl | 4 ---- src/state_channel/blockchain_state_channel_handler.erl | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/state_channel/blockchain_state_channel_common.erl b/src/state_channel/blockchain_state_channel_common.erl index 8684ffeb4c..f8c0d78b0a 100644 --- a/src/state_channel/blockchain_state_channel_common.erl +++ b/src/state_channel/blockchain_state_channel_common.erl @@ -213,7 +213,6 @@ handle_server_msg( BannerSC -> lager:debug("sc_handler client got banner, sc_id: ~p", [blockchain_state_channel_v1:id(BannerSC)]), - %% either we don't have a ledger or we do and the SC is valid case is_active_sc(BannerSC, Ledger) == ok of true -> blockchain_state_channels_client:banner(Banner, self()); @@ -225,7 +224,6 @@ handle_server_msg( PurchaseSC = blockchain_state_channel_purchase_v1:sc(Purchase), lager:debug("sc_handler client got purchase, sc_id: ~p", [blockchain_state_channel_v1:id(PurchaseSC)]), - %% either we don't have a ledger or we do and the SC is valid case is_active_sc(PurchaseSC, Ledger) == ok of true -> blockchain_state_channels_client:purchase(Purchase, self()); @@ -253,7 +251,6 @@ handle_client_msg(Msg, HandlerState) -> BannerSC -> lager:debug("sc_handler client got banner, sc_id: ~p", [blockchain_state_channel_v1:id(BannerSC)]), - %% either we don't have a ledger or we do and the SC is valid case is_active_sc(BannerSC, Ledger) == ok of true -> blockchain_state_channels_client:banner(Banner, self()); @@ -265,7 +262,6 @@ handle_client_msg(Msg, HandlerState) -> PurchaseSC = blockchain_state_channel_purchase_v1:sc(Purchase), lager:debug("sc_handler client got purchase, sc_id: ~p", [blockchain_state_channel_v1:id(PurchaseSC)]), - %% either we don't have a ledger or we do and the SC is valid case is_active_sc(PurchaseSC, Ledger) == ok of true -> blockchain_state_channels_client:purchase(Purchase, self()); diff --git a/src/state_channel/blockchain_state_channel_handler.erl b/src/state_channel/blockchain_state_channel_handler.erl index 0160397c8f..9f3e922902 100644 --- a/src/state_channel/blockchain_state_channel_handler.erl +++ b/src/state_channel/blockchain_state_channel_handler.erl @@ -111,7 +111,6 @@ handle_data(client, Data, HandlerState) -> BannerSC -> lager:debug("sc_handler client got banner, sc_id: ~p", [blockchain_state_channel_v1:id(BannerSC)]), - %% either we don't have a ledger or we do and the SC is valid case blockchain_state_channel_common:is_active_sc(BannerSC, Ledger) == ok of true -> blockchain_state_channels_client:banner(Banner, self()); @@ -123,7 +122,6 @@ handle_data(client, Data, HandlerState) -> PurchaseSC = blockchain_state_channel_purchase_v1:sc(Purchase), lager:debug("sc_handler client got purchase, sc_id: ~p", [blockchain_state_channel_v1:id(PurchaseSC)]), - %% either we don't have a ledger or we do and the SC is valid case blockchain_state_channel_common:is_active_sc(PurchaseSC, Ledger) == ok of true -> blockchain_state_channels_client:purchase(Purchase, self()); From e706b4e238c898787117d55be837b8a85dca6c55 Mon Sep 17 00:00:00 2001 From: jeffgrunewald Date: Tue, 21 Jun 2022 01:44:01 -0400 Subject: [PATCH 42/48] handling txn fees in balance clearing --- .../v2/blockchain_txn_payment_v2.erl | 4 +- test/blockchain_payment_v2_SUITE.erl | 93 ++++++++++++------- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/src/transactions/v2/blockchain_txn_payment_v2.erl b/src/transactions/v2/blockchain_txn_payment_v2.erl index 31d736d3fc..3304e327ae 100644 --- a/src/transactions/v2/blockchain_txn_payment_v2.erl +++ b/src/transactions/v2/blockchain_txn_payment_v2.erl @@ -183,6 +183,7 @@ absorb_v2_(Txn, Ledger, Chain) -> Nonce = ?MODULE:nonce(Txn), TotalAmounts = ?MODULE:total_amounts(Txn, Ledger), ShouldImplicitBurn = blockchain_ledger_v1:txn_fees_active(Ledger), + {MaxAmounts, _} = split_payment_amounts(Txn, Ledger), case blockchain_ledger_v1:debit_fee(Payer, Fee, Ledger, ShouldImplicitBurn, Hash, Chain) of {error, _Reason}=Error -> Error; @@ -192,7 +193,6 @@ absorb_v2_(Txn, Ledger, Chain) -> Error; ok -> Payments = ?MODULE:payments(Txn), - {MaxAmounts, _} = split_payment_amounts(Txn, Ledger), MaxPaymentsMap = maps:from_list(MaxAmounts), ok = lists:foreach( fun(Payment) -> @@ -569,7 +569,7 @@ calculate_hnt_fee(Payer, Fee, Ledger) when Fee > 0 -> FeeInHNT; {ok, Entry} -> DCBalance = blockchain_ledger_data_credits_entry_v1:balance(Entry), - case (DCBalance - Fee >= 0) of + case (DCBalance - Fee) >= 0 of true -> 0; false -> {ok, FeeInHNT} = blockchain_ledger_v1:dc_to_hnt(Fee, Ledger), diff --git a/test/blockchain_payment_v2_SUITE.erl b/test/blockchain_payment_v2_SUITE.erl index 57e9e230c8..2179cc036e 100644 --- a/test/blockchain_payment_v2_SUITE.erl +++ b/test/blockchain_payment_v2_SUITE.erl @@ -106,10 +106,11 @@ init_per_testcase(TestCase, Config) -> {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config0)), + DefaultVars = #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?txn_fees => true}, GroupVars = ?config(group_vars, Config0), ct:pal("group_vars: ~p", [GroupVars]), TestCaseVars = test_case_vars(TestCase), - ExtraVars = maps:merge(GroupVars, TestCaseVars), + ExtraVars = maps:merge(maps:merge(DefaultVars, GroupVars), TestCaseVars), ct:pal("extra vars: ~p", [ExtraVars]), TokenAllocations = ?config(token_allocations, Config0), ct:pal("token_allocations: ~p", [TokenAllocations]), @@ -124,7 +125,7 @@ init_per_testcase(TestCase, Config) -> in_consensus => true, have_init_dc => - true, + false, extra_vars => ExtraVars, token_allocations => @@ -252,7 +253,10 @@ single_payee_test(Config) -> Amount = 2500, Payment1 = blockchain_payment_v2:new(Recipient, Amount), - Tx = blockchain_txn_payment_v2:new(Payer, [Payment1], 1), + Tx = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Payer, [Payment1], 1), + 7 + ), SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), @@ -317,7 +321,10 @@ different_payees_test(Config) -> Amount2 = 2000, Payment2 = blockchain_payment_v2:new(Recipient2, Amount2), - Tx = blockchain_txn_payment_v2:new(Payer, [Payment1, Payment2], 1), + Tx = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Payer, [Payment1, Payment2], 1), + 8 + ), SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), @@ -417,6 +424,9 @@ balance_clearing_test(Config) -> Chain = ?config(chain, Config), EntryMod = ?config(entry_mod, Config), + meck:new(blockchain_ledger_v1, [passthrough]), + meck:expect(blockchain_ledger_v1, current_oracle_price, fun(_) -> {ok, 1050000000} end), + %% Test a payment transaction, add a block and check balances [_, {Payer, {_, PayerPrivKey, _}}, {Recipient2, {_, Recipient2PrivKey, _}}, {Recipient3, _} | _] = ConsensusMembers, @@ -424,13 +434,17 @@ balance_clearing_test(Config) -> %% Create a payment to payee1 Recipient1 = blockchain_swarm:pubkey_bin(), Amount = 2000, + Fee1 = 8, Payment1 = blockchain_payment_v2:new(Recipient1, Amount), %% Create a payment to payee2 Payment2 = blockchain_payment_v2:new(Recipient2, max), %% Submit a txn with mixed regular and balance-clearing `max' payments - Tx1 = blockchain_txn_payment_v2:new(Payer, [Payment1, Payment2], 1), + Tx1 = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Payer, [Payment1, Payment2], 1), + Fee1 + ), SigFun1 = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx1 = blockchain_txn_payment_v2:sign(Tx1, SigFun1), @@ -449,16 +463,22 @@ balance_clearing_test(Config) -> {ok, RecipientEntry1} = blockchain_ledger_v1:find_entry(Recipient1, Ledger), ?assertEqual(Balance + Amount, EntryMod:balance(RecipientEntry1)), + {ok, HntFee1} = blockchain_ledger_v1:dc_to_hnt(Fee1, Ledger), + {ok, RecipientEntry2_1} = blockchain_ledger_v1:find_entry(Recipient2, Ledger), - ?assertEqual(Balance + (Balance - Amount), EntryMod:balance(RecipientEntry2_1)), + ?assertEqual(Balance + (Balance - Amount) - HntFee1, EntryMod:balance(RecipientEntry2_1)), {ok, PayerEntry} = blockchain_ledger_v1:find_entry(Payer, Ledger), ?assertEqual(0, EntryMod:balance(PayerEntry)), %% Normal txn with an explicit amount is still processed normally Payment3 = blockchain_payment_v2:new(Recipient3, Amount), + Fee2 = 7, - Tx2 = blockchain_txn_payment_v2:new(Recipient2, [Payment3], 1), + Tx2 = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Recipient2, [Payment3], 1), + Fee2 + ), SigFun2 = libp2p_crypto:mk_sig_fun(Recipient2PrivKey), SignedTx2 = blockchain_txn_payment_v2:sign(Tx2, SigFun2), @@ -472,8 +492,9 @@ balance_clearing_test(Config) -> Ledger2 = blockchain:ledger(Chain), + {ok, HntFee2} = blockchain_ledger_v1:dc_to_hnt(Fee2, Ledger), {ok, RecipientEntry2_2} = blockchain_ledger_v1:find_entry(Recipient2, Ledger2), - ?assertEqual((Balance + (Balance - Amount)) - Amount, EntryMod:balance(RecipientEntry2_2)), + ?assertEqual((Balance + (Balance - Amount) - HntFee1) - Amount - HntFee2, EntryMod:balance(RecipientEntry2_2)), {ok, RecipientEntry3_1} = blockchain_ledger_v1:find_entry(Recipient3, Ledger2), ?assertEqual(Balance + Amount, EntryMod:balance(RecipientEntry3_1)), @@ -481,7 +502,10 @@ balance_clearing_test(Config) -> %% Balance-clearing `max' txn processed successfully in isolation Payment4 = blockchain_payment_v2:new(Recipient3, max), - Tx3 = blockchain_txn_payment_v2:new(Recipient2, [Payment4], 2), + Tx3 = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Recipient2, [Payment4], 2), + Fee2 + ), SignedTx3 = blockchain_txn_payment_v2:sign(Tx3, SigFun2), {ok, Block3} = test_utils:create_block(ConsensusMembers, [SignedTx3]), @@ -498,7 +522,9 @@ balance_clearing_test(Config) -> ?assertEqual(0, EntryMod:balance(RecipientEntry2_3)), {ok, RecipientEntry3_2} = blockchain_ledger_v1:find_entry(Recipient3, Ledger3), - ?assertEqual(Balance + Balance + (Balance - Amount), EntryMod:balance(RecipientEntry3_2)), + ?assertEqual(Balance + Balance + (Balance - Amount) - (HntFee1 + (HntFee2 * 2)), EntryMod:balance(RecipientEntry3_2)), + + meck:unload(blockchain_ledger_v1), ok. invalid_balance_clearing_test(Config) -> @@ -653,7 +679,10 @@ valid_memo_test(Config) -> Payment1 = blockchain_payment_v2:new(Recipient, Amount, Memo), Payment2 = blockchain_payment_v2:memo(blockchain_payment_v2:new(OtherRecipient, Amount), Memo), - Tx = blockchain_txn_payment_v2:new(Payer, [Payment1, Payment2], 1), + Tx = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Payer, [Payment1, Payment2], 1), + 8 + ), SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), @@ -730,7 +759,10 @@ valid_memo_not_set_test(Config) -> Memo = 0, Payment2 = blockchain_payment_v2:new(Recipient2, Amount, Memo), - Tx = blockchain_txn_payment_v2:new(Payer, [Payment1, Payment2], 1), + Tx = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Payer, [Payment1, Payment2], 1), + 8 + ), SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), @@ -801,7 +833,10 @@ big_memo_valid_test(Config) -> Amount = 100, Payment1 = blockchain_payment_v2:new(Recipient, Amount, ?VALID_GIANT_MEMO), - Tx = blockchain_txn_payment_v2:new(Payer, [Payment1], 1), + Tx = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Payer, [Payment1], 1), + 7 + ), SigFun = libp2p_crypto:mk_sig_fun(PayerPrivKey), SignedTx = blockchain_txn_payment_v2:sign(Tx, SigFun), @@ -856,24 +891,15 @@ big_memo_invalid_test(Config) -> %% Internal Functions %%-------------------------------------------------------------------- -test_case_vars(big_memo_valid_test) -> - #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?allow_payment_v2_memos => true}; -test_case_vars(big_memo_invalid_test) -> - #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?allow_payment_v2_memos => true}; -test_case_vars(valid_memo_test) -> - #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?allow_payment_v2_memos => true}; -test_case_vars(negative_memo_test) -> - #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?allow_payment_v2_memos => true}; -test_case_vars(BCEnabled) when - BCEnabled == balance_clearing_test orelse BCEnabled == invalid_balance_clearing_test --> - #{ - ?max_payments => ?MAX_PAYMENTS, - ?allow_zero_amount => false, - ?enable_balance_clearing => true - }; -test_case_vars(_) -> - #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false}. +test_case_vars(MemoTest) when MemoTest == big_memo_valid_test + orelse MemoTest == big_memo_invalid_test + orelse MemoTest == valid_memo_test + orelse MemoTest == negative_memo_test -> + #{?allow_payment_v2_memos => true}; +test_case_vars(BCTest) when BCTest == balance_clearing_test + orelse BCTest == invalid_balance_clearing_test -> + #{?enable_balance_clearing => true}; +test_case_vars(_) -> #{}. %% Helpers -------------------------------------------------------------------- @@ -908,7 +934,10 @@ transfer(Amount, {Src, SrcSigFun}, Dst, ExpectHeight, Chain, ConsensusMembers, E {error, address_entry_not_found} -> 1; {ok, Entry} -> EntryMod:nonce(Entry) + 1 end, - Tx = blockchain_txn_payment_v2:new(Src, [blockchain_payment_v2:new(Dst, Amount)], Nonce), + Tx = blockchain_txn_payment_v2:fee( + blockchain_txn_payment_v2:new(Src, [blockchain_payment_v2:new(Dst, Amount)], Nonce), + 7 + ), TxSigned = blockchain_txn_payment_v2:sign(Tx, SrcSigFun), {ok, Block} = test_utils:create_block(ConsensusMembers, [TxSigned]), _ = blockchain_gossip_handler:add_block(Block, Chain, self(), blockchain_swarm:tid()), From b9a56e8a2df51730e28f10c7d351019496d1d162 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sun, 26 Jun 2022 21:22:37 -0400 Subject: [PATCH 43/48] Switch from error to warning log level on iterator close exceptions --- src/blockchain_rocks.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blockchain_rocks.hrl b/src/blockchain_rocks.hrl index 6971a09ea9..1719436817 100644 --- a/src/blockchain_rocks.hrl +++ b/src/blockchain_rocks.hrl @@ -4,7 +4,7 @@ try rocksdb:iterator_close(IteratorHandle) catch ErrorClass:ErrorReason -> - lager:error("rocksdb:iterator_close error: ~p:~p", [ErrorClass, ErrorReason]) + lager:warning("rocksdb:iterator_close error: ~p:~p", [ErrorClass, ErrorReason]) end end)() ). From 0db34f8850b874ff5bf77a6d677c061646c74897 Mon Sep 17 00:00:00 2001 From: Evan Vigil-McClanahan Date: Mon, 27 Jun 2022 07:41:42 -0700 Subject: [PATCH 44/48] upgrade libp2p --- rebar.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.lock b/rebar.lock index 0c763d3e84..8eb4cd222b 100644 --- a/rebar.lock +++ b/rebar.lock @@ -92,7 +92,7 @@ {<<"lager">>,{pkg,<<"lager">>,<<"3.9.2">>},0}, {<<"libp2p">>, {git,"https://github.com/helium/erlang-libp2p.git", - {ref,"f2a33f4165b1597adb26b429f54dc50dd4afee22"}}, + {ref,"070fea4a05e8a9d7e5cd0af1c9c4c4f7a507aaa7"}}, 0}, {<<"libp2p_crypto">>, {git,"https://github.com/helium/libp2p-crypto.git", From 5e16e1291fb37dca05502c3ab8f9523e7b5d5d66 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 27 Jun 2022 09:22:24 -0700 Subject: [PATCH 45/48] Fix region cache prewarm and fix blockchain_payment_v2_SUITE tests --- src/region/blockchain_region_v1.erl | 32 +++++++++++-------- .../v1/blockchain_txn_vars_v1.erl | 2 +- test/blockchain_payment_v2_SUITE.erl | 12 +++++-- test/test_utils.erl | 5 ++- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/region/blockchain_region_v1.erl b/src/region/blockchain_region_v1.erl index b0f7b57f11..63a95f7e7a 100644 --- a/src/region/blockchain_region_v1.erl +++ b/src/region/blockchain_region_v1.erl @@ -173,20 +173,24 @@ polyfill_resolution(Ledger) -> prewarm_cache(Ledger) -> lager:info("starting cache prewarm: ~p", [h3_to_region]), Before = erlang:monotonic_time(second), - {ok, RB} = get_all_region_bins(Ledger), - blockchain_ledger_v1:cf_fold( - active_gateways, - fun({_, BG}, Acc) -> - G = blockchain_ledger_gateway_v2:deserialize(BG), - case blockchain_ledger_gateway_v2:location(G) of - undefined -> Acc; - Loc -> _ = h3_to_region(Loc, Ledger, RB) - end - end, - 0, Ledger), - Duration = erlang:monotonic_time(second) - Before, - lager:info("completed cache prewarm in ~p seconds: ~p", [Duration, h3_to_region]), - ok. + case get_all_region_bins(Ledger) of + {error, regulatory_regions_not_set} -> + ok; + {ok, RB} -> + blockchain_ledger_v1:cf_fold( + active_gateways, + fun({_, BG}, Acc) -> + G = blockchain_ledger_gateway_v2:deserialize(BG), + case blockchain_ledger_gateway_v2:location(G) of + undefined -> Acc; + Loc -> _ = h3_to_region(Loc, Ledger, RB) + end + end, + 0, Ledger), + Duration = erlang:monotonic_time(second) - Before, + lager:info("completed cache prewarm in ~p seconds: ~p", [Duration, h3_to_region]), + ok + end. clear_cache() -> e2qc:teardown(h3_to_region), diff --git a/src/transactions/v1/blockchain_txn_vars_v1.erl b/src/transactions/v1/blockchain_txn_vars_v1.erl index 9b39ef8329..ff40742e8a 100644 --- a/src/transactions/v1/blockchain_txn_vars_v1.erl +++ b/src/transactions/v1/blockchain_txn_vars_v1.erl @@ -621,7 +621,7 @@ delayed_absorb(Txn, Ledger) -> %% we've invalidated the region cache, so prewarm it. spawn(fun() -> timer:sleep(30000), - blockchain_region_v1:prewarm_cache(Ledger) + blockchain_region_v1:prewarm_cache(blockchain_ledger_v1:remove_context(Ledger)) end); _ -> ok diff --git a/test/blockchain_payment_v2_SUITE.erl b/test/blockchain_payment_v2_SUITE.erl index 2179cc036e..944b387e0a 100644 --- a/test/blockchain_payment_v2_SUITE.erl +++ b/test/blockchain_payment_v2_SUITE.erl @@ -102,12 +102,18 @@ test_cases() -> init_per_testcase(TestCase, Config) -> Config0 = blockchain_ct_utils:init_base_dir_config(?MODULE, TestCase, Config), - Balance = ?config(balance, Config0), + Balance = case ?config(balance, Config0) of + undefined -> 5000; + Balance0 -> Balance0 + end, {ok, Sup, {PrivKey, PubKey}, Opts} = test_utils:init(?config(base_dir, Config0)), DefaultVars = #{?max_payments => ?MAX_PAYMENTS, ?allow_zero_amount => false, ?txn_fees => true}, - GroupVars = ?config(group_vars, Config0), + GroupVars = case ?config(group_vars, Config0) of + undefined -> #{}; + GroupVars0 -> GroupVars0 + end, ct:pal("group_vars: ~p", [GroupVars]), TestCaseVars = test_case_vars(TestCase), ExtraVars = maps:merge(maps:merge(DefaultVars, GroupVars), TestCaseVars), @@ -184,6 +190,8 @@ end_per_testcase(_, Config) -> false -> ok end, + ct:pal("removing ~p", [?config(base_dir, Config)]), + os:cmd("rm -rf " ++ ?config(base_dir, Config)), ok. %%-------------------------------------------------------------------- diff --git a/test/test_utils.erl b/test/test_utils.erl index ead561b48b..2a71747c78 100644 --- a/test/test_utils.erl +++ b/test/test_utils.erl @@ -164,7 +164,10 @@ init_chain_with_opts(Opts) when is_map(Opts) -> Chain = blockchain_worker:blockchain(), {ok, HeadBlock} = blockchain:head_block(Chain), - ok = test_utils:wait_until(fun() ->{ok, 1} =:= blockchain:height(Chain) end), + ok = test_utils:wait_until(fun() -> case blockchain:height(Chain) of + {ok, 1} -> true; + Res -> Res + end end), ?assertEqual(blockchain_block:hash_block(GenesisBlock), blockchain_block:hash_block(HeadBlock)), ?assertEqual({ok, GenesisBlock}, blockchain:head_block(Chain)), From 82daeededc0d9641e6f9a8bbc2cdcea633f8c87f Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Mon, 27 Jun 2022 12:50:04 -0400 Subject: [PATCH 46/48] Add option to disable iterator_close exception logging --- src/blockchain_rocks.hrl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/blockchain_rocks.hrl b/src/blockchain_rocks.hrl index 1719436817..0b461321e6 100644 --- a/src/blockchain_rocks.hrl +++ b/src/blockchain_rocks.hrl @@ -4,7 +4,12 @@ try rocksdb:iterator_close(IteratorHandle) catch ErrorClass:ErrorReason -> - lager:warning("rocksdb:iterator_close error: ~p:~p", [ErrorClass, ErrorReason]) + case application:get_env(blockchain, log_rocksdb_iterator_close_errors, true) of + true -> + lager:warning("rocksdb:iterator_close error: ~p:~p", [ErrorClass, ErrorReason]); + false -> + ok + end end end)() ). From 14bff32c3a9bd0211d8041b43ea08e131c7f6649 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Wed, 29 Jun 2022 09:29:49 -0700 Subject: [PATCH 47/48] Move headers to include dir --- {src => include}/blockchain_rocks.hrl | 0 {src => include}/blockchain_term.hrl | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {src => include}/blockchain_rocks.hrl (100%) rename {src => include}/blockchain_term.hrl (100%) diff --git a/src/blockchain_rocks.hrl b/include/blockchain_rocks.hrl similarity index 100% rename from src/blockchain_rocks.hrl rename to include/blockchain_rocks.hrl diff --git a/src/blockchain_term.hrl b/include/blockchain_term.hrl similarity index 100% rename from src/blockchain_term.hrl rename to include/blockchain_term.hrl From e0f5e78ea05a4168eea9898af8cc302f4aacff5d Mon Sep 17 00:00:00 2001 From: joecaswell <1626320+joecaswell@users.noreply.github.com> Date: Tue, 15 Mar 2022 18:27:09 -0700 Subject: [PATCH 48/48] validate size and hash after download --- src/blockchain_worker.erl | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/blockchain_worker.erl b/src/blockchain_worker.erl index c723c9771b..5aa0b0fb8f 100644 --- a/src/blockchain_worker.erl +++ b/src/blockchain_worker.erl @@ -1243,7 +1243,16 @@ build_url(BaseUrl, Filename) -> set_filename(BaseFilename) -> BaseFilename ++ ".gz". -attempt_fetch_snap_source_snapshot(BaseUrl, #snapshot_info{height = Height, file_hash = Hash, +attempt_fetch_snap_source_snapshot(BaseUrl, SnapInfo) -> + case validate_snapshot_file(SnapInfo) of + {ok, Filename} -> + {ok, Filename}; + {invalid, Filename, Filepath} -> + _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath), + validate_snapshot_file(SnapInfo) + end. + +validate_snapshot_file(#snapshot_info{height = Height, file_hash = Hash, file_size = Size}) -> %% httpc and ssl applications are started in the top level blockchain supervisor Filename = set_filename(build_filename(Height)), @@ -1279,17 +1288,15 @@ attempt_fetch_snap_source_snapshot(BaseUrl, #snapshot_info{height = Height, file lager:info("Already have snapshot file for height ~p with hash ~p", [Height, Hash]), ok = file:write_file(HashStateFile, Hash), {ok, Filepath}; - {smaller, _} -> - %% see if this is a a resumable download - _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath); _ -> - %% file is bigger than it should be, or the hash is wrong, scrap it + %% file size or the hash is wrong, scrap it, + %% we'll try to resume from the scratch file, if there is one safe_delete(Filename), - _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath) + {invalid, Filename, Filepath} end end; - false -> - _ = do_snap_source_download(build_url(BaseUrl, Filename), Filepath) + %% no file? same as failed hash + false -> {invalid, Filename, Filepath} end. same_stored_hash(File, Hash) ->