diff --git a/tests/core_tests/hard_fork_1.cpp b/tests/core_tests/hard_fork_1.cpp index 41f561e7..6bbde3e7 100644 --- a/tests/core_tests/hard_fork_1.cpp +++ b/tests/core_tests/hard_fork_1.cpp @@ -341,9 +341,27 @@ bool hard_fork_1_checkpoint_basic_test::generate(std::vector& //------------------------------------------------------------------------------ +struct unique_amount_params +{ + unique_amount_params(uint64_t amount, uint64_t count) : amount(amount), count(count) {} + uint64_t amount; + uint64_t count; +}; + +// check that the given amount has only one non-zero gidit +// 3000 => true +// 11000 => false +bool does_amount_have_one_non_zero_digit(uint64_t amount) +{ + size_t count = 0; + auto f = [&count](uint64_t){ ++count; }; + decompose_amount_into_digits(amount, DEFAULT_DUST_THRESHOLD, f, f); + return count == 1; +} + + hard_fork_1_pos_and_locked_coins::hard_fork_1_pos_and_locked_coins() - : hard_fork_1_base_test(13) // hardfork height - , m_unique_amount(TESTS_DEFAULT_FEE * 9) + : hard_fork_1_base_test(25) // hardfork height { REGISTER_CALLBACK_METHOD(hard_fork_1_pos_and_locked_coins, check_outputs_with_unique_amount); } @@ -353,14 +371,24 @@ bool hard_fork_1_pos_and_locked_coins::generate(std::vector& e bool r = false; GENERATE_ACCOUNT(miner_acc); GENERATE_ACCOUNT(alice_acc); + GENERATE_ACCOUNT(bob_acc); MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); generator.set_hardfork_height(m_hardfork_height); DO_CALLBACK(events, "configure_core"); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3); - DO_CALLBACK_PARAMS(events, "check_outputs_with_unique_amount", static_cast(0)); + const uint64_t unique_amount_alice = TESTS_DEFAULT_FEE * 9; + const uint64_t unique_amount_bob = TESTS_DEFAULT_FEE * 3; + + CHECK_AND_ASSERT_MES(does_amount_have_one_non_zero_digit(unique_amount_alice), false, "does_amount_have_one_non_zero_digit failed for Alice"); + CHECK_AND_ASSERT_MES(does_amount_have_one_non_zero_digit(unique_amount_bob), false, "does_amount_have_one_non_zero_digit failed for Bob"); + + // make sure no outputs have such unique amounts + DO_CALLBACK_PARAMS(events, "check_outputs_with_unique_amount", unique_amount_params(unique_amount_alice, 0) ); + DO_CALLBACK_PARAMS(events, "check_outputs_with_unique_amount", unique_amount_params(unique_amount_bob, 0) ); // create few locked outputs in the blockchain with unique amount + // tx_0 : miner -> Alice std::vector extra; etc_tx_details_unlock_time ut = AUTO_VAL_INIT(ut); ut.v = 100; // locked until block 100 @@ -368,56 +396,184 @@ bool hard_fork_1_pos_and_locked_coins::generate(std::vector& e std::vector destinations; for (size_t i = 0; i < 5; ++i) - destinations.push_back(tx_destination_entry(m_unique_amount, alice_acc.get_public_address())); + destinations.push_back(tx_destination_entry(unique_amount_alice, alice_acc.get_public_address())); transaction tx_0 = AUTO_VAL_INIT(tx_0); r = construct_tx_to_key(events, tx_0, blk_0r, miner_acc, destinations, TESTS_DEFAULT_FEE, 0, 0, extra); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_0); + // tx_1 : miner -> Bob + extra.clear(); + uint64_t ut2_unlock_time = 100; // locked until block 100 + etc_tx_details_unlock_time2 ut2 = AUTO_VAL_INIT(ut2); + destinations.clear(); + for (size_t i = 0; i < 5; ++i) + { + destinations.push_back(tx_destination_entry(unique_amount_bob, bob_acc.get_public_address())); + ut2.unlock_time_array.push_back(ut2_unlock_time); + } + ut2.unlock_time_array.push_back(ut2_unlock_time); + extra.push_back(ut2); + transaction tx_1 = AUTO_VAL_INIT(tx_1); + r = construct_tx_to_key(events, tx_1, blk_0r, miner_acc, destinations, TESTS_DEFAULT_FEE, 0, 0, extra); + CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); + events.push_back(tx_1); + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); - DO_CALLBACK_PARAMS(events, "check_outputs_with_unique_amount", static_cast(5)); + // block with tx_1 should be rejected because etc_tx_details_unlock_time2 is not allowed prior to hardfork 1 + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK_TX1(events, blk_1b, blk_1, miner_acc, tx_1); + REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); - block blk_0a; + DO_CALLBACK_PARAMS(events, "check_outputs_with_unique_amount", unique_amount_params(unique_amount_alice, 5) ); + + // make sure outputs with m_unique_amount are still locked + r = false; + try { - crypto::hash prev_id = get_block_hash(blk_0); - size_t height = get_block_height(blk_0) + 1; + MAKE_TX(events, tx_1_bad, alice_acc, miner_acc, unique_amount_alice - TESTS_DEFAULT_FEE, blk_1r); + } + catch (std::runtime_error&) + { + r = true; + } + CHECK_AND_ASSERT_MES(r, false, "exception was not cought as expected"); + + // try to make a PoS block with locked stake before the hardfork + + block blk_2b; + { + const block& prev_block = blk_1r; + const transaction& stake = tx_0; + + crypto::hash prev_id = get_block_hash(prev_block); + size_t height = get_block_height(prev_block) + 1; currency::wide_difficulty_type diff = generator.get_difficulty_for_next_block(prev_id, false); - const transaction& stake = blk_0.miner_tx; crypto::public_key stake_tx_pub_key = get_tx_pub_key_from_extra(stake); size_t stake_output_idx = 0; size_t stake_output_gidx = 0; uint64_t stake_output_amount = stake.vout[stake_output_idx].amount; crypto::key_image stake_output_key_image; keypair kp; - generate_key_image_helper(miner_acc.get_keys(), stake_tx_pub_key, stake_output_idx, kp, stake_output_key_image); + generate_key_image_helper(alice_acc.get_keys(), stake_tx_pub_key, stake_output_idx, kp, stake_output_key_image); crypto::public_key stake_output_pubkey = boost::get(stake.vout[stake_output_idx].target).key; pos_block_builder pb; pb.step1_init_header(height, prev_id); pb.step2_set_txs(std::vector()); - pb.step3_build_stake_kernel(stake_output_amount, stake_output_gidx, stake_output_key_image, diff, prev_id, null_hash, blk_0r.timestamp); - pb.step4_generate_coinbase_tx(generator.get_timestamps_median(prev_id), generator.get_already_generated_coins(blk_0r), miner_acc.get_public_address()); - pb.step5_sign(stake_tx_pub_key, stake_output_idx, stake_output_pubkey, miner_acc); - blk_0a = pb.m_block; + pb.step3_build_stake_kernel(stake_output_amount, stake_output_gidx, stake_output_key_image, diff, prev_id, null_hash, prev_block.timestamp); + pb.step4_generate_coinbase_tx(generator.get_timestamps_median(prev_id), generator.get_already_generated_coins(prev_block), miner_acc.get_public_address()); + pb.step5_sign(stake_tx_pub_key, stake_output_idx, stake_output_pubkey, alice_acc); + blk_2b = pb.m_block; } + // it should not be accepted, because stake coins is still locked + DO_CALLBACK(events, "mark_invalid_block"); + events.push_back(blk_2b); + + MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc); + MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc); + // make sure hardfork went okay + CHECK_AND_ASSERT_MES(blk_2.major_version != CURRENT_BLOCK_MAJOR_VERSION && blk_3.major_version == CURRENT_BLOCK_MAJOR_VERSION, false, "hardfork did not happen as expected"); + + + // try to make a PoS block with locked stake after the hardfork + + block blk_4b; + { + const block& prev_block = blk_3; + const transaction& stake = tx_0; + + crypto::hash prev_id = get_block_hash(prev_block); + size_t height = get_block_height(prev_block) + 1; + currency::wide_difficulty_type diff = generator.get_difficulty_for_next_block(prev_id, false); + + crypto::public_key stake_tx_pub_key = get_tx_pub_key_from_extra(stake); + size_t stake_output_idx = 0; + size_t stake_output_gidx = 0; + uint64_t stake_output_amount = stake.vout[stake_output_idx].amount; + crypto::key_image stake_output_key_image; + keypair kp; + generate_key_image_helper(alice_acc.get_keys(), stake_tx_pub_key, stake_output_idx, kp, stake_output_key_image); + crypto::public_key stake_output_pubkey = boost::get(stake.vout[stake_output_idx].target).key; + + pos_block_builder pb; + pb.step1_init_header(height, prev_id); + pb.m_block.major_version = CURRENT_BLOCK_MAJOR_VERSION; + pb.step2_set_txs(std::vector()); + pb.step3_build_stake_kernel(stake_output_amount, stake_output_gidx, stake_output_key_image, diff, prev_id, null_hash, prev_block.timestamp); + pb.step4_generate_coinbase_tx(generator.get_timestamps_median(prev_id), generator.get_already_generated_coins(prev_block), miner_acc.get_public_address()); + pb.step5_sign(stake_tx_pub_key, stake_output_idx, stake_output_pubkey, alice_acc); + blk_4b = pb.m_block; + } + + // it should not be accepted, because stake coins is still locked + DO_CALLBACK(events, "mark_invalid_block"); + events.push_back(blk_4b); + + // blk_4 with tx_1 (etc_tx_details_unlock_time2) should be accepted after hardfork 1 + MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner_acc, tx_1); + + block prev = blk_4; + for(size_t i = 0; i < CURRENCY_MINED_MONEY_UNLOCK_WINDOW; ++i) + { + MAKE_NEXT_POS_BLOCK(events, b, prev, miner_acc, std::list{miner_acc}); + prev = b; + } + + + //REWIND_BLOCKS_N_WITH_TIME(events, blk_4r, blk_4, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + block blk_5; + { + const block& prev_block = prev; + const transaction& stake = tx_1; + + crypto::hash prev_id = get_block_hash(prev_block); + size_t height = get_block_height(prev_block) + 1; + currency::wide_difficulty_type diff = generator.get_difficulty_for_next_block(prev_id, false); + + crypto::public_key stake_tx_pub_key = get_tx_pub_key_from_extra(stake); + size_t stake_output_idx = 0; + size_t stake_output_gidx = 0; + uint64_t stake_output_amount = stake.vout[stake_output_idx].amount; + crypto::key_image stake_output_key_image; + keypair kp; + generate_key_image_helper(bob_acc.get_keys(), stake_tx_pub_key, stake_output_idx, kp, stake_output_key_image); + crypto::public_key stake_output_pubkey = boost::get(stake.vout[stake_output_idx].target).key; + + pos_block_builder pb; + pb.step1_init_header(height, prev_id); + pb.m_block.major_version = CURRENT_BLOCK_MAJOR_VERSION; + pb.step2_set_txs(std::vector()); + pb.step3_build_stake_kernel(stake_output_amount, stake_output_gidx, stake_output_key_image, diff, prev_id, null_hash, prev_block.timestamp); + pb.step4_generate_coinbase_tx(generator.get_timestamps_median(prev_id), generator.get_already_generated_coins(prev_block), miner_acc.get_public_address()); + pb.step5_sign(stake_tx_pub_key, stake_output_idx, stake_output_pubkey, bob_acc); + blk_5 = pb.m_block; + } + + // it should not be accepted, because stake coins is still locked + DO_CALLBACK(events, "mark_invalid_block"); + events.push_back(blk_5); + + return true; } bool hard_fork_1_pos_and_locked_coins::check_outputs_with_unique_amount(currency::core& c, size_t ev_index, const std::vector& events) { - size_t expected_outputs_count = 0; + unique_amount_params p(0, 0); const std::string& params = boost::get(events[ev_index]).callback_params; - CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(params, expected_outputs_count), false, "hex_to_pod failed, params = " << params); + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(params, p), false, "hex_to_pod failed, params = " << params); std::list pub_keys; - bool r = c.get_outs(m_unique_amount, pub_keys); + bool r = c.get_outs(p.amount, pub_keys); - CHECK_AND_ASSERT_MES(r && pub_keys.size() == expected_outputs_count, false, "amount " << print_money_brief(m_unique_amount) << ": " << pub_keys.size() << " != " << expected_outputs_count); + CHECK_AND_ASSERT_MES(r && pub_keys.size() == p.count, false, "amount " << print_money_brief(p.amount) << ": " << pub_keys.size() << " != " << p.count); return true; } diff --git a/tests/core_tests/hard_fork_1.h b/tests/core_tests/hard_fork_1.h index 261dcd8b..65e6dd57 100644 --- a/tests/core_tests/hard_fork_1.h +++ b/tests/core_tests/hard_fork_1.h @@ -45,6 +45,4 @@ struct hard_fork_1_pos_and_locked_coins : public hard_fork_1_base_test bool generate(std::vector& events) const; bool check_outputs_with_unique_amount(currency::core& c, size_t ev_index, const std::vector& events); - - uint64_t m_unique_amount; };