ons buy and ons update mapping

This commit is contained in:
Sean Darcy 2023-02-28 08:56:41 +11:00
parent 31dd71ce6f
commit 2e6b96afbc
17 changed files with 518 additions and 24 deletions

View file

@ -136,6 +136,13 @@ get_hard_fork_heights(network_type nettype, hf version) {
return found;
}
hard_fork get_latest_hard_fork(network_type nettype) {
if (nettype == network_type::MAINNET) return mainnet_hard_forks.back();
if (nettype == network_type::TESTNET) return testnet_hard_forks.back();
if (nettype == network_type::FAKECHAIN) return fakechain_hardforks.back();
return devnet_hard_forks.back();
}
hf hard_fork_ceil(network_type nettype, hf version) {
auto [it, end] = get_hard_forks(nettype);
for (; it != end; it++)

View file

@ -55,6 +55,9 @@ namespace cryptonote
std::pair<std::optional<uint64_t>, std::optional<uint64_t>>
get_hard_fork_heights(network_type type, hf version);
// Returns the latest hardfork
hard_fork get_latest_hard_fork(network_type type);
// Returns the lowest network version >= the given version, that is, it rounds up missing hf table
// entries to the next largest entry. Typically this returns the network version itself, but if
// some versions are skipped (particularly on testnet/devnet/fakechain) then this will return the

View file

@ -523,6 +523,58 @@ bool bind_and_run(ons_sql_type type, sql_compiled_statement& statement, void *co
} // end anonymous namespace
using namespace std::literals;
using stringtypemap = std::pair<std::string_view, mapping_type>;
static constexpr std::array ons_str_type_mappings = {
stringtypemap{"5"sv, mapping_type::lokinet_10years},
stringtypemap{"4"sv, mapping_type::lokinet_5years},
stringtypemap{"3"sv, mapping_type::lokinet_2years},
stringtypemap{"2"sv, mapping_type::lokinet},
stringtypemap{"1"sv, mapping_type::wallet},
stringtypemap{"0"sv, mapping_type::session},
stringtypemap{"session"sv, mapping_type::session},
stringtypemap{"wallet"sv, mapping_type::wallet},
stringtypemap{"lokinet"sv, mapping_type::lokinet},
stringtypemap{"lokinet_2years"sv, mapping_type::lokinet_2years},
stringtypemap{"lokinet_5years"sv, mapping_type::lokinet_5years},
stringtypemap{"lokinet_10years"sv, mapping_type::lokinet_10years}
};
std::optional<mapping_type>
parse_ons_type(std::string input)
{
// Lower-case the input:
for (auto& c : input)
if (c >= 'A' && c <= 'Z')
c += ('A' - 'a');
for (const auto& [str, map] : ons_str_type_mappings)
if (str == input)
return map;
return std::nullopt;
}
using inttypemap = std::pair<uint16_t, mapping_type>;
static constexpr std::array ons_int_type_mappings = {
inttypemap{5, mapping_type::lokinet_10years},
inttypemap{4, mapping_type::lokinet_5years},
inttypemap{3, mapping_type::lokinet_2years},
inttypemap{2, mapping_type::lokinet},
inttypemap{1, mapping_type::wallet},
inttypemap{0, mapping_type::session}
};
std::optional<mapping_type>
parse_ons_type(uint16_t input)
{
for (const auto& [inttype, map] : ons_int_type_mappings)
if (inttype == input)
return map;
return std::nullopt;
}
bool mapping_record::active(uint64_t blockchain_height) const

View file

@ -210,6 +210,12 @@ struct settings_record
int version;
};
std::optional<mapping_type>
parse_ons_type(std::string input);
std::optional<mapping_type>
parse_ons_type(uint16_t input);
struct mapping_record
{
// NOTE: We keep expired entries in the DB indefinitely because we need to

View file

@ -116,11 +116,14 @@ def balance():
if context.wallet is None:
click.echo("Wallet not loaded")
return
click.echo("Balance: {}".format(context.wallet.get_balance()))
click.echo("Balance: {:.2f} Oxen".format(context.wallet.get_balance()/1e9))
@walletcli.command()
def unlocked_balance():
click.echo("Unlocked Balance: {}".format(context.wallet.get_unlocked_balance()))
if context.wallet is None:
click.echo("Wallet not loaded")
return
click.echo("Unlocked Balance: {:.2f} Oxen".format(context.wallet.get_unlocked_balance()/1e9))
@walletcli.command()
def height():
@ -135,13 +138,51 @@ def transfer():
if address == "" or amount == 0.0:
click.prompt("Invalid address/amount entered")
return
amount_in_atomic_units = round(amount * 10e9, 0);
amount_in_atomic_units = round(amount * 1e9, 0);
destination = {"address": address, "amount": amount_in_atomic_units}
transfer_params = {"destinations": [destination]}
transfer_future = context.rpc_future("restricted.transfer", args=transfer_params);
transfer_response = transfer_future.get();
click.echo("Transfer Response: {}".format(transfer_response))
@walletcli.command()
def ons_buy_mapping():
ons_type = click.prompt("What type of mapping would you like", type=click.Choice(['session', 'wallet', 'lokinet', 'lokinet_2years', 'lokinet_5years', 'lokinet_10years']), default="session").strip()
ons_name = click.prompt("Please enter the ons name you would like to register", default="").strip()
ons_value = click.prompt("Please enter the value for the ons mapping", default="").strip()
ons_owner = click.prompt("Optional: Enter the address of a different owner", default="").strip()
ons_backup_owner = click.prompt("Optional: Enter the address of a backup owner", default="").strip()
ons_buy_params = {
"name": ons_name,
"value": ons_value,
"owner": ons_owner,
"backup_owner": ons_backup_owner,
"type": ons_type,
}
transfer_future = context.rpc_future("restricted.ons_buy_mapping", args=ons_buy_params);
transfer_response = transfer_future.get();
click.echo("ONS Buy Mapping Response: {}".format(transfer_response))
@walletcli.command()
def ons_update_mapping():
ons_name = click.prompt("Please enter the ons name you would like to update", default="").strip()
ons_type = click.prompt("Please enter the type of ONS mapping this is", type=click.Choice(['session', 'wallet', 'lokinet', 'lokinet_2years', 'lokinet_5years', 'lokinet_10years']), default="session").strip()
ons_value = click.prompt("Optional: Please enter a value to modify the ons mapping", default="").strip()
ons_owner = click.prompt("Optional: Please enter an address to modify the owner", default="").strip()
ons_backup_owner = click.prompt("Optional: Please enter an address to modify the backup owner", default="").strip()
ons_update_params = {
"name": ons_name,
"value": ons_value,
"owner": ons_owner,
"backup_owner": ons_backup_owner,
"type": ons_type,
}
transfer_future = context.rpc_future("restricted.ons_update_mapping", args=ons_update_params);
transfer_response = transfer_future.get();
click.echo("ONS Update Mapping Response: {}".format(transfer_response))
@walletcli.command()
def quit():
if context.wallet:

View file

@ -56,6 +56,9 @@ namespace wallet
virtual std::future<std::string>
submit_transaction(const cryptonote::transaction& tx, bool blink) = 0;
virtual std::future<std::string>
ons_names_to_owners(const std::string& name_hash, uint16_t type) = 0;
};
} // namespace wallet

