1
0
Fork 0
forked from lthn/blockchain

core: asset operation refactoring WIP

This commit is contained in:
sowle 2024-10-18 03:15:33 +02:00
parent 7345096a91
commit 2632446ee5
No known key found for this signature in database
GPG key ID: C07A24B2D89D49FC
6 changed files with 346 additions and 117 deletions

View file

@ -830,7 +830,7 @@ bool blockchain_storage::purge_transaction_from_blockchain(const crypto::hash& t
fee = get_tx_fee(tx_res_ptr->tx);
purge_transaction_keyimages_from_blockchain(tx, true);
bool r = unprocess_blockchain_tx_extra(tx);
bool r = unprocess_blockchain_tx_extra(tx, tx_res_ptr->m_keeper_block_height);
CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra for tx " << tx_id);
r = unprocess_blockchain_tx_attachments(tx, get_current_blockchain_size(), 0/*TODO: add valid timestamp here in future if need*/);
@ -3805,7 +3805,7 @@ bool blockchain_storage::pop_transaction_from_global_index(const transaction& tx
return true;
}
//------------------------------------------------------------------
bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx)
bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx, const uint64_t height)
{
tx_extra_info ei = AUTO_VAL_INIT(ei);
bool r = parse_and_validate_tx_extra(tx, ei);
@ -3818,9 +3818,7 @@ bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx)
if (ei.m_asset_operation.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED)
{
crypto::public_key asset_id = currency::null_pkey;
CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ei.m_asset_operation, nullptr, &asset_id), false, "get_or_calculate_asset_id failed");
r = pop_asset_info(asset_id);
r = pop_asset_info(ei.m_asset_operation, height);
CHECK_AND_ASSERT_MES(r, false, "failed to pop_alias_info");
}
return true;
@ -3862,15 +3860,16 @@ bool blockchain_storage::get_asset_info(const crypto::public_key& asset_id, asse
{
CRITICAL_REGION_LOCAL(m_read_lock);
auto as_ptr = m_db_assets.find(asset_id);
if (as_ptr)
{
if (as_ptr->size())
{
result = as_ptr->back().descriptor;
return true;
}
}
return false;
if (!as_ptr)
return false;
if (as_ptr->empty())
return false;
// the last history item must have opt_descriptor, but we check it just to be sure
if (!as_ptr->back().opt_descriptor.has_value())
return false;
result = as_ptr->back().opt_descriptor.get();
return true;
}
//------------------------------------------------------------------
uint64_t blockchain_storage::get_assets(uint64_t offset, uint64_t count, std::list<asset_descriptor_with_id>& assets) const
@ -3882,15 +3881,19 @@ uint64_t blockchain_storage::get_assets(uint64_t offset, uint64_t count, std::li
if (i < offset)
return true; // continue
CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.size(), "asset_descriptor_history unexpectedly have 0 size, asset_id: " << asset_id);
assets.push_back(asset_descriptor_with_id());
static_cast<asset_descriptor_base&>(assets.back()) = asset_descriptor_history.back().descriptor;
assets.back().asset_id = asset_id;
CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.size(), "unexpectedly, the asset_descriptor_history is empty; asset_id: " << asset_id);
CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.back().opt_descriptor.has_value(), "the last element of asset_descriptor_history doesn't have descriptor; asset_id: " << asset_id);
asset_descriptor_with_id& added_item = assets.emplace_back();
asset_descriptor_base& added_item_adb = static_cast<asset_descriptor_base&>(added_item);
added_item_adb = asset_descriptor_history.back().opt_descriptor.get();
replace_asset_ticker_and_full_name_if_invalid(added_item_adb, asset_id);
added_item.asset_id = asset_id;
if (assets.size() >= count)
{
return false;
return false; // stop
}
return true;
return true; // continue
});
return assets.size();
}
@ -4084,21 +4087,71 @@ bool blockchain_storage::put_alias_info(const transaction & tx, extra_alias_entr
return true;
}
//------------------------------------------------------------------
bool blockchain_storage::pop_asset_info(const crypto::public_key& asset_id)
bool blockchain_storage::pop_asset_info(const asset_descriptor_operation& ado, const uint64_t height)
{
CRITICAL_REGION_LOCAL(m_read_lock);
crypto::public_key asset_id = currency::null_pkey;
CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ado, nullptr, &asset_id), false, "get_or_calculate_asset_id failed");
auto asset_history_ptr = m_db_assets.find(asset_id);
CHECK_AND_ASSERT_MES(asset_history_ptr && asset_history_ptr->size(), false, "empty name list in pop_asset_info");
assets_container::t_value_type local_asset_hist = *asset_history_ptr;
local_asset_hist.pop_back();
if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height))
{
// NEW HF5 handling
assets_container::t_value_type local_asset_hist = *asset_history_ptr;
asset_descriptor_operation& last_ado = local_asset_hist.back(); // above we made sure that the history isn't empty
CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing during asset pop, op: " << (int)ado.operation_type);
asset_descriptor_base& last_adb = last_ado.opt_descriptor.get();
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT)
{
// just change the most recent history record, don't pop
CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)");
last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key();
if (!last_adb.hidden_supply)
{
CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)");
last_ado.opt_amount.get() -= ado.opt_amount.get();
}
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN)
{
// just change the most recent history record, don't pop
CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)");
last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key();
if (!last_adb.hidden_supply)
{
CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)");
last_ado.opt_amount.get() += ado.opt_amount.get();
}
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)
{
// just pop the most recent history record
local_asset_hist.pop_back();
}
else
{
CHECK_AND_ASSERT_MES(false, false, "invalid operation: " << (int)ado.operation_type);
}
}
else
{
// HF4
local_asset_hist.pop_back();
}
// both HF4 and HF5
if (local_asset_hist.size())
m_db_assets.set(asset_id, local_asset_hist);
else
m_db_assets.erase(asset_id);
LOG_PRINT_MAGENTA("[ASSET_POP]: " << asset_id << ": " << (!local_asset_hist.empty() ? "(prev)" : "(erased)"), LOG_LEVEL_1);
return true;
}
//------------------------------------------------------------------
@ -4107,16 +4160,18 @@ bool blockchain_storage::validate_ado_ownership(asset_op_verification_context& a
bool r = false;
CHECK_AND_ASSERT_MES(avc.asset_op_history->size() != 0, false, "asset with id " << avc.asset_id << " has empty history record");
const asset_descriptor_operation& last_ado = avc.asset_op_history->back();
CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "last ado is missing opt_descriptor; asset id: " << avc.asset_id);
const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get();
if (is_hardfork_active(ZANO_HARDFORK_05)) // TODO: consider changing to height-specific check
if (is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height))
{
if (last_ado.descriptor.owner_eth_pub_key.has_value())
if (last_adb.owner_eth_pub_key.has_value())
{
CHECK_AND_ASSERT_MES(last_ado.descriptor.owner == null_pkey, false, "owner_eth_pub_key is set but owner pubkey is nonzero");
CHECK_AND_ASSERT_MES(last_adb.owner == null_pkey, false, "owner_eth_pub_key is set but owner pubkey is nonzero");
asset_operation_ownership_proof_eth aoop_eth{};
r = get_type_in_variant_container(avc.tx.proofs, aoop_eth);
CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof_eth is missing");
if (!crypto::verify_eth_signature(avc.tx_id, last_ado.descriptor.owner_eth_pub_key.value(), aoop_eth.eth_sig))
if (!crypto::verify_eth_signature(avc.tx_id, last_adb.owner_eth_pub_key.value(), aoop_eth.eth_sig))
{
LOG_ERROR("Failed to validate secp256k1 signature for hash: " << avc.tx_id << ", signature: " << aoop_eth.eth_sig);
return false;
@ -4133,12 +4188,13 @@ bool blockchain_storage::validate_ado_ownership(asset_op_verification_context& a
r = get_type_in_variant_container(avc.tx.proofs, aoop);
CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof is missing");
return crypto::verify_schnorr_sig(avc.tx_id, last_ado.descriptor.owner, aoop.gss);
return crypto::verify_schnorr_sig(avc.tx_id, last_adb.owner, aoop.gss);
}
//------------------------------------------------------------------
bool blockchain_storage::validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const
bool blockchain_storage::validate_asset_operation_hf4(asset_op_verification_context& avc) const
{
CRITICAL_REGION_LOCAL(m_read_lock);
CHECK_AND_ASSERT_MES(!is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height), false, "validate_asset_operation_hf4 was called in HF5");
CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(avc.ado, &avc.asset_id_pt, &avc.asset_id), false, "get_or_calculate_asset_id failed");
avc.asset_op_history = m_db_assets.find(avc.asset_id);
@ -4150,16 +4206,25 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER)
{
CHECK_AND_ASSERT_MES(!avc.asset_op_history, false, "asset with id " << avc.asset_id << " has already been registered");
avc.amount_to_validate = ado.descriptor.current_supply;
if(this->is_hardfork_active(ZANO_HARDFORK_05))
{
CHECK_AND_ASSERT_MES(validate_ado_initial(ado.descriptor), false, "validate_ado_initial failed!");
}
CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id);
// CZ please review the following two added lines, do we need them _here_? I think they should go to hardfork_specific_terms...
// anyway, we musn't miss these checks for the sake of consensus
CHECK_AND_ASSERT_MES(!ado.opt_descriptor.get().owner_eth_pub_key.has_value(), false, "owner_eth_pub_key is prohibited before HF5");
CHECK_AND_ASSERT_MES(!ado.opt_asset_id_salt.has_value(), false, "opt_asset_id_salt is prohibited before HF5");
CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id);
avc.amount_to_validate = ado.opt_descriptor.get().current_supply;
}
else
{
CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size() > 0, false, "asset with id " << avc.asset_id << " has not been registered");
const asset_descriptor_operation& last_ado = avc.asset_op_history->back();
CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in last ado, op: " << (int)ado.operation_type);
const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get();
CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in ado, op: " << (int)ado.operation_type);
const asset_descriptor_base adb = ado.opt_descriptor.get();
// check ownership permission
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)
{
@ -4171,23 +4236,23 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)
{
//check that total current_supply haven't changed
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply == last_ado.descriptor.current_supply, false, "update operation attempted to change emission, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "update operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(adb.current_supply == last_adb.current_supply, false, "update operation attempted to change emission, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "update operation modifies asset descriptor in a prohibited manner");
need_to_validate_ao_amount_commitment = false;
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT)
{
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_ado.descriptor.current_supply, false, "emit operation does not increase the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "emit operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_ado.descriptor.meta_info, false, "emit operation is not allowed to update meta info");
avc.amount_to_validate = ado.descriptor.current_supply - last_ado.descriptor.current_supply;
CHECK_AND_ASSERT_MES(adb.current_supply > last_adb.current_supply, false, "emit operation does not increase the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "emit operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(adb.meta_info == last_adb.meta_info, false, "emit operation is not allowed to update meta info");
avc.amount_to_validate = adb.current_supply - last_adb.current_supply;
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN)
{
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_ado.descriptor.current_supply, false, "burn operation does not decrease the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "burn operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_ado.descriptor.meta_info, false, "burn operation is not allowed to update meta info");
avc.amount_to_validate = last_ado.descriptor.current_supply - ado.descriptor.current_supply;
CHECK_AND_ASSERT_MES(adb.current_supply < last_adb.current_supply, false, "burn operation does not decrease the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "burn operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(adb.meta_info == last_adb.meta_info, false, "burn operation is not allowed to update meta info");
avc.amount_to_validate = last_adb.current_supply - adb.current_supply;
}
else
{
@ -4205,35 +4270,182 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state
return true;
}
//------------------------------------------------------------------
bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado)
bool blockchain_storage::validate_asset_operation(asset_op_verification_context& avc) const
{
CRITICAL_REGION_LOCAL(m_read_lock);
CHECK_AND_ASSERT_MES(is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height), false, "validate_asset_operation was called before HF5");
CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(avc.ado, &avc.asset_id_pt, &avc.asset_id), false, "get_or_calculate_asset_id failed");
avc.asset_op_history = m_db_assets.find(avc.asset_id);
const asset_descriptor_operation& ado = avc.ado;
bool need_to_validate_ao_amount_commitment = true;
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER)
{
CHECK_AND_ASSERT_MES(!avc.asset_op_history, false, "asset with id " << avc.asset_id << " has already been registered");
CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id);
avc.amount_to_validate = ado.opt_descriptor.get().current_supply;
// HF5 specific
CHECK_AND_ASSERT_MES(validate_ado_initial(ado.opt_descriptor.get()), false, "validate_ado_initial failed!");
CHECK_AND_ASSERT_MES(ado.opt_amount.has_value() && avc.amount_to_validate == ado.opt_amount.get(), false, "opt_amount is missing or incorrect");
}
else
{
CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size() > 0, false, "asset with id " << avc.asset_id << " has not been registered");
const asset_descriptor_operation& last_ado = avc.asset_op_history->back();
CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in last ado, op: " << (int)ado.operation_type);
const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get();
// check ownership permission
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)
{
bool r = validate_ado_ownership(avc);
CHECK_AND_ASSERT_MES(r, false, "Failed to validate ownership of asset_descriptor_operation, rejecting");
}
avc.amount_to_validate = 0;
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)
{
CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (update)");
//check that total current_supply haven't changed
CHECK_AND_ASSERT_MES(ado.opt_descriptor.get().current_supply == last_adb.current_supply, false, "update operation attempted to change emission, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.opt_descriptor.get(), last_adb), false, "update operation modifies asset descriptor in a prohibited manner");
need_to_validate_ao_amount_commitment = false;
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT)
{
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_adb.current_supply, false, "emit operation does not increase the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "emit operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "emit operation is not allowed to update meta info");
if (!last_adb.hidden_supply)
{
CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "opt_amount is missing (emit)");
avc.amount_to_validate = ado.opt_amount.get();
}
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN)
{
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_adb.current_supply, false, "burn operation does not decrease the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "burn operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "burn operation is not allowed to update meta info");
if (!last_adb.hidden_supply)
{
CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "opt_amount is missing (burn)");
avc.amount_to_validate = ado.opt_amount.get();
}
}
else
{
LOG_ERROR("Unknown operation type: " << (int)ado.operation_type);
return false;
}
}
if (need_to_validate_ao_amount_commitment)
{
bool r = validate_asset_operation_amount_commitment(avc);
CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation");
}
return true;
}
//------------------------------------------------------------------
bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, const uint64_t height)
{
CRITICAL_REGION_LOCAL(m_read_lock);
asset_op_verification_context avc = { tx, tx_id, ado };
CHECK_AND_ASSERT_MES(validate_asset_operation_against_current_blochain_state(avc), false, "asset operation validation failed");
assets_container::t_value_type local_asset_history{};
if (avc.asset_op_history)
local_asset_history = *avc.asset_op_history;
local_asset_history.push_back(ado);
m_db_assets.set(avc.asset_id, local_asset_history);
switch(ado.operation_type)
asset_op_verification_context avc = { tx, tx_id, ado, height };
if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height))
{
case ASSET_DESCRIPTOR_OPERATION_REGISTER:
LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(ado.descriptor.current_supply, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1);
break;
case ASSET_DESCRIPTOR_OPERATION_UPDATE:
LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1);
break;
case ASSET_DESCRIPTOR_OPERATION_EMIT:
LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1);
break;
case ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN:
LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1);
break;
default:
LOG_ERROR("Unknown operation type: " << (int)ado.operation_type);
CHECK_AND_ASSERT_MES(validate_asset_operation(avc), false, "asset operation validation failed (HF5)");
// NEW HF5 handling here
assets_container::t_value_type local_asset_history{};
if (avc.asset_op_history)
local_asset_history = *avc.asset_op_history;
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER)
{
CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (register)"); // validated in validate_asset_operation(), just in case
const asset_descriptor_base& adb = ado.opt_descriptor.get();
local_asset_history.push_back(ado);
LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(adb.current_supply, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1);
}
else
{
CHECK_AND_ASSERT_MES(!local_asset_history.empty(), false, "local_asset_history is empty (update/emit/burn)");
asset_descriptor_operation& last_ado = local_asset_history.back();
CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "last_ado.opt_descriptor is missing (update/emit/burn)");
asset_descriptor_base& last_adb = local_asset_history.back().opt_descriptor.get();
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)
{
local_asset_history.push_back(ado);
LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1);
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT)
{
// just change the most recent history record, don't push
CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)");
last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key();
if (!last_adb.hidden_supply)
{
CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)");
last_ado.opt_amount.get() += ado.opt_amount.get();
}
LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, last_adb.decimal_point) << ", " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1);
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN)
{
// just change the most recent history record, don't push
CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)");
last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key();
if (!last_adb.hidden_supply)
{
CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)");
last_ado.opt_amount.get() -= ado.opt_amount.get();
}
LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, last_adb.decimal_point) << ", " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1);
}
else
CHECK_AND_ASSERT_MES(false, false, "Unknown operation type: " << (int)ado.operation_type);
}
m_db_assets.set(avc.asset_id, local_asset_history);
}
else
{
// HF4
CHECK_AND_ASSERT_MES(validate_asset_operation_hf4(avc), false, "asset operation validation failed (HF4)");
assets_container::t_value_type local_asset_history{};
if (avc.asset_op_history)
local_asset_history = *avc.asset_op_history;
local_asset_history.push_back(ado);
m_db_assets.set(avc.asset_id, local_asset_history);
const asset_descriptor_base& adb = ado.opt_descriptor.get(); // in HF4 descriptor must always be present, validated in validate_asset_operation_hf4()
switch(ado.operation_type)
{
case ASSET_DESCRIPTOR_OPERATION_REGISTER:
LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(adb.current_supply, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1);
break;
case ASSET_DESCRIPTOR_OPERATION_UPDATE:
LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1);
break;
case ASSET_DESCRIPTOR_OPERATION_EMIT:
LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1);
break;
case ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN:
LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1);
break;
default:
LOG_ERROR("Unknown operation type: " << (int)ado.operation_type);
}
}
return true;
@ -4309,7 +4521,7 @@ bool blockchain_storage::prevalidate_alias_info(const transaction& tx, const ext
return true;
}
//------------------------------------------------------------------
bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id)
bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id, const uint64_t height)
{
//check transaction extra
tx_extra_info ei = AUTO_VAL_INIT(ei);
@ -4325,7 +4537,7 @@ bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, cons
}
if (ei.m_asset_operation.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED)
{
r = put_asset_info(tx, tx_id, ei.m_asset_operation);
r = put_asset_info(tx, tx_id, ei.m_asset_operation, height);
CHECK_AND_ASSERT_MES(r, false, "failed to put_asset_info");
}
@ -4525,7 +4737,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const
CHECK_AND_ASSERT_MES(validate_tx_for_hardfork_specific_terms(tx, tx_id, bl_height), false, "tx " << tx_id << ": hardfork-specific validation failed");
TIME_MEASURE_START_PD(tx_process_extra);
bool r = process_blockchain_tx_extra(tx, tx_id);
bool r = process_blockchain_tx_extra(tx, tx_id, bl_height);
CHECK_AND_ASSERT_MES(r, false, "failed to process_blockchain_tx_extra");
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_process_extra);
@ -4541,7 +4753,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const
{
LOG_ERROR("critical internal error: add_transaction_input_visitor failed. but key_images should be already checked");
purge_transaction_keyimages_from_blockchain(tx, false);
bool r = unprocess_blockchain_tx_extra(tx);
bool r = unprocess_blockchain_tx_extra(tx, bl_height);
CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra");
unprocess_blockchain_tx_attachments(tx, bl_height, timestamp);
@ -4562,7 +4774,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const
{
LOG_ERROR("critical internal error: tx with id: " << tx_id << " in block id: " << bl_id << " already in blockchain");
purge_transaction_keyimages_from_blockchain(tx, true);
bool r = unprocess_blockchain_tx_extra(tx);
bool r = unprocess_blockchain_tx_extra(tx, bl_height);
CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra");
unprocess_blockchain_tx_attachments(tx, bl_height, timestamp);
@ -6967,7 +7179,7 @@ bool blockchain_storage::is_hardfork_active(size_t hardfork_id) const
//------------------------------------------------------------------
bool blockchain_storage::is_hardfork_active_for_height(size_t hardfork_id, uint64_t height) const
{
return m_core_runtime_config.is_hardfork_active_for_height(hardfork_id, height);
return m_core_runtime_config.is_hardfork_active_for_height(hardfork_id, height != UINT64_MAX ? height : m_db_blocks.size());
}
//------------------------------------------------------------------
bool blockchain_storage::prevalidate_block(const block& bl)

View file

@ -381,7 +381,8 @@ namespace currency
const alt_chain_type& alt_chain = alt_chain_type(),
uint64_t split_height = 0)const;
bool validate_ado_ownership(asset_op_verification_context& avc) const;
bool validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const;
bool validate_asset_operation_hf4(asset_op_verification_context& avc) const;
bool validate_asset_operation(asset_op_verification_context& avc) const;
void set_core_runtime_config(const core_runtime_config& pc) const;
const core_runtime_config& get_core_runtime_config()const;
@ -679,14 +680,14 @@ namespace currency
uint64_t get_adjusted_time()const;
bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps);
bool update_next_comulative_size_limit();
bool process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id);
bool unprocess_blockchain_tx_extra(const transaction& tx);
bool process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id, const uint64_t height);
bool unprocess_blockchain_tx_extra(const transaction& tx, const uint64_t height);
bool process_blockchain_tx_attachments(const transaction& tx, uint64_t h, const crypto::hash& bl_id, uint64_t timestamp);
bool unprocess_blockchain_tx_attachments(const transaction& tx, uint64_t h, uint64_t timestamp);
bool pop_alias_info(const extra_alias_entry& ai);
bool put_alias_info(const transaction& tx, extra_alias_entry& ai);
bool pop_asset_info(const crypto::public_key& asset_id);
bool put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado);
bool pop_asset_info(const asset_descriptor_operation& ado, const uint64_t height);
bool put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, const uint64_t height);
void fill_addr_to_alias_dict();
//bool resync_spent_tx_flags();
bool prune_ring_signatures_and_attachments_if_need();

