use blvm_node::node::mempool::MempoolManager;
use blvm_node::rpc::rawtx::RawTxRpc;
use blvm_node::storage::Storage;
use serde_json::json;
use std::sync::Arc;
use tempfile::TempDir;
fn create_test_rawtx_rpc() -> RawTxRpc {
let temp_dir = TempDir::new().unwrap();
let storage = Arc::new(Storage::new(temp_dir.path()).unwrap());
let mempool = Arc::new(MempoolManager::new());
RawTxRpc::with_dependencies(storage, mempool, None, None)
}
#[tokio::test]
async fn test_testmempoolaccept_output_format() {
let rawtx = create_test_rawtx_rpc();
let simple_tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let params = json!([simple_tx_hex]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
assert!(result.is_array());
let results = result.as_array().unwrap();
assert_eq!(results.len(), 1);
let tx_result = &results[0];
assert!(tx_result.get("txid").is_some(), "Missing txid field");
assert!(tx_result.get("wtxid").is_some(), "Missing wtxid field");
assert!(tx_result.get("allowed").is_some(), "Missing allowed field");
assert!(tx_result.get("vsize").is_some(), "Missing vsize field");
assert!(tx_result.get("fees").is_some(), "Missing fees field");
let fees = tx_result.get("fees").unwrap();
assert!(fees.get("base").is_some(), "Missing fees.base field");
if let Some(allowed) = tx_result.get("allowed").and_then(|v| v.as_bool()) {
if allowed {
assert!(
fees.get("effective-feerate").is_some(),
"Missing fees.effective-feerate when allowed"
);
assert!(
fees.get("effective-includes").is_some(),
"Missing fees.effective-includes when allowed"
);
let includes = fees.get("effective-includes").unwrap();
assert!(includes.is_array(), "effective-includes should be array");
} else {
assert!(
tx_result.get("reject-reason").is_some(),
"Missing reject-reason when not allowed"
);
}
}
}
#[tokio::test]
async fn test_wtxid_non_segwit() {
let rawtx = create_test_rawtx_rpc();
let simple_tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let params = json!([simple_tx_hex]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
let tx_result = &result.as_array().unwrap()[0];
let txid = tx_result.get("txid").unwrap().as_str().unwrap();
let wtxid = tx_result.get("wtxid").unwrap().as_str().unwrap();
assert_eq!(
txid, wtxid,
"wtxid should equal txid for non-SegWit transactions"
);
}
#[tokio::test]
async fn test_segwit_wtxid_calculation() {
let rawtx = create_test_rawtx_rpc();
let segwit_tx_hex = "0100000000010100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000160014000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000000210200000000000000000000000000000000000000000000000000000000000000000000000000";
let params = json!([segwit_tx_hex]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
let tx_result = &result.as_array().unwrap()[0];
let txid = tx_result.get("txid").unwrap().as_str().unwrap();
let wtxid = tx_result.get("wtxid").unwrap().as_str().unwrap();
assert!(!wtxid.is_empty(), "wtxid should not be empty");
assert_eq!(
wtxid.len(),
64,
"wtxid should be 64 hex characters (32 bytes)"
);
}
#[tokio::test]
async fn test_package_validation_duplicates() {
let rawtx = create_test_rawtx_rpc();
let tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let params = json!([[tx_hex, tx_hex]]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
let results = result.as_array().unwrap();
assert_eq!(results.len(), 2);
for tx_result in results {
assert!(
tx_result.get("package-error").is_some(),
"Should have package-error for duplicate transactions"
);
assert_eq!(
tx_result.get("allowed").unwrap().as_bool(),
Some(false),
"Should not be allowed when package validation fails"
);
}
}
#[tokio::test]
async fn test_package_validation_conflicts() {
let rawtx = create_test_rawtx_rpc();
let tx1_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let tx2_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let params = json!([[tx1_hex, tx2_hex]]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
let results = result.as_array().unwrap();
assert_eq!(results.len(), 2);
}
#[tokio::test]
async fn test_effective_includes_present() {
let rawtx = create_test_rawtx_rpc();
let simple_tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let params = json!([simple_tx_hex]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
let tx_result = &result.as_array().unwrap()[0];
let fees = tx_result.get("fees").unwrap();
assert!(
fees.get("effective-includes").is_some(),
"effective-includes should always be present when allowed"
);
let includes = fees.get("effective-includes").unwrap();
assert!(includes.is_array(), "effective-includes should be an array");
}
#[tokio::test]
async fn test_vsize_calculation() {
let rawtx = create_test_rawtx_rpc();
let simple_tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let params = json!([simple_tx_hex]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
let tx_result = &result.as_array().unwrap()[0];
let vsize = tx_result.get("vsize").unwrap().as_u64().unwrap();
assert!(vsize > 0, "vsize should be positive");
assert!(vsize < 1000000, "vsize should be reasonable");
}
#[tokio::test]
async fn test_effective_feerate_calculation() {
let rawtx = create_test_rawtx_rpc();
let simple_tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let params = json!([simple_tx_hex]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
let tx_result = &result.as_array().unwrap()[0];
if let Some(allowed) = tx_result.get("allowed").and_then(|v| v.as_bool()) {
if allowed {
let fees = tx_result.get("fees").unwrap();
let feerate = fees.get("effective-feerate").unwrap().as_f64().unwrap();
assert!(feerate >= 0.0, "effective-feerate should be non-negative");
}
}
}
#[tokio::test]
async fn test_reject_reason_format() {
let rawtx = create_test_rawtx_rpc();
let invalid_tx_hex = "0000";
let params = json!([invalid_tx_hex]);
let result = rawtx.testmempoolaccept(¶ms).await;
if let Ok(result) = result {
let results = result.as_array().unwrap();
if !results.is_empty() {
let tx_result = &results[0];
if let Some(allowed) = tx_result.get("allowed").and_then(|v| v.as_bool()) {
if !allowed {
assert!(
tx_result.get("reject-reason").is_some(),
"Should have reject-reason when not allowed"
);
let reason = tx_result.get("reject-reason").unwrap();
assert!(
reason.is_string() || reason.is_null(),
"reject-reason should be string or null"
);
}
}
}
}
}
#[tokio::test]
async fn test_single_transaction_format() {
let rawtx = create_test_rawtx_rpc();
let simple_tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4443acf83e08e2c1252d0efcb9a4ba20b4e93f883d634390d26ed65f763194ea3273f11a6718b3615b4d94e82801b0eaac00000000";
let params = json!([simple_tx_hex]);
let result = rawtx.testmempoolaccept(¶ms).await.unwrap();
assert!(result.is_array());
let results = result.as_array().unwrap();
assert_eq!(results.len(), 1);
assert!(
results[0].get("package-error").is_none(),
"Single transaction should not have package-error"
);
}