View file

@ -467,6 +467,44 @@ namespace wallet
return fut;
}
std::future<std::string>
DefaultDaemonComms::ons_names_to_owners(const std::string& name_hash, const uint16_t type)
{
auto p = std::make_shared<std::promise<std::string> >();
auto fut = p->get_future();
auto req_cb = [p=std::move(p)](bool ok, std::vector<std::string> response)
{
oxenc::bt_dict_consumer dc{response[1]};
if (not dc.skip_until("result"))
{
auto reason = dc.consume_string();
p->set_value(std::string("ONS names to owners rejected, reason: ") + reason);
return;
}
auto result_list = dc.consume_list_consumer();
const auto result = result_list.consume_dict_data();
p->set_value(std::string(result));
return;
};
oxenc::bt_dict req_params_dict;
oxenc::bt_list name_hash_list;
name_hash_list.push_back(name_hash);
oxenc::bt_list type_list;
type_list.push_back(type);
req_params_dict["name_hash"] = name_hash_list;
req_params_dict["type"] = type_list;
omq->request(conn, "rpc.ons_names_to_owners", req_cb, oxenc::bt_serialize(req_params_dict));
return fut;
}
void
DefaultDaemonComms::register_wallet(wallet::Wallet& wallet, int64_t height, bool check_sync_height, bool new_wallet)
{

View file

@ -57,6 +57,9 @@ namespace wallet
std::future<std::string>
submit_transaction(const cryptonote::transaction& tx, bool blink);
std::future<std::string>
ons_names_to_owners(const std::string& name_hash, const uint16_t type);
private:
void

View file

@ -453,4 +453,36 @@ namespace wallet
return returned_keys;
}
ons::generic_signature
Keyring::generate_ons_signature(const std::string& curr_owner, const ons::generic_owner* new_owner, const ons::generic_owner* new_backup_owner, const ons::mapping_value& encrypted_value, const crypto::hash& prev_txid, const cryptonote::network_type& nettype)
{
ons::generic_signature result;
cryptonote::address_parse_info curr_owner_parsed = {};
if (!cryptonote::get_account_address_from_str(curr_owner_parsed, nettype, curr_owner))
throw std::runtime_error("Could not parse address");
//TODO sean this should actually get it from the db
cryptonote::subaddress_index index = {0,0};
//std::optional<cryptonote::subaddress_index> index = get_subaddress_index(curr_owner_parsed.address);
//if (!index) return false;
auto sig_data = ons::tx_extra_signature(
encrypted_value.to_view(),
new_owner,
new_backup_owner,
prev_txid);
if (sig_data.empty())
throw std::runtime_error("Could not generate signature");
cryptonote::account_base account;
account.create_from_keys(cryptonote::account_public_address{spend_public_key, view_public_key}, spend_private_key, view_private_key);
auto& hwdev = account.get_device();
hw::mode_resetter rst{key_device};
key_device.generate_ons_signature(sig_data, account.get_keys(), index, result.monero);
result.type = ons::generic_owner_sig_type::monero;
return result;
}
} // namespace wallet

