Skip to content

[RPCSAGA] New internal descriptor module.#675

Open
jaoleal wants to merge 1 commit intogetfloresta:masterfrom
jaoleal:rpcsaga_newdescriptormodule
Open

[RPCSAGA] New internal descriptor module.#675
jaoleal wants to merge 1 commit intogetfloresta:masterfrom
jaoleal:rpcsaga_newdescriptormodule

Conversation

@jaoleal
Copy link
Collaborator

@jaoleal jaoleal commented Oct 15, 2025

What is the purpose of this pull request?

  • Bug fix
  • Documentation update
  • New feature
  • Test
  • Other:

Which crates are being modified?

  • floresta-chain
  • floresta-common
  • floresta-compact-filters
  • floresta-electrum
  • floresta-node
  • floresta-rpc
  • floresta-watch-only
  • floresta-wire
  • bin/florestad
  • bin/floresta-cli
  • Other:

Description and Notes

Part of #670

The star of the show guys, this is the main descriptor module that should satisfy all the next steps to #670 as well everything related to descriptors in floresta.

The point of this PR.

Solely introducing this new descriptor module, as well
defining a strong API and extensive test module.

Im afraid these tests arent that great so ill still work a little bit more on them.

I worked a lot on the docs so it should be easy to understand the whys and the objective of each piece.

How to verify the changes you have done?

Read the module docs

Read and execute the tests

Share your thougts about the API and if you found a test case that should be covered.

@jaoleal jaoleal force-pushed the rpcsaga_newdescriptormodule branch from bfddc52 to 0362b82 Compare October 15, 2025 02:16
@jaoleal
Copy link
Collaborator Author

jaoleal commented Oct 15, 2025

Dropped #666

@jaoleal jaoleal force-pushed the rpcsaga_newdescriptormodule branch 2 times, most recently from 50eb66d to fcbd1b6 Compare October 15, 2025 02:33
@jaoleal
Copy link
Collaborator Author

jaoleal commented Oct 15, 2025

Sorry, i messed with cargo lock

@Davidson-Souza Davidson-Souza added enhancement New feature or request code quality Generally improves code readability and maintainability Wallet Changes related to the watch-only wallet labels Oct 15, 2025
Copy link
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.

Preliminary comments, haven't looked at the tests yet

