#![cfg(feature = "bip70-http")]
use blvm_node::config::PaymentConfig;
use blvm_node::node::mempool::MempoolManager;
use blvm_node::payment::processor::PaymentProcessor;
use blvm_node::payment::state_machine::{PaymentState, PaymentStateMachine};
use blvm_node::rpc::payment::PaymentRpc;
use blvm_node::storage::Storage;
use blvm_protocol::block::calculate_tx_id;
use blvm_protocol::payment::PaymentOutput;
use serde_json::json;
use std::sync::Arc;
use tempfile::TempDir;
mod common;
use common::valid_transaction;
fn create_test_outputs() -> Vec<PaymentOutput> {
vec![
PaymentOutput {
script: vec![0x51, 0x87],
amount: Some(100_000),
},
PaymentOutput {
script: vec![0x52, 0x87],
amount: Some(50_000),
},
]
}
fn create_rpc() -> (PaymentRpc, Arc<PaymentStateMachine>) {
let processor =
Arc::new(PaymentProcessor::new(PaymentConfig::default()).expect("payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
(
PaymentRpc::with_state_machine(state_machine.clone()),
state_machine,
)
}
#[tokio::test]
async fn verify_on_chain_payment_in_mempool() {
let (rpc, state_machine) = create_rpc();
let (payment_id, _) = state_machine
.create_payment_request(create_test_outputs(), None, false)
.await
.expect("create payment request");
let tx_hash = [0x42u8; 32];
state_machine
.mark_in_mempool(&payment_id, tx_hash)
.await
.expect("mark in mempool");
let result = rpc
.verify_on_chain_payment(&json!([payment_id, hex::encode(tx_hash)]))
.await
.expect("verify_on_chain_payment");
assert_eq!(result["verified"], true);
assert_eq!(result["state"], "in_mempool");
assert_eq!(result["amount_sats"], 150_000);
assert_eq!(result["tx_hash"], hex::encode(tx_hash));
}
#[tokio::test]
async fn verify_on_chain_payment_settled() {
let (rpc, state_machine) = create_rpc();
let (payment_id, _) = state_machine
.create_payment_request(create_test_outputs(), None, false)
.await
.expect("create payment request");
let tx_hash = [0x43u8; 32];
let block_hash = [0x44u8; 32];
state_machine
.mark_in_mempool(&payment_id, tx_hash)
.await
.expect("mark in mempool");
state_machine
.mark_settled(&payment_id, tx_hash, block_hash, 6, None)
.await
.expect("mark settled");
let result = rpc
.verify_on_chain_payment(&json!([payment_id, hex::encode(tx_hash)]))
.await
.expect("verify_on_chain_payment");
assert_eq!(result["verified"], true);
assert_eq!(result["state"], "settled");
assert_eq!(result["amount_sats"], 150_000);
}
#[tokio::test]
async fn verify_on_chain_payment_tx_mismatch() {
let (rpc, state_machine) = create_rpc();
let (payment_id, _) = state_machine
.create_payment_request(create_test_outputs(), None, false)
.await
.expect("create payment request");
let tx_hash = [0x45u8; 32];
state_machine
.mark_in_mempool(&payment_id, tx_hash)
.await
.expect("mark in mempool");
let wrong_hash = [0x99u8; 32];
let result = rpc
.verify_on_chain_payment(&json!([payment_id, hex::encode(wrong_hash)]))
.await
.expect("verify_on_chain_payment");
assert_eq!(result["verified"], false);
assert_eq!(result["state"], "tx_mismatch");
}
#[tokio::test]
async fn verify_on_chain_payment_pending() {
let (rpc, state_machine) = create_rpc();
let (payment_id, _) = state_machine
.create_payment_request(create_test_outputs(), None, false)
.await
.expect("create payment request");
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("get state");
assert!(matches!(state, PaymentState::RequestCreated { .. }));
let tx_hash = [0x46u8; 32];
let result = rpc
.verify_on_chain_payment(&json!([payment_id, hex::encode(tx_hash)]))
.await
.expect("verify_on_chain_payment");
assert_eq!(result["verified"], false);
assert_eq!(result["state"], "pending");
}
#[tokio::test]
async fn verify_on_chain_payment_not_found() {
let (rpc, _) = create_rpc();
let result = rpc
.verify_on_chain_payment(&json!(["missing-id", hex::encode([0u8; 32])]))
.await
.expect("verify_on_chain_payment");
assert_eq!(result["verified"], false);
assert_eq!(result["state"], "not_found");
}
fn chain_rpc_fixture() -> (TempDir, PaymentRpc, [u8; 32]) {
let temp_dir = TempDir::new().unwrap();
let storage = Arc::new(Storage::new(temp_dir.path()).unwrap());
let mempool = Arc::new(MempoolManager::new());
let tx = valid_transaction();
let tx_hash = calculate_tx_id(&tx);
mempool.add_transaction(tx).unwrap();
let processor =
Arc::new(PaymentProcessor::new(PaymentConfig::default()).expect("payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let rpc = PaymentRpc::with_state_machine(state_machine).with_chain_access(mempool, storage);
(temp_dir, rpc, tx_hash)
}
#[tokio::test]
async fn verify_on_chain_payment_by_tx_chain_only() {
let (_dir, rpc, tx_hash) = chain_rpc_fixture();
let min_amount = 1u64;
let result = rpc
.verify_on_chain_payment_by_tx(&json!([
"foreign-payment-id",
hex::encode(tx_hash),
min_amount
]))
.await
.expect("verify_on_chain_payment_by_tx");
assert_eq!(result["verified"], true);
assert_eq!(result["state"], "in_mempool");
assert!(result["amount_sats"].as_u64().unwrap() >= min_amount);
}
#[tokio::test]
async fn verify_on_chain_payment_by_tx_underpaid() {
let (_dir, rpc, tx_hash) = chain_rpc_fixture();
let result = rpc
.verify_on_chain_payment_by_tx(&json!([
"foreign-payment-id",
hex::encode(tx_hash),
u64::MAX
]))
.await
.expect("verify_on_chain_payment_by_tx");
assert_eq!(result["verified"], false);
assert_eq!(result["state"], "underpaid");
}
#[tokio::test]
async fn verify_on_chain_payment_by_tx_local_pr_outputs() {
let temp_dir = TempDir::new().unwrap();
let storage = Arc::new(Storage::new(temp_dir.path()).unwrap());
let mempool = Arc::new(MempoolManager::new());
let processor =
Arc::new(PaymentProcessor::new(PaymentConfig::default()).expect("payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor.clone()));
let (payment_id, _) = state_machine
.create_payment_request(create_test_outputs(), None, false)
.await
.expect("create payment request");
let tx = valid_transaction();
let tx_hash = calculate_tx_id(&tx);
mempool.add_transaction(tx).unwrap();
let rpc = PaymentRpc::with_state_machine(state_machine).with_chain_access(mempool, storage);
let result = rpc
.verify_on_chain_payment_by_tx(&json!([payment_id, hex::encode(tx_hash), 1u64]))
.await
.expect("verify_on_chain_payment_by_tx");
assert_eq!(result["verified"], false);
assert_eq!(result["state"], "output_mismatch");
}
#[tokio::test]
async fn verify_on_chain_payment_by_tx_delegates_when_tracked() {
let (rpc, state_machine) = create_rpc();
let (payment_id, _) = state_machine
.create_payment_request(create_test_outputs(), None, false)
.await
.expect("create payment request");
let tx_hash = [0x42u8; 32];
state_machine
.mark_in_mempool(&payment_id, tx_hash)
.await
.expect("mark in mempool");
let result = rpc
.verify_on_chain_payment_by_tx(&json!([payment_id, hex::encode(tx_hash), 100_000u64]))
.await
.expect("verify_on_chain_payment_by_tx");
assert_eq!(result["verified"], true);
assert_eq!(result["state"], "in_mempool");
}