View file

@ -3,6 +3,7 @@
#include <crypto/crypto.h>
#include <cryptonote_basic/subaddress_index.h>
#include <cryptonote_basic/cryptonote_basic.h>
#include <cryptonote_core/oxen_name_system.h>
#include <device/device_default.hpp>
#include <ringct/rctSigs.h>
@ -118,6 +119,9 @@ namespace wallet
virtual cryptonote::account_keys
export_keys();
virtual ons::generic_signature
generate_ons_signature(const std::string& curr_owner, const ons::generic_owner* new_owner, const ons::generic_owner* new_backup_owner, const ons::mapping_value& encrypted_value, const crypto::hash& prev_txid, const cryptonote::network_type& nettype);
cryptonote::network_type nettype;
private:

View file

@ -15,7 +15,7 @@ namespace wallet
throw std::runtime_error("Transaction amounts must be positive");
sum_recipient_amounts += recipient.amount;
}
if (new_recipients.empty() || sum_recipient_amounts < 0)
if (sum_recipient_amounts < 0)
throw std::runtime_error("Transaction amounts must be positive");
}
@ -55,8 +55,7 @@ namespace wallet
int64_t
PendingTransaction::get_fee(int64_t n_inputs) const
{
// TODO sean add this
int64_t fixed_fee = 0;
int64_t fixed_fee = burn_fixed;
// TODO sean add this
int64_t burn_pct = 0;
int64_t fee_percent = oxen::BLINK_BURN_TX_FEE_PERCENT_V18; // 100%
@ -121,6 +120,8 @@ namespace wallet
tx.output_unlock_times.push_back(unlock_time);
tx.output_unlock_times.push_back(change_unlock_time);
tx.extra = std::move(extra);
return true;
}

