feat(wallet): Add new address methods

Introduce a new API for getting addresses from the Wallet that
reflects a similiar interface as the underlying indexer
`KeychainTxOutIndex` in preparation for removing `AddressIndex` enum.

Before this change, the only way to get an address was via the methods
`try_get{_internal}_address` which required a `&mut` reference to the
wallet, matching on the desired AddressIndex variant. This is too
restrictive since for example peeking or listing unused addresses
shouldn't change the state of the wallet. Hence we provide separate
methods for each use case which makes for a more efficient API.
This commit is contained in:
valued mammal 2024-04-10 15:16:40 -04:00 committed by 志宇
parent f00de9e0c1
commit d3763e5e37
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8

View File

@ -768,6 +768,161 @@ impl Wallet {
})
}
/// Peek an address of the given `keychain` at `index` without revealing it.
///
/// For non-wildcard descriptors this returns the same address at every provided index.
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index.
pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo {
let keychain = self.map_keychain(keychain);
let mut spk_iter = self.indexed_graph.index.unbounded_spk_iter(&keychain);
if !spk_iter.descriptor().has_wildcard() {
index = 0;
}
let (index, spk) = spk_iter
.nth(index as usize)
.expect("derivation index is out of bounds");
AddressInfo {
index,
address: Address::from_script(&spk, self.network).expect("must have address form"),
keychain,
}
}
/// Attempt to reveal the next address of the given `keychain`.
///
/// This will increment the internal derivation index. If the keychain's descriptor doesn't
/// contain a wildcard or every address is already revealed up to the maximum derivation
/// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki),
/// then returns the last revealed address.
///
/// # Errors
///
/// If writing to persistent storage fails.
pub fn reveal_next_address(
&mut self,
keychain: KeychainKind,
) -> Result<AddressInfo, D::WriteError>
where
D: PersistBackend<ChangeSet>,
{
let keychain = self.map_keychain(keychain);
let ((index, spk), index_changeset) = self.indexed_graph.index.reveal_next_spk(&keychain);
self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
Ok(AddressInfo {
index,
address: Address::from_script(spk, self.network).expect("must have address form"),
keychain,
})
}
/// Reveal addresses up to and including the target `index` and return an iterator
/// of newly revealed addresses.
///
/// If the target `index` is unreachable, we make a best effort to reveal up to the last
/// possible index. If all addresses up to the given `index` are already revealed, then
/// no new addresses are returned.
///
/// # Errors
///
/// If writing to persistent storage fails.
pub fn reveal_addresses_to(
&mut self,
keychain: KeychainKind,
index: u32,
) -> Result<impl Iterator<Item = AddressInfo> + '_, D::WriteError>
where
D: PersistBackend<ChangeSet>,
{
let keychain = self.map_keychain(keychain);
let (spk_iter, index_changeset) =
self.indexed_graph.index.reveal_to_target(&keychain, index);
self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
Ok(spk_iter.map(move |(index, spk)| AddressInfo {
index,
address: Address::from_script(&spk, self.network).expect("must have address form"),
keychain,
}))
}
/// Get the next unused address for the given `keychain`, i.e. the address with the lowest
/// derivation index that hasn't been used.
///
/// This will attempt to derive and reveal a new address if no newly revealed addresses
/// are available. See also [`reveal_next_address`](Self::reveal_next_address).
///
/// # Errors
///
/// If writing to persistent storage fails.
pub fn next_unused_address(
&mut self,
keychain: KeychainKind,
) -> Result<AddressInfo, D::WriteError>
where
D: PersistBackend<ChangeSet>,
{
let keychain = self.map_keychain(keychain);
let ((index, spk), index_changeset) = self.indexed_graph.index.next_unused_spk(&keychain);
self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
Ok(AddressInfo {
index,
address: Address::from_script(spk, self.network).expect("must have address form"),
keychain,
})
}
/// Marks an address used of the given `keychain` at `index`.
///
/// Returns whether the given index was present and then removed from the unused set.
pub fn mark_used(&mut self, keychain: KeychainKind, index: u32) -> bool {
self.indexed_graph.index.mark_used(keychain, index)
}
/// Undoes the effect of [`mark_used`] and returns whether the `index` was inserted
/// back into the unused set.
///
/// Since this is only a superficial marker, it will have no effect if the address at the given
/// `index` was actually used, i.e. the wallet has previously indexed a tx output for the
/// derived spk.
///
/// [`mark_used`]: Self::mark_used
pub fn unmark_used(&mut self, keychain: KeychainKind, index: u32) -> bool {
self.indexed_graph.index.unmark_used(keychain, index)
}
/// List addresses that are revealed but unused.
///
/// Note if the returned iterator is empty you can reveal more addresses
/// by using [`reveal_next_address`](Self::reveal_next_address) or
/// [`reveal_addresses_to`](Self::reveal_addresses_to).
pub fn list_unused_addresses(
&self,
keychain: KeychainKind,
) -> impl DoubleEndedIterator<Item = AddressInfo> + '_ {
let keychain = self.map_keychain(keychain);
self.indexed_graph
.index
.unused_keychain_spks(&keychain)
.map(move |(index, spk)| AddressInfo {
index,
address: Address::from_script(spk, self.network).expect("must have address form"),
keychain,
})
}
/// Return whether or not a `script` is part of this wallet (either internal or external)
pub fn is_mine(&self, script: &Script) -> bool {
self.indexed_graph.index.index_of_spk(script).is_some()