/// Sums the given index to the inner range.
///
/// Same as [`DescriptorUnit::sum_index`] but for an array.
pub fn arr_sum_index(to_sum: &mut Vec<DescriptorUnit>, idx: u32) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pub fn arr_sum_index(to_sum: &mut Vec<DescriptorUnit>, idx: u32) {
pub fn batch_sum_index(to_sum: &mut [DescriptorUnit], idx: u32) {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

using batch instead of arr

ret.push(
self.descriptor
.at_derivation_index(i)
.expect("Concrete Descriptors should always be valid"),
Copy link
Member

Choose a reason for hiding this comment

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

Why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They are "validated" inside DescrpitorRequest::into_unit which should be the only way to make a DescriptorUnit.

Updating the string with new naming

@jaoleal jaoleal self-assigned this Oct 20, 2025
@jaoleal jaoleal force-pushed the rpcsaga_newdescriptormodule branch from fcbd1b6 to f84c678 Compare November 10, 2025 20:39
@jaoleal jaoleal force-pushed the rpcsaga_newdescriptormodule branch 2 times, most recently from 1558172 to 4419780 Compare November 17, 2025 18:10
@jaoleal jaoleal force-pushed the rpcsaga_newdescriptormodule branch from 4419780 to ecd83af Compare November 17, 2025 19:13
@jaoleal
Copy link
Collaborator Author

jaoleal commented Nov 17, 2025

ecd83af updated with lattest suggestions and new tests, pcc looks fine

Copy link
Collaborator

@moisesPompilio moisesPompilio left a comment

Choose a reason for hiding this comment

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

In the DescriptorRequest struct, optional fields use skip_serializing_if so that None values are omitted from the JSON output. These fields do not make sense when empty, and Bitcoin Core follows the same behavior.

Comment on lines +80 to +81
#[serde(default)]
pub range: DerivationRange,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
#[serde(default)]
pub range: DerivationRange,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range: Optional<DerivationRange>,

The ranger must be optional because not every descriptor supports it. During descriptor validation we need to know whether a ranger is required. For example, pkh([d6043800/0'/0'/18']03efdee34c0009fd175f3b20b5e5a5517fd5d16746f2e635b44617adafeaebc388)#4ahsl9pk does not support a ranger since it represents a single address. But tr(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/86h/0h/0h/0/*) does support a ranger because the * indicates derivation.

//! matches the [`DescriptorId::Label`].
//!
//! [`RescanRequest`] - Tells from where the user needs for floresta to rescan, it counts with [`RescanRequest::check_override`] thats used to return the most abrangent [`RescanRequest`]
//! to satisfy the user [`DescriptorRequest`].
Copy link
Collaborator

Choose a reason for hiding this comment

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

The texts ended up very long. Adding some line breaks would improve readability

Comment on lines +102 to +121
impl DescriptorRequest {
/// Interpret [`DescriptorRequest`]s returning the extracted and already derived
/// descriptors.
///
/// The returned [`RescanRequest`] is the more embracing in order to satisfy the users request.
/// You can read further on [`RescanRequest::check_override`].
pub fn handle_requests(
requests: Vec<DescriptorRequest>,
) -> Result<(Vec<DescriptorUnit>, RescanRequest), DescriptorError> {
let mut rescan_request: RescanRequest = RescanRequest::SpecifiedTime(u32::MAX);
let mut descriptors: Vec<DescriptorUnit> = Vec::new();

for request in requests {
rescan_request = rescan_request.check_override(&request.timestamp);

descriptors.append(&mut request.into_unit()?);
}

Ok((descriptors, rescan_request))
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
impl DescriptorRequest {
/// Interpret [`DescriptorRequest`]s returning the extracted and already derived
/// descriptors.
///
/// The returned [`RescanRequest`] is the more embracing in order to satisfy the users request.
/// You can read further on [`RescanRequest::check_override`].
pub fn handle_requests(
requests: Vec<DescriptorRequest>,
) -> Result<(Vec<DescriptorUnit>, RescanRequest), DescriptorError> {
let mut rescan_request: RescanRequest = RescanRequest::SpecifiedTime(u32::MAX);
let mut descriptors: Vec<DescriptorUnit> = Vec::new();
for request in requests {
rescan_request = rescan_request.check_override(&request.timestamp);
descriptors.append(&mut request.into_unit()?);
}
Ok((descriptors, rescan_request))
}
pub struct ImportDescriptorsRequest (pub Vec<DescriptorRequest>);
impl ImportDescriptorsRequest {
pub fn process(
self,
) -> Vec<DescriptorResponse> {
for request in self.0 {
match request.handle_requests() {
Ok((descriptors, rescan)) => {
DescriptorResponse::Success(DescriptorResult { descriptors, rescan })
}
Err(e) => {
DescriptorResponse::Failure(e)
}
}
}
}
}
pub enum DescriptorResponse{
Success(DescriptorResult),
Failure(DescriptorError),
}
impl DescriptorRequest {
/// Interpret [`DescriptorRequest`]s returning the extracted and already derived
/// descriptors.
///
/// The returned [`RescanRequest`] is the more embracing in order to satisfy the users request.
/// You can read further on [`RescanRequest::check_override`].
pub fn handle_requests(
Self
) -> Result<(DescriptorUnit, RescanRequest), DescriptorError> {
let mut rescan_request: RescanRequest = RescanRequest::SpecifiedTime(u32::MAX);
let mut descriptors: Vec<DescriptorUnit> = Vec::new();
rescan_request = rescan_request.check_override(&Self.timestamp);
descriptors.append(&mut Self.into_unit()?);
Ok((descriptors, rescan_request))
}

I think this function is not ideal because processing all descriptors at once means a single error stops the whole import and only returns that error. This is not compliant with Bitcoin Core and makes it hard to know which descriptor failed.

It would be better to have a struct like pub struct ImportDescriptorsRequest(pub Vec<DescriptorRequest>); with a process method that handles each descriptor individually. This way, one failing descriptor doesn’t affect the others, and it’s easy to identify which failed by its position in the input vector. The method could return a Vec of enums indicating success or failure, matching the input vector’s order. Then handle_request of DescriptorRequest would only deal with a single descriptor at a time.

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

Labels

code quality Generally improves code readability and maintainability enhancement New feature or request Wallet Changes related to the watch-only wallet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants