mirror of https://github.com/oxen-io/oxen-core.git
Merge c764fbd5d4
into 7bda65c5d2
This commit is contained in:
commit
71d2be3b44
|
@ -80,10 +80,14 @@ def load_test_wallet():
|
|||
click.echo("Wallet already loaded")
|
||||
return
|
||||
|
||||
spend_priv = "e6c9165356c619a64a0d26fafd99891acccccf8717a8067859d972ecd8bcfc0a"
|
||||
spend_pub = "b76f2d7c8a036ff65c564dcb27081c04fe3f2157942e23b0496ca797ba728e4f"
|
||||
view_priv = "961d67bb5b3ed1af8678bbfcf621f9c15c2b7bff080892890020bdfd47fe4f0a"
|
||||
view_pub = "8a0ebacd613e0b03b8f27bc64bd961ea2ebf4c671c6e7f3268651acf0823fed5"
|
||||
# spend_priv = "e6c9165356c619a64a0d26fafd99891acccccf8717a8067859d972ecd8bcfc0a"
|
||||
# spend_pub = "b76f2d7c8a036ff65c564dcb27081c04fe3f2157942e23b0496ca797ba728e4f"
|
||||
# view_priv = "961d67bb5b3ed1af8678bbfcf621f9c15c2b7bff080892890020bdfd47fe4f0a"
|
||||
# view_pub = "8a0ebacd613e0b03b8f27bc64bd961ea2ebf4c671c6e7f3268651acf0823fed5"
|
||||
view_pub = "ed26f4f9ed44baccb0aa32bfd91fd546115a60c77e6e8098cd4debf8f33cb9f9"
|
||||
spend_pub = "9834c238ebecb78b1f30115c50b956e9e5e0d86072c61d57e65ee04f9c650b40"
|
||||
view_priv = "5f51194e0f839ee32fdd85765be009b1fceb70e78204e4bfa3010e2ade61fc0d"
|
||||
spend_priv = "0ac3dc5fff3a7a303b893a50119ff2da3125f6a51b980e409d6c8a3a3f7ec80b"
|
||||
|
||||
keyring = pywallet3.Keyring(spend_priv, spend_pub, view_priv, view_pub, context.options["network"])
|
||||
click.echo("Wallet address " + click.style("{}", fg='cyan', bold=True).format(keyring.get_main_address()) + " loaded")
|
||||
|
@ -195,13 +199,71 @@ def transfer():
|
|||
if address == "" or amount == 0.0:
|
||||
click.prompt("Invalid address/amount entered")
|
||||
return
|
||||
amount_in_atomic_units = round(amount * OXEN_ATOMIC_UNITS, 0);
|
||||
amount_in_atomic_units = round(amount * OXEN_ATOMIC_UNITS);
|
||||
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(name='register_service_node')
|
||||
@click.argument('fee', nargs=1, type=click.INT)
|
||||
@click.argument('address_amount', nargs=-1)
|
||||
@click.argument('hardfork', nargs=1, type=click.INT)
|
||||
@click.argument('service_node_pubkey', nargs=1)
|
||||
@click.argument('signature', nargs=1)
|
||||
def register_service_node(fee, address_amount, hardfork, service_node_pubkey, signature):
|
||||
|
||||
addresses = address_amount[::2]
|
||||
amounts = [int(x) for x in address_amount[1::2]]
|
||||
print(fee)
|
||||
|
||||
register_params= {
|
||||
"fee": fee,
|
||||
"addresses": addresses,
|
||||
"amounts": amounts,
|
||||
"hardfork": hardfork,
|
||||
"service_node_key": service_node_pubkey,
|
||||
"signature": signature
|
||||
}
|
||||
register_future = context.rpc_future("restricted.register_service_node", args=register_params);
|
||||
register_response = register_future.get();
|
||||
click.echo("Register Service Node Response: {}".format(register_response))
|
||||
|
||||
@walletcli.command()
|
||||
def stake():
|
||||
service_node_key = click.prompt("Enter the public key of the service node you wish to stake to: ", default="").strip()
|
||||
amount = click.prompt("Enter the amount in oxen to be contributed to (Optional: 0 will automatically contribute the minimum){}".format(service_node_key), default=0.0)
|
||||
if service_node_key == "":
|
||||
click.prompt("Invalid public key entered")
|
||||
return
|
||||
amount_in_atomic_units = round(amount * OXEN_ATOMIC_UNITS);
|
||||
|
||||
get_address_future = context.rpc_future("rpc.get_address");
|
||||
get_address_response = get_address_future.get();
|
||||
|
||||
stake_params = {
|
||||
"destination": get_address_response['address'],
|
||||
"service_node_key": service_node_key,
|
||||
"amount": amount_in_atomic_units
|
||||
}
|
||||
stake_future = context.rpc_future("restricted.stake", args=stake_params);
|
||||
stake_response = stake_future.get();
|
||||
click.echo("Stake Response: {}".format(stake_response))
|
||||
|
||||
@walletcli.command()
|
||||
def unstake():
|
||||
service_node_key = click.prompt("Enter the public key of the service node you wish to unstake from: ", default="").strip()
|
||||
if service_node_key == "":
|
||||
click.prompt("Invalid public key entered")
|
||||
return
|
||||
unstake_params = {
|
||||
"service_node_key": service_node_key,
|
||||
}
|
||||
stake_future = context.rpc_future("restricted.request_stake_unlock", args=unstake_params);
|
||||
stake_response = stake_future.get();
|
||||
click.echo("Unstake Response: {}".format(stake_response))
|
||||
|
||||
lokinet_years_dict = {"1": "lokinet", "2": "lokinet_2years", "5": "lokinet_5years", "10": "lokinet_10years"}
|
||||
|
||||
# TODO better names for these ONS commands
|
||||
|
@ -241,13 +303,13 @@ def ons_update_mapping():
|
|||
}
|
||||
ons_value = click.prompt("Optional: Please enter a value to modify the ons mapping", default="").strip()
|
||||
if len(ons_value) > 0:
|
||||
ons_buy_params["value"] = ons_value
|
||||
ons_update_params["value"] = ons_value
|
||||
ons_owner = click.prompt("Optional: Please enter an address to modify the owner", default="").strip()
|
||||
if len(ons_owner) > 0:
|
||||
ons_buy_params["owner"] = ons_owner
|
||||
ons_update_params["owner"] = ons_owner
|
||||
ons_backup_owner = click.prompt("Optional: Please enter an address to modify the backup owner", default="").strip()
|
||||
if len(ons_backup_owner) > 0:
|
||||
ons_buy_params["backup_owner"] = ons_backup_owner
|
||||
ons_update_params["backup_owner"] = ons_backup_owner
|
||||
|
||||
transfer_future = context.rpc_future("restricted.ons_update_mapping", args=ons_update_params);
|
||||
transfer_response = transfer_future.get();
|
||||
|
|
|
@ -52,8 +52,11 @@ class DaemonComms {
|
|||
virtual std::future<std::string> submit_transaction(
|
||||
const cryptonote::transaction& tx, bool blink) = 0;
|
||||
|
||||
virtual std::future<std::pair<std::string, crypto::hash>> ons_names_to_owners(
|
||||
const std::string& name_hash, uint16_t type) = 0;
|
||||
};
|
||||
virtual std::future<std::pair<std::string, crypto::hash>>
|
||||
ons_names_to_owners(const std::string& name_hash, uint16_t type) = 0;
|
||||
|
||||
virtual std::future<oxenc::bt_list_consumer>
|
||||
get_service_nodes(const std::vector<std::string>& service_node_keys) = 0;
|
||||
};
|
||||
|
||||
} // namespace wallet
|
||||
|
|
|
@ -447,6 +447,48 @@ std::vector<Output> WalletDB::available_outputs(std::optional<int64_t> min_amoun
|
|||
return outs;
|
||||
}
|
||||
|
||||
Output WalletDB::get_output_from_key_image(const std::string& key_image) {
|
||||
Output out;
|
||||
|
||||
std::string query =
|
||||
"SELECT amount, output_index, global_index, "
|
||||
"unlock_time, block_height, output_key, derivation, rct_mask, key_images.key_image, "
|
||||
"spent_height, spending FROM outputs JOIN key_images ON outputs.key_image = "
|
||||
"key_images.id WHERE spent_height = 0 AND spending = FALSE AND key_image.key_image = ?";
|
||||
|
||||
auto st = prepared_st(query);
|
||||
|
||||
st->bind(1, key_image);
|
||||
|
||||
while (st->executeStep()) {
|
||||
auto from_db =
|
||||
db::get<int64_t,
|
||||
int64_t,
|
||||
int64_t,
|
||||
int64_t,
|
||||
int64_t,
|
||||
std::string,
|
||||
std::string,
|
||||
std::string,
|
||||
std::string,
|
||||
int64_t,
|
||||
int64_t>(st);
|
||||
out.amount = std::get<0>(from_db);
|
||||
out.output_index = std::get<1>(from_db);
|
||||
out.global_index = std::get<2>(from_db);
|
||||
out.unlock_time = std::get<3>(from_db);
|
||||
out.block_height = std::get<4>(from_db);
|
||||
tools::hex_to_type(std::get<5>(from_db), out.key);
|
||||
tools::hex_to_type(std::get<6>(from_db), out.derivation);
|
||||
tools::hex_to_type(std::get<7>(from_db), out.rct_mask);
|
||||
tools::hex_to_type(std::get<8>(from_db), out.key_image);
|
||||
out.spent_height = std::get<9>(from_db);
|
||||
out.spending = std::get<10>(from_db);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
int64_t WalletDB::chain_output_count() {
|
||||
return get_metadata_int("output_count");
|
||||
}
|
||||
|
|
|
@ -93,6 +93,10 @@ class WalletDB : public db::Database {
|
|||
// TODO: subaddress specification
|
||||
std::vector<Output> available_outputs(std::optional<int64_t> min_amount);
|
||||
|
||||
// Finds a single output from our available spends with the provided key image
|
||||
// used when unstaking a service node
|
||||
Output get_output_from_key_image(const std::string& key_image);
|
||||
|
||||
// Gets the total number of outputs on the chain. Since all Oxen outputs are RingCT
|
||||
// and thus mixable, this can be used for decoy selection.
|
||||
int64_t chain_output_count();
|
||||
|
|
|
@ -476,8 +476,42 @@ std::future<std::pair<std::string, crypto::hash>> DefaultDaemonComms::ons_names_
|
|||
return fut;
|
||||
}
|
||||
|
||||
void DefaultDaemonComms::register_wallet(
|
||||
wallet::Wallet& wallet, int64_t height, bool check_sync_height, bool new_wallet) {
|
||||
std::future<oxenc::bt_list_consumer>
|
||||
DefaultDaemonComms::get_service_nodes(const std::vector<std::string>& service_node_keys)
|
||||
{
|
||||
auto p = std::make_shared<std::promise<oxenc::bt_list_consumer>>();
|
||||
auto fut = p->get_future();
|
||||
auto req_cb = [p=std::move(p)](bool ok, std::vector<std::string> response)
|
||||
{
|
||||
try
|
||||
{
|
||||
oxenc::bt_dict_consumer dc{response[1]};
|
||||
|
||||
if (not dc.skip_until("service_node_states"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
|
||||
auto result_list = dc.consume_list_consumer();
|
||||
p->set_value(result_list);
|
||||
} catch (...) {
|
||||
p->set_exception(std::current_exception());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
oxenc::bt_list key_list_bt;
|
||||
std::copy( service_node_keys.begin(), service_node_keys.end(), std::back_inserter( key_list_bt) );
|
||||
oxenc::bt_dict req_params_dict{
|
||||
{"service_node_pubkeys", key_list_bt}
|
||||
};
|
||||
|
||||
omq->request(conn, "rpc.get_service_nodes", 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)
|
||||
{
|
||||
oxen::log::trace(logcat, "Daemon Comms register_wallet called");
|
||||
omq->job(
|
||||
[this, w = wallet.shared_from_this(), height, check_sync_height, new_wallet]() {
|
||||
|
|
|
@ -49,7 +49,11 @@ class DefaultDaemonComms : public DaemonComms,
|
|||
std::future<std::pair<std::string, crypto::hash>> ons_names_to_owners(
|
||||
const std::string& name_hash, const uint16_t type);
|
||||
|
||||
private:
|
||||
std::future<oxenc::bt_list_consumer>
|
||||
get_service_nodes(const std::vector<std::string>& service_node_keys);
|
||||
|
||||
private:
|
||||
|
||||
void for_each_wallet(std::function<void(std::shared_ptr<Wallet>)> func);
|
||||
|
||||
void get_blocks();
|
||||
|
|
|
@ -101,6 +101,22 @@ crypto::key_image Keyring::key_image(
|
|||
return ret;
|
||||
}
|
||||
|
||||
crypto::key_image Keyring::generate_key_image(const crypto::secret_key& output_private_key) {
|
||||
crypto::key_image ret;
|
||||
crypto::public_key output_pubkey_computed;
|
||||
key_device.secret_key_to_public_key(output_private_key, output_pubkey_computed);
|
||||
key_device.generate_key_image(output_pubkey_computed, output_private_key, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
crypto::signature Keyring::generate_key_image_signature(const crypto::secret_key& output_private_key, const crypto::key_image& key_image) {
|
||||
crypto::signature ret;
|
||||
crypto::public_key output_pubkey_computed;
|
||||
key_device.secret_key_to_public_key(output_private_key, output_pubkey_computed);
|
||||
key_device.generate_key_image_signature(key_image, output_pubkey_computed, output_private_key, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: replace later when removing wallet2½ layer
|
||||
std::pair<uint64_t, rct::key> Keyring::output_amount_and_mask(
|
||||
const rct::rctSig& rv, const crypto::key_derivation& derivation, unsigned int i) {
|
||||
|
@ -213,7 +229,7 @@ crypto::hash Keyring::get_transaction_prefix_hash(const cryptonote::transaction_
|
|||
|
||||
void Keyring::sign_transaction(PendingTransaction& ptx) {
|
||||
auto hf_version = cryptonote::hf::hf19_reward_batching;
|
||||
auto tx_key = generate_tx_key(hf_version);
|
||||
auto tx_key = ptx.tx_secret_key.value_or(generate_tx_key(hf_version));
|
||||
|
||||
rct::ctkeyV inSk;
|
||||
rct::keyV dest_keys;
|
||||
|
@ -495,4 +511,20 @@ ons::generic_signature Keyring::generate_ons_signature(
|
|||
return result;
|
||||
}
|
||||
|
||||
crypto::signature Keyring::generate_stake_unlock_signature(const Output& locked_stake_output) {
|
||||
crypto::signature signature;
|
||||
|
||||
// Calculate the outputs spending keypair
|
||||
auto output_private_key = derive_output_secret_key(locked_stake_output.derivation, locked_stake_output.output_index, locked_stake_output.subaddress_index);
|
||||
|
||||
crypto::public_key output_pubkey_computed;
|
||||
key_device.secret_key_to_public_key(output_private_key, output_pubkey_computed);
|
||||
|
||||
// Use the keypair for our signature to go into the txextra for stake unlock
|
||||
if (!key_device.generate_unlock_signature(output_pubkey_computed, output_private_key, signature))
|
||||
throw std::runtime_error("Hardware device failed to sign the unlock request");
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
} // namespace wallet
|
||||
|
|
|
@ -77,6 +77,10 @@ class Keyring : public WalletKeys {
|
|||
uint64_t output_index,
|
||||
const cryptonote::subaddress_index& sub_index);
|
||||
|
||||
virtual crypto::key_image generate_key_image(const crypto::secret_key& output_private_key);
|
||||
|
||||
virtual crypto::signature generate_key_image_signature(const crypto::secret_key& output_private_key, const crypto::key_image& key_image);
|
||||
|
||||
virtual std::pair<uint64_t, rct::key> output_amount_and_mask(
|
||||
const rct::rctSig& rv, const crypto::key_derivation& derivation, unsigned int i);
|
||||
|
||||
|
@ -116,6 +120,9 @@ class Keyring : public WalletKeys {
|
|||
const crypto::hash& prev_txid,
|
||||
const cryptonote::network_type& nettype);
|
||||
|
||||
virtual crypto::signature generate_stake_unlock_signature(
|
||||
const Output& locked_stake_output);
|
||||
|
||||
cryptonote::network_type nettype;
|
||||
|
||||
crypto::secret_key spend_private_key;
|
||||
|
|
|
@ -45,7 +45,6 @@ int64_t PendingTransaction::get_fee() const {
|
|||
}
|
||||
int64_t PendingTransaction::get_fee(int64_t n_inputs) const {
|
||||
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%
|
||||
if (blink)
|
||||
|
|
|
@ -37,6 +37,8 @@ struct PendingTransaction {
|
|||
|
||||
uint64_t burn_fixed = 0;
|
||||
|
||||
std::optional<crypto::secret_key> tx_secret_key = std::nullopt;
|
||||
|
||||
std::vector<uint8_t> extra = {};
|
||||
size_t extra_size() const { return extra.size(); };
|
||||
|
||||
|
|
|
@ -253,11 +253,40 @@ void parse_request(SUBMIT_MULTISIG& req, rpc_input in) {}
|
|||
|
||||
void parse_request(GET_VERSION& req, rpc_input in) {}
|
||||
|
||||
void parse_request(STAKE& req, rpc_input in) {}
|
||||
void parse_request(STAKE& req, rpc_input in) {
|
||||
get_values(in,
|
||||
"amount", req.request.amount,
|
||||
"destination", req.request.destination,
|
||||
"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,
|
||||
"service_node_key", req.request.service_node_key,
|
||||
"subaddr_indices", req.request.subaddr_indices
|
||||
);
|
||||
}
|
||||
|
||||
void parse_request(REGISTER_SERVICE_NODE& req, rpc_input in) {}
|
||||
void parse_request(REGISTER_SERVICE_NODE& req, rpc_input in) {
|
||||
get_values(in,
|
||||
"addresses", req.request.addresses,
|
||||
"amounts", req.request.amounts,
|
||||
"do_not_relay", req.request.do_not_relay,
|
||||
"fee", req.request.fee,
|
||||
"get_tx_hex", req.request.get_tx_hex,
|
||||
"get_tx_key", req.request.get_tx_key,
|
||||
"get_tx_metadata", req.request.get_tx_metadata,
|
||||
"hardfork", req.request.hardfork,
|
||||
"service_node_key", req.request.service_node_key,
|
||||
"signature", req.request.signature
|
||||
);
|
||||
}
|
||||
|
||||
void parse_request(REQUEST_STAKE_UNLOCK& req, rpc_input in) {}
|
||||
void parse_request(REQUEST_STAKE_UNLOCK& req, rpc_input in) {
|
||||
get_values(in,
|
||||
"service_node_key", req.request.service_node_key
|
||||
);
|
||||
}
|
||||
|
||||
void parse_request(CAN_REQUEST_STAKE_UNLOCK& req, rpc_input in) {}
|
||||
|
||||
|
|
|
@ -2182,58 +2182,63 @@ struct GET_VERSION : RPC_COMMAND {
|
|||
struct STAKE : RESTRICTED {
|
||||
static constexpr auto names() { return NAMES("stake"); }
|
||||
|
||||
struct REQUEST {
|
||||
std::string destination; // Primary Public address that the rewards will go to.
|
||||
uint64_t amount; // Amount of Loki to stake in atomic units.
|
||||
std::set<uint32_t> subaddr_indices; // (Optional) Transfer from this set of subaddresses.
|
||||
// (Defaults to 0)
|
||||
std::string service_node_key; // Service Node Public Address.
|
||||
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)
|
||||
struct REQUEST
|
||||
{
|
||||
std::string destination; // Primary Public address that the rewards will go to.
|
||||
uint64_t amount; // Amount of Loki to stake in atomic units.
|
||||
std::vector<uint32_t> subaddr_indices; // (Optional) Transfer from this set of subaddresses. (Defaults to 0)
|
||||
std::string service_node_key; // Service Node Public Address.
|
||||
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;
|
||||
};
|
||||
|
||||
/// Register Service Node.
|
||||
///
|
||||
/// Inputs:
|
||||
///
|
||||
/// - \p register_service_node_str -- String supplied by the prepare_registration command.
|
||||
/// - \p get_tx_key -- (Optional) Return the transaction key after sending.
|
||||
/// - \p do_not_relay -- (Optional) If true, the newly created transaction will not be relayed to
|
||||
/// the oxen network. (Defaults to false)
|
||||
/// - \p get_tx_hex -- Return the transaction as hex string after sending (Defaults to false)
|
||||
/// - \p get_tx_metadata -- Return the metadata needed to relay the transaction. (Defaults to false)
|
||||
///
|
||||
/// Outputs:
|
||||
///
|
||||
/// - \p tx_hash -- Publicly searchable transaction hash.
|
||||
/// - \p tx_key -- Transaction key if get_tx_key is true, otherwise, blank string.
|
||||
/// - \p amount -- Amount transferred for the transaction in atomic units.
|
||||
/// - \p fee -- Value in atomic units of the fee charged for the tx.
|
||||
/// - \p tx_blob -- Raw transaction represented as hex string, if get_tx_hex is true.
|
||||
/// - \p tx_metadata -- Set of transaction metadata needed to relay this transfer later, if
|
||||
/// `get_tx_metadata` is `true`.
|
||||
/// - \p multisig_txset -- Set of multisig transactions in the process of being signed (empty for
|
||||
/// non-multisig).
|
||||
/// - \p unsigned_txset -- Set of unsigned tx for cold-signing purposes.
|
||||
struct REGISTER_SERVICE_NODE : RESTRICTED {
|
||||
OXEN_RPC_DOC_INTROSPECT
|
||||
/// Register Service Node.
|
||||
///
|
||||
/// Inputs:
|
||||
///
|
||||
/// - \p fee_basis_points --
|
||||
/// - \p addresses --
|
||||
/// - \p amounts --
|
||||
/// - \p fee --
|
||||
/// - \p timestamp --
|
||||
/// - \p service_node_key --
|
||||
/// - \p signature --
|
||||
/// - \p get_tx_key -- (Optional) Return the transaction key after sending.
|
||||
/// - \p do_not_relay -- (Optional) If true, the newly created transaction will not be relayed to the oxen network. (Defaults to false)
|
||||
/// - \p get_tx_hex -- Return the transaction as hex string after sending (Defaults to false)
|
||||
/// - \p get_tx_metadata -- Return the metadata needed to relay the transaction. (Defaults to false)
|
||||
///
|
||||
/// Outputs:
|
||||
///
|
||||
/// - \p tx_hash -- Publicly searchable transaction hash.
|
||||
/// - \p tx_key -- Transaction key if get_tx_key is true, otherwise, blank string.
|
||||
/// - \p amount -- Amount transferred for the transaction in atomic units.
|
||||
/// - \p fee -- Value in atomic units of the fee charged for the tx.
|
||||
/// - \p tx_blob -- Raw transaction represented as hex string, if get_tx_hex is true.
|
||||
/// - \p tx_metadata -- Set of transaction metadata needed to relay this transfer later, if `get_tx_metadata` is `true`.
|
||||
/// - \p multisig_txset -- Set of multisig transactions in the process of being signed (empty for non-multisig).
|
||||
/// - \p unsigned_txset -- Set of unsigned tx for cold-signing purposes.
|
||||
struct REGISTER_SERVICE_NODE : RESTRICTED
|
||||
{
|
||||
static constexpr auto names() { return NAMES("register_service_node"); }
|
||||
|
||||
struct REQUEST {
|
||||
std::string
|
||||
register_service_node_str; // String supplied by the prepare_registration command.
|
||||
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)
|
||||
struct REQUEST
|
||||
{
|
||||
uint64_t fee;
|
||||
std::vector<std::string> addresses;
|
||||
std::vector<uint64_t> amounts;
|
||||
uint64_t hardfork;
|
||||
std::string service_node_key;
|
||||
std::string signature;
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -2252,7 +2257,7 @@ struct REQUEST_STAKE_UNLOCK : RESTRICTED {
|
|||
|
||||
struct REQUEST {
|
||||
std::string service_node_key; // Service Node Public Key.
|
||||
};
|
||||
} request;
|
||||
};
|
||||
|
||||
/// Check if Service Node can unlock its stake.
|
||||
|
|
|
@ -56,17 +56,18 @@ void RequestHandler::set_wallet(std::weak_ptr<wallet::Wallet> ptr) {
|
|||
wallet = ptr;
|
||||
}
|
||||
|
||||
// TODO sean something here
|
||||
std::string RequestHandler::submit_transaction(wallet::PendingTransaction& ptx) {
|
||||
std::string response;
|
||||
if (auto w = wallet.lock()) {
|
||||
w->keys->sign_transaction(ptx);
|
||||
std::string RequestHandler::submit_transaction(wallet::PendingTransaction& ptx)
|
||||
{
|
||||
std::string response;
|
||||
if (auto w = wallet.lock())
|
||||
{
|
||||
w->keys->sign_transaction(ptx);
|
||||
|
||||
auto submit_future = w->daemon_comms->submit_transaction(ptx.tx, false);
|
||||
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");
|
||||
response = submit_future.get();
|
||||
if (submit_future.wait_for(5s) != std::future_status::ready)
|
||||
throw rpc_error(500, "request to daemon timed out");
|
||||
response = submit_future.get();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
@ -343,11 +344,83 @@ void RequestHandler::invoke(GET_VERSION& command, rpc_context context) {
|
|||
}
|
||||
}
|
||||
|
||||
void RequestHandler::invoke(STAKE& command, rpc_context context) {}
|
||||
void RequestHandler::invoke(STAKE& command, rpc_context context) {
|
||||
oxen::log::info(logcat, "RPC Handler received STAKE command");
|
||||
wallet::PendingTransaction ptx;
|
||||
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;
|
||||
|
||||
void RequestHandler::invoke(REGISTER_SERVICE_NODE& command, rpc_context context) {}
|
||||
ptx = w->tx_constructor->create_stake_transaction(
|
||||
command.request.destination,
|
||||
command.request.service_node_key,
|
||||
command.request.amount,
|
||||
change_dest
|
||||
);
|
||||
}
|
||||
command.response["result"] = submit_transaction(ptx);
|
||||
command.response["status"] = "200";
|
||||
}
|
||||
|
||||
void RequestHandler::invoke(REQUEST_STAKE_UNLOCK& command, rpc_context context) {}
|
||||
void RequestHandler::invoke(REGISTER_SERVICE_NODE& command, rpc_context context) {
|
||||
oxen::log::info(logcat, "RPC Handler received REGISTER SERVICE NODE command");
|
||||
wallet::PendingTransaction ptx;
|
||||
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;
|
||||
|
||||
ptx = w->tx_constructor->create_register_service_node_transaction(
|
||||
command.request.fee,
|
||||
command.request.addresses,
|
||||
command.request.amounts,
|
||||
command.request.hardfork,
|
||||
command.request.service_node_key,
|
||||
command.request.signature,
|
||||
change_dest,
|
||||
w->keys
|
||||
);
|
||||
}
|
||||
command.response["result"] = submit_transaction(ptx);
|
||||
command.response["status"] = "200";
|
||||
}
|
||||
|
||||
void RequestHandler::invoke(REQUEST_STAKE_UNLOCK& command, rpc_context context) {
|
||||
oxen::log::info(logcat, "RPC Handler received REQUEST_STAKE_UNLOCK command");
|
||||
wallet::PendingTransaction ptx;
|
||||
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;
|
||||
|
||||
ptx = w->tx_constructor->create_stake_unlock_transaction(
|
||||
command.request.service_node_key,
|
||||
change_dest,
|
||||
w->keys
|
||||
);
|
||||
}
|
||||
command.response["result"] = submit_transaction(ptx);
|
||||
command.response["status"] = "200";
|
||||
}
|
||||
|
||||
void RequestHandler::invoke(CAN_REQUEST_STAKE_UNLOCK& command, rpc_context context) {}
|
||||
|
||||
|
|
|
@ -9,13 +9,16 @@
|
|||
#include "output_selection/output_selection.hpp"
|
||||
#include "pending_transaction.hpp"
|
||||
|
||||
// TODO: nettype-based tx construction parameters
|
||||
namespace wallet
|
||||
{
|
||||
static auto logcat = oxen::log::Cat("wallet");
|
||||
|
||||
namespace wallet {
|
||||
// create_transaction will create a vanilla spend transaction without any special features.
|
||||
PendingTransaction TransactionConstructor::create_transaction(
|
||||
const std::vector<cryptonote::tx_destination_entry>& recipients,
|
||||
const cryptonote::tx_destination_entry& change_recipient) {
|
||||
// create_transaction will create a vanilla spend transaction without any special features.
|
||||
PendingTransaction
|
||||
TransactionConstructor::create_transaction(
|
||||
const std::vector<cryptonote::tx_destination_entry>& recipients,
|
||||
const cryptonote::tx_destination_entry& change_recipient)
|
||||
{
|
||||
PendingTransaction new_tx(recipients);
|
||||
auto [hf, hf_uint8] =
|
||||
cryptonote::get_ideal_block_version(db->network_type(), db->scan_target_height());
|
||||
|
@ -127,7 +130,6 @@ PendingTransaction TransactionConstructor::create_ons_update_transaction(
|
|||
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");
|
||||
|
||||
// TODO sean stuff goes here
|
||||
const auto [curr_owner, prev_txid] = submit_ons_future.get();
|
||||
|
||||
ons::mapping_value encrypted_value;
|
||||
|
@ -185,9 +187,360 @@ PendingTransaction TransactionConstructor::create_ons_update_transaction(
|
|||
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
|
||||
void TransactionConstructor::select_inputs(PendingTransaction& ptx) const {
|
||||
void
|
||||
TransactionConstructor::validate_register_service_node_parameters(
|
||||
const std::string& service_node_key,
|
||||
const service_nodes::registration_details& registration,
|
||||
cryptonote::hf hf_version
|
||||
)
|
||||
{
|
||||
auto staking_requirement = service_nodes::get_staking_requirement(nettype, db->scan_target_height());
|
||||
auto now = std::chrono::system_clock::now();
|
||||
|
||||
if (uint64_t(hf_version) != registration.hf)
|
||||
throw service_nodes::invalid_registration{"hardfork is invalid"};
|
||||
// Validate registration
|
||||
service_nodes::validate_registration(hf_version, nettype, staking_requirement, std::chrono::system_clock::to_time_t(now), registration);
|
||||
auto hash = service_nodes::get_registration_hash(registration);
|
||||
if (!crypto::check_key(registration.service_node_pubkey))
|
||||
throw service_nodes::invalid_registration{"Service Node Key is not a valid public key (" + tools::type_to_hex(registration.service_node_pubkey) + ")"};
|
||||
|
||||
if (!crypto::check_signature(hash, registration.service_node_pubkey, registration.signature))
|
||||
throw service_nodes::invalid_registration{"Registration signature verification failed for pubkey/hash: " +
|
||||
tools::type_to_hex(registration.service_node_pubkey) + "/" + tools::type_to_hex(hash)};
|
||||
|
||||
// Check Service Node is able to be registered
|
||||
auto get_service_node_future = daemon->get_service_nodes({service_node_key});
|
||||
if (get_service_node_future.wait_for(5s) != std::future_status::ready)
|
||||
throw std::runtime_error("request to daemon for get_service_nodes timed out");
|
||||
|
||||
auto response = get_service_node_future.get();
|
||||
if(!response.is_finished())
|
||||
throw service_nodes::invalid_registration{"This service node is already registered"};
|
||||
}
|
||||
|
||||
PendingTransaction
|
||||
TransactionConstructor::create_register_service_node_transaction(
|
||||
const uint64_t fee,
|
||||
const std::vector<std::string>& addresses,
|
||||
const std::vector<uint64_t>& amounts,
|
||||
const uint64_t registration_hardfork,
|
||||
const std::string& service_node_key,
|
||||
const std::string& signature_str,
|
||||
const cryptonote::tx_destination_entry& change_recipient,
|
||||
std::shared_ptr<Keyring> keyring)
|
||||
{
|
||||
|
||||
std::vector<cryptonote::tx_destination_entry> recipients;
|
||||
auto& staked_amount_to_self = recipients.emplace_back();
|
||||
staked_amount_to_self.original = change_recipient.original;
|
||||
staked_amount_to_self.amount = amounts[0];
|
||||
staked_amount_to_self.addr = change_recipient.addr;
|
||||
staked_amount_to_self.is_subaddress = change_recipient.is_subaddress;
|
||||
staked_amount_to_self.is_integrated = change_recipient.is_integrated;
|
||||
|
||||
PendingTransaction new_tx(recipients);
|
||||
auto [hf, hf_uint8] = cryptonote::get_ideal_block_version(db->network_type(), db->scan_target_height());
|
||||
new_tx.tx.version = cryptonote::transaction::get_max_version_for_hf(hf);
|
||||
new_tx.tx.type = cryptonote::txtype::stake;
|
||||
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;
|
||||
|
||||
cryptonote::add_service_node_contributor_to_tx_extra(new_tx.extra, change_recipient.addr);
|
||||
|
||||
crypto::public_key service_node_public_key;
|
||||
if (!tools::hex_to_type(service_node_key, service_node_public_key))
|
||||
throw std::runtime_error("could not read service node key");
|
||||
cryptonote::add_service_node_pubkey_to_tx_extra(new_tx.extra, service_node_public_key);
|
||||
|
||||
crypto::signature signature;
|
||||
if (!tools::hex_to_type(signature_str, signature))
|
||||
throw std::runtime_error("could not read signature");
|
||||
service_nodes::registration_details registration{service_node_public_key, {}, fee, registration_hardfork, false, signature};
|
||||
cryptonote::address_parse_info addr_info;
|
||||
for (size_t i = 0; i < amounts.size(); i++) {
|
||||
cryptonote::get_account_address_from_str(addr_info, nettype, addresses[i]);
|
||||
if (addr_info.has_payment_id)
|
||||
throw service_nodes::invalid_registration{"Can't use a payment id for staking tx"};
|
||||
|
||||
if (addr_info.is_subaddress)
|
||||
throw service_nodes::invalid_registration{"Can't use a subaddress for staking tx"};
|
||||
registration.reserved.emplace_back(addr_info.address, amounts[i]);
|
||||
}
|
||||
if (!cryptonote::add_service_node_registration_to_tx_extra(new_tx.extra, registration))
|
||||
throw std::runtime_error("Failed to serialize service node registration tx extra");
|
||||
|
||||
|
||||
new_tx.tx_secret_key = keyring->generate_tx_key(hf);
|
||||
cryptonote::add_tx_secret_key_to_tx_extra(new_tx.extra, *new_tx.tx_secret_key);
|
||||
// TODO this sends the secret key to the hardware device so it know to use it
|
||||
//if (!hwdev.update_staking_tx_secret_key(tx_sk)) {
|
||||
//log::warning(globallogcat, "Failed to add tx secret key to stake transaction");
|
||||
//return false;
|
||||
//}
|
||||
|
||||
cryptonote::tx_extra_tx_key_image_proofs key_image_proofs;
|
||||
|
||||
auto& proof = key_image_proofs.proofs.emplace_back();
|
||||
proof.key_image = keyring->generate_key_image(*new_tx.tx_secret_key);
|
||||
proof.signature = keyring->generate_key_image_signature(*new_tx.tx_secret_key, proof.key_image);
|
||||
cryptonote::add_tx_key_image_proofs_to_tx_extra(new_tx.extra, key_image_proofs);
|
||||
|
||||
validate_register_service_node_parameters(service_node_key, registration, hf);
|
||||
|
||||
new_tx.update_change();
|
||||
|
||||
//TODO sean get use the new tx_key from somewhere
|
||||
select_inputs_and_finalise(new_tx);
|
||||
return new_tx;
|
||||
}
|
||||
|
||||
void
|
||||
TransactionConstructor::validate_stake_parameters(
|
||||
const std::string& service_node_key,
|
||||
uint64_t& amount,
|
||||
const cryptonote::tx_destination_entry& change_recipient
|
||||
)
|
||||
{
|
||||
if (change_recipient.is_integrated)
|
||||
throw std::runtime_error{"Payment IDs cannot be used in a staking transaction"};
|
||||
|
||||
if (change_recipient.is_subaddress)
|
||||
throw std::runtime_error{"Subaddresses cannot be used in a staking transaction"};
|
||||
|
||||
/// check that the service node is registered
|
||||
auto get_service_node_future = daemon->get_service_nodes({service_node_key});
|
||||
if (get_service_node_future.wait_for(5s) != std::future_status::ready)
|
||||
throw std::runtime_error("request to daemon for get_service_nodes timed out");
|
||||
|
||||
auto response = get_service_node_future.get();
|
||||
if(response.is_finished())
|
||||
throw std::runtime_error("Could not find service node in service node list, please make sure it is registered first.");
|
||||
auto snode_info = response.consume_dict_consumer();
|
||||
|
||||
const auto hf_version = cryptonote::get_latest_hard_fork(nettype).version;
|
||||
|
||||
if (not snode_info.skip_until("contributors"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
auto contributors = snode_info.consume_list_consumer();
|
||||
|
||||
if (not snode_info.skip_until("staking_requirement"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
const auto staking_req = snode_info.consume_integer<int64_t>();
|
||||
|
||||
if (not snode_info.skip_until("total_contributed"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
const auto total_contributed = snode_info.consume_integer<int64_t>();
|
||||
|
||||
uint64_t total_res = 0;
|
||||
if (snode_info.skip_until("total_reserved"))
|
||||
total_res = snode_info.consume_integer<int64_t>();
|
||||
|
||||
size_t total_existing_contributions = 0; // Count both contributions and reserved spots
|
||||
bool is_preexisting_contributor = false;
|
||||
uint64_t reserved_amount_not_contributed_yet = 0;
|
||||
while (not contributors.is_finished())
|
||||
{
|
||||
auto contributor = contributors.consume_dict_consumer();
|
||||
|
||||
if (not contributor.skip_until("address"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
auto contributor_address = contributor.consume_string();
|
||||
|
||||
if (not contributor.skip_until("amount"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
auto amount = contributor.consume_integer<int64_t>();
|
||||
|
||||
if (not contributor.skip_until("locked_contributions"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
auto locked_contributions = contributor.consume_list_consumer();
|
||||
|
||||
while (not locked_contributions.is_finished())
|
||||
{
|
||||
locked_contributions.consume_dict_consumer();
|
||||
total_existing_contributions++;
|
||||
}
|
||||
|
||||
int64_t reserved = 0;
|
||||
if (contributor.skip_until("reserved"))
|
||||
reserved = contributor.consume_integer<int64_t>();
|
||||
|
||||
if (reserved > amount)
|
||||
total_existing_contributions++; // reserved contributor spot
|
||||
|
||||
if (contributor_address == change_recipient.address(nettype, {}))
|
||||
{
|
||||
is_preexisting_contributor = true;
|
||||
reserved_amount_not_contributed_yet = reserved - amount;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t max_contrib_total = staking_req - total_res + reserved_amount_not_contributed_yet;
|
||||
|
||||
uint64_t min_contrib_total = service_nodes::get_min_node_contribution(hf_version, staking_req, total_res, total_existing_contributions);
|
||||
if (min_contrib_total == UINT64_MAX || reserved_amount_not_contributed_yet > min_contrib_total)
|
||||
min_contrib_total = reserved_amount_not_contributed_yet;
|
||||
|
||||
if (max_contrib_total == 0)
|
||||
throw std::runtime_error("The service node cannot receive any more Oxen from this wallet");
|
||||
|
||||
const bool full = total_existing_contributions >= oxen::MAX_CONTRIBUTORS_HF19;
|
||||
|
||||
if (full && !is_preexisting_contributor)
|
||||
throw std::runtime_error("The service node already has the maximum number of participants and this wallet is not one of them");
|
||||
|
||||
|
||||
if (amount == 0)
|
||||
{
|
||||
oxen::log::info(logcat, "No amount provided to stake txn, assuming minimum contributrion of: {}", cryptonote::print_money(min_contrib_total));
|
||||
amount = min_contrib_total;
|
||||
}
|
||||
|
||||
if (amount < min_contrib_total)
|
||||
{
|
||||
const uint64_t DUST = oxen::MAX_CONTRIBUTORS_HF19;
|
||||
if (min_contrib_total - amount <= DUST)
|
||||
{
|
||||
oxen::log::info(logcat, "Seeing as this is insufficient by dust amounts, amount was increased automatically to ", cryptonote::print_money(min_contrib_total));
|
||||
amount = min_contrib_total;
|
||||
}
|
||||
else
|
||||
throw std::runtime_error(fmt::format("You must contribute at least {} oxen to become a contributor for this service node.", min_contrib_total));
|
||||
}
|
||||
|
||||
if (amount > max_contrib_total)
|
||||
{
|
||||
oxen::log::info(logcat, "You may only contribute up to {} more oxen to this service node. Reducing your stake from {} to {}", max_contrib_total, amount, max_contrib_total);
|
||||
amount = max_contrib_total;
|
||||
}
|
||||
}
|
||||
|
||||
PendingTransaction
|
||||
TransactionConstructor::create_stake_transaction(
|
||||
const std::string& destination,
|
||||
const std::string& service_node_key,
|
||||
const uint64_t requested_amount,
|
||||
const cryptonote::tx_destination_entry& change_recipient
|
||||
)
|
||||
{
|
||||
uint64_t amount = requested_amount;
|
||||
double amount_fraction = 0;
|
||||
validate_stake_parameters(service_node_key, amount, change_recipient);
|
||||
|
||||
std::vector<cryptonote::tx_destination_entry> recipients;
|
||||
auto& staked_amount_to_self = recipients.emplace_back();
|
||||
staked_amount_to_self.original = change_recipient.original;
|
||||
staked_amount_to_self.amount = amount;
|
||||
staked_amount_to_self.addr = change_recipient.addr;
|
||||
staked_amount_to_self.is_subaddress = change_recipient.is_subaddress;
|
||||
staked_amount_to_self.is_integrated = change_recipient.is_integrated;
|
||||
|
||||
PendingTransaction new_tx(recipients);
|
||||
auto [hf, hf_uint8] = cryptonote::get_ideal_block_version(db->network_type(), db->scan_target_height());
|
||||
new_tx.tx.version = cryptonote::transaction::get_max_version_for_hf(hf);
|
||||
new_tx.tx.type = cryptonote::txtype::stake;
|
||||
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;
|
||||
|
||||
crypto::public_key service_node_public_key;
|
||||
if (!tools::hex_to_type(service_node_key, service_node_public_key))
|
||||
throw std::runtime_error("could not read service node key");
|
||||
|
||||
cryptonote::add_service_node_pubkey_to_tx_extra(new_tx.extra, service_node_public_key);
|
||||
cryptonote::add_service_node_contributor_to_tx_extra(new_tx.extra, change_recipient.addr);
|
||||
|
||||
new_tx.update_change();
|
||||
|
||||
select_inputs_and_finalise(new_tx);
|
||||
return new_tx;
|
||||
}
|
||||
|
||||
PendingTransaction
|
||||
TransactionConstructor::create_stake_unlock_transaction(
|
||||
const std::string& service_node_key,
|
||||
const cryptonote::tx_destination_entry& change_recipient,
|
||||
std::shared_ptr<Keyring> keyring)
|
||||
{
|
||||
|
||||
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());
|
||||
new_tx.tx.version = cryptonote::transaction::get_max_version_for_hf(hf);
|
||||
new_tx.tx.type = cryptonote::txtype::stake;
|
||||
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;
|
||||
|
||||
crypto::public_key service_node_public_key;
|
||||
if (!tools::hex_to_type(service_node_key, service_node_public_key))
|
||||
throw std::runtime_error("could not read service node key");
|
||||
cryptonote::add_service_node_pubkey_to_tx_extra(new_tx.extra, service_node_public_key);
|
||||
|
||||
auto get_service_node_future = daemon->get_service_nodes({service_node_key});
|
||||
if (get_service_node_future.wait_for(5s) != std::future_status::ready)
|
||||
throw std::runtime_error("request to daemon for get_service_nodes timed out");
|
||||
|
||||
auto response = get_service_node_future.get();
|
||||
if(response.is_finished())
|
||||
throw std::runtime_error("Could not find service node in service node list, please make sure it is registered first.");
|
||||
auto snode_info = response.consume_dict_consumer();
|
||||
|
||||
const auto hf_version = cryptonote::get_latest_hard_fork(nettype).version;
|
||||
|
||||
if (not snode_info.skip_until("contributors"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
auto contributors = snode_info.consume_list_consumer();
|
||||
|
||||
cryptonote::tx_extra_tx_key_image_unlock unlock = {};
|
||||
unlock.nonce = cryptonote::tx_extra_tx_key_image_unlock::FAKE_NONCE;
|
||||
// Loop over contributors
|
||||
bool found_our_contribution = false;
|
||||
while (not contributors.is_finished())
|
||||
{
|
||||
auto contributor = contributors.consume_dict_consumer();
|
||||
|
||||
if (not contributor.skip_until("address"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
auto contributor_address = contributor.consume_string();
|
||||
if (contributor_address != change_recipient.address(nettype, {}))
|
||||
continue;
|
||||
|
||||
found_our_contribution = true;
|
||||
|
||||
if (not contributor.skip_until("key_image"))
|
||||
throw std::runtime_error{"Invalid response from daemon"};
|
||||
|
||||
const auto key_image = response.consume_string();
|
||||
if(!tools::hex_to_type(key_image, unlock.key_image))
|
||||
throw std::runtime_error{"Failed to parse hex representation of key image"s + key_image};
|
||||
|
||||
const auto locked_stake_output = db->get_output_from_key_image(key_image);
|
||||
|
||||
unlock.signature = keyring->generate_stake_unlock_signature(locked_stake_output);
|
||||
}
|
||||
|
||||
// If did not find then throw
|
||||
if (not found_our_contribution)
|
||||
throw std::runtime_error{"did not find our contribution in this service node"};
|
||||
|
||||
add_tx_key_image_unlock_to_tx_extra(new_tx.extra, unlock);
|
||||
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
|
||||
void
|
||||
TransactionConstructor::select_inputs(PendingTransaction& ptx) const
|
||||
{
|
||||
const int64_t single_input_size = ptx.get_fee(1);
|
||||
const int64_t double_input_size = ptx.get_fee(2);
|
||||
const int64_t additional_input = double_input_size - single_input_size;
|
||||
|
|
|
@ -59,6 +59,45 @@ class TransactionConstructor {
|
|||
const cryptonote::tx_destination_entry& change_recipient,
|
||||
std::shared_ptr<Keyring> keyring);
|
||||
|
||||
void
|
||||
validate_register_service_node_parameters(
|
||||
const std::string& service_node_key,
|
||||
const service_nodes::registration_details& registration,
|
||||
cryptonote::hf hf_version
|
||||
);
|
||||
|
||||
PendingTransaction
|
||||
create_register_service_node_transaction(
|
||||
const uint64_t fee,
|
||||
const std::vector<std::string>& addresses,
|
||||
const std::vector<uint64_t>& amounts,
|
||||
const uint64_t registration_hardfork,
|
||||
const std::string& service_node_key,
|
||||
const std::string& signature_str,
|
||||
const cryptonote::tx_destination_entry& change_recipient,
|
||||
std::shared_ptr<Keyring> keyring);
|
||||
|
||||
void
|
||||
validate_stake_parameters(
|
||||
const std::string& service_node_key,
|
||||
uint64_t& amount,
|
||||
const cryptonote::tx_destination_entry& change_recipient
|
||||
);
|
||||
|
||||
PendingTransaction
|
||||
create_stake_transaction(
|
||||
const std::string& destination,
|
||||
const std::string& service_node_key,
|
||||
const uint64_t requested_amount,
|
||||
const cryptonote::tx_destination_entry& change_recipient
|
||||
);
|
||||
|
||||
PendingTransaction
|
||||
create_stake_unlock_transaction(
|
||||
const std::string& service_node_key,
|
||||
const cryptonote::tx_destination_entry& change_recipient,
|
||||
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;
|
||||
|
||||
|
|
|
@ -130,6 +130,12 @@ def eat_ons_data():
|
|||
return ons_data
|
||||
|
||||
def eat_uint64_t():
|
||||
return eat_uint(8)
|
||||
|
||||
def eat_uint8_t():
|
||||
return eat_uint(1)
|
||||
|
||||
def eat_uint(size_in_bytes):
|
||||
global tx_extra_list
|
||||
amount = ''.join(tx_extra_list[:8*2])
|
||||
tx_extra_list = tx_extra_list[8*2:]
|
||||
|
@ -140,13 +146,119 @@ def eat_burn():
|
|||
global tx_extra_list
|
||||
return {"amount": eat_uint64_t()}
|
||||
|
||||
def eat_varint():
|
||||
global tx_extra_list
|
||||
length = 0
|
||||
x = 0
|
||||
b = 1
|
||||
for i in range(0, len(tx_extra_list), 2):
|
||||
length = length + 2
|
||||
z = int(''.join(tx_extra_list[i:i+2]), 16)
|
||||
x += b * (z & ~0x80)
|
||||
if z & 0x80 == 0:
|
||||
break
|
||||
b <<= 7
|
||||
tx_extra_list = tx_extra_list[length:]
|
||||
return x
|
||||
|
||||
def eat_service_node_contributor():
|
||||
global tx_extra_list
|
||||
spend_public_key = ''.join(tx_extra_list[:64])
|
||||
tx_extra_list = tx_extra_list[64:]
|
||||
view_public_key = ''.join(tx_extra_list[:64])
|
||||
tx_extra_list = tx_extra_list[64:]
|
||||
return {"spend_public_key": spend_public_key,
|
||||
"view_public_key": view_public_key }
|
||||
|
||||
def eat_service_node_pubkey():
|
||||
global tx_extra_list
|
||||
public_key = ''.join(tx_extra_list[:64])
|
||||
tx_extra_list = tx_extra_list[64:]
|
||||
return {"service_node_pubkey": public_key}
|
||||
|
||||
def eat_list(elem_size):
|
||||
global tx_extra_list
|
||||
results = []
|
||||
size_hex = ''.join(tx_extra_list[:2])
|
||||
tx_extra_list = tx_extra_list[2:]
|
||||
size = int(size_hex, 16)
|
||||
for i in range(size):
|
||||
result = ''.join(tx_extra_list[:elem_size])
|
||||
tx_extra_list = tx_extra_list[elem_size:]
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
def eat_uint64_t_list():
|
||||
global tx_extra_list
|
||||
results = []
|
||||
size_hex = ''.join(tx_extra_list[:2])
|
||||
tx_extra_list = tx_extra_list[2:]
|
||||
size = int(size_hex, 16)
|
||||
for i in range(size):
|
||||
result = eat_uint64_t()
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
def eat_varint_list():
|
||||
global tx_extra_list
|
||||
results = []
|
||||
size_hex = ''.join(tx_extra_list[:2])
|
||||
tx_extra_list = tx_extra_list[2:]
|
||||
size = int(size_hex, 16)
|
||||
for i in range(size):
|
||||
result = eat_varint()
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
def eat_service_node_registration():
|
||||
global tx_extra_list
|
||||
public_spend_keys = eat_list(64)
|
||||
public_view_keys = eat_list(64)
|
||||
fee = eat_uint64_t()
|
||||
amounts= eat_varint_list()
|
||||
hf = eat_uint8_t()
|
||||
signature = ''.join(tx_extra_list[:128])
|
||||
tx_extra_list = tx_extra_list[128:]
|
||||
return {"public_spend_keys": public_spend_keys,
|
||||
"public_view_keys": public_view_keys,
|
||||
"fee": fee,
|
||||
"amounts":amounts,
|
||||
"hf":hf,
|
||||
"signature": signature}
|
||||
|
||||
def eat_tx_secret_key():
|
||||
global tx_extra_list
|
||||
tx_secret_key = ''.join(tx_extra_list[:64])
|
||||
tx_extra_list = tx_extra_list[64:]
|
||||
return {"tx_secret_key": tx_secret_key}
|
||||
|
||||
def eat_tx_key_image_proofs():
|
||||
global tx_extra_list
|
||||
size_hex = ''.join(tx_extra_list[:2])
|
||||
tx_extra_list = tx_extra_list[2:]
|
||||
size = int(size_hex, 16)
|
||||
key_images = []
|
||||
signatures = []
|
||||
for i in range(size):
|
||||
tx_key_image = ''.join(tx_extra_list[:64])
|
||||
key_images.append(tx_key_image)
|
||||
tx_extra_list = tx_extra_list[64:]
|
||||
signature = ''.join(tx_extra_list[:128])
|
||||
signatures.append(signature)
|
||||
tx_extra_list = tx_extra_list[128:]
|
||||
return {"key_images": key_images,
|
||||
"signatures": signatures}
|
||||
|
||||
eat_data_functions = {
|
||||
"TX_EXTRA_TAG_PUBKEY": eat_pubkey_data,
|
||||
"TX_EXTRA_NONCE": eat_nonce_data,
|
||||
"TX_EXTRA_TAG_OXEN_NAME_SYSTEM": eat_ons_data,
|
||||
"TX_EXTRA_TAG_BURN": eat_burn
|
||||
"TX_EXTRA_TAG_BURN": eat_burn,
|
||||
"TX_EXTRA_TAG_SERVICE_NODE_CONTRIBUTOR": eat_service_node_contributor,
|
||||
"TX_EXTRA_TAG_SERVICE_NODE_PUBKEY": eat_service_node_pubkey,
|
||||
"TX_EXTRA_TAG_SERVICE_NODE_REGISTER": eat_service_node_registration,
|
||||
"TX_EXTRA_TAG_TX_SECRET_KEY": eat_tx_secret_key,
|
||||
"TX_EXTRA_TAG_TX_KEY_IMAGE_PROOFS": eat_tx_key_image_proofs
|
||||
}
|
||||
|
||||
# Main loop that reads over every item
|
||||
|
|
Loading…
Reference in New Issue