Skip to content

Commit 87f5aad

Browse files
agentboxagentbox
authored andcommitted
Improve RPC error messages in payjoin-cli
bitcoind-async-client discards JSON-RPC error details from HTTP 500 response bodies, returning only "Obtained failure status(500): Internal Server Error". This leaves users without actionable information when e.g. a wallet is not loaded. Add an rpc_context helper that inspects the ClientError variant and appends diagnostic hints: - Status(500, _): suggest checking wallet loading and debug.log - Connection errors: suggest checking if bitcoind is running Apply the helper to every RPC call site in wallet.rs so all bitcoind errors surface useful context. The upstream library should also be fixed to parse JSON-RPC errors from non-2xx response bodies. Closes payjoin#1258
1 parent 3647419 commit 87f5aad

File tree

1 file changed

+33
-11
lines changed

1 file changed

+33
-11
lines changed

payjoin-cli/src/app/wallet.rs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::collections::HashMap;
22
use std::sync::Arc;
33

4-
use anyhow::{anyhow, Context, Result};
4+
use anyhow::{anyhow, Result};
55
use bitcoind_async_client::corepc_types::model::ListUnspentItem;
6+
use bitcoind_async_client::error::ClientError;
67
use bitcoind_async_client::traits::{Broadcaster, Reader, Signer, Wallet};
78
use bitcoind_async_client::types::{CreateRawTransactionOutput, WalletCreateFundedPsbtOptions};
89
use bitcoind_async_client::{Auth, Client as AsyncBitcoinRpc};
@@ -72,13 +73,15 @@ impl BitcoindWallet {
7273
)
7374
.await
7475
})
75-
})?;
76+
})
77+
.map_err(|e| rpc_context(e, "Failed to create funded PSBT"))?;
7678

7779
let processed = tokio::task::block_in_place(|| {
7880
tokio::runtime::Handle::current().block_on(async {
7981
self.rpc.wallet_process_psbt(&result.psbt.to_string(), None, None, None).await
8082
})
81-
})?
83+
})
84+
.map_err(|e| rpc_context(e, "Failed to process PSBT"))?
8285
.psbt;
8386

8487
Ok(processed)
@@ -93,15 +96,17 @@ impl BitcoindWallet {
9396
tokio::runtime::Handle::current().block_on(async {
9497
self.rpc.wallet_process_psbt(&psbt_str, Some(true), None, None).await
9598
})
96-
})?;
99+
})
100+
.map_err(|e| rpc_context(e, "Failed to process PSBT"))?;
97101
Ok(processed.psbt)
98102
}
99103

100104
pub fn can_broadcast(&self, tx: &Transaction) -> Result<bool> {
101105
let mempool_results = tokio::task::block_in_place(|| {
102106
tokio::runtime::Handle::current()
103107
.block_on(async { self.rpc.test_mempool_accept(tx).await })
104-
})?;
108+
})
109+
.map_err(|e| rpc_context(e, "Failed to test mempool accept"))?;
105110

106111
mempool_results
107112
.results
@@ -116,7 +121,7 @@ impl BitcoindWallet {
116121
tokio::runtime::Handle::current()
117122
.block_on(async { self.rpc.send_raw_transaction(tx).await })
118123
})
119-
.context("Failed to broadcast transaction")
124+
.map_err(|e| rpc_context(e, "Failed to broadcast transaction"))
120125
}
121126

122127
/// Check if a script belongs to this wallet
@@ -126,7 +131,7 @@ impl BitcoindWallet {
126131
tokio::runtime::Handle::current()
127132
.block_on(async { self.rpc.get_address_info(&address).await })
128133
})
129-
.context("Failed to get address info")?;
134+
.map_err(|e| rpc_context(e, "Failed to get address info"))?;
130135
Ok(info.is_mine)
131136
} else {
132137
Ok(false)
@@ -150,7 +155,8 @@ impl BitcoindWallet {
150155
},
151156
}
152157
})
153-
})?;
158+
})
159+
.map_err(|e| rpc_context(e, "Failed to get transaction"))?;
154160
Ok(raw_tx)
155161
}
156162

@@ -159,7 +165,7 @@ impl BitcoindWallet {
159165
let addr = tokio::task::block_in_place(|| {
160166
tokio::runtime::Handle::current().block_on(async { self.rpc.get_new_address().await })
161167
})
162-
.context("Failed to get new address")?;
168+
.map_err(|e| rpc_context(e, "Failed to get new address"))?;
163169
Ok(addr)
164170
}
165171

@@ -169,7 +175,7 @@ impl BitcoindWallet {
169175
tokio::runtime::Handle::current()
170176
.block_on(async { self.rpc.list_unspent(None, None, None, None, None).await })
171177
})
172-
.context("Failed to list unspent")?;
178+
.map_err(|e| rpc_context(e, "Failed to list unspent"))?;
173179
Ok(unspent.0.into_iter().map(input_pair_from_corepc).collect())
174180
}
175181

@@ -184,10 +190,26 @@ impl BitcoindWallet {
184190
tokio::task::block_in_place(|| {
185191
tokio::runtime::Handle::current().block_on(async { self.rpc.network().await })
186192
})
187-
.map_err(|_| anyhow!("Failed to get blockchain info"))
193+
.map_err(|e| rpc_context(e, "Failed to get blockchain info"))
188194
}
189195
}
190196

197+
/// Wrap an RPC client error with a user-friendly context message.
198+
///
199+
/// `bitcoind-async-client` discards JSON-RPC error details from HTTP 500
200+
/// response bodies (see issue #1258), so we add hints about common causes.
201+
fn rpc_context(err: ClientError, operation: &str) -> anyhow::Error {
202+
let hint = match &err {
203+
ClientError::Status(500, _) =>
204+
". bitcoind returned HTTP 500; check that the wallet is \
205+
loaded and bitcoind is fully synced (see debug.log for \
206+
the full RPC error)",
207+
ClientError::Connection(_) => ". Is bitcoind running?",
208+
_ => "",
209+
};
210+
anyhow::Error::new(err).context(format!("{operation}{hint}"))
211+
}
212+
191213
pub fn input_pair_from_corepc(utxo: ListUnspentItem) -> InputPair {
192214
let psbtin = Input {
193215
// NOTE: non_witness_utxo is not necessary because bitcoin-cli always supplies

0 commit comments

Comments
 (0)