Skip to content

Refactor(electrum): Remove unwraps and expects#956

Open
vatsalkeshav wants to merge 5 commits intogetfloresta:masterfrom
vatsalkeshav:463-electrum-only
Open

Refactor(electrum): Remove unwraps and expects#956
vatsalkeshav wants to merge 5 commits intogetfloresta:masterfrom
vatsalkeshav:463-electrum-only

Conversation

@vatsalkeshav
Copy link
Copy Markdown

Description and Notes

This pr removes all .unwrap() and non-infallible .expect() calls in non-test code of floresta-electrum

contributes to issue #463

note to reviewers : added self.addresses_to_scan.extend(addresses) to avoid silent address drop in at get_block_height failure in rescan_with_block_filters fn

Contributor Checklist

  • I've followed the contribution guidelines
  • I've verified one of the following:
    • Ran just pcc (recommended but slower)
    • Ran just lint-features '-- -D warnings' && cargo test --release
    • Confirmed CI passed on my fork
  • I've linked any related issue(s) in the sections above

Finally, you are encouraged to sign all your commits (it proves authorship and guards against tampering—see How (and why) to sign Git commits and GitHub's guide to signing commits).

remove all .unwrap() and non-infallible .expect() calls in non-test code of /floresta-electrum/s/electrum_protocol.rs

contribute to issue getfloresta#463
@jaoleal jaoleal self-requested a review April 14, 2026 12:10
@jaoleal
Copy link
Copy Markdown
Member

jaoleal commented Apr 14, 2026

Did you tested this ? I dont think that the approach of just logging the error is efficient but i may be missing something about the electrum protocol...

Ill take a read about it and after that ill do a review...

Also, another strange thing are the unwrap_or calls, this default behavior is not desired, we need to know when this call fails.

Copy link
Copy Markdown
Member

@Davidson-Souza Davidson-Souza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass through the code


let position = self.address_cache.get_position(&prevout.txid).unwrap();
for (utxo, prevout) in utxos.into_iter() {
let height = self.address_cache.get_height(&prevout.txid).unwrap_or(0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting a None means we don't have it cached, so we should send and empty reply

Copy link
Copy Markdown
Author

@vatsalkeshav vatsalkeshav Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.
I initially left unwrap_or(0) as electrum treats 0 as sentinel for unconfirmed tx - sorry for that

.chain
.get_block_hash(0)
.expect("Genesis block should be present");
.expect("genesis block is always in the chain store");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.expect("genesis block is always in the chain store");
.expect("Genesis block is always in the chain store");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +547 to +553
let unconfirmed = match self.address_cache.find_unconfirmed() {
Ok(txs) => txs,
Err(e) => {
error!("Could not fetch unconfirmed transactions for rebroadcast: {e}");
return;
}
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be propagated back. Our convension is to not handle errors in utility functions like this

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

let Ok(blocks) =
cfilters.match_any(_addresses, start_height, stop_height, self.chain.clone())
else {
info!("Could not match block filters");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are errors, not info

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 722 to 735
match self.chain.get_height() {
Ok(chain_height) => {
if chain_height == height {
for client in &mut self.clients.values() {
let res = client.write(
serde_json::to_string(&result)
.expect("serde_json::Value is always serializable")
.as_bytes(),
);
if res.is_err() {
info!("Could not write to client {client:?}");
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please reduce nesting. I think you can use combinators here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +765 to +769
client.write(
serde_json::to_string(&res)
.expect("serde_json::Value is always serializable")
.as_bytes(),
)?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
client.write(
serde_json::to_string(&res)
.expect("serde_json::Value is always serializable")
.as_bytes(),
)?;
let res = serde_json::to_string(&res)
.expect("serde_json::Value is always serializable");
client.write(
res.as_bytes(),
)?;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@Davidson-Souza Davidson-Souza added the reliability Related to runtime reliability, stability and production readiness label Apr 14, 2026
@github-project-automation github-project-automation bot moved this to Backlog in Floresta Apr 14, 2026
@Davidson-Souza Davidson-Souza moved this from Backlog to Needs review in Floresta Apr 14, 2026
@Davidson-Souza
Copy link
Copy Markdown
Member

Is this enough to forbid clippy::unwrap_used in floresta-electrum?

@vatsalkeshav
Copy link
Copy Markdown
Author

vatsalkeshav commented Apr 15, 2026

Is this enough to forbid clippy::unwrap_used in floresta-electrum?

yes it is

Thanks for the review!

@vatsalkeshav
Copy link
Copy Markdown
Author

Did you tested this ? I dont think that the approach of just logging the error is efficient but i may be missing something about the electrum protocol...

Ill take a read about it and after that ill do a review...

Also, another strange thing are the unwrap_or calls, this default behavior is not desired, we need to know when this call fails.

fixed unwrap_or calls after review

error!("main loop receiver dropped: {e:?}");
break;
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer the a if let Err(e) = self.message_transmitter.send(Message::Message((self.client_id, line))) instead of a whole match.

Theres an example on line 80 in this same file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +105 to +108
match self.message_transmitter.send(Message::Disconnect(self.client_id)) {
Ok(_) => {}
Err(e) => error!("main loop receiver dropped: {e:?}"),
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +113 to +116
match self.message_transmitter.send(Message::Disconnect(self.client_id)) {
Ok(_) => {}
Err(e) => error!("main loop receiver dropped: {e:?}"),
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Err(super::error::Error::InvalidParams)
let Some(proof) = self.address_cache.get_merkle_proof(&tx_id) else {
return Err(super::error::Error::InvalidParams);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return Err(super::error::Error::InvalidParams);
return Err(Error::InvalidParams);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +503 to +505
let Some(height) = self.address_cache.get_height(&tx_id) else {
return Err(super::error::Error::InvalidParams);
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let Some(height) = self.address_cache.get_height(&tx_id) else {
return Err(super::error::Error::InvalidParams);
};
let Some(height) = self.address_cache.get_height(&tx_id) else {
return Err(Error::InvalidParams);
};

let unconfirmed = self
.address_cache
.find_unconfirmed()
.map_err(|e| super::error::Error::WatchOnly(Box::new(e)))?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.map_err(|e| super::error::Error::WatchOnly(Box::new(e)))?;
.map_err(|e| Error::WatchOnly(Box::new(e)))?;


pub async fn rebroadcast_mempool_transactions(&self) {
let unconfirmed = self.address_cache.find_unconfirmed().unwrap();
pub async fn rebroadcast_mempool_transactions(&self) -> Result<(), super::error::Error> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub async fn rebroadcast_mempool_transactions(&self) -> Result<(), super::error::Error> {
pub async fn rebroadcast_mempool_transactions(&self) -> Result<(), Error> {

let Ok(blocks) =
cfilters.match_any(_addresses, start_height, stop_height, self.chain.clone())
else {
error!("Could not match block filters");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
error!("Could not match block filters");
error!("Could not find matching block filters");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +880 to +890
match message_transmitter
.send(Message::NewClient((client.client_id, client)))
.expect("Main loop is broken");
id_count += 1;
{
Ok(_) => {
id_count += 1;
}
Err(e) => {
error!("main loop receiver dropped: {e:?}");
break;
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +898 to +906
match message_transmitter.send(Message::NewClient((client.client_id, client))) {
Ok(_) => {
id_count += 1;
}
Err(e) => {
error!("main loop receiver dropped: {e:?}");
break;
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

… instead of pattern matching, shorter erro path
Copy link
Copy Markdown
Member

@jaoleal jaoleal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading a little about the Electrum protocol, it appears to leave to the server implementation to decide the error handling, similar to how the jsonrpc server should handle errors, following the spec of course.

Maybe we should follow another implementation as a standard ? Should we define our own standards for that as were doing on #831 ?

cc @Davidson-Souza @csgui

Vec::from_hex(&tx).map_err(|_| super::error::Error::InvalidParams)?;
let tx: Transaction =
deserialize(&hex).map_err(|_| super::error::Error::InvalidParams)?;
let hex: Vec<_> = Vec::from_hex(&tx).map_err(|_| Error::InvalidParams)?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let hex: Vec<_> = Vec::from_hex(&tx).map_err(|_| Error::InvalidParams)?;
let hex = Vec::from_hex(&tx).map_err(|_| Error::InvalidParams)?;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +346 to +351
let Some(height) = self.address_cache.get_height(&prevout.txid) else {
return json_rpc_res!(request, []);
};
let Some(position) = self.address_cache.get_position(&prevout.txid) else {
return json_rpc_res!(request, []);
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be returning errors here too no ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isnt focused on error handling but it should replace unwraps with errors atleast right ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense
but None means not cached, so @davidsonsouza suggested sending []
I'll try forcing the error with electrum daemon to be sure though

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, if it was a recommendation from Davidson you can just forget this, thank you for telling me this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reliability Related to runtime reliability, stability and production readiness

Projects

Status: Needs review

Development

Successfully merging this pull request may close these issues.

3 participants