anya-core 1.2.0

Enterprise-grade Bitcoin Infrastructure Platform
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
use std::error::Error;
//! Tests for the Bitcoin blockchain adapter
//!
//! These tests connect to a Bitcoin testnet node to verify functionality of the adapter
//! with real blockchain data. To run these tests, you need access to a Bitcoin testnet node.
//!
//! Run with: `cargo test --test bitcoin_adapter -- --ignored`

use std::collections::HashMap;
use std::env;
use std::time::Duration;

use bitcoin::Network;
use tokio::time::sleep;

use crate::blockchain::{
    BlockchainAdapter, NodePort, WalletPort, SmartContractPort, MetricsPort, SecurityPort,
    AlertComparison, TransactionParams, TxInput,
};
use crate::bitcoin::rpc::BitcoinRpcClient;
use crate::blockchain::bitcoin::adapter::{BitcoinAdapter, BitcoinAdapterConfig};

// Test fixture to set up a Bitcoin testnet adapter
async fn setup_testnet_adapter() -> Option<BitcoinAdapter>  -> Result<(), Box<dyn Error>> {
    // Try to get connection details from environment variables, or use defaults for a local node
    let rpc_url = env::var("BITCOIN_TESTNET_RPC_URL").unwrap_or_else(|_| "http://localhost:18332".to_string());
    let rpc_user = env::var("BITCOIN_TESTNET_RPC_USER").unwrap_or_else(|_| "bitcoin".to_string());
    let rpc_password = env::var("BITCOIN_TESTNET_RPC_PASSWORD").unwrap_or_else(|_| "password".to_string());
    
    // Create a config with shorter intervals for testing
    let config = BitcoinAdapterConfig {
        network: Network::Testnet,
        rpc_url,
        rpc_user,
        rpc_password,
        timeout: 10,
        metrics_interval: 5,
        security_interval: 5,
        mempool_interval: 5,
        fee_estimation_blocks: vec![1, 6, 144],
        enable_security_monitoring: true,
        chain_split_threshold: 3,
        fee_spike_threshold: 2.0,
        max_utxo_cache_size: 100,
        max_block_cache_size: 10,
        max_tx_cache_size: 100,
    };
    
    // Try to create the adapter, but return None if it fails (e.g., if testnet node is not available)
    match BitcoinAdapter::new(config).await {
        Ok(adapter) => Some(adapter),
        Err(e) => {
            eprintln!("Failed to connect to Bitcoin testnet node: {}", e);
            eprintln!("Skipping tests that require a testnet node");
            None
        }
    }
}

// Helper function to check if we should skip a test
fn should_skip_test(adapter: &Option<BitcoinAdapter>) -> bool  -> Result<(), Box<dyn Error>> {
    if adapter.is_none() {
        eprintln!("Skipping test as no testnet node is available");
        return true;
    }
    false
}

// Test the initialization of the adapter
#[tokio::test]
#[ignore] // Ignore by default as it requires a testnet node
async fn test_adapter_initialization()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    
    // Initialize the adapter
    let result = adapter.initialize().await;
    assert!(result.is_ok(), "Failed to initialize adapter: {:?}", result);
    
    // Verify the chain ID
    let chain_id = adapter.get_chain_id();
    assert_eq!(chain_id, "bitcoin-testnet", "Chain ID should be bitcoin-testnet");
    
    // Verify we can get blockchain state
    let state = adapter.get_blockchain_state().await;
    assert!(state.is_ok(), "Failed to get blockchain state: {:?}", state);
    
    // Check that the state has a sane block height (testnet should be well above 0)
    let state = state?;
    assert!(state.best_block_height > 0, "Block height should be greater than 0");
    
    println!("Adapter initialized with state: {:?}", state);
}

