use crate::transaction::Transaction;
use super::types::{KvContext, KvProtocol};
pub fn kv_store_interpreter(
tx: &Transaction,
output_index: usize,
ctx: Option<&KvContext>,
) -> Option<String> {
let ctx = ctx?;
if output_index >= tx.outputs.len() {
return None;
}
let output = &tx.outputs[output_index];
let chunks = output.locking_script.chunks();
let mut fields: Vec<Vec<u8>> = Vec::new();
for chunk in chunks {
if let Some(data) = &chunk.data {
fields.push(data.clone());
} else {
break;
}
}
let has_tags = fields.len() == KvProtocol::FIELD_COUNT;
let is_old_format = fields.len() == KvProtocol::OLD_FIELD_COUNT;
if !has_tags && !is_old_format {
return None;
}
let key = String::from_utf8(fields.get(KvProtocol::KEY)?.clone()).ok()?;
if key != ctx.key {
return None;
}
let protocol_str = String::from_utf8(fields.get(KvProtocol::PROTOCOL_ID)?.clone()).ok()?;
let expected_protocol = format!("[{},\"{}\"]", ctx.protocol_id.0, ctx.protocol_id.1);
if protocol_str != expected_protocol {
return None;
}
String::from_utf8(fields.get(KvProtocol::VALUE)?.clone()).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::private_key::PrivateKey;
use crate::script::templates::push_drop::PushDrop;
use crate::script::templates::ScriptTemplateLock;
use crate::transaction::{Transaction, TransactionOutput};
fn make_kv_tx(key: &str, value: &str, protocol_id: &str) -> Transaction {
let pk = PrivateKey::from_hex("1").unwrap();
let fields = vec![
protocol_id.as_bytes().to_vec(),
key.as_bytes().to_vec(),
value.as_bytes().to_vec(),
pk.to_public_key().to_der(), b"[]".to_vec(), ];
let pd = PushDrop::new(fields, pk);
let lock_script = pd.lock().unwrap();
let mut tx = Transaction::new();
tx.outputs.push(TransactionOutput {
satoshis: Some(1),
locking_script: lock_script,
change: false,
});
tx
}
#[test]
fn test_interpreter_extracts_matching_key() {
let tx = make_kv_tx("mykey", "myvalue", "[1,\"kvstore\"]");
let ctx = KvContext {
key: "mykey".to_string(),
protocol_id: (1, "kvstore".to_string()),
};
let result = kv_store_interpreter(&tx, 0, Some(&ctx));
assert_eq!(result, Some("myvalue".to_string()));
}
#[test]
fn test_interpreter_returns_none_for_wrong_key() {
let tx = make_kv_tx("mykey", "myvalue", "[1,\"kvstore\"]");
let ctx = KvContext {
key: "otherkey".to_string(),
protocol_id: (1, "kvstore".to_string()),
};
let result = kv_store_interpreter(&tx, 0, Some(&ctx));
assert_eq!(result, None);
}
#[test]
fn test_interpreter_returns_none_for_wrong_protocol() {
let tx = make_kv_tx("mykey", "myvalue", "[2,\"other\"]");
let ctx = KvContext {
key: "mykey".to_string(),
protocol_id: (1, "kvstore".to_string()),
};
let result = kv_store_interpreter(&tx, 0, Some(&ctx));
assert_eq!(result, None);
}
#[test]
fn test_interpreter_returns_none_no_context() {
let tx = make_kv_tx("mykey", "myvalue", "[1,\"kvstore\"]");
let result = kv_store_interpreter(&tx, 0, None);
assert_eq!(result, None);
}
#[test]
fn test_interpreter_returns_none_empty_tx() {
let tx = Transaction::new();
let ctx = KvContext {
key: "mykey".to_string(),
protocol_id: (1, "kvstore".to_string()),
};
let result = kv_store_interpreter(&tx, 0, Some(&ctx));
assert_eq!(result, None);
}
}