View file

@ -35,7 +35,11 @@ namespace wallet
uint64_t fee_per_byte = cryptonote::FEE_PER_BYTE_V13;
uint64_t fee_per_output = cryptonote::FEE_PER_OUTPUT_V18;
size_t mixin_count = cryptonote::TX_OUTPUT_DECOYS;
size_t extra_size() const {return 0;};
uint64_t burn_fixed = 0;
std::vector<uint8_t> extra = {};
size_t extra_size() const {return extra.size();};
PendingTransaction() = default;

View file

@ -360,12 +360,41 @@ void parse_request(SET_LOG_CATEGORIES& req, rpc_input in) {
}
void parse_request(ONS_BUY_MAPPING& req, rpc_input in) {
get_values(in,
"account_index", req.request.account_index,
"backup_owner", req.request.backup_owner,
"do_not_relay", req.request.do_not_relay,
"get_tx_hex", req.request.get_tx_hex,
"get_tx_key", req.request.get_tx_key,
"get_tx_metadata", req.request.get_tx_metadata,
"name", req.request.name,
"owner", req.request.owner,
"priority", req.request.priority,
"subaddr_indices", req.request.subaddr_indices,
"type", req.request.type,
"value", req.request.value
);
}
void parse_request(ONS_RENEW_MAPPING& req, rpc_input in) {
}
void parse_request(ONS_UPDATE_MAPPING& req, rpc_input in) {
get_values(in,
"account_index", req.request.account_index,
"backup_owner", req.request.backup_owner,
"do_not_relay", req.request.do_not_relay,
"get_tx_hex", req.request.get_tx_hex,
"get_tx_key", req.request.get_tx_key,
"get_tx_metadata", req.request.get_tx_metadata,
"name", req.request.name,
"owner", req.request.owner,
"priority", req.request.priority,
"signature", req.request.signature,
"subaddr_indices", req.request.subaddr_indices,
"type", req.request.type,
"value", req.request.value
);
}
void parse_request(ONS_MAKE_UPDATE_SIGNATURE& req, rpc_input in) {

View file

@ -2481,33 +2481,31 @@ namespace wallet::rpc {
static constexpr auto names() { return NAMES("ons_buy_mapping"); }
static constexpr const char *description =
R"(Buy a Loki Name System (ONS) mapping that maps a unique name to a Session ID or Lokinet address.
R"(Buy an Oxen Name System (ONS) mapping that maps a unique name to a Session ID, Oxen Address or Lokinet address.
Currently supports Session, Lokinet and Wallet registrations. Lokinet registrations can be for 1, 2, 5, or 10 years by specifying a type value of "lokinet", "lokinet_2y", "lokinet_5y", "lokinet_10y". Session registrations do not expire.
Currently supports Session, Wallet and Lokinet registrations. Lokinet registrations can be for 1, 2, 5, or 10 years by specifying a type value of "lokinet", "lokinet_2y", "lokinet_5y", "lokinet_10y". Session and Wallet registrations do not expire.
The owner of the ONS entry (by default, the purchasing wallet) will be permitted to submit ONS update transactions to the Loki blockchain (for example to update a Session pubkey or the target Lokinet address). You may change the primary owner or add a backup owner in the registration and can change them later with update transactions. Owner addresses can be either Loki wallets, or generic ed25519 pubkeys (for advanced uses).
For Session, the recommended owner or backup owner is the ed25519 public key of the user's Session ID.
When specifying owners, either a wallet (sub)address or standard ed25519 public key is supported per mapping. Updating the value that a name maps to requires one of the owners to sign the update transaction. For wallets, this is signed using the (sub)address's spend key.
For more information on updating and signing see the ONS_UPDATE_MAPPING documentation.)";
struct REQUEST
{
std::string type; // The mapping type: "session", "lokinet", "lokinet_2y", "lokinet_5y", "lokinet_10y", "wallet".
std::string owner; // (Optional): The ed25519 public key or wallet address that has authority to update the mapping.
std::string backup_owner; // (Optional): The secondary, backup public key that has authority to update the mapping.
std::string name; // The name to purchase via Oxen Name Service
std::string value; // The value that the name maps to via Oxen Name Service, (i.e. For Session: [display name->session public key], for wallets: [name->wallet address], for Lokinet: [name->domain name]).
std::string type; // The mapping type: "session", "wallet", "lokinet", "lokinet_2y", "lokinet_5y", "lokinet_10y".
std::string owner; // (Optional): The ed25519 public key or wallet address that has authority to update the mapping.
std::string backup_owner; // (Optional): The secondary, backup public key that has authority to update the mapping.
std::string name; // The name to purchase via Oxen Name Service
std::string value; // The value that the name maps to via Oxen Name Service, (i.e. For Session: [display name->session public key], for wallets: [name->wallet address], for Lokinet: [name->domain name]).
uint32_t account_index; // (Optional) Transfer from this account index. (Defaults to 0)
std::set<uint32_t> subaddr_indices; // (Optional) Transfer from this set of subaddresses. (Defaults to 0)
uint32_t priority; // Set a priority for the transaction. Accepted values are: or 0-4 for: default, unimportant, normal, elevated, priority.
bool get_tx_key; // (Optional) Return the transaction key after sending.
bool do_not_relay; // (Optional) If true, the newly created transaction will not be relayed to the oxen network. (Defaults to false)
bool get_tx_hex; // Return the transaction as hex string after sending (Defaults to false)
bool get_tx_metadata; // Return the metadata needed to relay the transaction. (Defaults to false)
uint32_t account_index; // (Optional) Transfer from this account index. (Defaults to 0)
std::vector<uint32_t> subaddr_indices; // (Optional) Transfer from this set of subaddresses. (Defaults to 0)
uint32_t priority; // Set a priority for the transaction. Accepted values are: or 0-4 for: default, unimportant, normal, elevated, priority.
bool get_tx_key; // (Optional) Return the transaction key after sending.
bool do_not_relay; // (Optional) If true, the newly created transaction will not be relayed to the oxen network. (Defaults to false)
bool get_tx_hex; // Return the transaction as hex string after sending (Defaults to false)
bool get_tx_metadata; // Return the metadata needed to relay the transaction. (Defaults to false)
} request;
};
@ -2619,7 +2617,7 @@ If signing is performed externally then you must first encrypt the `value` (if b
std::string signature; // (Optional): Signature derived using libsodium generichash on {current txid blob, new value blob} of the mapping to update. By default the hash is signed using the wallet's spend key as an ed25519 keypair, if signature is specified.
uint32_t account_index; // (Optional) Transfer from this account index. (Defaults to 0)
std::set<uint32_t> subaddr_indices; // (Optional) Transfer from this set of subaddresses. (Defaults to 0)
std::vector<uint32_t> subaddr_indices; // (Optional) Transfer from this set of subaddresses. (Defaults to 0)
uint32_t priority; // Set a priority for the transaction. Accepted values are: 0-4 for: default, unimportant, normal, elevated, priority.
bool get_tx_key; // (Optional) Return the transaction key after sending.
bool do_not_relay; // (Optional) If true, the newly created transaction will not be relayed to the oxen network. (Defaults to false)

View file

@ -12,6 +12,7 @@
#include <memory>
#include <common/hex.h>
#include <oxenc/base64.h>
#include "mnemonics/electrum-words.h"
@ -24,6 +25,8 @@ using cryptonote::rpc::rpc_error;
namespace {
static auto logcat = oxen::log::Cat("wallet");
template <typename RPC>
void register_rpc_command(std::unordered_map<std::string, std::shared_ptr<const rpc_command>>& regs)
{
@ -471,12 +474,83 @@ void RequestHandler::invoke(SET_LOG_CATEGORIES& command, rpc_context context) {
}
void RequestHandler::invoke(ONS_BUY_MAPPING& command, rpc_context context) {
//TODO sean these params need to be accounted for
// "do_not_relay", req.request.do_not_relay.
// "get_tx_hex", req.request.get_tx_hex.
// "get_tx_key", req.request.get_tx_key.
// "get_tx_metadata", req.request.get_tx_metadata.
// "priority", req.request.priority,
// "subaddr_indices", req.request.subaddr_indices,
oxen::log::info(logcat, "RPC Handler received ONS_BUY_MAPPING command");
if (auto w = wallet.lock())
{
cryptonote::tx_destination_entry change_dest;
change_dest.original = w->keys->get_main_address();
cryptonote::address_parse_info change_addr_info;
cryptonote::get_account_address_from_str(change_addr_info, w->nettype, change_dest.original);
change_dest.amount = 0;
change_dest.addr = change_addr_info.address;
change_dest.is_subaddress = change_addr_info.is_subaddress;
change_dest.is_integrated = change_addr_info.has_payment_id;
auto ptx = w->tx_constructor->create_ons_buy_transaction(
change_dest,
command.request.type,
command.request.owner,
command.request.backup_owner,
command.request.name,
command.request.value
);
w->keys->sign_transaction(ptx);
auto submit_future = w->daemon_comms->submit_transaction(ptx.tx, false);
if (submit_future.wait_for(5s) != std::future_status::ready)
throw rpc_error(500, "request to daemon timed out");
command.response["status"] = "200";
command.response["result"] = submit_future.get();
}
}
void RequestHandler::invoke(ONS_RENEW_MAPPING& command, rpc_context context) {
}
void RequestHandler::invoke(ONS_UPDATE_MAPPING& command, rpc_context context) {
oxen::log::info(logcat, "RPC Handler received ONS_UPDATE_MAPPING command");
if (auto w = wallet.lock())
{
cryptonote::tx_destination_entry change_dest;
change_dest.original = w->keys->get_main_address();
cryptonote::address_parse_info change_addr_info;
cryptonote::get_account_address_from_str(change_addr_info, w->nettype, change_dest.original);
change_dest.amount = 0;
change_dest.addr = change_addr_info.address;
change_dest.is_subaddress = change_addr_info.is_subaddress;
change_dest.is_integrated = change_addr_info.has_payment_id;
auto ptx = w->tx_constructor->create_ons_update_transaction(
change_dest,
command.request.type ,
command.request.owner,
command.request.backup_owner,
command.request.name,
command.request.value,
w->keys
);
w->keys->sign_transaction(ptx);
auto submit_future = w->daemon_comms->submit_transaction(ptx.tx, false);
if (submit_future.wait_for(5s) != std::future_status::ready)
throw rpc_error(500, "request to daemon timed out");
command.response["status"] = "200";
command.response["result"] = submit_future.get();
}
}
void RequestHandler::invoke(ONS_MAKE_UPDATE_SIGNATURE& command, rpc_context context) {

View file

@ -5,6 +5,8 @@
#include "decoy_selection/decoy_selection.hpp"
#include "db_schema.hpp"
#include <oxenc/base64.h>
#include <cryptonote_basic/hardfork.h>
//TODO: nettype-based tx construction parameters
@ -29,6 +31,179 @@ namespace wallet
return new_tx;
}
PendingTransaction
TransactionConstructor::create_ons_buy_transaction(
const cryptonote::tx_destination_entry& change_recipient,
const std::string& type_str,
const std::string& owner_str,
const std::string& backup_owner_str,
const std::string& name,
const std::string& value
)
{
std::vector<cryptonote::tx_destination_entry> recipients;
PendingTransaction new_tx(recipients);
auto [hf, hf_uint8] = cryptonote::get_ideal_block_version(db->network_type(), db->scan_target_height());
cryptonote::oxen_construct_tx_params tx_params{hf, cryptonote::txtype::oxen_name_system, 0, 0};
new_tx.tx.version = cryptonote::transaction::get_max_version_for_hf(tx_params.hf_version);
new_tx.tx.type = tx_params.tx_type;
new_tx.fee_per_byte = fee_per_byte;
new_tx.fee_per_output = fee_per_output;
new_tx.change = change_recipient;
new_tx.blink = false;
std::string reason = "";
const auto type = ons::parse_ons_type(type_str);
if (!type.has_value())
throw std::runtime_error("invalid type provided");
const auto lower_name = tools::lowercase_ascii_string(name);
if (!ons::validate_ons_name(*type, lower_name, &reason))
throw std::runtime_error(reason);
const auto name_hash = ons::name_to_hash(lower_name);
ons::mapping_value encrypted_value;
if (!ons::mapping_value::validate(nettype, *type, value, &encrypted_value, &reason))
throw std::runtime_error(reason);
if (!encrypted_value.encrypt(lower_name, &name_hash))
throw std::runtime_error("Fail to encrypt mapping value=" + value);
ons::generic_owner owner;
ons::generic_owner backup_owner;
if (owner_str == "")
owner = ons::make_monero_owner(change_recipient.addr, change_recipient.is_subaddress);
else if (!ons::parse_owner_to_generic_owner(nettype, owner_str, owner, &reason))
throw std::runtime_error(reason);
if (backup_owner_str != "" && !ons::parse_owner_to_generic_owner(nettype, backup_owner_str, backup_owner, &reason))
throw std::runtime_error(reason);
// No prev_txid for initial ons buy
crypto::hash prev_txid = {};
auto ons_buy_data = cryptonote::tx_extra_oxen_name_system::make_buy(
owner,
backup_owner_str != "" ? &backup_owner : nullptr,
*type,
name_hash,
encrypted_value.to_string(),
prev_txid);
new_tx.burn_fixed = ons::burn_needed(cryptonote::get_latest_hard_fork(nettype).version, *type);
new_tx.update_change();
//Finally save the data to the extra field of our transaction
cryptonote::add_oxen_name_system_to_tx_extra(new_tx.extra, ons_buy_data);
cryptonote::add_burned_amount_to_tx_extra(new_tx.extra, new_tx.burn_fixed);
select_inputs_and_finalise(new_tx);
return new_tx;
}
PendingTransaction
TransactionConstructor::create_ons_update_transaction(
const cryptonote::tx_destination_entry& change_recipient,
const std::string& type_str,
const std::string& owner_str,
const std::string& backup_owner_str,
const std::string& name,
const std::string& value,
std::shared_ptr<Keyring> keyring
)
{
if (value == "" && owner_str == "" && backup_owner_str == "")
throw std::runtime_error("Value, owner and backup owner are not specified. Atleast one field must be specified for updating the ONS record");
const auto lower_name = tools::lowercase_ascii_string(name);
std::string reason;
const auto type = ons::parse_ons_type(type_str);
if (!type.has_value())
throw std::runtime_error("invalid type provided");
if (!ons::validate_ons_name(*type, lower_name, &reason))
throw std::runtime_error(reason);
const auto name_hash = ons::name_to_hash(lower_name);
auto submit_ons_future = daemon->ons_names_to_owners(oxenc::to_base64(tools::view_guts(name_hash)), ons::db_mapping_type(*type));
if (submit_ons_future.wait_for(5s) != std::future_status::ready)
throw std::runtime_error("request to daemon for ons_names_to_owners timed out");
const auto ons_response = submit_ons_future.get();
crypto::hash prev_txid;
std::string curr_owner;
oxenc::bt_dict_consumer dc{ons_response};
if (not dc.skip_until("owner"))
{
auto reason = dc.consume_string();
throw std::runtime_error("Submit ons names to owners rejected, reason: " + reason);
}
curr_owner = dc.consume_string();
if (not dc.skip_until("txid"))
{
auto reason = dc.consume_string();
throw std::runtime_error("Submit ons names to owners rejected, reason: " + reason);
}
tools::hex_to_type<crypto::hash>(dc.consume_string(), prev_txid);
ons::mapping_value encrypted_value;
if (value != "")
{
if (!ons::mapping_value::validate(nettype, *type, value, &encrypted_value, &reason))
throw std::runtime_error(reason);
if (!encrypted_value.encrypt(lower_name, &name_hash))
throw std::runtime_error("Fail to encrypt name");
}
ons::generic_owner owner;
if (owner_str != "" && !ons::parse_owner_to_generic_owner(nettype, owner_str, owner, &reason))
throw std::runtime_error(reason);
ons::generic_owner backup_owner;
if (backup_owner_str != "" && !ons::parse_owner_to_generic_owner(nettype, backup_owner_str, backup_owner, &reason))
throw std::runtime_error(reason);
const auto signature = keyring->generate_ons_signature(
curr_owner,
owner_str != "" ? &owner : nullptr,
backup_owner_str != "" ? &backup_owner : nullptr,
encrypted_value,
prev_txid,
nettype
);
std::vector<cryptonote::tx_destination_entry> recipients;
PendingTransaction new_tx(recipients);
auto [hf, hf_uint8] = cryptonote::get_ideal_block_version(db->network_type(), db->scan_target_height());
cryptonote::oxen_construct_tx_params tx_params{hf, cryptonote::txtype::oxen_name_system, 0, 0};
new_tx.tx.version = cryptonote::transaction::get_max_version_for_hf(tx_params.hf_version);
new_tx.tx.type = tx_params.tx_type;
new_tx.fee_per_byte = fee_per_byte;
new_tx.fee_per_output = fee_per_output;
new_tx.change = change_recipient;
new_tx.blink = false;
auto ons_update_data = cryptonote::tx_extra_oxen_name_system::make_update(
signature,
*type,
name_hash,
encrypted_value.to_string(),
owner_str != "" ? &owner : nullptr,
backup_owner_str != "" ? &backup_owner : nullptr,
prev_txid);
//Finally save the data to the extra field of our transaction
cryptonote::add_oxen_name_system_to_tx_extra(new_tx.extra, ons_update_data);
new_tx.update_change();
select_inputs_and_finalise(new_tx);
return new_tx;
}
// SelectInputs will choose some available unspent outputs from the database and allocate to the
// transaction can be called multiple times and will add until enough is sufficient

View file

@ -5,6 +5,7 @@
#include <memory>
#include "pending_transaction.hpp"
#include "daemon_comms.hpp"
#include "keyring.hpp"
#include "decoy_selection/decoy_selection.hpp"
namespace wallet
@ -33,9 +34,32 @@ namespace wallet
PendingTransaction
create_transaction(const std::vector<cryptonote::tx_destination_entry>& recipients, const cryptonote::tx_destination_entry& change_recipient);
PendingTransaction
create_ons_buy_transaction(
const cryptonote::tx_destination_entry& change_recipient,
const std::string& type_str,
const std::string& owner_str,
const std::string& backup_owner_str,
const std::string& name,
const std::string& value
);
PendingTransaction
create_ons_update_transaction(
const cryptonote::tx_destination_entry& change_recipient,
const std::string& type_str,
const std::string& owner_str,
const std::string& backup_owner_str,
const std::string& name,
const std::string& value,
std::shared_ptr<Keyring> keyring
);
uint64_t fee_per_byte = cryptonote::FEE_PER_BYTE_V13;
uint64_t fee_per_output = cryptonote::FEE_PER_OUTPUT_V18;
cryptonote::network_type nettype = cryptonote::network_type::TESTNET;
std::unique_ptr<DecoySelector> decoy_selector;
private: