Skip to content

Commit ecccf06

Browse files
committed
fix: address platform wallet review feedback
1 parent 0d17a63 commit ecccf06

2 files changed

Lines changed: 53 additions & 43 deletions

File tree

  • packages

packages/rs-platform-wallet/src/wallet/core/broadcast.rs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -134,28 +134,18 @@ impl<B: TransactionBroadcaster + ?Sized> CoreWallet<B> {
134134
.build()
135135
.map_err(|e| PlatformWalletError::TransactionBuild(e.to_string()))?;
136136

137-
// Re-validate the selected outpoints are still spendable while
138-
// we still hold the write lock. The lock makes our build atomic
139-
// against other callers on this handle, but external mempool /
140-
// block events processed before we acquired the lock may have
141-
// invalidated UTXOs that were still in the spendable set when
142-
// `select_inputs` ran.
143-
//
144-
// We deliberately do NOT mark the inputs as spent here — that
145-
// happens after a successful broadcast (see #3466 review). A
146-
// failed broadcast must not leave UTXOs falsely marked spent.
137+
// Sanity-check that the builder only selected outpoints from
138+
// the same height-aware spendable set we handed to input
139+
// selection. We deliberately do NOT mark the inputs as spent here
140+
// — that happens after a successful broadcast (see #3466 review).
141+
// A failed broadcast must not leave UTXOs falsely marked spent.
147142
let selected: BTreeSet<OutPoint> =
148143
tx.input.iter().map(|txin| txin.previous_output).collect();
149-
let still_spendable: BTreeSet<OutPoint> = info
150-
.get_spendable_utxos()
151-
.into_iter()
152-
.map(|utxo| utxo.outpoint)
153-
.collect();
154-
if !selected.is_subset(&still_spendable) {
144+
let spendable_outpoints: BTreeSet<OutPoint> =
145+
spendable.iter().map(|utxo| utxo.outpoint).collect();
146+
if !selected.is_subset(&spendable_outpoints) {
155147
return Err(PlatformWalletError::TransactionBuild(
156-
"Selected UTXOs are no longer available (concurrent transaction). \
157-
Please retry."
158-
.to_string(),
148+
"Transaction builder selected an unavailable UTXO. Please retry.".to_string(),
159149
));
160150
}
161151

@@ -164,6 +154,11 @@ impl<B: TransactionBroadcaster + ?Sized> CoreWallet<B> {
164154

165155
// Broadcast first; if the network rejects we leave wallet state
166156
// untouched so the caller can retry without manual sync repair.
157+
// This is intentional even if the remote accepted the transaction
158+
// but the broadcast path returned an error: in that ambiguous case
159+
// later attempts may reuse the same inputs locally, but the network
160+
// rejects the duplicate spend instead of us marking UTXOs spent for
161+
// a transaction that might not have propagated.
167162
self.broadcast_transaction(&tx).await?;
168163

169164
// Now that the tx is in flight, register it as a mempool transaction
@@ -175,15 +170,23 @@ impl<B: TransactionBroadcaster + ?Sized> CoreWallet<B> {
175170
// broadcast failure.
176171
{
177172
let mut wm = self.wallet_manager.write().await;
178-
let (wallet, info) =
179-
wm.get_wallet_mut_and_info_mut(&self.wallet_id)
180-
.ok_or_else(|| {
181-
crate::error::PlatformWalletError::WalletNotFound(
182-
"Wallet not found in wallet manager".to_string(),
183-
)
184-
})?;
185-
info.check_core_transaction(&tx, TransactionContext::Mempool, wallet, true, true)
186-
.await;
173+
if let Some((wallet, info)) = wm.get_wallet_mut_and_info_mut(&self.wallet_id) {
174+
let check_result = info
175+
.check_core_transaction(&tx, TransactionContext::Mempool, wallet, true, true)
176+
.await;
177+
if !check_result.is_relevant {
178+
tracing::warn!(
179+
txid = %tx.txid(),
180+
"broadcast transaction was not relevant during post-broadcast wallet registration"
181+
);
182+
}
183+
} else {
184+
tracing::warn!(
185+
wallet_id = %hex::encode(self.wallet_id),
186+
txid = %tx.txid(),
187+
"wallet missing during post-broadcast transaction registration"
188+
);
189+
}
187190
}
188191

189192
Ok(tx)

packages/rs-sdk/src/platform/dpns_usernames/mod.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ pub fn convert_to_homograph_safe_chars(input: &str) -> String {
3535
.collect()
3636
}
3737

38+
fn extract_dpns_label(name: &str) -> &str {
39+
if let Some(dot_pos) = name.rfind('.') {
40+
let (label_part, suffix) = name.split_at(dot_pos);
41+
if suffix.eq_ignore_ascii_case(".dash") {
42+
return label_part;
43+
}
44+
}
45+
name
46+
}
47+
3848
/// Check if a username is valid according to DPNS rules
3949
///
4050
/// A username is valid if:
@@ -422,21 +432,7 @@ impl Sdk {
422432

423433
let dpns_contract = self.fetch_dpns_contract().await?;
424434

425-
// Extract label from full name if needed
426-
// Handle both "alice" and "alice.dash" formats
427-
let label = if let Some(dot_pos) = name.rfind('.') {
428-
let (label_part, suffix) = name.split_at(dot_pos);
429-
// Strip ".dash" / ".DASH" / mixed case — DPNS itself is case-insensitive.
430-
if suffix.eq_ignore_ascii_case(".dash") {
431-
label_part
432-
} else {
433-
// If it's not ".dash", treat the whole thing as the label
434-
name
435-
}
436-
} else {
437-
// No dot found, use the whole name as the label
438-
name
439-
};
435+
let label = extract_dpns_label(name);
440436

441437
// Validate the label before proceeding
442438
if label.is_empty() {
@@ -499,6 +495,17 @@ mod tests {
499495
assert_eq!(convert_to_homograph_safe_chars("test123"), "test123");
500496
}
501497

498+
#[test]
499+
fn test_extract_dpns_label() {
500+
assert_eq!(extract_dpns_label("alice.dash"), "alice");
501+
assert_eq!(extract_dpns_label("alice.DASH"), "alice");
502+
assert_eq!(extract_dpns_label("alice.DaSh"), "alice");
503+
assert_eq!(extract_dpns_label("Alice.DASH"), "Alice");
504+
assert_eq!(extract_dpns_label("alice"), "alice");
505+
assert_eq!(extract_dpns_label("alice.eth"), "alice.eth");
506+
assert_eq!(extract_dpns_label(".dash"), "");
507+
}
508+
502509
#[test]
503510
fn test_is_valid_username() {
504511
// Valid usernames

0 commit comments

Comments
 (0)