// Test the NodePort functionality
#[tokio::test]
#[ignore]
async fn test_node_port()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    let _ = adapter.initialize().await?;
    
    // Get blockchain state
    let state = adapter.get_blockchain_state().await?;
    println!("Current block height: {}", state.best_block_height);
    
    // Get a block by height - use a recent block
    let height = state.best_block_height - 5; // Go back a few blocks to ensure it's stable
    let block = adapter.get_block_by_height(height).await?;
    println!("Block at height {}: {}", height, block.hash);
    
    // Verify we can get the same block by hash
    let block_by_hash = adapter.get_block_by_hash(&block.hash).await?;
    assert_eq!(block.height, block_by_hash.height, "Blocks should have the same height");
    
    // Get raw block
    let raw_block = adapter.get_raw_block(&block.hash).await?;
    assert!(!raw_block.is_empty(), "Raw block should not be empty");
    
    // Get a transaction from the block
    if block.tx_count > 0 {
        // Get the transactions in the block
        let transactions = adapter.get_block_by_hash(&block.hash).await?.tx_ids;
        if let Some(txid) = transactions.get(0) {
            // Get the transaction
            let tx = adapter.get_transaction(txid).await?;
            println!("Transaction {}: size={}, vsize={}", txid, tx.size, tx.vsize);
            
            // Get raw transaction
            let raw_tx = adapter.get_raw_transaction(txid).await?;
            assert!(!raw_tx.is_empty(), "Raw transaction should not be empty");
        }
    }
    
    // Get mempool status
    let mempool = adapter.get_mempool_status().await?;
    println!("Mempool: {} transactions, {} bytes", mempool.tx_count, mempool.size);
    
    // Get mempool transactions
    let mempool_txs = adapter.get_mempool_transactions().await?;
    println!("Mempool has {} transactions", mempool_txs.len());
    
    // Estimate fee
    let fee = adapter.estimate_fee(6).await?;
    println!("Estimated fee for 6 blocks: {} sat/byte", fee);
    
    // Get peer info
    let peers = adapter.get_peer_info().await?;
    println!("Connected to {} peers", peers.len());
    assert!(!peers.is_empty(), "Should be connected to at least one peer");
    
    // Get network hashrate
    let hashrate = adapter.get_network_hashrate().await?;
    println!("Network hashrate: {} hashes/sec", hashrate);
    assert!(hashrate > 0.0, "Network hashrate should be greater than 0");
}

// Test the WalletPort functionality
// Note: This test only tests read-only functions as we don't have a wallet
#[tokio::test]
#[ignore]
async fn test_wallet_port_readonly()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    let _ = adapter.initialize().await?;
    
    // Get blockchain state to find a block
    let state = adapter.get_blockchain_state().await?;
    let height = state.best_block_height - 5; // Go back a few blocks to ensure it's stable
    
    // Get a block
    let block = adapter.get_block_by_height(height).await?;
    
    // Find a transaction in the block
    if let Some(txid) = block.tx_ids.get(0) {
        // Analyze the transaction
        let analysis = adapter.analyze_transaction(&txid).await?;
        println!("Transaction analysis: {} inputs, {} outputs", analysis.inputs.len(), analysis.outputs.len());
        
        // If the transaction has outputs with addresses, test get_address_balance and related functions
        for output in &analysis.outputs {
            if let Some(address) = &output.address {
                // Get address balance
                let balance = adapter.get_address_balance(address).await?;
                println!("Address {} balance: {} confirmed, {} unconfirmed", 
                         address, balance.confirmed, balance.unconfirmed);
                
                // Get address transactions
                let txs = adapter.get_address_transactions(address, Some(10)).await?;
                println!("Address {} has {} transactions", address, txs.len());
                
                // Get address UTXOs
                let utxos = adapter.get_address_utxos(address).await?;
                println!("Address {} has {} UTXOs", address, utxos.len());
                
                // Only need to test one address
                break;
            }
        }
        
        // Create a raw transaction (unsigned)
        let inputs = vec![
            TxInput {
                txid: txid.clone(),
                vout: 0,
                sequence: None,
            }
        ];
        
        // Create a dummy output
        let mut outputs = HashMap::new();
        outputs.insert("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(), 0.0001); // Testnet address
        
        // This will fail because we don't have the private keys, but it tests the interface
        let result = adapter.create_raw_transaction(inputs, outputs).await;
        println!("Create raw transaction result: {:?}", result);
    }
}

// Test the SmartContractPort functionality
#[tokio::test]
#[ignore]
async fn test_smart_contract_port()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    let _ = adapter.initialize().await?;
    
    // Test deploy_contract with a simple P2SH script
    // This is a multi-sig script: 1-of-2 multisig with dummy public keys
    let script_hex = "5121030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000020252ae";
    
    // This will likely fail in practice without a funded wallet, but it tests the interface
    let result = adapter.deploy_contract(script_hex, "", &["0.0001".to_string()]).await;
    println!("Deploy contract result: {:?}", result);
    
    // Get blockchain state to find a block
    let state = adapter.get_blockchain_state().await?;
    
    // Find a P2SH address to test call_contract
    // Use a known P2SH address from testnet (replace with a real one if needed)
    let p2sh_address = "2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc";
    
    // Test call_contract
    let result = adapter.call_contract(p2sh_address, "", "", &[]).await;
    println!("Call contract result: {:?}", result);
    
    // Test get_contract_balance
    let result = adapter.get_contract_balance(p2sh_address).await;
    println!("Contract balance result: {:?}", result);
    
    // Test get_contract_events
    let result = adapter.get_contract_events(
        p2sh_address, "", "all", 
        Some(state.best_block_height - 100), 
        Some(state.best_block_height)
    ).await;
    println!("Contract events result: {:?}", result);
}

// Test the MetricsPort functionality
#[tokio::test]
#[ignore]
async fn test_metrics_port()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    let _ = adapter.initialize().await?;
    
    // Get metrics
    let metrics = adapter.get_metrics().await?;
    println!("Block count: {}, TX count: {}", metrics.block_count, metrics.tx_count);
    assert!(metrics.block_count > 0, "Block count should be greater than 0");
    
    // Get network hashrate
    let hashrate = adapter.get_network_hashrate().await?;
    println!("Network hashrate: {} hashes/sec", hashrate);
    
    // Get mempool size
    let mempool_size = adapter.get_mempool_size().await?;
    println!("Mempool size: {} transactions", mempool_size);
    
    // Get fee estimates
    let fee_estimates = adapter.get_fee_estimates().await?;
    println!("Fee estimates: {:?}", fee_estimates);
    assert!(!fee_estimates.is_empty(), "Should have at least one fee estimate");
    
    // Get node version
    let version = adapter.get_node_version().await?;
    println!("Node version: {}", version);
    assert!(!version.is_empty(), "Node version should not be empty");
    
    // Get transaction volume
    let volume = adapter.get_transaction_volume(10).await?;
    println!("Transaction volume for last 10 blocks: {} satoshis", volume);
    
    // Get block time average
    let avg_block_time = adapter.get_block_time_average(10).await?;
    println!("Average block time for last 10 blocks: {} seconds", avg_block_time);
    assert!(avg_block_time > 0.0, "Average block time should be greater than 0");
    
    // Get difficulty
    let difficulty = adapter.get_difficulty().await?;
    println!("Current difficulty: {}", difficulty);
    assert!(difficulty > 0.0, "Difficulty should be greater than 0");
    
    // Get mempool fee histogram
    let histogram = adapter.get_mempool_fee_histogram().await?;
    println!("Mempool fee histogram has {} data points", histogram.len());
}

// Test the SecurityPort functionality
#[tokio::test]
#[ignore]
async fn test_security_port()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    let _ = adapter.initialize().await?;
    
    // Check for chain splits
    let chain_split = adapter.check_chain_split().await?;
    println!("Chain split detection result: {:?}", chain_split);
    
    // Get blockchain state to find a block
    let state = adapter.get_blockchain_state().await?;
    let height = state.best_block_height - 5; // Go back a few blocks to ensure it's stable
    
    // Get a block
    let block = adapter.get_block_by_height(height).await?;
    
    // Find a transaction to test double spend detection
    if let Some(txid) = block.tx_ids.get(0) {
        // Check for double spends (this should be none for a confirmed transaction)
        let double_spend = adapter.detect_double_spend(txid, 1).await?;
        println!("Double spend detection result: {:?}", double_spend);
        
        // Check transaction malleability
        let malleability = adapter.check_transaction_malleability(txid).await?;
        println!("Transaction malleability check result: {:?}", malleability);
    }
    
    // Check for anomalous fees
    let anomalies = adapter.detect_anomalous_fees().await?;
    println!("Detected {} fee anomalies", anomalies.len());
    
    // Monitor large transactions
    let large_txs = adapter.monitor_large_transactions(1.0).await?;
    println!("Detected {} large transactions (>1 BTC)", large_txs.len());
    
    // Check reorg depth (should be None for a stable block)
    let reorg = adapter.check_reorg_depth(height).await?;
    println!("Reorg depth check result: {:?}", reorg);
}

// Test monitoring functionality
#[tokio::test]
#[ignore]
async fn test_monitoring()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    let _ = adapter.initialize().await?;
    
    // Start monitoring
    let result = adapter.start_monitoring().await;
    assert!(result.is_ok(), "Failed to start monitoring: {:?}", result);
    
    // Wait for some monitoring to happen
    println!("Waiting for monitoring to collect data...");
    sleep(Duration::from_secs(10)).await;
    
    // Get unusual transactions
    let unusual_txs = adapter.get_unusual_transactions().await?;
    println!("Detected {} unusual transactions", unusual_txs.len());
    
    // Get security alerts
    let alerts = adapter.get_security_alerts().await?;
    println!("Detected {} security alerts", alerts.len());
    
    // Test alert comparison
    let alert = AlertComparison {
        field: "block_height".to_string(),
        comparison: "gt".to_string(),
        value: "1000".to_string(), // Testnet should be well above 1000 blocks
        extra: None,
    };
    
    let alert_result = adapter.compare_with_alert(&alert).await?;
    assert!(alert_result, "Block height should be greater than 1000 on testnet");
    
    // Stop monitoring
    let result = adapter.stop_monitoring().await;
    assert!(result.is_ok(), "Failed to stop monitoring: {:?}", result);
}

