Adds RPC endpoint for accrued batching balances

This allows a user to query the daemon and ask for the accrued balance
of oxen rewards that have been batched but not yet paid out.

The endpoint takes in an array of addresses (Strings) and returns an
array of atomic oxen amounts (integers) that reflect the current
database balances for those requested addresses.

For example an address has accrued 2x batch payments of 16.5 oxen but
has not yet been paid out will show 33000000000 being the atomic amount
in the database.
This commit is contained in:
Sean Darcy 2022-05-19 13:02:26 +10:00
parent 3ac7e55056
commit f45d0588ae
7 changed files with 142 additions and 1 deletions

View File

@ -224,6 +224,41 @@ namespace cryptonote {
return payments;
}
uint64_t BlockchainSQLite::get_accrued_earnings(const std::string& address) {
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
SQLite::Statement select_earnings {
db,
"SELECT amount FROM batched_payments_accrued WHERE address = ?;"
};
select_earnings.bind(1, address);
uint64_t amount{};
while (select_earnings.executeStep()) {
amount = static_cast<uint64_t>(select_earnings.getColumn(0).getInt64() / 1000);
}
return amount;
}
std::pair<std::vector<std::string>, std::vector<uint64_t>> BlockchainSQLite::get_all_accrued_earnings() {
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
SQLite::Statement select_earnings {
db,
"SELECT address, amount FROM batched_payments_accrued;"
};
std::vector<uint64_t> amounts;
std::vector<std::string> addresses;
while (select_earnings.executeStep()) {
addresses.emplace_back(select_earnings.getColumn(0).getString());
amounts.emplace_back(static_cast<uint64_t>(select_earnings.getColumn(1).getInt64() / 1000));
}
return std::make_pair(addresses, amounts);
}
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::calculate_rewards(uint8_t hf_version, uint64_t distribution_amount, service_nodes::service_node_info sn_info) {
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);

View File

@ -66,6 +66,13 @@ public:
// get_payments -> passing a block height will return an array of payments that should be created in a coinbase transaction on that block given the current batching DB state.
std::optional<std::vector<cryptonote::batch_sn_payment>> get_sn_payments(uint64_t block_height);
// get_accrued_earnings -> queries the database for the amount that has been accrued to `service_node_address` will return the atomic value in oxen that
// the service node is owed.
uint64_t get_accrued_earnings(const std::string& address);
// get_all_accrued_earnings -> queries the database for all the amount that has been accrued to service nodes will return
// 2 vectors corresponding to the addresses and the atomic value in oxen that the service nodes are owed.
std::pair<std::vector<std::string>, std::vector<uint64_t>> get_all_accrued_earnings();
// calculate_rewards -> takes the list of contributors from sn_info with their SN contribution amounts and will calculate how much of the block rewards should be the allocated to the contributors. The function will return a list suitable for passing to add_sn_payments
std::vector<cryptonote::batch_sn_payment> calculate_rewards(uint8_t hf_version, uint64_t distribution_amount, service_nodes::service_node_info sn_info);

View File

