Skip to content

Commit

Permalink
Merge pull request #81 from mempool/mononaut/test-mempool-accept
Browse files Browse the repository at this point in the history
Add testmempoolaccept endpoint
  • Loading branch information
softsimon authored Apr 15, 2024
2 parents d4f788f + ac32e4b commit eea193a
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 1 deletion.
34 changes: 34 additions & 0 deletions src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,26 @@ struct NetworkInfo {
relayfee: f64, // in BTC/kB
}

#[derive(Serialize, Deserialize, Debug)]
struct MempoolFees {
base: f64,
#[serde(rename = "effective-feerate")]
effective_feerate: f64,
#[serde(rename = "effective-includes")]
effective_includes: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct MempoolAcceptResult {
txid: String,
wtxid: String,
allowed: Option<bool>,
vsize: Option<u32>,
fees: Option<MempoolFees>,
#[serde(rename = "reject-reason")]
reject_reason: Option<String>,
}

pub trait CookieGetter: Send + Sync {
fn get(&self) -> Result<Vec<u8>>;
}
Expand Down Expand Up @@ -582,6 +602,20 @@ impl Daemon {
.chain_err(|| "failed to parse txid")
}

pub fn test_mempool_accept(
&self,
txhex: Vec<String>,
maxfeerate: Option<f64>,
) -> Result<Vec<MempoolAcceptResult>> {
let params = match maxfeerate {
Some(rate) => json!([txhex, format!("{:.8}", rate)]),
None => json!([txhex]),
};
let result = self.request("testmempoolaccept", params)?;
serde_json::from_value::<Vec<MempoolAcceptResult>>(result)
.chain_err(|| "invalid testmempoolaccept reply")
}

// Get estimated feerates for the provided confirmation targets using a batch RPC request
// Missing estimates are logged but do not cause a failure, whatever is available is returned
#[allow(clippy::float_cmp)]
Expand Down
10 changes: 9 additions & 1 deletion src/new_index/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::time::{Duration, Instant};

use crate::chain::{Network, OutPoint, Transaction, TxOut, Txid};
use crate::config::Config;
use crate::daemon::Daemon;
use crate::daemon::{Daemon, MempoolAcceptResult};
use crate::errors::*;
use crate::new_index::{ChainQuery, Mempool, ScriptStats, SpendingInput, Utxo};
use crate::util::{is_spendable, BlockId, Bytes, TransactionStatus};
Expand Down Expand Up @@ -87,6 +87,14 @@ impl Query {
Ok(txid)
}

pub fn test_mempool_accept(
&self,
txhex: Vec<String>,
maxfeerate: Option<f64>,
) -> Result<Vec<MempoolAcceptResult>> {
self.daemon.test_mempool_accept(txhex, maxfeerate)
}

pub fn utxo(&self, scripthash: &[u8]) -> Result<Vec<Utxo>> {
let mut utxos = self.chain.utxo(
scripthash,
Expand Down
42 changes: 42 additions & 0 deletions src/rest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,48 @@ fn handle_request(
.map_err(|err| HttpError::from(err.description().to_string()))?;
http_message(StatusCode::OK, txid.to_hex(), 0)
}
(&Method::POST, Some(&"txs"), Some(&"test"), None, None, None) => {
let txhexes: Vec<String> =
serde_json::from_str(String::from_utf8(body.to_vec())?.as_str())?;

if txhexes.len() > 25 {
Result::Err(HttpError::from(
"Exceeded maximum of 25 transactions".to_string(),
))?
}

let maxfeerate = query_params
.get("maxfeerate")
.map(|s| {
s.parse::<f64>()
.map_err(|_| HttpError::from("Invalid maxfeerate".to_string()))
})
.transpose()?;

// pre-checks
txhexes.iter().enumerate().try_for_each(|(index, txhex)| {
// each transaction must be of reasonable size (more than 60 bytes, within 400kWU standardness limit)
if !(120..800_000).contains(&txhex.len()) {
Result::Err(HttpError::from(format!(
"Invalid transaction size for item {}",
index
)))
} else {
// must be a valid hex string
Vec::<u8>::from_hex(txhex)
.map_err(|_| {
HttpError::from(format!("Invalid transaction hex for item {}", index))
})
.map(|_| ())
}
})?;

let result = query
.test_mempool_accept(txhexes, maxfeerate)
.map_err(|err| HttpError::from(err.description().to_string()))?;

json_response(result, TTL_SHORT)
}
(&Method::GET, Some(&"txs"), Some(&"outspends"), None, None, None) => {
let txid_strings: Vec<&str> = query_params
.get("txids")
Expand Down

0 comments on commit eea193a

Please sign in to comment.