-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cuprated: RPC handlers #355
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Boog900 I think this mostly needs review on:
- general direction and broader changes
- viability of new
tower::Service
request/response types
I don't think you have to review each handler fn
individually in-depth since there will eventually be the testing harness that proves input/output matches monerod
. With that said, here's some info:
RPC code structure
Directory | Contains |
---|---|
binaries/cuprated/src/rpc/handlers/ |
The RPC handler functions, shared functions, and helper functions |
binaries/cuprated/src/rpc/service/ |
fn versions of our tower::Service s to reduce noise |
RPC handlers
Color | Meaning |
---|---|
🟢 | Ready |
🟡 | Ready but could be more efficient |
🟣 | Ready but callstack depends on other things |
🟠 | Depends on other things |
🔴 | Waiting on binary strings |
🔵 | Unsupported (for now) |
⚪ | Unsupported (probably forever) |
⚫ | I think these could be deprecated or have different behavior |
JSON-RPC fn |
Status | Details |
---|---|---|
get_block_count | 🟢 | |
get_last_block_header | 🟢 | |
get_block_header_by_hash | 🟢 | |
get_block_header_by_height | 🟢 | |
get_block | 🟢 | |
hard_fork_info | 🟢 | |
on_get_block_hash | 🟢 | |
get_block_headers_range | 🟡 | Would benefit from a request that allows retrieving a range of (Block, ExtendedBlockHeader) |
get_connections | 🟣 | Waiting on address book Service impl |
set_bans | 🟣 | ^ |
get_bans | 🟣 | ^ |
banned | 🟣 | ^ |
get_version | 🟣 | Waiting on blockchain context Service impl |
get_output_histogram | 🟣 | ^ |
get_fee_estimate | 🟣 | ^ |
calc_pow | 🟣 | ^ |
flush_transaction_pool | 🟣 | Waiting on txpool manager Service impl |
relay_tx | 🟣 | ^ |
get_coinbase_tx_sum | 🟣 | Waiting on blockchain Service impl |
get_alternate_chains | 🟣 | ^ |
sync_info | 🟣 | Waiting on multiple Service impls |
get_miner_data | 🟣 | Waiting on txpool Service impl |
submit_block | 🟣 | ^ |
get_info | 🟣 | Needs access to unimplemented things from various places |
generate_blocks | 🟣 | Needs access to cuprated 's Chain and --regtest |
add_aux_pow | 🟣 | Waiting on crypto functions |
get_transaction_pool_backlog | 🔴 | |
get_output_distribution | 🔴 | |
get_tx_ids_loose | 🔵 | Not implemented in monerod release branch yet |
flush_cache | ⚪ | cuprated does not need this |
prune_blockchain | ⚫ | I don't think an always available RPC method is necessary for something that is done once. This could return if the chain is pruned + the pruning seed but not actually prune if that makes things more complex. Pruning itself should be done with the equivalent of --prune-blockchain . |
Other JSON fn |
Status | Details |
---|---|---|
get_height | 🟢 | |
get_outs | 🟢 | |
is_key_image_spent | 🟣 | Waiting on txpool Service impl |
get_transaction_pool_hashes | 🟣 | ^ |
get_transaction_pool | 🟣 | ^ |
get_transaction_pool_stats | 🟣 | ^ |
save_bc | 🟣 | Waiting on blockchain manager Service impl |
stop_daemon | 🟣 | ^ |
pop_blocks | 🟣 | ^ |
get_peer_list | 🟣 | Waiting on address book Service impl |
get_public_nodes | 🟣 | ^ |
get_alt_blocks_hashes | 🟣 | Waiting on blockchain Service impl |
send_raw_transaction | 🟣 | Waiting on txpool manager Service impl |
get_transactions | 🟣 | Waiting on JSON representation of Transaction |
get_limit | 🟠 | Waiting on P2P interface |
set_limit | 🟠 | ^ |
out_peers | 🟠 | ^ |
in_peers | 🟠 | ^ |
get_net_stats | 🟠 | ^ |
set_log_level | 🔵 | Will use tracing levels |
set_log_categories | 🔵 | Could be re-purposed to use tracing filters |
set_bootstrap_daemon | 🔵 | Needs bootstrap implementation |
start_mining | ⚪ | cuprated does not mine |
stop_mining | ⚪ | ^ |
mining_status | ⚪ | ^ |
set_log_hash_rate | ⚪ | ^ |
update | ⚫ | This could return if an update is available and related info but not actually self-update. Software upgrades should be done by the user and/or a package manager. |
Binary fn |
Status | Details |
---|---|---|
get_blocks_by_height | 🟢 | |
get_hashes | 🟢 | |
get_output_indexes | 🟢 | |
get_outs | 🟢 | |
get_blocks | 🟣 | Waiting on txpool Service impl |
get_transaction_pool_hashes | 🟣 | ^ |
get_output_distribution | 🔴 | Although this is binary, the internal fn is shared with JSON-RPC's get_output_distribution |
@@ -291,6 +291,7 @@ macro_rules! define_response { | |||
} | |||
) => { | |||
$( #[$attr] )* | |||
#[cfg_attr(feature = "serde", serde(default))] // TODO: link epee field not serializing oddity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the time being, I'm making response types in cuprate-rpc-types
have serde(default)
since:
monerod
will not serialize fields with empty containersmonerod
's response types sometimes change depending on branches
serde(default)
is a bit of a blunt tool to use but I think it works for now. I will think about this more when integrating RPC into cuprated
and with more tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said in the other issue I am ok with this for now, I don't like the idea of this being permanent though
let txid = { | ||
let height = u32_to_usize(output.height); | ||
let tx_idx = u64_to_usize(output.tx_idx); | ||
if let Some(hash) = table_block_txs_hashes.get(&height)?.get(tx_idx) { | ||
*hash | ||
} else { | ||
let miner_tx_id = table_block_infos.get(&height)?.mining_tx_index; | ||
let tx_blob = table_tx_blobs.get(&miner_tx_id)?; | ||
Transaction::read(&mut tx_blob.0.as_slice())?.hash() | ||
} | ||
}; | ||
|
||
Ok(OutputOnChain { | ||
height: output.height as usize, | ||
time_lock, | ||
key, | ||
commitment, | ||
txid, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function now returns the txid
for each output due to https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_outs.
This is more expensive, the txid
is not required by default as well. Should this be something like txid: Option<[u8; 32]>
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think it should be, this is called ot get outputs when syncing so hashing miner txs/extra lookups is just wasted work.
fn key_images_spent(env: &ConcreteEnv, key_images: HashSet<KeyImage>) -> ResponseResult { | ||
fn key_images_spent(env: &ConcreteEnv, key_images: Vec<KeyImage>) -> ResponseResult { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was changed to Vec
due to https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#is_key_image_spent behavior.
curl http://127.0.0.1:18081/is_key_image_spent -d '{"key_images":["8d1bd8181bf7d857bdb281e0153d84cd55a3fcaa57c3e570f4a49f935850b5e3","8d1bd8181bf7d857bdb281e0153d84cd55a3fcaa57c3e570f4a49f935850b5e3"]}' -H 'Content-Type: application/json'
{
"credits": 0,
"spent_status": [1,1],
"status": "OK",
"top_hash": "",
"untrusted": false
}
If cuprated
used HashSet
then it would respond with something like "spent_status": [1]
. Small detail but I think it could matter with wallets due to indexing code depending on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kinda want to say this should be a separate request, I can imagine it being more efficient to check if any one KI is spent rather than always needing to check every KIs spent status in the blockchain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just leaving the review in the current state will probably do another pass. Looks good so far but there are defiantly areas where we could improve performance when the inner services get the required requests filled in.
let Some((index, start_height)) = | ||
blockchain::find_first_unknown(&mut state.blockchain_read, hashes).await? | ||
else { | ||
return Err(anyhow!("Failed")); | ||
}; | ||
|
||
let m_blocks_ids = bytes.split_off(index); | ||
|
||
Ok(GetHashesResponse { | ||
base: helper::access_response_base(false), | ||
m_blocks_ids, | ||
start_height, | ||
current_height, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to only be doing part of the request - finding the split point, we also need to give the next hashes in the chain back. This can be all done with BlockchainReadRequest::NextChainEntry
// FIXME: is there a cheaper way to get this? | ||
let difficulty = blockchain_context::batch_get_difficulties( | ||
&mut state.blockchain_context, | ||
vec![(height, hardfork)], | ||
) | ||
.await? | ||
.first() | ||
.copied() | ||
.ok_or_else(|| anyhow!("Failed to get block difficulty"))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// FIXME: is there a cheaper way to get this?
yes - get the cumulative difficulty of this block take away the cumulative difficulty of the block before it will give you the difficulty of this block. Cumulative difficulty should be in the extended header. Also this current method is incorrect as this request takes in a timestamp not height.
Separate but related: I think we should create a specific DB request for as much data as we can as we currently need to call the service multiple times for this request.
This can all be a TODO, no need to do in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hinto-janai can you add a link to this comment in the code please.
|
||
// TODO: this is hardcoded for the current address scheme + mainnet, | ||
// create/use a more well-defined wallet lib. | ||
let parse_wallet_address = || { | ||
if request.wallet_address.len() == 95 { | ||
Ok(()) | ||
} else { | ||
Err(()) | ||
} | ||
}; | ||
let is_correct_address_type = || !request.wallet_address.starts_with("4"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend monero-address: https://github.com/serai-dex/serai/tree/develop/networks/monero/wallet/address
mut state: CupratedRpcHandler, | ||
request: GetBlockHeaderByHeightRequest, | ||
) -> Result<GetBlockHeaderByHeightResponse, Error> { | ||
helper::check_height(&mut state, request.height).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a TODO, this check should be done in the DB to prevent needing to do multiple DB requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(there are lots of places where we could improve performance, could just be left as a wider todo)
let block_weight_limit = usize_to_u64(c.effective_median_weight); | ||
let block_weight_median = usize_to_u64(c.median_weight_for_block_reward); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let block_weight_limit = usize_to_u64(c.effective_median_weight); | |
let block_weight_median = usize_to_u64(c.median_weight_for_block_reward); | |
let block_weight_limit = usize_to_u64(c.effective_median_weight * 2); | |
let block_weight_median = usize_to_u64(c.effective_median_weight); |
that matches monerod
behavior but technically it should be this:
let block_weight_limit = usize_to_u64(c.effective_median_weight); | |
let block_weight_median = usize_to_u64(c.median_weight_for_block_reward); | |
let block_weight_limit = usize_to_u64(c.median_weight_for_block_reward * 2); | |
let block_weight_median = usize_to_u64(c.median_weight_for_block_reward); |
Both will be the same for the current HF although past HFs these values are different. Should we copy monerod
's bad behavior here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy for now, submit patch to monerod
eventually, then update cuprated
?
let major_version = c.current_hf.as_u8(); | ||
let height = usize_to_u64(c.chain_height); | ||
let prev_id = Hex(c.top_hash); | ||
let seed_hash = Hex(c.top_hash); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the Rx seed hash, it'll have to be a todo, I'll add it to the context in a future PR.
let prev_id = Hex(c.top_hash); | ||
let seed_hash = Hex(c.top_hash); | ||
let difficulty = c.next_difficulty.hex_prefix(); | ||
let median_weight = usize_to_u64(c.median_weight_for_block_reward); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as the other comment, this is actually the correct value but if we are trying to match Monero, the value should be effective_median_weight
.
tokio::time::sleep(Duration::from_millis(100)).await; | ||
} | ||
|
||
tokio::task::spawn_blocking(|| add_aux_pow_inner(state, request)).await? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should spawn on a rayon thread, using rayon_spawn_async
blockchain::key_images_spent(&mut state.blockchain_read, key_images.clone()) | ||
.await? | ||
.into_iter() | ||
.for_each(|ki| { | ||
if ki { | ||
spent_status.push(KeyImageSpentStatus::SpentInBlockchain.to_u8()); | ||
} else { | ||
spent_status.push(KeyImageSpentStatus::Unspent.to_u8()); | ||
} | ||
}); | ||
|
||
txpool::key_images_spent(&mut state.txpool_read, key_images, !restricted) | ||
.await? | ||
.into_iter() | ||
.for_each(|ki| { | ||
if ki { | ||
spent_status.push(KeyImageSpentStatus::SpentInPool.to_u8()); | ||
} else { | ||
spent_status.push(KeyImageSpentStatus::Unspent.to_u8()); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be returning a list with double the length of the key images? I don't think this is correct. We should have a list the exact same length right
index_map.into_values().map(|out| OutKeyBin { | ||
key: out.key.map_or([0; 32], |e| e.compress().0), | ||
mask: out.commitment.compress().0, | ||
unlocked: matches!(out.time_lock, Timelock::None), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should return true
even if the output is locked but the lock time has passed right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Co-authored-by: Boog900 <boog900@tutanota.com>
Co-authored-by: Boog900 <boog900@tutanota.com>
for (_, index_vec) in outputs { | ||
for (_, out) in index_vec { | ||
let out_key = OutKeyBin { | ||
key: out.key.map_or([0; 32], |e| e.compress().0), | ||
mask: out.commitment.compress().0, | ||
unlocked: helper::timelock_is_unlocked(&mut state, out.time_lock).await?, | ||
height: usize_to_u64(out.height), | ||
txid: if request.get_txid { out.txid } else { [0; 32] }, | ||
}; | ||
|
||
outs.push(out_key); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1/2
Fixed timelock check, although now it is for
due to async
.
TODO: optimized async iterators?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outputs unlock time is not just done by looking at the timestamp/block height, it has specific rules, here is the function in Cuprate:
cuprate/consensus/rules/src/transactions.rs
Lines 220 to 223 in 9842535
/// Checks if an outputs unlock time has passed. | |
/// | |
/// <https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html> | |
pub const fn output_unlocked( |
You will need to get the blockchian context for the time for unlock & chain height:
cuprate/consensus/context/src/lib.rs
Lines 138 to 141 in 9842535
/// Returns the timestamp the should be used when checking locked outputs. | |
/// | |
/// ref: <https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time> | |
pub fn current_adjusted_timestamp_for_time_lock(&self) -> u64 { |
That only has to be done once, you could reuse the context for each output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Boog900 ready for review again, although WASM CI for cuprate-rpc-types
still needs fixing.
async fn get_blocks( | ||
mut state: CupratedRpcHandler, | ||
request: GetBlocksRequest, | ||
) -> Result<GetBlocksResponse, Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
putting the comment here for a lack of a better place, but because the request
type uses Bytes
internally it is good to drop it as soon as you can.
What this means is we should extract all the data needed out of the request before doing any async/blocking operations
|
||
let block_hashes: Vec<[u8; 32]> = (&request.block_ids).into(); | ||
|
||
let (blocks, missing_hashes, blockchain_height) = | ||
blockchain::block_complete_entries(&mut state.blockchain_read, block_hashes).await?; | ||
|
||
if !missing_hashes.is_empty() { | ||
return Err(anyhow!("Missing blocks")); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to be incorrect, monerod
seems to be getting the next hashes in the chain, using the incoming hashes as a representation of the clients current chain, and giving the blocks for those future hashes instead: https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L733
// FIXME: is there a cheaper way to get this? | ||
let difficulty = blockchain_context::batch_get_difficulties( | ||
&mut state.blockchain_context, | ||
vec![(height, hardfork)], | ||
) | ||
.await? | ||
.first() | ||
.copied() | ||
.ok_or_else(|| anyhow!("Failed to get block difficulty"))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hinto-janai can you add a link to this comment in the code please.
/// [`cuprate_types::blockchain::BlockchainResponse::ChainHeight`] minus 1. | ||
pub(super) async fn top_height(state: &mut CupratedRpcHandler) -> Result<(u64, [u8; 32]), Error> { | ||
let (chain_height, hash) = blockchain::chain_height(&mut state.blockchain_read).await?; | ||
let height = chain_height.saturating_sub(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather this just be a normal subtraction/checked with unwrap. Saturating is confusing IMO
} | ||
|
||
let too_many_blocks = || { | ||
request.end_height.saturating_sub(request.start_height) + 1 > RESTRICTED_BLOCK_HEADER_RANGE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this needs to be saturating_sub
here
// This method can be a bit heavy, so rate-limit restricted use. | ||
if state.is_restricted() { | ||
tokio::time::sleep(Duration::from_millis(100)).await; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This IMO is not a great way to do rate limiting, it should be handled with a Semaphore or even better just rate restrict the whole RPC interface. When we have performance characteristics we can look at rate limiting further.
impl EpeeObjectBuilder<Transaction> for () { | ||
fn add_field<B: Buf>(&mut self, _: &str, _: &mut B) -> error::Result<bool> { | ||
unreachable!() | ||
} | ||
|
||
fn finish(self) -> error::Result<Transaction> { | ||
unreachable!() | ||
} | ||
} | ||
|
||
#[cfg(feature = "epee")] | ||
impl EpeeObject for Transaction { | ||
type Builder = (); | ||
|
||
fn number_of_fields(&self) -> u64 { | ||
unreachable!() | ||
} | ||
|
||
fn write_fields<B: BufMut>(self, _: &mut B) -> error::Result<()> { | ||
unreachable!() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this a TODO?
let height = u32_to_usize(rct_output.height); | ||
let tx_idx = u64_to_usize(rct_output.tx_idx); | ||
if let Some(hash) = table_block_txs_hashes.get(&height)?.get(tx_idx) { | ||
*hash | ||
} else { | ||
let miner_tx_id = table_block_infos.get(&height)?.mining_tx_index; | ||
let tx_blob = table_tx_blobs.get(&miner_tx_id)?; | ||
Transaction::read(&mut tx_blob.0.as_slice())?.hash() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am pretty sure this isn't correct the get(tx_idx)
should use th local index of the tx in the block, right? this currently used the global index.
let height = u32_to_usize(rct_output.height); | |
let tx_idx = u64_to_usize(rct_output.tx_idx); | |
if let Some(hash) = table_block_txs_hashes.get(&height)?.get(tx_idx) { | |
*hash | |
} else { | |
let miner_tx_id = table_block_infos.get(&height)?.mining_tx_index; | |
let tx_blob = table_tx_blobs.get(&miner_tx_id)?; | |
Transaction::read(&mut tx_blob.0.as_slice())?.hash() | |
} | |
let height = u32_to_usize(rct_output.height); | |
let tx_idx = u64_to_usize(rct_output.tx_idx); | |
let miner_tx_id = table_block_infos.get(&height)?.mining_tx_index; | |
if miner_tx_id == tx_idx { | |
let tx_blob = table_tx_blobs.get(&miner_tx_id)?; | |
Transaction::read(&mut tx_blob.0.as_slice())?.hash() | |
} else { | |
table_block_txs_hashes.get(&height)?[tx_idx - miner_tx_id - 1)] | |
} |
let txid = { | ||
let height = u32_to_usize(output.height); | ||
let tx_idx = u64_to_usize(output.tx_idx); | ||
if let Some(hash) = table_block_txs_hashes.get(&height)?.get(tx_idx) { | ||
*hash | ||
} else { | ||
let miner_tx_id = table_block_infos.get(&height)?.mining_tx_index; | ||
let tx_blob = table_tx_blobs.get(&miner_tx_id)?; | ||
Transaction::read(&mut tx_blob.0.as_slice())?.hash() | ||
} | ||
}; | ||
|
||
Ok(OutputOnChain { | ||
height: output.height as usize, | ||
time_lock, | ||
key, | ||
commitment, | ||
txid, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think it should be, this is called ot get outputs when syncing so hashing miner txs/extra lookups is just wasted work.
|
||
SendRawTransaction, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the line numbers above this are incorrect: https://github.com/monero-project/monero/blob/8d6aff95908e029d6b131638fbbf845e8cff04fc/src/rpc/core_rpc_server_commands_defs.h#L631-L683
What
Implements the
{json-rpc, binary, json}
handlers incuprated
; adds various types and changes some misc things as needed.How
See below review.