626 lines
31 KiB
C++
626 lines
31 KiB
C++
// 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<currency::txin_v>& 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<txin_to_key>(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<typename entry_t, typename container_t>
|
|
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<tools::wallet2> 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<tools::wallet2> 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<currency::payload_items_v> 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<tx_service_attachment>(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<test_event_entry>& 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<tx_source_entry>& 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<tx_source_entry> sources;
|
|
std::vector<tx_destination_entry> 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<account_public_address>({ 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<account_public_address>({ 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<account_public_address>({ 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<account_public_address>({ a_keys.account_address, cpd.b_addr }))); // seems to be correct
|
|
destinations.push_back(tx_destination_entry(TESTS_DEFAULT_FEE, std::list<account_public_address>({ a_keys.account_address, cpd.b_addr }))); // double multisig - incorrect
|
|
}
|
|
else
|
|
destinations.push_back(tx_destination_entry(ms_amount, std::list<account_public_address>({ 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_v> 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<attachment_v> 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<test_event_entry>& 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<tx_source_entry>& 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<tx_source_entry> sources;
|
|
std::vector<tx_destination_entry> 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<attachment_v> 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<currency::tx_out_bare>( escrow_template_tx.vout[ms_idx]).amount :boost::get<currency::tx_out_bare>( 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<txout_multisig>(boost::get<currency::tx_out_bare>(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<tx_source_entry> 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_v> extra({ tsa });
|
|
|
|
// outputs
|
|
std::vector<tx_destination_entry> 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<attachment_v> 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<extra_attachment_info>(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<extra_attachment_info>(*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<txin_multisig>(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<test_event_entry>& 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<tx_source_entry>& 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<tx_source_entry> 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<tx_destination_entry> 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_v> 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<attachment_v> 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<test_event_entry>& 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<tx_source_entry>& 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<tx_source_entry> 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<tx_destination_entry> 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_v> extra;
|
|
extra.push_back(tsa);
|
|
|
|
// attachments
|
|
std::vector<attachment_v> 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;
|
|
}
|