@ -2774,6 +2774,44 @@ namespace cryptonote { namespace rpc {
}
//------------------------------------------------------------------------------------------------------------------------------
GET_ACCRUED_BATCHED_EARNINGS::response core_rpc_server::invoke(GET_ACCRUED_BATCHED_EARNINGS::request&& req, rpc_context context)
{
GET_ACCRUED_BATCHED_EARNINGS::response res{};
PERF_TIMER(on_get_accrued_batched_earnings);
auto& blockchain = m_core.get_blockchain_storage();
bool at_least_one_succeeded = false;
if (req.addresses.size() > 0)
{
for (const auto& address : req.addresses)
{
res.addresses.emplace_back(address);
if (cryptonote::is_valid_address(address, nettype()))
{
uint64_t amount = blockchain.sqlite_db()->get_accrued_earnings(address);
res.amounts.emplace_back(amount);
at_least_one_succeeded = true;
} else {
res.amounts.emplace_back(0);
}
}
} else {
auto [addresses, amounts] = blockchain.sqlite_db()->get_all_accrued_earnings();
res.addresses = std::move(addresses);
res.amounts = std::move(amounts);
at_least_one_succeeded = true;
}
if (!at_least_one_succeeded)
throw rpc_error{ERROR_WRONG_PARAM, "Failed to query any service nodes batched amounts at all"};
res.status = STATUS_OK;
return res;
}
//------------------------------------------------------------------------------------------------------------------------------
GET_QUORUM_STATE::response core_rpc_server::invoke(GET_QUORUM_STATE::request&& req, rpc_context context)
{
GET_QUORUM_STATE::response res{};

View File

@ -248,6 +248,7 @@ namespace cryptonote::rpc {
PRUNE_BLOCKCHAIN::response invoke(PRUNE_BLOCKCHAIN::request&& req, rpc_context context);
GET_OUTPUT_BLACKLIST::response invoke(GET_OUTPUT_BLACKLIST::request&& req, rpc_context context);
GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context);
GET_ACCRUED_BATCHED_EARNINGS::response invoke(GET_ACCRUED_BATCHED_EARNINGS::request&& req, rpc_context context);
GET_SERVICE_NODE_REGISTRATION_CMD_RAW::response invoke(GET_SERVICE_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context);
GET_SERVICE_NODE_REGISTRATION_CMD::response invoke(GET_SERVICE_NODE_REGISTRATION_CMD::request&& req, rpc_context context);
GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::response invoke(GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::request&& req, rpc_context context);

View File

@ -92,6 +92,15 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::request)
KV_SERIALIZE(stake_info)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_ACCRUED_BATCHED_EARNINGS::request)
KV_SERIALIZE(addresses)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_ACCRUED_BATCHED_EARNINGS::response)
KV_SERIALIZE(status)
KV_SERIALIZE(addresses)
KV_SERIALIZE(amounts)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::sn_reg_info::contribution)
KV_SERIALIZE(wallet)

View File

@ -1964,6 +1964,29 @@ namespace rpc {
};
};
OXEN_RPC_DOC_INTROSPECT
// Accesses the amounts accrued to addresses in the batching database
struct GET_ACCRUED_BATCHED_EARNINGS: PUBLIC
{
static constexpr auto names() { return NAMES("get_accrued_batched_earnings"); }
struct request
{
std::vector<std::string> addresses; // Array of addresses to query the batching database about.
KV_MAP_SERIALIZABLE
};
struct response
{
std::string status; // Generic RPC error code. "OK" is the success value.
std::vector<std::string> addresses; // Array of addresses to query the batching database about.
std::vector<uint64_t> amounts; // An array of amounts according to the provided addressses
KV_MAP_SERIALIZABLE
};
};
OXEN_RPC_DOC_INTROSPECT
// Get the service private keys of the queried daemon, encoded in hex. Do not ever share
// these keys: they would allow someone to impersonate your service node. All three keys are used
@ -2658,7 +2681,8 @@ namespace rpc {
ONS_NAMES_TO_OWNERS,
ONS_OWNERS_TO_NAMES,
ONS_RESOLVE,
FLUSH_CACHE
FLUSH_CACHE,
GET_ACCRUED_BATCHED_EARNINGS
>;
} } // namespace cryptonote::rpc

View File

@ -0,0 +1,27 @@
#!/usr/bin/python3
import sys
sys.path.append('../testdata')
import config
import requests
import json
def instruct_daemon(method, params):
payload = json.dumps({"method": method, "params": params}, skipkeys=False)
print(payload)
headers = {'content-type': "application/json"}
try:
response = requests.request("POST", "http://"+config.listen_ip+":"+config.listen_port+"/json_rpc", data=payload, headers=headers)
return json.loads(response.text)
except requests.exceptions.RequestException as e:
print(e)
except:
print('No response from daemon, check daemon is running on this machine')
# answer = instruct_daemon('get_accrued_batched_earnings', {"addresses": [config.wallet_address]})
answer = instruct_daemon('get_accrued_batched_earnings', {"addresses": []})
print(json.dumps(answer, indent=4, sort_keys=True))