mirror of https://github.com/oxen-io/oxen-core.git
Wallet3 Unstake Tx
This commit is contained in:
parent
d9b5eb5e12
commit
71177fe9df
|
@ -247,6 +247,19 @@ def stake():
|
|||
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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -495,4 +495,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
|
||||
|
|
|
@ -116,6 +116,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;
|
||||
|
|
|
@ -282,7 +282,11 @@ void parse_request(REGISTER_SERVICE_NODE& req, rpc_input in) {
|
|||
);
|
||||
}
|
||||
|
||||
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) {}
|
||||
|
||||
|
|
|
@ -2257,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.
|
||||
|
|
|
@ -397,7 +397,29 @@ void RequestHandler::invoke(REGISTER_SERVICE_NODE& command, rpc_context context)
|
|||
command.response["status"] = "200";
|
||||
}
|
||||
|
||||
void RequestHandler::invoke(REQUEST_STAKE_UNLOCK& command, rpc_context context) {}
|
||||
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) {}
|
||||
|
||||
|
|
|
@ -442,6 +442,82 @@ PendingTransaction TransactionConstructor::create_ons_update_transaction(
|
|||
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
|
||||
|
|
|
@ -92,6 +92,12 @@ class TransactionConstructor {
|
|||
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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue