use std::io::Cursor;
use bsv::primitives::public_key::PublicKey;
use bsv::transaction::transaction::Transaction;
use bsv::wallet::cached_key_deriver::CachedKeyDeriver;
use crate::error::{WalletError, WalletResult};
use crate::services::traits::WalletServices;
use crate::signer::complete_signed::complete_signed_transaction;
use crate::signer::types::{PendingSignAction, SignerSignActionResult, ValidSignActionArgs};
use crate::storage::action_types::StorageProcessActionArgs;
use crate::storage::manager::WalletStorageManager;
use crate::wallet::types::AuthId;
pub async fn signer_sign_action(
storage: &WalletStorageManager,
services: &(dyn WalletServices + Send + Sync),
key_deriver: &CachedKeyDeriver,
identity_pub_key: &PublicKey,
auth: &str,
args: &ValidSignActionArgs,
pending: &PendingSignAction,
) -> WalletResult<SignerSignActionResult> {
let mut cursor = Cursor::new(&pending.tx);
let mut tx = Transaction::from_binary(&mut cursor)
.map_err(|e| WalletError::Internal(format!("Failed to reconstruct unsigned tx: {}", e)))?;
let is_no_send = args.is_no_send.unwrap_or(pending.args.is_no_send);
let is_delayed = args.is_delayed.unwrap_or(pending.args.is_delayed);
let is_send_with = args.is_send_with.unwrap_or(pending.args.is_send_with);
let signed_tx_bytes = complete_signed_transaction(
&mut tx,
&pending.pdi,
&args.spends,
key_deriver,
identity_pub_key,
)?;
let txid = tx
.id()
.map_err(|e| WalletError::Internal(format!("Failed to compute txid: {}", e)))?;
let beef = super::create_action::build_beef(&tx, &pending.dcr.input_beef)?;
crate::signer::verify_unlock_scripts::verify_unlock_scripts(&txid, &beef)?;
let beef_bytes = super::create_action::serialize_beef_atomic(&beef, &txid)?;
let process_args = StorageProcessActionArgs {
is_new_tx: args.is_new_tx,
is_send_with,
is_no_send,
is_delayed,
reference: Some(pending.reference.clone()),
txid: Some(txid.clone()),
raw_tx: Some(signed_tx_bytes),
send_with: if is_send_with {
pending.args.options.send_with.clone()
} else {
vec![]
},
};
let auth_id = AuthId {
identity_key: auth.to_string(),
user_id: None,
is_active: None,
};
let process_result = storage.process_action(&auth_id, &process_args).await?;
if !is_no_send && !is_delayed {
let post_results = services
.post_beef(&beef_bytes, std::slice::from_ref(&txid))
.await;
let outcome = crate::signer::broadcast_outcome::classify_broadcast_results(&post_results);
match &outcome {
crate::signer::broadcast_outcome::BroadcastOutcome::Success
| crate::signer::broadcast_outcome::BroadcastOutcome::OrphanMempool { .. } => {
let _ = crate::signer::broadcast_outcome::apply_success_or_orphan_outcome(
storage, &txid, &outcome,
)
.await;
}
crate::signer::broadcast_outcome::BroadcastOutcome::DoubleSpend { .. }
| crate::signer::broadcast_outcome::BroadcastOutcome::InvalidTx { .. } => {
if let Err(e) =
crate::signer::broadcast_outcome::handle_permanent_broadcast_failure(
storage, services, &txid, &outcome,
)
.await
{
tracing::error!(
error = %e,
txid = %txid,
"signAction: permanent failure handler errored"
);
}
}
crate::signer::broadcast_outcome::BroadcastOutcome::ServiceError { details } => {
if let Err(e) = crate::signer::broadcast_outcome::apply_service_error_outcome(
storage,
&txid,
details.clone(),
)
.await
{
tracing::error!(
error = %e,
txid = %txid,
"signAction: service error retry transition failed"
);
}
}
}
}
let return_txid_only = args.options.return_txid_only.0.unwrap_or(false)
|| pending.args.options.return_txid_only.0.unwrap_or(false);
let result = SignerSignActionResult {
txid: Some(txid),
tx: if return_txid_only {
None
} else {
Some(beef_bytes)
},
send_with_results: process_result.send_with_results.unwrap_or_default(),
not_delayed_results: process_result.not_delayed_results,
};
Ok(result)
}
#[cfg(test)]
#[allow(clippy::unnecessary_unwrap, clippy::unnecessary_literal_unwrap)]
mod tests {
#[test]
fn test_merge_prior_options_explicit_false_overrides_prior_true() {
let sign_no_send: Option<bool> = Some(false);
let prior_no_send: bool = true;
let merged = sign_no_send.unwrap_or(prior_no_send);
assert!(!merged);
}
#[test]
fn test_merge_prior_options_none_inherits_prior() {
let sign_no_send: Option<bool> = None;
let prior_no_send: bool = true;
let merged = sign_no_send.unwrap_or(prior_no_send);
assert!(merged);
}
#[test]
fn test_merge_prior_options_explicit_true_overrides_prior_false() {
let sign_no_send: Option<bool> = Some(true);
let prior_no_send: bool = false;
let merged = sign_no_send.unwrap_or(prior_no_send);
assert!(merged);
}
#[test]
fn test_merge_prior_options_none_inherits_false() {
let sign_no_send: Option<bool> = None;
let prior_no_send: bool = false;
let merged = sign_no_send.unwrap_or(prior_no_send);
assert!(!merged);
}
#[test]
fn test_return_txid_only_sign_action() {
use bsv::wallet::types::BooleanDefaultFalse;
let sign_return = BooleanDefaultFalse(Some(true));
let prior_return = BooleanDefaultFalse(Some(false));
let return_txid_only = sign_return.0.unwrap_or(false) || prior_return.0.unwrap_or(false);
assert!(return_txid_only);
let sign_return = BooleanDefaultFalse(None);
let prior_return = BooleanDefaultFalse(Some(true));
let return_txid_only = sign_return.0.unwrap_or(false) || prior_return.0.unwrap_or(false);
assert!(return_txid_only);
let sign_return = BooleanDefaultFalse(None);
let prior_return = BooleanDefaultFalse(None);
let return_txid_only = sign_return.0.unwrap_or(false) || prior_return.0.unwrap_or(false);
assert!(!return_txid_only);
}
#[test]
fn test_send_with_uses_prior_args() {
let prior_send_with = vec!["tx1".to_string(), "tx2".to_string()];
let is_send_with = true;
let result: Vec<String> = if is_send_with {
prior_send_with.clone()
} else {
vec![]
};
assert_eq!(result.len(), 2);
let is_send_with = false;
let result: Vec<String> = if is_send_with {
prior_send_with
} else {
vec![]
};
assert!(result.is_empty());
}
}