View file

@ -803,12 +803,13 @@ namespace currency
{
uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED;
uint8_t version = 1;
boost::optional<crypto::public_key> opt_amount_commitment; // premultiplied by 1/8
boost::optional<crypto::public_key> opt_asset_id; // target asset_id - for update/emit
boost::optional<asset_descriptor_base> opt_descriptor; //used in deploy/update
boost::optional<uint64_t> opt_amount; //used in burn/emit
std::vector<asset_descriptor_operation_etc_fields> etc; //reserved for future use
// register emit burn update
boost::optional<crypto::public_key> opt_amount_commitment; // + + + - (premultiplied by 1/8)
boost::optional<crypto::public_key> opt_asset_id; // - + + +
boost::optional<asset_descriptor_base> opt_descriptor; // + - - +
boost::optional<uint64_t> opt_amount; // ? ? ? - (only for non-hidden supply)
boost::optional<uint32_t> opt_asset_id_salt; // ? - - - (optional)
std::vector<asset_descriptor_operation_etc_fields> etc; // (reserved for future use)
BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_LAST_VER, version)
@ -819,6 +820,7 @@ namespace currency
FIELD(opt_asset_id)
FIELD(opt_descriptor)
FIELD(opt_amount)
FIELD(opt_asset_id_salt)
FIELD(etc)
END_SERIALIZE()
@ -832,16 +834,18 @@ namespace currency
BOOST_SERIALIZE(opt_asset_id)
BOOST_SERIALIZE(opt_descriptor)
BOOST_SERIALIZE(opt_amount)
BOOST_SERIALIZE(opt_asset_id_salt)
BOOST_SERIALIZE(etc)
END_BOOST_SERIALIZATION_TOTAL_FIELDS(7)
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(version) DOC_DSCR("Asset operation type struct version") DOC_EXMP(2) DOC_END
KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(opt_amount_commitment) DOC_DSCR("Asset operation amount commitment(optional)") DOC_EXMP("5688b56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.(optional)") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END
KV_SERIALIZE(opt_descriptor) DOC_DSCR("Asset operation amount commitment(optional)") DOC_EXMP_AUTO() DOC_END
KV_SERIALIZE(opt_amount) DOC_DSCR("Asset operation amount(optional, needed only for burn/emit)") DOC_EXMP_AUTO() DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(opt_amount_commitment) DOC_DSCR("(optional) Asset operation amount commitment (register/emit/burn).") DOC_EXMP("5688b56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("(optional) ID of an asset (emit/burn/update).") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END
KV_SERIALIZE(opt_descriptor) DOC_DSCR("(optional) Asset operation descriptor (register/update).") DOC_EXMP_AUTO() DOC_END
KV_SERIALIZE(opt_amount) DOC_DSCR("(optional) Asset operation amount (register/emit/burn when supply is non-hidden).") DOC_EXMP_AUTO() DOC_END
KV_SERIALIZE(opt_asset_id_salt) DOC_DSCR("(optional) Asset ID salt. May only be used for asset registration.") DOC_EXMP_AUTO() DOC_END
//KV_SERIALIZE(etc) DOC_DSCR("Extra operations") DOC_EXMP_AUTO() DOC_END <---- serialization for variant not supported yet
END_KV_SERIALIZE_MAP()

View file

@ -619,7 +619,8 @@ namespace currency
{
// make sure that amount commitment corresponds to opt_amount_commitment_g_proof
CHECK_AND_ASSERT_MES(aop.opt_amount_commitment_g_proof.has_value(), false, "opt_amount_commitment_g_proof is absent");
crypto::point_t A = crypto::point_t(context.ado.amount_commitment).modify_mul8() - context.amount_to_validate * context.asset_id_pt;
CHECK_AND_ASSERT_MES(context.ado.opt_amount_commitment.has_value(), false, "amount_commitment is absent");
crypto::point_t A = crypto::point_t(context.ado.opt_amount_commitment.get()).modify_mul8() - context.amount_to_validate * context.asset_id_pt;
bool r = crypto::check_signature(context.tx_id, A.to_public_key(), aop.opt_amount_commitment_g_proof.get());
CHECK_AND_ASSERT_MES(r, false, "opt_amount_commitment_g_proof check failed");
@ -675,12 +676,14 @@ namespace currency
{
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT)
{
CHECK_AND_ASSERT_MES(ado.opt_amount_commitment.has_value(), false, "opt_amount_commitment is missing");
// amount_commitment supposed to be validated earlier in validate_asset_operation_amount_commitment()
sum_of_pseudo_out_amount_commitments += crypto::point_t(ado.amount_commitment); // *1/8
sum_of_pseudo_out_amount_commitments += crypto::point_t(ado.opt_amount_commitment.get()); // *1/8
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN)
{
outs_commitments_sum += crypto::point_t(ado.amount_commitment); // *1/8
CHECK_AND_ASSERT_MES(ado.opt_amount_commitment.has_value(), false, "opt_amount_commitment is missing");
outs_commitments_sum += crypto::point_t(ado.opt_amount_commitment.get()); // *1/8
}
}
size_t zc_sigs_count = 0;
@ -2186,18 +2189,23 @@ namespace currency
// otherwise, it must be a register operation
CHECK_AND_ASSERT_MES(ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER, false, "unexpected asset operation type: " << (int)ado.operation_type << ", " << get_asset_operation_type_string(ado.operation_type));
CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing: " << (int)ado.operation_type << ", " << get_asset_operation_type_string(ado.operation_type));
const asset_descriptor_base &adb = ado.opt_descriptor.get();
// calculate asset id
crypto::hash_helper_t::hs_t hsc;
hsc.add_32_chars(CRYPTO_HDS_ASSET_ID);
hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.ticker));
hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.full_name));
hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.meta_info));
hsc.add_scalar(crypto::scalar_t(ado.descriptor.total_max_supply));
hsc.add_scalar(crypto::scalar_t(ado.descriptor.decimal_point));
hsc.add_pub_key(ado.descriptor.owner);
if (ado.descriptor.owner_eth_pub_key.has_value())
hsc.add_eth_pub_key(ado.descriptor.owner_eth_pub_key.value());
hsc.add_hash(crypto::hash_helper_t::h(adb.ticker));
hsc.add_hash(crypto::hash_helper_t::h(adb.full_name));
hsc.add_hash(crypto::hash_helper_t::h(adb.meta_info));
hsc.add_scalar(crypto::scalar_t(adb.total_max_supply));
hsc.add_scalar(crypto::scalar_t(adb.decimal_point));
hsc.add_pub_key(adb.owner);
if (adb.owner_eth_pub_key.has_value())
hsc.add_eth_pub_key(adb.owner_eth_pub_key.value());
if (ado.opt_asset_id_salt.has_value())
hsc.add_scalar(crypto::scalar_t(ado.opt_asset_id_salt.get()));
crypto::hash h = hsc.calc_hash_no_reduce();
// this hash function needs to be computationally expensive (s.a. the whitepaper)
@ -2242,16 +2250,11 @@ namespace currency
{
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER)
{
//crypto::secret_key asset_control_key{};
//bool r = derive_key_pair_from_key_pair(sender_account_keys.account_address.spend_public_key, tx_key.sec, asset_control_key, ado.descriptor.owner, CRYPTO_HDS_ASSET_CONTROL_KEY);
//CHECK_AND_ASSERT_MES(r, false, "derive_key_pair_from_key_pair failed");
//
// old:
// asset_control_key = Hs(CRYPTO_HDS_ASSET_CONTROL_KEY, 8 * tx_key.sec * sender_account_keys.account_address.spend_public_key, 0)
// ado.descriptor.owner = asset_control_key * G
CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (register)");
asset_descriptor_base& adb = ado.opt_descriptor.get();
if (!ado.descriptor.owner_eth_pub_key.has_value())
ado.descriptor.owner = sender_account_keys.account_address.spend_public_key;
if (!adb.owner_eth_pub_key.has_value())
adb.owner = sender_account_keys.account_address.spend_public_key;
CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ado, &gen_context.ao_asset_id_pt, &gen_context.ao_asset_id), false, "get_or_calculate_asset_id failed");
@ -2268,10 +2271,11 @@ namespace currency
amount_of_emitted_asset += item.amount;
}
}
ado.descriptor.current_supply = amount_of_emitted_asset; // TODO: consider setting current_supply beforehand, not setting it hear in ad-hoc manner -- sowle
adb.current_supply = amount_of_emitted_asset;
ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle
gen_context.ao_amount_commitment = amount_of_emitted_asset * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G;
ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key();
ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key();
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN)
{
@ -2298,10 +2302,10 @@ namespace currency
amount_of_burned_assets -= item.amount;
}
}
ado.descriptor.current_supply -= amount_of_burned_assets;
ado.opt_amount = amount_of_burned_assets; // TODO: support hidden supply -- sowle
gen_context.ao_amount_commitment = amount_of_burned_assets * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G;
ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key();
ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key();
if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_handle_asset_descriptor_operation_before_burn{ &ado });
@ -2325,10 +2329,10 @@ namespace currency
item.asset_id = gen_context.ao_asset_id;
}
}
ado.descriptor.current_supply += amount_of_emitted_asset;
ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle
gen_context.ao_amount_commitment = amount_of_emitted_asset * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G;
ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key();
ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key();
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)

View file

@ -259,6 +259,7 @@ namespace currency
const transaction& tx;
const crypto::hash& tx_id;
const asset_descriptor_operation& ado;
uint64_t height = UINT64_MAX; // default value means the height of the upcoming block (top_block + 1)
crypto::public_key asset_id = currency::null_pkey;
crypto::point_t asset_id_pt = crypto::c_point_0;
uint64_t amount_to_validate = 0;

View file

@ -1316,6 +1316,13 @@ namespace tools
{
WALLET_RPC_BEGIN_TRY_ENTRY();
if (!currency::validate_asset_ticker_and_full_name(req.asset_descriptor))
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT;
er.message = "asset ticker or full_name is invalid";
return false;
}
std::vector<currency::tx_destination_entry> currency_destinations;
rpc_destinations_to_currency_destinations(req.destinations, true, !req.do_not_split_destinations, currency_destinations);