// Test transaction creation and broadcasting (read-only version)
#[tokio::test]
#[ignore]
async fn test_transaction_creation_readonly()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    let _ = adapter.initialize().await?;
    
    // Create a transaction params object
    // Note: This will not be broadcast since we're not controlling a wallet
    let params = TransactionParams {
        inputs: None, // Let the wallet select inputs
        outputs: HashMap::from([
            ("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(), 0.0001)
        ]),
        fee_rate: Some(5.0), // 5 sat/byte
        change_address: None,
        op_return_data: Some(vec![1, 2, 3, 4, 5]),
        rbf: Some(true),
        locktime: None,
    };
    
    // This will fail because we don't have a funded wallet, but it tests the interface
    let result = adapter.create_transaction(params).await;
    println!("Create transaction result: {:?}", result);
}

// A integration test that exercises multiple components together
#[tokio::test]
#[ignore]
async fn test_integration_workflow()  -> Result<(), Box<dyn Error>> {
    let adapter = setup_testnet_adapter().await;
    if should_skip_test(&adapter) {
        return;
    }
    
    let adapter = adapter?;
    let _ = adapter.initialize().await?;
    
    // 1. Get the blockchain state
    let state = adapter.get_blockchain_state().await?;
    println!("Current block height: {}", state.best_block_height);
    
    // 2. Get a recent block
    let height = state.best_block_height - 3;
    let block = adapter.get_block_by_height(height).await?;
    println!("Block at height {}: hash={}, tx_count={}", height, block.hash, block.tx_count);
    
    // 3. Get the first transaction in the block
    if let Some(txid) = block.tx_ids.get(0) {
        // 4. Get transaction details
        let tx = adapter.get_transaction(txid).await?;
        println!("Transaction {}: size={}, vsize={}", txid, tx.size, tx.vsize);
        
        // 5. Analyze the transaction
        let analysis = adapter.analyze_transaction(txid).await?;
        println!("Transaction has {} inputs and {} outputs", analysis.inputs.len(), analysis.outputs.len());
        
        // 6. Check if the transaction is in the mempool (should be false for a confirmed tx)
        let in_mempool = adapter.is_in_mempool(txid).await?;
        assert!(!in_mempool, "Confirmed transaction should not be in mempool");
        
        // 7. Check for double spends
        let double_spend = adapter.detect_double_spend(txid, 3).await?;
        assert!(double_spend.is_none(), "Confirmed transaction should not have double spends");
        
        // 8. Check transaction malleability
        let malleability = adapter.check_transaction_malleability(txid).await?;
        println!("Malleability check: {:?}", malleability);
        
        // 9. Get outputs and check UTXOs
        for (i, output) in analysis.outputs.iter().enumerate() {
            if let Some(address) = &output.address {
                // 10. Get address balance
                let balance = adapter.get_address_balance(address).await?;
                println!("Output #{} to {}: {} confirmed, {} unconfirmed", 
                         i, address, balance.confirmed, balance.unconfirmed);
                
                // 11. Get UTXO for this output
                let utxo = adapter.get_utxo(txid, i as u32).await?;
                if let Some(utxo_info) = utxo {
                    println!("UTXO: txid={}, vout={}, amount={}", 
                             utxo_info.txid, utxo_info.vout, utxo_info.amount);
                } else {
                    println!("UTXO was spent or doesn't exist");
                }
                
                // Only check one output
                break;
            }
        }
    }
    
    // 12. Get current fee estimates
    let fee_estimates = adapter.get_fee_estimates().await?;
    println!("Fee estimates: {:?}", fee_estimates);
    
    // 13. Get metrics
    let metrics = adapter.get_metrics().await?;
    println!("Metrics: block_count={}, tx_count={}, difficulty={}", 
             metrics.block_count, metrics.tx_count, metrics.difficulty);
    
    // 14. Check for chain splits
    let chain_split = adapter.check_chain_split().await?;
    println!("Chain split detection: {:?}", chain_split);
}