Skip to content
Open
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
22 changes: 22 additions & 0 deletions src/electrum/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,27 @@ impl Connection {
.collect::<Vec<_>>()))
}

fn blockchain_scripthash_get_mempool(&self, params: &[Value]) -> Result<Value> {
let script_hash = hash_from_value(params.get(0))?;
// ask for one extra more than the limit and fail if it exists, to avoid silently truncating
let mempool_txids = self
.query
.mempool()
.history_txids(&script_hash[..], self.txs_limit + 1);
ensure!(mempool_txids.len() <= self.txs_limit, ErrorKind::TooPopular);

Ok(json!(mempool_txids
.into_iter()
.map(|txid| {
let fee = self.query.get_mempool_tx_fee(&txid);
let has_unconfirmed_parents = self.query.has_unconfirmed_parents(&txid);
// per the Electrum protocol: 0 if all inputs are confirmed, -1 otherwise
let height = if has_unconfirmed_parents { -1 } else { 0 };
GetHistoryResult { txid, height, fee }
})
.collect::<Vec<_>>()))
}

fn blockchain_scripthash_listunspent(&self, params: &[Value]) -> Result<Value> {
let script_hash = hash_from_value(params.get(0))?;
let utxos = self.query.utxo(&script_hash[..])?;
Expand Down Expand Up @@ -517,6 +538,7 @@ impl Connection {
#[cfg(not(feature = "liquid"))]
"blockchain.scripthash.get_balance" => self.blockchain_scripthash_get_balance(&params),
"blockchain.scripthash.get_history" => self.blockchain_scripthash_get_history(&params),
"blockchain.scripthash.get_mempool" => self.blockchain_scripthash_get_mempool(&params),
"blockchain.scripthash.listunspent" => self.blockchain_scripthash_listunspent(&params),
"blockchain.scripthash.subscribe" => self.blockchain_scripthash_subscribe(&params),
"blockchain.scripthash.unsubscribe" => self.blockchain_scripthash_unsubscribe(&params),
Expand Down
53 changes: 53 additions & 0 deletions tests/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,59 @@ fn test_electrum_jsonrpc_errors() {
assert_eq!(s, expected);
}

/// Test blockchain.scripthash.get_mempool returns only the unconfirmed history of a scripthash
#[cfg_attr(not(feature = "liquid"), test)]
#[cfg_attr(feature = "liquid", allow(dead_code))]
fn test_electrum_scripthash_get_mempool() -> Result<()> {
use bitcoin::hashes::{sha256, Hash};
use bitcoin::hex::DisplayHex;

let (_electrum_server, electrum_addr, mut tester) = common::init_electrum_tester()?;

let addr = tester.newaddress()?;

// Electrum protocol script hash: single SHA256 of the scriptPubKey, in reversed byte order
let mut hash = sha256::Hash::hash(addr.script_pubkey().as_bytes()).to_byte_array();
hash.reverse();
let scripthash = hash.to_lower_hex_string();

let request = |id: u32| {
format!(
"{{\"jsonrpc\": \"2.0\", \"method\": \"blockchain.scripthash.get_mempool\", \"params\": [\"{}\"], \"id\": {}}}",
scripthash, id
)
};

let mut stream = TcpStream::connect(electrum_addr).unwrap();

// no mempool history yet
let s = write_and_read(&mut stream, &request(1));
assert_eq!(s, "{\"id\":1,\"jsonrpc\":\"2.0\",\"result\":[]}");

// an unconfirmed tx funding the address shows up (tester.send syncs the mempool index)
let txid = tester.send(&addr, "0.1 BTC".parse().unwrap())?;

let s = write_and_read(&mut stream, &request(2));
let v: electrumd::jsonrpc::serde_json::Value =
electrumd::jsonrpc::serde_json::from_str(&s).unwrap();
let entries = v["result"].as_array().expect("result array");
assert_eq!(entries.len(), 1);
assert_eq!(
entries[0]["tx_hash"].as_str(),
Some(txid.to_string().as_str())
);
// height 0: all inputs confirmed (no unconfirmed parents)
assert_eq!(entries[0]["height"].as_i64(), Some(0));
assert!(entries[0]["fee"].as_u64().is_some());

// once confirmed, it no longer appears in the mempool result
tester.mine()?;
let s = write_and_read(&mut stream, &request(3));
assert_eq!(s, "{\"id\":3,\"jsonrpc\":\"2.0\",\"result\":[]}");

Ok(())
}

fn write_and_read(stream: &mut TcpStream, write: &str) -> String {
stream.write_all(write.as_bytes()).unwrap();
stream.write(b"\n").unwrap();
Expand Down
Loading