use bsv_wallet_toolbox::StorageSqlx;
use serde::Serialize;
pub const IMMEDIATE_HINT: &str = "immediateBroadcastHint";
pub const INPUTS_LOCKED: &str = "locked";
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PostWord {
pub word: &'static str,
pub request: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub hint: Option<serde_json::Value>,
pub inputs: &'static str,
}
pub fn last_note(history: &str, what: &str) -> Option<serde_json::Value> {
let h: serde_json::Value = serde_json::from_str(history).ok()?;
h.get("notes")?
.as_array()?
.iter()
.rev()
.find(|n| n.get("what").and_then(|w| w.as_str()) == Some(what))
.cloned()
}
pub async fn post_word(storage: &StorageSqlx, txid: &str) -> Option<PostWord> {
let row: Option<(String, String)> =
sqlx::query_as("SELECT status, history FROM proven_tx_reqs WHERE txid = ? LIMIT 1")
.bind(txid)
.fetch_optional(storage.pool())
.await
.ok()?;
let (request, history) = row?;
if !matches!(request.as_str(), "unsent" | "sending" | "unprocessed") {
return None;
}
if crate::retire_guard::broadcaster_took(storage.pool(), txid)
.await
.unwrap_or(true)
{
return None;
}
Some(PostWord {
word: "built",
request,
hint: last_note(&history, IMMEDIATE_HINT),
inputs: INPUTS_LOCKED,
})
}
pub fn atomic_subject(bytes: &[u8]) -> Option<String> {
if bytes.len() < 36 || bytes[..4] != [0x01, 0x01, 0x01, 0x01] {
return None;
}
let mut txid = bytes[4..36].to_vec();
txid.reverse();
Some(hex::encode(txid))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_last_hint_is_read_from_the_toolbox_history() {
let h = r#"{"notes":[{"what":"immediateBroadcastHint","outcome":"serviceError"},{"what":"other"},{"what":"immediateBroadcastHint","outcome":"invalidTx","nextReaskMinutes":2}]}"#;
let n = last_note(h, IMMEDIATE_HINT).unwrap();
assert_eq!(n["outcome"], "invalidTx");
assert!(last_note("{}", IMMEDIATE_HINT).is_none());
assert!(last_note("not json", IMMEDIATE_HINT).is_none());
}
#[test]
fn the_subject_of_an_atomic_beef_is_read_from_its_prefix() {
let mut b = vec![1, 1, 1, 1];
let mut id: Vec<u8> = (0u8..32).collect();
b.extend(&id);
b.extend([2, 0, 0xbe, 0xef]);
id.reverse();
assert_eq!(atomic_subject(&b), Some(hex::encode(id)));
assert_eq!(atomic_subject(&[2, 0, 0xbe, 0xef]), None);
}
}