Skip to content
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

feat(rpc): add verbose support to getrawmempool #9249

Merged
merged 6 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion zebra-chain/src/transaction/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
parameters::{Network, NetworkUpgrade},
primitives::{Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof},
sapling::{self, AnchorVariant, PerSpendAnchor, SharedAnchor},
serialization::ZcashDeserializeInto,
serialization::{self, ZcashDeserializeInto},
sprout, transparent,
value_balance::{ValueBalance, ValueBalanceError},
LedgerState,
Expand Down Expand Up @@ -814,6 +814,8 @@ impl Arbitrary for VerifiedUnminedTx {
)
}),
any::<f32>(),
serialization::arbitrary::datetime_u32(),
any::<block::Height>(),
)
.prop_map(
|(
Expand All @@ -822,6 +824,8 @@ impl Arbitrary for VerifiedUnminedTx {
legacy_sigop_count,
(conventional_actions, mut unpaid_actions),
fee_weight_ratio,
time,
height,
)| {
if unpaid_actions > conventional_actions {
unpaid_actions = conventional_actions;
Expand All @@ -837,6 +841,8 @@ impl Arbitrary for VerifiedUnminedTx {
conventional_actions,
unpaid_actions,
fee_weight_ratio,
time: Some(time),
height: Some(height),
}
},
)
Expand Down
11 changes: 11 additions & 0 deletions zebra-chain/src/transaction/unmined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::{fmt, sync::Arc};

use crate::{
amount::{Amount, NonNegative},
block::Height,
serialization::ZcashSerialize,
transaction::{
AuthDigest, Hash,
Expand Down Expand Up @@ -358,6 +359,14 @@ pub struct VerifiedUnminedTx {
///
/// [ZIP-317]: https://zips.z.cash/zip-0317#block-production
pub fee_weight_ratio: f32,

/// The time the transaction was added to the mempool, or None if it has not
/// reached the mempool yet.
pub time: Option<chrono::DateTime<chrono::Utc>>,

/// The tip height when the transaction was added to the mempool, or None if
/// it has not reached the mempool yet.
pub height: Option<Height>,
}

impl fmt::Debug for VerifiedUnminedTx {
Expand Down Expand Up @@ -399,6 +408,8 @@ impl VerifiedUnminedTx {
fee_weight_ratio,
conventional_actions,
unpaid_actions,
time: None,
height: None,
})
}

Expand Down
76 changes: 54 additions & 22 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//! Some parts of the `zcashd` RPC documentation are outdated.
//! So this implementation follows the `zcashd` server and `lightwalletd` client implementations.

#[cfg(feature = "getblocktemplate-rpcs")]
use std::collections::HashMap;
use std::{collections::HashSet, fmt::Debug, sync::Arc};

use chrono::Utc;
Expand Down Expand Up @@ -56,6 +58,10 @@ pub mod trees;

pub mod types;

use types::GetRawMempool;
#[cfg(feature = "getblocktemplate-rpcs")]
use types::MempoolObject;

#[cfg(feature = "getblocktemplate-rpcs")]
pub mod get_block_template_rpcs;

Expand Down Expand Up @@ -215,11 +221,15 @@ pub trait Rpc {

/// Returns all transaction ids in the memory pool, as a JSON array.
///
/// # Parameters
///
/// - `verbose`: (boolean, optional, default=false) true for a json object, false for array of transaction ids.
///
/// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html)
/// method: post
/// tags: blockchain
#[method(name = "getrawmempool")]
async fn get_raw_mempool(&self) -> Result<Vec<String>>;
async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempool>;

/// Returns information about the given block's Sapling & Orchard tree state.
///
Expand Down Expand Up @@ -1063,7 +1073,10 @@ where
.ok_or_misc_error("No blocks in state")
}

async fn get_raw_mempool(&self) -> Result<Vec<String>> {
async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempool> {
#[allow(unused)]
let verbose = verbose.unwrap_or(false);

#[cfg(feature = "getblocktemplate-rpcs")]
use zebra_chain::block::MAX_BLOCK_BYTES;

Expand All @@ -1074,7 +1087,7 @@ where
let mut mempool = self.mempool.clone();

#[cfg(feature = "getblocktemplate-rpcs")]
let request = if should_use_zcashd_order {
let request = if should_use_zcashd_order || verbose {
mempool::Request::FullTransactions
} else {
mempool::Request::TransactionIds
Expand All @@ -1094,27 +1107,46 @@ where
#[cfg(feature = "getblocktemplate-rpcs")]
mempool::Response::FullTransactions {
mut transactions,
transaction_dependencies: _,
transaction_dependencies,
last_seen_tip_hash: _,
} => {
// Sort transactions in descending order by fee/size, using hash in serialized byte order as a tie-breaker
transactions.sort_by_cached_key(|tx| {
// zcashd uses modified fee here but Zebra doesn't currently
// support prioritizing transactions
std::cmp::Reverse((
i64::from(tx.miner_fee) as u128 * MAX_BLOCK_BYTES as u128
/ tx.transaction.size as u128,
// transaction hashes are compared in their serialized byte-order.
tx.transaction.id.mined_id(),
))
});

let tx_ids: Vec<String> = transactions
.iter()
.map(|unmined_tx| unmined_tx.transaction.id.mined_id().encode_hex())
.collect();
if verbose {
let map = transactions
.iter()
.map(|unmined_tx| {
(
unmined_tx.transaction.id.mined_id().encode_hex(),
MempoolObject::from_verified_unmined_tx(
unmined_tx,
&transactions,
&transaction_dependencies,
),
)
})
.collect::<HashMap<_, _>>();
Ok(GetRawMempool::Verbose(map))
} else {
// Sort transactions in descending order by fee/size, using
// hash in serialized byte order as a tie-breaker. Note that
// this is only done in not verbose because in verbose mode
// a dictionary is returned, where order does not matter.
transactions.sort_by_cached_key(|tx| {
// zcashd uses modified fee here but Zebra doesn't currently
// support prioritizing transactions
std::cmp::Reverse((
i64::from(tx.miner_fee) as u128 * MAX_BLOCK_BYTES as u128
/ tx.transaction.size as u128,
// transaction hashes are compared in their serialized byte-order.
tx.transaction.id.mined_id(),
))
});
let tx_ids: Vec<String> = transactions
.iter()
.map(|unmined_tx| unmined_tx.transaction.id.mined_id().encode_hex())
.collect();

Ok(tx_ids)
Ok(GetRawMempool::TxIds(tx_ids))
}
}

mempool::Response::TransactionIds(unmined_transaction_ids) => {
Expand All @@ -1126,7 +1158,7 @@ where
// Sort returned transaction IDs in numeric/string order.
tx_ids.sort();

Ok(tx_ids)
Ok(GetRawMempool::TxIds(tx_ids))
}

_ => unreachable!("unmatched response to a transactionids request"),
Expand Down
38 changes: 33 additions & 5 deletions zebra-rpc/src/methods/tests/prop.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Randomised property tests for RPC methods.

#[cfg(feature = "getblocktemplate-rpcs")]
use std::collections::HashMap;
use std::{collections::HashSet, fmt::Debug, sync::Arc};

use futures::{join, FutureExt, TryFutureExt};
Expand Down Expand Up @@ -27,7 +29,12 @@ use zebra_state::{BoxError, GetBlockTemplateChainInfo};

use zebra_test::mock_service::MockService;

use crate::methods::{self, types::Balance};
#[cfg(feature = "getblocktemplate-rpcs")]
use crate::methods::types::MempoolObject;
use crate::methods::{
self,
types::{Balance, GetRawMempool},
};

use super::super::{
AddressBalance, AddressStrings, NetworkUpgradeStatus, RpcImpl, RpcServer, SentTransactionHash,
Expand Down Expand Up @@ -228,7 +235,8 @@ proptest! {
/// returns those IDs as hexadecimal strings.
#[test]
fn mempool_transactions_are_sent_to_caller(transactions in any::<Vec<VerifiedUnminedTx>>(),
network in any::<Network>()) {
network in any::<Network>(),
verbose in any::<Option<bool>>()) {
let (runtime, _init_guard) = zebra_test::init_async();
let _guard = runtime.enter();
let (mut mempool, mut state, rpc, mempool_tx_queue) = mock_services(network, NoChainTip);
Expand All @@ -254,7 +262,7 @@ proptest! {
.expect_request(mempool::Request::TransactionIds)
.map_ok(|r|r.respond(mempool::Response::TransactionIds(transaction_ids)));

(expected_response, mempool_query)
(GetRawMempool::TxIds(expected_response), mempool_query)
};

// Note: this depends on `SHOULD_USE_ZCASHD_ORDER` being true.
Expand All @@ -278,18 +286,38 @@ proptest! {
.map(|tx| tx.transaction.id.mined_id().encode_hex::<String>())
.collect::<Vec<_>>();

let transaction_dependencies = Default::default();
let expected_response = if verbose.unwrap_or(false) {
let map = transactions
.iter()
.map(|unmined_tx| {
(
unmined_tx.transaction.id.mined_id().encode_hex(),
MempoolObject::from_verified_unmined_tx(
unmined_tx,
&transactions,
&transaction_dependencies,
),
)
})
.collect::<HashMap<_, _>>();
GetRawMempool::Verbose(map)
} else {
GetRawMempool::TxIds(expected_response)
};

let mempool_query = mempool
.expect_request(mempool::Request::FullTransactions)
.map_ok(|r| r.respond(mempool::Response::FullTransactions {
transactions,
transaction_dependencies: Default::default(),
transaction_dependencies,
last_seen_tip_hash: [0; 32].into(),
}));

(expected_response, mempool_query)
};

let (rpc_rsp, _) = tokio::join!(rpc.get_raw_mempool(), mempool_query);
let (rpc_rsp, _) = tokio::join!(rpc.get_raw_mempool(verbose), mempool_query);

prop_assert_eq!(rpc_rsp?, expected_response);

Expand Down
8 changes: 6 additions & 2 deletions zebra-rpc/src/methods/tests/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,13 @@ async fn test_rpc_response_data_for_network(network: &Network) {
});

// make the api call
let get_raw_mempool = rpc.get_raw_mempool();
let get_raw_mempool = rpc.get_raw_mempool(None);
let (response, _) = futures::join!(get_raw_mempool, mempool_req);
let get_raw_mempool = response.expect("We should have a GetRawTransaction struct");
let GetRawMempool::TxIds(get_raw_mempool) =
response.expect("We should have a GetRawTransaction struct")
else {
panic!("should return TxIds for non verbose");
};

snapshot_rpc_getrawmempool(get_raw_mempool, &settings);

Expand Down
2 changes: 2 additions & 0 deletions zebra-rpc/src/methods/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,8 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
conventional_actions,
unpaid_actions: 0,
fee_weight_ratio: 1.0,
time: None,
height: None,
};

let next_fake_tip_hash =
Expand Down
2 changes: 2 additions & 0 deletions zebra-rpc/src/methods/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Types used in RPC methods.

mod get_blockchain_info;
mod get_raw_mempool;
mod zec;

pub use get_blockchain_info::Balance;
pub use get_raw_mempool::{GetRawMempool, MempoolObject};
pub use zec::Zec;
Loading
Loading