mod common;
use blvm_node::config::PaymentConfig;
use blvm_node::module::encryption::{
load_encrypted_module, store_encrypted_module, EncryptedModuleMetadata, ModuleEncryption,
};
use blvm_node::module::registry::client::ModuleRegistry;
use blvm_node::module::registry::manifest::{ModuleManifest, PaymentSection};
use blvm_node::network::module_registry_extensions::handle_get_module;
use blvm_node::network::protocol::GetModuleMessage;
use blvm_node::payment::processor::PaymentProcessor;
use blvm_node::payment::state_machine::{PaymentState, PaymentStateMachine};
use blvm_protocol::address::{BitcoinAddress, Network};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::sync::Arc;
use tempfile::TempDir;
fn test_payment_tb1_addresses() -> (String, String) {
let author = BitcoinAddress::new(Network::Testnet, 0, vec![0x01; 20]).unwrap();
let marketplace = BitcoinAddress::new(Network::Testnet, 0, vec![0x02; 20]).unwrap();
(author.encode().unwrap(), marketplace.encode().unwrap())
}
fn create_test_manifest_with_payment(name: &str) -> ModuleManifest {
use blvm_node::module::registry::manifest::{MaintainerSignature, SignatureSection};
let test_key = common::test_secp256k1_scalar_one();
let (author_addr, commons_addr) = test_payment_tb1_addresses();
let price_sats = 100000u64;
let message_data = format!("{}||{}||{}", author_addr, commons_addr, price_sats);
let message_hash: [u8; 32] = Sha256::digest(message_data.as_bytes()).into();
let (signature_hex, pubkey_hex) =
common::ecdsa_compact_sig_hex_and_pubkey_hex(&test_key, &message_hash);
let signature_section = SignatureSection {
maintainers: vec![MaintainerSignature {
name: "test-maintainer".to_string(),
public_key: pubkey_hex,
signature: "dummy".to_string(),
}],
threshold: Some("1-of-1".to_string()),
};
let payment_section = PaymentSection {
required: true,
price_sats: Some(price_sats),
author_payment_code: None,
author_address: Some(author_addr),
commons_payment_code: None,
commons_address: Some(commons_addr),
payment_signature: Some(signature_hex),
};
ModuleManifest {
name: name.to_string(),
version: "1.0.0".to_string(),
description: Some(format!("Test module: {}", name)),
author: Some("Test Author".to_string()),
capabilities: Vec::new(),
rpc_overrides: Vec::new(),
dependencies: HashMap::new(),
optional_dependencies: HashMap::new(),
entry_point: format!("{}.so", name),
config_schema: HashMap::new(),
binary: None,
signatures: Some(signature_section),
payment: Some(payment_section),
}
}
async fn create_mock_registry(
temp_dir: &TempDir,
module_name: &str,
_module_binary: &[u8],
) -> Arc<ModuleRegistry> {
use blvm_node::module::registry::cache::{CachedModule, LocalCache};
use blvm_node::module::registry::cas::ContentAddressableStorage;
let cache_dir = temp_dir.path().join("cache");
let cas_dir = temp_dir.path().join("cas");
std::fs::create_dir_all(&cache_dir).unwrap();
std::fs::create_dir_all(&cas_dir).unwrap();
let cas = Arc::new(tokio::sync::RwLock::new(
ContentAddressableStorage::new(&cas_dir).unwrap(),
));
let cache = Arc::new(tokio::sync::RwLock::new(LocalCache::new()));
let manifest = create_test_manifest_with_payment(module_name);
let manifest_toml = toml::to_string(&manifest).unwrap();
let manifest_hash = cas.write().await.store(manifest_toml.as_bytes()).unwrap();
let binary_hash = cas.write().await.store(b"test_binary").unwrap();
let combined = format!("{}{}", hex::encode(manifest_hash), hex::encode(binary_hash));
let module_hash = Sha256::digest(combined.as_bytes());
let mut hash = [0u8; 32];
hash.copy_from_slice(&module_hash);
let cached = CachedModule {
name: module_name.to_string(),
version: "1.0.0".to_string(),
hash,
manifest_hash,
binary_hash,
verified_at: 0,
verified_by: Vec::new(),
local_path: temp_dir.path().join(format!("{}.so", module_name)),
expires_at: None,
};
{
let mut guard = cache.write().await;
guard.cache(cached);
guard.save(&cache_dir).unwrap();
}
Arc::new(ModuleRegistry::new(&cache_dir, &cas_dir, Vec::new()).unwrap())
}
#[tokio::test]
async fn test_handle_get_module_payment_required_no_payment_id() {
let temp_dir = TempDir::new().unwrap();
let registry = create_mock_registry(&temp_dir, "paid-module", b"module_binary").await;
let message = GetModuleMessage {
request_id: 1,
name: "paid-module".to_string(),
version: None,
payment_id: None,
};
let result = handle_get_module(message, Some(registry), None, None, None, None, None).await;
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("requires payment") || error_msg.contains("payment"));
}
#[tokio::test]
async fn test_handle_get_module_free_module() {
let temp_dir = TempDir::new().unwrap();
let manifest = ModuleManifest {
name: "free-module".to_string(),
version: "1.0.0".to_string(),
description: None,
author: None,
capabilities: Vec::new(),
rpc_overrides: Vec::new(),
dependencies: HashMap::new(),
optional_dependencies: HashMap::new(),
entry_point: "free-module.so".to_string(),
config_schema: HashMap::new(),
binary: None,
signatures: None,
payment: None, };
}
#[tokio::test]
async fn test_handle_get_module_payment_pending_serves_encrypted() {
let temp_dir = TempDir::new().unwrap();
let modules_dir = temp_dir.path().join("modules");
std::fs::create_dir_all(&modules_dir).unwrap();
let module_name = "paid-module";
let module_binary = b"original_module_binary";
let payment_id = "test_payment_123";
let encryption = ModuleEncryption::new();
let module_hash = {
let hash = Sha256::digest(module_binary);
let mut h = [0u8; 32];
h.copy_from_slice(&hash);
h
};
let (encrypted_binary, nonce) = encryption
.encrypt_module(module_binary, payment_id, &module_hash)
.unwrap();
let metadata = EncryptedModuleMetadata {
payment_id: payment_id.to_string(),
module_hash: module_hash.to_vec(),
nonce,
encrypted_at: 1234567890,
payment_method: "on-chain".to_string(),
};
store_encrypted_module(&modules_dir, module_name, &encrypted_binary, &metadata)
.await
.unwrap();
let config = PaymentConfig::default();
let processor = Arc::new(PaymentProcessor::new(config).unwrap());
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let registry = create_mock_registry(&temp_dir, module_name, module_binary).await;
let message = GetModuleMessage {
request_id: 1,
name: module_name.to_string(),
version: None,
payment_id: Some(payment_id.to_string()),
};
let result = handle_get_module(
message,
Some(registry),
None,
Some(state_machine),
Some(Arc::new(encryption)),
Some(modules_dir),
None,
)
.await;
if let Ok(module_msg) = result {
if let Some(binary) = module_msg.binary {
assert_ne!(binary, module_binary);
let (loaded_encrypted, loaded_metadata) =
load_encrypted_module(&temp_dir.path().join("modules"), module_name)
.await
.unwrap();
assert_eq!(binary, loaded_encrypted);
}
} else {
}
}
#[tokio::test]
async fn test_handle_get_module_payment_confirmed_serves_decrypted() {
let temp_dir = TempDir::new().unwrap();
let modules_dir = temp_dir.path().join("modules");
std::fs::create_dir_all(&modules_dir).unwrap();
let module_name = "paid-module";
let module_binary = b"original_module_binary";
let payment_id = "test_payment_123";
let encryption = ModuleEncryption::new();
let module_hash = {
let hash = Sha256::digest(module_binary);
let mut h = [0u8; 32];
h.copy_from_slice(&hash);
h
};
let (encrypted_binary, nonce) = encryption
.encrypt_module(module_binary, payment_id, &module_hash)
.unwrap();
let metadata = EncryptedModuleMetadata {
payment_id: payment_id.to_string(),
module_hash: module_hash.to_vec(),
nonce,
encrypted_at: 1234567890,
payment_method: "on-chain".to_string(),
};
store_encrypted_module(&modules_dir, module_name, &encrypted_binary, &metadata)
.await
.unwrap();
let config = PaymentConfig::default();
let processor = Arc::new(PaymentProcessor::new(config).unwrap());
let state_machine = Arc::new(PaymentStateMachine::new(processor));
use blvm_node::Hash;
let tx_hash = [0u8; 32];
let block_hash = [0u8; 32];
state_machine
.mark_settled(payment_id, tx_hash, block_hash, 1, None)
.await
.unwrap();
let registry = create_mock_registry(&temp_dir, module_name, module_binary).await;
let message = GetModuleMessage {
request_id: 1,
name: module_name.to_string(),
version: None,
payment_id: Some(payment_id.to_string()),
};
let result = handle_get_module(
message,
Some(registry),
None,
Some(state_machine),
Some(Arc::new(encryption)),
Some(modules_dir),
None,
)
.await;
if let Ok(module_msg) = result {
if let Some(binary) = module_msg.binary {
assert_eq!(binary, module_binary);
}
}
}