// Copyright (c) 2014-2018 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #pragma once #include "chaingen.h" #include "wallet_tests_basic.h" using namespace epee; using namespace crypto; using namespace currency; //------------------------------------------------------------------------------ // escrow-specific test helpers //------------------------------------------------------------------------------ inline bool are_all_inputs_mixed(const std::vector& vin, size_t* p_min_fake_outs_count = nullptr) { if (p_min_fake_outs_count) *p_min_fake_outs_count = SIZE_MAX; for (const auto& in : vin) { if (in.type().hash_code() == typeid(txin_gen).hash_code()) { // skip it } else if (in.type().hash_code() == typeid(txin_to_key).hash_code()) { size_t sz = boost::get(in).key_offsets.size(); if (p_min_fake_outs_count && sz > 0) *p_min_fake_outs_count = std::min(*p_min_fake_outs_count, sz - 1); if (sz <= 1) return false; } else if (in.type().hash_code() == typeid(txin_multisig).hash_code()) { return false; // multisig inputs are pretty much direct by design } else { return false; // unknown input type } } return true; } template void remove_all_entries_from_variant_container_by_type(container_t& container) { for(auto it = container.begin(); it != container.end(); /* nothing */) { if (it->type().hash_code() == typeid(entry_t).hash_code()) it = container.erase(it); else ++it; } } inline bool refresh_wallet_and_check_1_contract_state(const char* wallet_name, std::shared_ptr wallet, uint32_t expected_contract_state, size_t block_to_be_fetched = SIZE_MAX) { bool stub_bool = false, r = false; size_t blocks_fetched = 0; LOG_PRINT_CYAN("Refreshing " << wallet_name << "'s wallet...", LOG_LEVEL_0); wallet->refresh(blocks_fetched); CHECK_AND_ASSERT_MES(block_to_be_fetched == SIZE_MAX || blocks_fetched == block_to_be_fetched, false, wallet_name << ": incorrect amount of fetched blocks: " << blocks_fetched << ", expected: " << block_to_be_fetched); LOG_PRINT_CYAN("Scan tx pool for " << wallet_name << "'s wallet...", LOG_LEVEL_0); wallet->scan_tx_pool(stub_bool); tools::escrow_contracts_container contracts; r = wallet->get_contracts(contracts); CHECK_AND_ASSERT_MES(r && contracts.size() == 1, false, "get_contracts() for " << wallet_name << " failed or returned wrong contracts count: " << contracts.size()); CHECK_AND_ASSERT_MES(contracts.begin()->second.state == expected_contract_state, false, wallet_name << " has invalid contract state: " << tools::wallet_public::get_escrow_contract_state_name(contracts.begin()->second.state) << ", expected: " << tools::wallet_public::get_escrow_contract_state_name(expected_contract_state)); return true; } inline bool refresh_wallet_and_check_contract_state(const char* wallet_name, std::shared_ptr wallet, uint32_t expected_contract_state, crypto::hash contract_id, size_t block_to_be_fetched = SIZE_MAX) { bool stub_bool = false, r = false; size_t blocks_fetched = 0; LOG_PRINT_CYAN("Refreshing " << wallet_name << "'s wallet...", LOG_LEVEL_0); wallet->refresh(blocks_fetched); CHECK_AND_ASSERT_MES(block_to_be_fetched == SIZE_MAX || blocks_fetched == block_to_be_fetched, false, wallet_name << ": incorrect amount of fetched blocks: " << blocks_fetched << ", expected: " << block_to_be_fetched); wallet->scan_tx_pool(stub_bool); tools::escrow_contracts_container contracts; r = wallet->get_contracts(contracts); CHECK_AND_ASSERT_MES(r, false, "get_contracts() for " << wallet_name << " failed"); for (const auto& c : contracts) { if (c.first == contract_id) { CHECK_AND_ASSERT_MES(c.second.state == expected_contract_state, false, wallet_name << " has invalid contract state: " << tools::wallet_public::get_escrow_contract_state_name(c.second.state) << ", expected: " << tools::wallet_public::get_escrow_contract_state_name(expected_contract_state)); return true; } } LOG_ERROR("No contracts found for " << wallet_name << ", by contract_id = " << contract_id); return false; } inline bool extract_cancellation_template_tx_from_request_tx(const transaction& cancel_request_tx, const account_keys& b_keys, transaction& cancel_template_tx) { std::vector decrypted_att; CHECK_AND_ASSERT_MES(decrypt_payload_items(true, cancel_request_tx, b_keys, decrypted_att), false, "decrypt_payload_items failed"); for (auto& v : decrypted_att) { if (v.type() == typeid(tx_service_attachment)) { const tx_service_attachment& sa = boost::get(v); if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL) { bc_services::escrow_cancel_templates_body cpb = AUTO_VAL_INIT(cpb); CHECK_AND_ASSERT_MES(t_unserializable_object_from_blob(cpb, sa.body), false, "t_unserializable_object_from_blob failed"); cancel_template_tx = cpb.tx_cancel_template; return true; } } } return false; // not found } //------------------------------------------------------------------------------ enum escrow_custom_config_field { eccf_normal = 0, eccf_template_inv_crypt_address = (uint64_t)1 << 0, eccf_template_inv_a_inputs = (uint64_t)1 << 1, eccf_template_inv_ms_amount = (uint64_t)1 << 2, eccf_template_inv_sa_instruction = (uint64_t)1 << 3, eccf_template_inv_sa_flags = (uint64_t)1 << 4, eccf_template_no_multisig = (uint64_t)1 << 5, eccf_template_inv_multisig = (uint64_t)1 << 6, eccf_template_inv_multisig_low_min_sig = (uint64_t)1 << 7, eccf_template_no_a_sigs = (uint64_t)1 << 8, eccf_template_no_tx_flags = (uint64_t)1 << 9, eccf_proposal_inv_sa_flags = (uint64_t)1 << 10, eccf_proposal_sa_empty_body = (uint64_t)1 << 11, eccf_proposal_inv_flags = (uint64_t)1 << 12, eccf_proposal_additional_attach = (uint64_t)1 << 13, eccf_template_additional_extra = (uint64_t)1 << 14, eccf_template_additional_attach = (uint64_t)1 << 15, eccf_template_more_than_1_multisig = (uint64_t)1 << 16, eccf_template_inv_multisig_2 = (uint64_t)1 << 17, eccf_rel_template_inv_sigs_count = (uint64_t)1 << 18, eccf_rel_template_all_money_to_b = (uint64_t)1 << 19, eccf_rel_template_no_extra_att_inf = (uint64_t)1 << 20, eccf_rel_template_inv_ms_amount = (uint64_t)1 << 21, eccf_rel_template_inv_tsa_flags = (uint64_t)1 << 22, eccf_rel_template_inv_instruction = (uint64_t)1 << 23, eccf_rel_template_signed_parts_in_etc = (uint64_t)1 << 24, eccf_rel_template_inv_tx_flags = (uint64_t)1 << 25, eccf_acceptance_no_tsa_encryption = (uint64_t)1 << 26, eccf_acceptance_no_tsa_compression = (uint64_t)1 << 27, eccf_rel_template_all_money_to_a = (uint64_t)1 << 28, eccf_cancellation_no_tsa_compression = (uint64_t)1 << 29, eccf_cancellation_no_tsa_encryption = (uint64_t)1 << 30, eccf_cancellation_inv_crypt_address = (uint64_t)1 << 31, }; inline bool build_custom_escrow_template(const std::vector& events, const block& head, const account_keys& a_keys, bc_services::contract_private_details& cpd, uint64_t unlock_time, uint64_t expiration_time, size_t nmix, uint64_t b_fee_release, uint64_t custom_config_mask, uint64_t tx_version, size_t tx_hardfork_id, transaction& escrow_template_tx, /* OUT */ crypto::secret_key& tx_key_sec, /* OUT */ std::vector& used_sources /* IN/OUT */) { // NOTE: this function is intended to produce incorrect output, customized by custom_config_mask. To create correct output use custom_config_mask == 0. // coding style: // value_to_be_used = (~custom_config_mask & eccf_some_desctiption_here) ? correct_value : incorrect_value; std::vector sources; std::vector destinations; bool r = false, need_to_resign = false; uint64_t a_inputs_amount = (~custom_config_mask & eccf_template_inv_a_inputs) ? cpd.amount_a_pledge + cpd.amount_to_pay : TESTS_DEFAULT_FEE * 2; uint64_t ms_amount = (~custom_config_mask & eccf_template_inv_ms_amount) ? cpd.amount_a_pledge + cpd.amount_b_pledge + cpd.amount_to_pay + b_fee_release : cpd.amount_a_pledge + cpd.amount_b_pledge + cpd.amount_to_pay; r = fill_tx_sources(sources, events, head, a_keys, a_inputs_amount, nmix, used_sources); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed"); int64_t change = get_sources_total_amount(sources) - a_inputs_amount; CHECK_AND_ASSERT_MES(change >= 0, false, "fill_tx_sources failed (2)"); if (change > 0) destinations.push_back(tx_destination_entry(change, a_keys.account_address)); if (custom_config_mask & eccf_template_no_multisig) destinations.push_back(tx_destination_entry(ms_amount, a_keys.account_address)); // incorrect else if (custom_config_mask & eccf_template_inv_multisig) destinations.push_back(tx_destination_entry(ms_amount, std::list({ a_keys.account_address, a_keys.account_address }))); // incorrect else if (custom_config_mask & eccf_template_inv_multisig_2) { destinations.push_back(tx_destination_entry(ms_amount, std::list({ a_keys.account_address, a_keys.account_address, a_keys.account_address }))); // incorrect destinations.back().minimum_sigs = 2; // pretend to be correct } else if (custom_config_mask & eccf_template_inv_multisig_low_min_sig) { destinations.push_back(tx_destination_entry(ms_amount, std::list({ a_keys.account_address, cpd.b_addr }))); // seems to be correct destinations.back().minimum_sigs = 1; // incorrect } else if (custom_config_mask & eccf_template_more_than_1_multisig) { destinations.push_back(tx_destination_entry(ms_amount, std::list({ a_keys.account_address, cpd.b_addr }))); // seems to be correct destinations.push_back(tx_destination_entry(TESTS_DEFAULT_FEE, std::list({ a_keys.account_address, cpd.b_addr }))); // double multisig - incorrect } else destinations.push_back(tx_destination_entry(ms_amount, std::list({ a_keys.account_address, cpd.b_addr }))); // truly correct tx_service_attachment sa = AUTO_VAL_INIT(sa); sa.service_id = BC_ESCROW_SERVICE_ID; sa.instruction = (~custom_config_mask & eccf_template_inv_sa_instruction) ? BC_ESCROW_SERVICE_INSTRUCTION_PRIVATE_DETAILS : BC_ESCROW_SERVICE_INSTRUCTION_PUBLIC_DETAILS; sa.flags = (~custom_config_mask & eccf_template_inv_sa_flags) ? TX_SERVICE_ATTACHMENT_ENCRYPT_BODY : 0; bc_services::pack_attachment_as_gzipped_json(cpd, sa); account_public_address crypt_addr = (~custom_config_mask & eccf_template_inv_crypt_address) ? cpd.b_addr : null_pub_addr; uint64_t flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; std::vector extra; extra.push_back(sa); if (custom_config_mask & eccf_template_additional_extra) extra.push_back(extra_user_data({ get_random_text(1024) })); std::vector attachments; if (custom_config_mask & eccf_template_additional_attach) attachments.push_back(tx_comment({ get_random_text(1024) })); r = construct_tx(a_keys, sources, destinations, extra, attachments, escrow_template_tx, tx_version, tx_hardfork_id, tx_key_sec, unlock_time, crypt_addr, expiration_time, CURRENCY_TO_KEY_OUT_RELAXED, true, flags); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); if (custom_config_mask & eccf_template_no_tx_flags) { for(auto it = escrow_template_tx.extra.begin(); it != escrow_template_tx.extra.end(); ++it) if (it->type().hash_code() == typeid(etc_tx_details_flags).hash_code()) { escrow_template_tx.extra.erase(it); break; } need_to_resign = true; } if (need_to_resign) { r = resign_tx(a_keys, sources, escrow_template_tx); CHECK_AND_ASSERT_MES(r, false, "resign_tx failed"); } if (custom_config_mask & eccf_template_no_a_sigs) { escrow_template_tx.signatures.clear(); escrow_template_tx.signatures.push_back(currency::NLSAG_sig()); } append_vector_by_another_vector(used_sources, sources); return true; } inline bool build_custom_escrow_proposal(const std::vector& events, const block& head, const account_keys& a_keys, bc_services::contract_private_details& cpd, uint64_t unlock_time, uint64_t expiration_time, uint64_t template_unlock_time, uint64_t template_expiration_time, size_t nmix, uint64_t a_fee_proposal, uint64_t b_fee_release, uint64_t custom_config_mask, uint64_t tx_version, size_t tx_hardfork_id, transaction& escrow_proposal_tx, /* OUT */ std::vector& used_sources,/* IN/OUT */ bc_services::proposal_body* p_pb = nullptr /* OUT */ ) { // NOTE: this function is intended to produce incorrect output, customized by custom_config_mask. To create correct output use custom_config_mask == 0. // coding style: // value_to_be_used = (~custom_config_mask & eccf_some_desctiption_here) ? correct_value : incorrect_value; std::vector sources; std::vector destinations; bool r = false; bc_services::proposal_body local_pb = AUTO_VAL_INIT(local_pb); if (p_pb == nullptr) p_pb = &local_pb; if (~custom_config_mask & eccf_proposal_sa_empty_body) { r = build_custom_escrow_template(events, head, a_keys, cpd, template_unlock_time, template_expiration_time, nmix, b_fee_release, custom_config_mask, tx_version, tx_hardfork_id, p_pb->tx_template, p_pb->tx_onetime_secret_key, used_sources); CHECK_AND_ASSERT_MES(r, false, "build_custom_escrow_template failed"); } tx_service_attachment sa = AUTO_VAL_INIT(sa); sa.service_id = BC_ESCROW_SERVICE_ID; sa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_PROPOSAL; bc_services::pack_attachment_as_gzipped_bin(*p_pb, sa); sa.flags = (~custom_config_mask & eccf_proposal_inv_sa_flags) ? (sa.flags | TX_SERVICE_ATTACHMENT_ENCRYPT_BODY) : 0; std::vector attachments; attachments.push_back(sa); if (custom_config_mask & eccf_proposal_additional_attach) attachments.push_back(std::string(get_random_text(1024))); r = fill_tx_sources(sources, events, head, a_keys, a_fee_proposal, nmix, used_sources); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed"); int64_t change = get_sources_total_amount(sources) - a_fee_proposal; CHECK_AND_ASSERT_MES(change >= 0, false, "fill_tx_sources failed (2)"); if (change > 0) destinations.push_back(tx_destination_entry(change, a_keys.account_address)); account_public_address crypt_addr = cpd.b_addr; crypto::secret_key tx_key_sec; // stub, not used r = construct_tx(a_keys, sources, destinations, empty_extra, attachments, escrow_proposal_tx, tx_version, tx_hardfork_id, tx_key_sec, unlock_time, crypt_addr, expiration_time, 0, true, (~custom_config_mask & eccf_proposal_inv_flags) ? 0 : TX_FLAG_SIGNATURE_MODE_SEPARATE); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); append_vector_by_another_vector(used_sources, sources); return true; } inline bool build_custom_escrow_release_template( const std::string& release_type, const account_keys& b_keys, const bc_services::contract_private_details& cpd, uint64_t unlock_time, uint64_t expiration_time, uint64_t tx_version, size_t tx_hardfork_id, const transaction& escrow_template_tx, /* IN (needed for ms output, tx pub key) */ uint64_t custom_config_mask, /* IN */ transaction& tx /* OUT */ ) { // NOTE: this function is intended to produce incorrect output, customized by custom_config_mask. To create correct output use custom_config_mask == 0. // coding style: // value_to_be_used = (~custom_config_mask & eccf_some_desctiption_here) ? correct_value : incorrect_value; size_t ms_idx = get_multisig_out_index(escrow_template_tx.vout); CHECK_AND_ASSERT_MES(ms_idx < escrow_template_tx.vout.size(), false, "Incorrect ms output index: " << ms_idx); crypto::hash ms_id = get_multisig_out_id(escrow_template_tx, ms_idx); // inputs // create multisig (A-B) source, add keys from B tx_source_entry se = AUTO_VAL_INIT(se); se.amount = (~custom_config_mask & eccf_rel_template_inv_ms_amount) ?boost::get( escrow_template_tx.vout[ms_idx]).amount :boost::get( escrow_template_tx.vout[ms_idx]).amount + 10 * TESTS_DEFAULT_FEE; se.multisig_id = ms_id; se.real_output_in_tx_index = ms_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(escrow_template_tx); se.ms_keys_count = boost::get(boost::get(escrow_template_tx.vout[ms_idx]).target).keys.size(); se.ms_sigs_count = (~custom_config_mask & eccf_rel_template_inv_sigs_count) ? 2 : 1; std::vector sources({ se }); // extra tx_service_attachment tsa = AUTO_VAL_INIT(tsa); tsa.service_id = BC_ESCROW_SERVICE_ID; tsa.instruction = (~custom_config_mask & eccf_rel_template_inv_instruction) ? release_type : BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN; tsa.body = release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL ? "" : "the roof, the roof, the roof is on fire"; // allow non-empty body for release templates (only one will have it for test purposes) tsa.flags = (~custom_config_mask & eccf_rel_template_inv_tsa_flags) ? 0 : TX_SERVICE_ATTACHMENT_DEFLATE_BODY; //tsa.security = null_pkey; std::vector extra({ tsa }); // outputs std::vector destinations; if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) { if (custom_config_mask & eccf_rel_template_all_money_to_b) { destinations.push_back(tx_destination_entry(cpd.amount_a_pledge + cpd.amount_b_pledge + cpd.amount_to_pay, cpd.b_addr)); } else if (custom_config_mask & eccf_rel_template_inv_ms_amount) { destinations.push_back(tx_destination_entry(cpd.amount_a_pledge, cpd.a_addr)); destinations.push_back(tx_destination_entry(cpd.amount_b_pledge + cpd.amount_to_pay, cpd.b_addr)); destinations.push_back(tx_destination_entry(10 * TESTS_DEFAULT_FEE, cpd.b_addr)); } else { // correct destinations.push_back(tx_destination_entry(cpd.amount_a_pledge, cpd.a_addr)); destinations.push_back(tx_destination_entry(cpd.amount_b_pledge + cpd.amount_to_pay, cpd.b_addr)); } } else if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) { if (custom_config_mask & eccf_rel_template_all_money_to_b) { destinations.push_back(tx_destination_entry(cpd.amount_a_pledge + cpd.amount_b_pledge + cpd.amount_to_pay, cpd.b_addr)); } else { // correct destinations.push_back(tx_destination_entry(cpd.amount_a_pledge + cpd.amount_b_pledge + cpd.amount_to_pay, null_pub_addr)); } } else if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL) { if (custom_config_mask & eccf_rel_template_all_money_to_a) { destinations.push_back(tx_destination_entry(cpd.amount_a_pledge + cpd.amount_b_pledge + cpd.amount_to_pay, cpd.a_addr)); } else { // correct destinations.push_back(tx_destination_entry(cpd.amount_a_pledge + cpd.amount_to_pay, cpd.a_addr)); destinations.push_back(tx_destination_entry(cpd.amount_b_pledge, cpd.b_addr)); } } else { return false; } // attachments std::vector attachments; if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) { attachments.push_back(std::string("We don't need no water -- so let all the coins burn!")); // attachments are allowed and must be normally handled, so one of release template will always have them attachments.push_back(tx_comment({ "Burn, all the coins, burn!" })); } uint64_t tx_flags = 0; if (custom_config_mask & eccf_rel_template_inv_tx_flags) { tx_flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; sources.back().separately_signed_tx_complete = true; // correctly finalize separately-constructed tx to avoid errors in construct_tx and sign_multisig_input_in_tx } crypto::secret_key one_time_secret_key = AUTO_VAL_INIT(one_time_secret_key); account_public_address crypt_address = AUTO_VAL_INIT(crypt_address); bool r = construct_tx(b_keys, sources, destinations, extra, attachments, tx, tx_version, tx_hardfork_id, one_time_secret_key, unlock_time, crypt_address, expiration_time, 0, true, tx_flags); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); bool tx_fully_signed = false; r = sign_multisig_input_in_tx(tx, 0, b_keys, escrow_template_tx, &tx_fully_signed); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, returned: " << r << ", tx_fully_signed: " << tx_fully_signed); bool need_to_resign = false; if (custom_config_mask & eccf_rel_template_no_extra_att_inf) { // remove extra_attachment_info remove_all_entries_from_variant_container_by_type(tx.extra); for(auto in : tx.vin) { auto p_etc_details = get_input_etc_details(in); if (p_etc_details != nullptr) remove_all_entries_from_variant_container_by_type(*p_etc_details); } need_to_resign = true; } if (custom_config_mask & eccf_rel_template_signed_parts_in_etc) { CHECK_AND_ASSERT_MES(tx.vin.size() == 1, false, "Invalid vin size: " << tx.vin.size()); signed_parts sp; sp.n_extras = 10; sp.n_outs = 10; boost::get(tx.vin.back()).etc_details.push_back(sp); need_to_resign = true; } if (need_to_resign) { r = resign_tx(b_keys, sources, tx, &escrow_template_tx); CHECK_AND_ASSERT_MES(r, false, "resign_tx failed"); } return true; } inline bool build_custom_escrow_accept_proposal( const std::vector& events, const block& head, size_t nmix, const account_keys& b_keys, const bc_services::contract_private_details& cpd, uint64_t unlock_time, uint64_t expiration_time, uint64_t release_unlock_time, uint64_t release_expiration_time, uint64_t b_fee_accept, uint64_t b_fee_release, uint64_t custom_config_mask, /* IN */ crypto::secret_key one_time_secret_key, /* IN */ uint64_t tx_version, /* IN */ size_t tx_hardfork_id, /* IN */ transaction& tx, /* IN (escrow template), OUT */ std::vector& used_sources /* IN/OUT */ ) { // NOTE: this function is intended to produce incorrect output, customized by custom_config_mask. To create correct output use custom_config_mask == 0. // coding style: // value_to_be_used = (~custom_config_mask & eccf_some_desctiption_here) ? correct_value : incorrect_value; bool r = false; // complete separately-signed tx: B-part inputs std::vector sources; uint64_t b_sources_amount = cpd.amount_b_pledge + b_fee_accept + b_fee_release; r = fill_tx_sources(sources, events, head, b_keys, b_sources_amount, nmix, used_sources, true, true, false); CHECK_AND_ASSERT_MES(r && !sources.empty(), false, "fill_tx_sources failed"); uint64_t b_found_sources_amount = get_sources_total_amount(sources); CHECK_AND_ASSERT_MES(b_found_sources_amount >= b_sources_amount, false, "fill_tx_sources returned incorrect sourses, found amount: " << b_found_sources_amount << ", requested: " << b_sources_amount); uint64_t b_change = b_found_sources_amount - b_sources_amount; // complete separately-signed tx: B-part outputs std::vector destinations; if (b_change > 0) destinations.push_back(tx_destination_entry(b_change, cpd.b_addr)); // generate release templates bc_services::escrow_relese_templates_body rtb = AUTO_VAL_INIT(rtb); r = build_custom_escrow_release_template(BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL, b_keys, cpd, release_unlock_time, release_expiration_time, tx_version, tx_hardfork_id, tx, custom_config_mask, rtb.tx_normal_template); CHECK_AND_ASSERT_MES(r, false, "build_custom_escrow_release_template(normal) failed"); r = build_custom_escrow_release_template(BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN, b_keys, cpd, release_unlock_time, release_expiration_time, tx_version, tx_hardfork_id, tx, custom_config_mask, rtb.tx_burn_template); CHECK_AND_ASSERT_MES(r, false, "build_custom_escrow_release_template(burn) failed"); // put release templates into the extra tx_service_attachment tsa = AUTO_VAL_INIT(tsa); tsa.service_id = BC_ESCROW_SERVICE_ID; tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_TEMPLATES; bc_services::pack_attachment_as_gzipped_bin(rtb, tsa); if (custom_config_mask & eccf_acceptance_no_tsa_compression) tsa.flags &= ~TX_SERVICE_ATTACHMENT_DEFLATE_BODY; // clear flag if (~custom_config_mask & eccf_acceptance_no_tsa_encryption) tsa.flags |= TX_SERVICE_ATTACHMENT_ENCRYPT_BODY; std::vector extra; extra.push_back(tsa); if (unlock_time != 0) extra.push_back(etc_tx_details_unlock_time({ unlock_time })); // should be explicitly specified here, as construct_tx ignores it when append_mode is true if (expiration_time != 0) extra.push_back(etc_tx_details_expiration_time({expiration_time})); // should be explicitly specified here, as construct_tx ignores it when append_mode is true // attachments std::vector attachments; // construct tx (2nd phase - adding B-part and complete) uint64_t tx_flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; sources.back().separately_signed_tx_complete = true; account_public_address crypt_address = AUTO_VAL_INIT(crypt_address); r = construct_tx(b_keys, sources, destinations, extra, attachments, tx, tx_version, tx_hardfork_id, one_time_secret_key, 0, crypt_address, 0, 0, true, tx_flags); // see comment above regarding unlock_time and expiration_time CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); return true; } inline bool build_custom_escrow_cancel_proposal( const std::vector& events, const block& head, size_t nmix, const account_keys& a_keys, const bc_services::contract_private_details& cpd, uint64_t unlock_time, uint64_t expiration_time, uint64_t release_unlock_time, uint64_t release_expiration_time, uint64_t a_fee_cancellation_request, uint64_t custom_config_mask, /* IN */ const transaction& escrow_template_tx, /* IN */ uint64_t tx_version, /* IN */ size_t tx_hardfork_id, /* IN */ transaction& tx, /* OUT */ std::vector& used_sources /* IN/OUT */ ) { // NOTE: this function is intended to produce incorrect output, customized by custom_config_mask. To create correct output use custom_config_mask == 0. // coding style: // value_to_be_used = (~custom_config_mask & eccf_some_desctiption_here) ? correct_value : incorrect_value; bool r = false; // inputs std::vector sources; r = fill_tx_sources(sources, events, head, a_keys, a_fee_cancellation_request, nmix, used_sources, true, true, false); CHECK_AND_ASSERT_MES(r && !sources.empty(), false, "fill_tx_sources failed"); uint64_t a_found_sources_amount = get_sources_total_amount(sources); CHECK_AND_ASSERT_MES(a_found_sources_amount >= a_fee_cancellation_request, false, "fill_tx_sources returned incorrect sourses, found amount: " << a_found_sources_amount << ", requested: " << a_fee_cancellation_request); uint64_t a_change = a_found_sources_amount - a_fee_cancellation_request; // outputs std::vector destinations; if (a_change > 0) destinations.push_back(tx_destination_entry(a_change, cpd.a_addr)); // generate cancel release template bc_services::escrow_cancel_templates_body ctb = AUTO_VAL_INIT(ctb); r = build_custom_escrow_release_template(BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL, a_keys, cpd, release_unlock_time, release_expiration_time, tx_version, tx_hardfork_id, escrow_template_tx, custom_config_mask, ctb.tx_cancel_template); CHECK_AND_ASSERT_MES(r, false, "build_custom_escrow_release_template(cancel) failed"); // put release templates into the extra tx_service_attachment tsa = AUTO_VAL_INIT(tsa); tsa.service_id = BC_ESCROW_SERVICE_ID; tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL; bc_services::pack_attachment_as_gzipped_bin(ctb, tsa); if (custom_config_mask & eccf_cancellation_no_tsa_compression) tsa.flags &= ~TX_SERVICE_ATTACHMENT_DEFLATE_BODY; // clear flag if (~custom_config_mask & eccf_cancellation_no_tsa_encryption) tsa.flags |= TX_SERVICE_ATTACHMENT_ENCRYPT_BODY; std::vector extra; extra.push_back(tsa); // attachments std::vector attachments; uint64_t tx_flags = 0; account_public_address crypt_address = AUTO_VAL_INIT(crypt_address); if (~custom_config_mask & eccf_cancellation_inv_crypt_address) crypt_address = cpd.b_addr; crypto::secret_key one_time_secret_key = AUTO_VAL_INIT(one_time_secret_key); r = construct_tx(a_keys, sources, destinations, extra, attachments, tx, tx_version, tx_hardfork_id, one_time_secret_key, unlock_time, crypt_address, expiration_time, 0, true, tx_flags); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); return true; } inline bool operator==(const bc_services::contract_private_details& lhs, const bc_services::contract_private_details& rhs) { return lhs.amount_a_pledge == rhs.amount_a_pledge && lhs.amount_b_pledge == rhs.amount_b_pledge && lhs.amount_to_pay == rhs.amount_to_pay && lhs.a_addr == rhs.a_addr && lhs.b_addr == rhs.b_addr && lhs.comment == rhs.comment && lhs.title == rhs.title; } inline bool check_contract_state(const tools::escrow_contracts_container& contracts, const bc_services::contract_private_details& cpd, tools::wallet_public::escrow_contract_details::contract_state state, const char* party) { CHECK_AND_ASSERT_MES(contracts.size() == 1, false, party << " has incorrect number of contracts: " << contracts.size()); CHECK_AND_ASSERT_MES(contracts.begin()->second.private_detailes == cpd, false, party << " has invalid contract's private details"); CHECK_AND_ASSERT_MES(contracts.begin()->second.state == state, false, party << " has invalid contract state: " << tools::wallet_public::get_escrow_contract_state_name(contracts.begin()->second.state) << ", expected state: " << tools::wallet_public::get_escrow_contract_state_name(state)); return true; }