跳至主要內容

Rust Bitcoin 開發入門(四):進階應用與整合

14 分鐘閱讀
Rust Bitcoin 開發入門(四):進階應用與整合

這是 Rust Bitcoin 開發入門系列的最後一篇。本篇探討如何構建完整的 Bitcoin 應用,包括與節點互動、錢包開發和實際部署考量。

系列文章導航:


1. 區塊鏈數據存取策略

1.1 理解不同的存取方式

要構建實際的 Bitcoin 應用,你需要與區塊鏈數據互動。有幾種主要的方式可以做到這一點,每種都有其優缺點。

Bitcoin Core RPC 是最直接的方式。你運行一個完整節點,透過 JSON-RPC 介面查詢數據。這種方式提供了最高的安全性和隱私性——你不依賴任何第三方。缺點是需要存儲完整的區塊鏈(數百 GB),且初始同步需要數天。

Electrum 協議 使用客戶端-伺服器模式。輕量級客戶端連接到 Electrum 伺服器,伺服器維護一個索引的區塊鏈數據庫。客戶端可以快速查詢地址餘額和交易歷史,而不需要下載完整區塊鏈。缺點是你需要信任伺服器提供正確的數據(除非你自己運行伺服器)。

Esplora API 是 Blockstream 開發的另一種輕量級解決方案,透過 REST API 提供區塊鏈數據。它類似於 Electrum,但使用標準的 HTTP 介面,更容易整合。

選擇哪種方式取決於你的應用需求。對於個人錢包,Electrum 或 Esplora 通常足夠。對於需要最高安全性的應用(如交易所),你應該運行自己的完整節點。

1.2 Bitcoin Core RPC

Bitcoin Core 提供了一個豐富的 JSON-RPC 介面,可以查詢區塊鏈狀態、管理錢包和廣播交易。使用 bitcoincore-rpc crate 可以方便地從 Rust 存取這個介面。

首先需要配置 Bitcoin Core 啟用 RPC:

1
2
3
4
5
# bitcoin.conf
server=1
rpcuser=your_username
rpcpassword=your_password
rpcport=8332  # mainnet

然後在 Rust 中連接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use bitcoincore_rpc::{Auth, Client, RpcApi};

fn connect_to_node() -> anyhow::Result<Client> {
    let rpc = Client::new(
        "http://127.0.0.1:8332",
        Auth::UserPass("your_username".into(), "your_password".into()),
    )?;

    // 驗證連接
    let info = rpc.get_blockchain_info()?;
    println!("已連接到 Bitcoin Core");
    println!("區塊數: {}", info.blocks);
    println!("同步進度: {:.2}%", info.verification_progress * 100.0);

    Ok(rpc)
}

RPC 提供了廣泛的功能。你可以查詢區塊鏈信息、獲取特定區塊和交易、管理錢包、估算費率、廣播交易等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn rpc_examples(rpc: &Client) -> anyhow::Result<()> {
    // 獲取最新區塊
    let best_hash = rpc.get_best_block_hash()?;
    let block = rpc.get_block(&best_hash)?;
    println!("最新區塊包含 {} 筆交易", block.txdata.len());

    // 估算費率(6 區塊確認目標)
    let fee = rpc.estimate_smart_fee(6, None)?;
    if let Some(rate) = fee.fee_rate {
        println!("建議費率: {} sat/vB", rate.to_sat() / 1000);
    }

    // 獲取 mempool 狀態
    let mempool = rpc.get_mempool_info()?;
    println!("Mempool 中有 {} 筆待確認交易", mempool.size);

    Ok(())
}

1.3 Electrum 協議

Electrum 協議設計用於輕量級客戶端。它讓你可以快速查詢特定地址的餘額和歷史,而不需要掃描整個區塊鏈。

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
use electrum_client::{Client, ElectrumApi};

fn electrum_example() -> anyhow::Result<()> {
    // 連接到 Electrum 伺服器
    let client = Client::new("ssl://electrum.blockstream.info:60002")?;

    // 查詢地址餘額
    let address = Address::from_str("bc1q...")?;
    let script = address.script_pubkey();

    let balance = client.script_get_balance(&script)?;
    println!("已確認餘額: {} sat", balance.confirmed);
    println!("未確認餘額: {} sat", balance.unconfirmed);

    // 獲取交易歷史
    let history = client.script_get_history(&script)?;
    println!("共有 {} 筆相關交易", history.len());

    // 獲取未花費輸出
    let utxos = client.script_list_unspent(&script)?;
    for utxo in utxos {
        println!("UTXO: {}:{} - {} sat",
            utxo.tx_hash, utxo.tx_pos, utxo.value);
    }

    Ok(())
}

Electrum 的一個重要特性是訂閱功能。你可以訂閱特定地址的變化通知,這對於支付處理器等需要監控入帳的應用很有用。

1
2
3
4
5
6
7
8
9
10
fn monitor_address(client: &Client, script: &Script) -> anyhow::Result<()> {
    // 訂閱地址狀態變化
    let status = client.script_subscribe(script)?;
    println!("初始狀態: {:?}", status);

    // 在實際應用中,你會在後台執行緒中監聽變化
    // 當地址收到新交易時,會收到通知

    Ok(())
}

2. 使用 BDK 構建錢包

2.1 什麼是 BDK

BDK(Bitcoin Development Kit)是一個現代化的錢包開發框架。它抽象了錢包開發的複雜性,讓開發者可以專注於應用邏輯,而不是底層的區塊鏈細節。

BDK 的設計理念是基於「描述符」(descriptors)。描述符是一種標準化的方式來描述如何生成地址和花費資金。這種方法比傳統的「地址列表」方式更靈活,可以輕鬆支持各種腳本類型(P2PKH、P2WPKH、多簽等)。

BDK 的架構分為幾層:

錢包層 處理地址生成、交易構建和簽名。它不關心如何獲取區塊鏈數據或如何存儲狀態。

區塊鏈後端 提供區塊鏈數據。BDK 支持多種後端:Electrum、Esplora、Bitcoin Core RPC 等。你可以根據需求選擇。

數據庫後端 存儲錢包狀態(已知交易、UTXO 等)。BDK 支持內存數據庫、SQLite 等。

2.2 創建 BDK 錢包

讓我們從創建一個簡單的錢包開始。我們將使用助記詞生成密鑰,創建原生 SegWit(BIP84)錢包。

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
use bdk::{
    Wallet,
    database::MemoryDatabase,
    bitcoin::Network,
};
use bip39::Mnemonic;

fn create_wallet() -> anyhow::Result<Wallet<MemoryDatabase>> {
    // 生成新的助記詞(實際應用中應該讓用戶備份)
    let mnemonic = Mnemonic::generate(12)?;
    println!("請備份您的助記詞: {}", mnemonic);

    // 從助記詞派生主密鑰
    let seed = mnemonic.to_seed("");
    let xprv = bitcoin::bip32::Xpriv::new_master(Network::Testnet, &seed)?;

    // 創建 BIP84 描述符
    // 外部鏈(接收地址):m/84'/1'/0'/0/*
    // 內部鏈(找零地址):m/84'/1'/0'/1/*
    let external = format!("wpkh({}/84'/1'/0'/0/*)", xprv);
    let internal = format!("wpkh({}/84'/1'/0'/1/*)", xprv);

    // 創建錢包
    let wallet = Wallet::new(
        &external,
        Some(&internal),
        Network::Testnet,
        MemoryDatabase::default(),
    )?;

    // 生成幾個地址
    println!("\n生成的地址:");
    for i in 0..3 {
        let addr = wallet.get_address(bdk::wallet::AddressIndex::New)?;
        println!("  {}: {}", i, addr);
    }

    Ok(wallet)
}

描述符的威力在於其靈活性。你可以輕鬆創建不同類型的錢包:

1
2
3
4
5
6
7
8
// 單簽 P2WPKH(原生 SegWit)
let single_sig = "wpkh([fingerprint/84'/0'/0']xpub.../0/*)";

// 2-of-3 多簽
let multisig = "wsh(multi(2,[fp1]xpub1.../0/*,[fp2]xpub2.../0/*,[fp3]xpub3.../0/*))";

// Taproot
let taproot = "tr([fingerprint/86'/0'/0']xpub.../0/*)";

2.3 同步和查詢

創建錢包後,需要與區塊鏈同步以獲取餘額和交易歷史。

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
use bdk::{
    blockchain::{ElectrumBlockchain, GetHeight},
    SyncOptions,
};

fn sync_and_query(wallet: &Wallet<MemoryDatabase>) -> anyhow::Result<()> {
    // 創建區塊鏈後端
    let blockchain = ElectrumBlockchain::from(
        electrum_client::Client::new("ssl://electrum.blockstream.info:60002")?
    );

    println!("當前區塊高度: {}", blockchain.get_height()?);

    // 同步錢包
    println!("正在同步...");
    wallet.sync(&blockchain, SyncOptions::default())?;
    println!("同步完成");

    // 查詢餘額
    let balance = wallet.get_balance()?;
    println!("\n餘額:");
    println!("  已確認: {} sat", balance.confirmed);
    println!("  待確認入帳: {} sat", balance.untrusted_pending);
    println!("  待確認出帳: {} sat", balance.trusted_pending);

    // 列出 UTXO
    let utxos = wallet.list_unspent()?;
    if !utxos.is_empty() {
        println!("\nUTXO:");
        for utxo in utxos {
            println!("  {}:{} - {} sat",
                utxo.outpoint.txid,
                utxo.outpoint.vout,
                utxo.txout.value);
        }
    }

    Ok(())
}

2.4 構建和發送交易

BDK 的交易構建器提供了直觀的 API 來創建交易。它自動處理選幣、費用計算和找零。

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
use bdk::{SignOptions, FeeRate};
use bdk::blockchain::Blockchain;

fn send_transaction(
    wallet: &Wallet<MemoryDatabase>,
    blockchain: &ElectrumBlockchain,
    recipient: &Address,
    amount_sat: u64,
) -> anyhow::Result<Txid> {
    // 構建交易
    let mut builder = wallet.build_tx();

    builder
        // 添加接收者
        .add_recipient(recipient.script_pubkey(), amount_sat)
        // 設置費率(sat/vB)
        .fee_rate(FeeRate::from_sat_per_vb(10.0))
        // 啟用 RBF(允許稍後提高費率)
        .enable_rbf();

    // 完成構建
    let (mut psbt, tx_details) = builder.finish()?;

    println!("交易詳情:");
    println!("  發送金額: {} sat", amount_sat);
    println!("  手續費: {} sat", tx_details.fee.unwrap_or(0));

    // 簽名
    let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
    if !finalized {
        anyhow::bail!("簽名未完成");
    }

    // 提取最終交易
    let tx = psbt.extract_tx();
    let txid = tx.compute_txid();

    // 廣播
    blockchain.broadcast(&tx)?;
    println!("交易已廣播: {}", txid);

    Ok(txid)
}

BDK 還支持更複雜的場景,如手動選幣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn advanced_transaction(wallet: &Wallet<MemoryDatabase>) -> anyhow::Result<()> {
    let mut builder = wallet.build_tx();

    // 手動選擇特定的 UTXO
    let utxos = wallet.list_unspent()?;
    if let Some(utxo) = utxos.first() {
        builder.add_utxo(utxo.outpoint)?;
    }

    // 或者使用特定的選幣策略
    // builder.coin_selection(LargestFirstCoinSelection);

    // 添加 OP_RETURN 數據
    // builder.add_data(&[1, 2, 3, 4]);

    // 設置自定義序列號(用於時間鎖)
    // builder.set_sequence(Sequence::from_height(144));

    Ok(())
}

3. 安全考量

3.1 密鑰管理

密鑰管理是 Bitcoin 應用最關鍵的安全面向。私鑰一旦洩露,資金就會永久丟失。以下是一些最佳實踐。

內存安全:敏感數據(私鑰、助記詞)在使用後應該立即從內存中清除。Rust 的 zeroize crate 提供了這個功能。

1
2
3
4
5
6
7
8
9
use zeroize::Zeroize;

fn secure_key_handling() {
    let mut secret = [0u8; 32];
    // ... 使用 secret ...

    // 使用完畢後清零
    secret.zeroize();
}

永遠不要記錄敏感數據:日誌中不應該出現私鑰、助記詞或其他敏感信息。這是一個常見的錯誤。

1
2
3
4
5
// 錯誤!不要這樣做
println!("私鑰: {}", private_key);

// 正確:只記錄非敏感信息
println!("正在處理地址: {}", address);

考慮使用硬體錢包:對於生產環境,考慮整合硬體錢包(如 Ledger 或 Trezor)。私鑰永遠不離開硬體設備,大大降低了被盜風險。

3.2 輸入驗證

永遠不要信任外部輸入。在處理用戶提供的地址或金額之前,必須驗證其有效性。

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
fn validate_inputs(
    address_str: &str,
    amount_sat: u64,
    network: Network,
) -> anyhow::Result<(Address, Amount)> {
    // 驗證地址格式
    let address = Address::from_str(address_str)
        .map_err(|e| anyhow::anyhow!("無效的地址格式: {}", e))?;

    // 驗證網路
    if !address.is_valid_for_network(network) {
        anyhow::bail!("地址與網路不匹配");
    }

    // 驗證金額
    if amount_sat < 546 {
        anyhow::bail!("金額低於粉塵限制");
    }

    if amount_sat > 21_000_000 * 100_000_000 {
        anyhow::bail!("金額超過 Bitcoin 總供應量");
    }

    let amount = Amount::from_sat(amount_sat);
    Ok((address.assume_checked(), amount))
}

3.3 錯誤處理

適當的錯誤處理對於安全和用戶體驗都很重要。使用結構化的錯誤類型,並確保不在錯誤消息中洩露敏感信息。

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
use thiserror::Error;

#[derive(Error, Debug)]
pub enum WalletError {
    #[error("餘額不足")]
    InsufficientFunds {
        required: u64,
        available: u64,
    },

    #[error("無效的接收地址")]
    InvalidAddress,

    #[error("網路連接失敗")]
    NetworkError(#[from] std::io::Error),

    #[error("交易構建失敗")]
    TransactionError(String),
}

// 在日誌中只顯示安全的信息
impl WalletError {
    pub fn safe_message(&self) -> &str {
        match self {
            Self::InsufficientFunds { .. } => "餘額不足",
            Self::InvalidAddress => "地址驗證失敗",
            Self::NetworkError(_) => "網路連接問題",
            Self::TransactionError(_) => "交易處理失敗",
        }
    }
}

4. 完整錢包應用範例

4.1 架構設計

一個生產級的錢包應用需要考慮許多面向:用戶界面、狀態管理、錯誤處理、日誌記錄等。這裡我們展示一個簡化但完整的命令列錢包。

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
pub struct WalletApp {
    wallet: Wallet<SqliteDatabase>,
    blockchain: ElectrumBlockchain,
    network: Network,
}

impl WalletApp {
    /// 創建新錢包
    pub fn create(
        name: &str,
        mnemonic: &str,
        passphrase: &str,
        network: Network,
        data_dir: &Path,
    ) -> anyhow::Result<Self> {
        // 驗證助記詞
        let mnemonic = Mnemonic::parse(mnemonic)?;

        // 派生密鑰
        let seed = mnemonic.to_seed(passphrase);
        let xprv = Xpriv::new_master(network, &seed)?;

        // 構建描述符
        let coin_type = if network == Network::Bitcoin { "0" } else { "1" };
        let external = format!("wpkh({}/84'/{}'/ 0'/0/*)", xprv, coin_type);
        let internal = format!("wpkh({}/84'/{}'/0'/1/*)", xprv, coin_type);

        // 創建數據庫
        let db_path = data_dir.join(format!("{}.db", name));
        let database = SqliteDatabase::new(&db_path)?;

        // 創建錢包
        let wallet = Wallet::new(&external, Some(&internal), network, database)?;

        // 連接區塊鏈
        let electrum_url = match network {
            Network::Bitcoin => "ssl://electrum.blockstream.info:60002",
            _ => "ssl://electrum.blockstream.info:60002",
        };
        let blockchain = ElectrumBlockchain::from(
            electrum_client::Client::new(electrum_url)?
        );

        Ok(Self { wallet, blockchain, network })
    }

    /// 同步錢包
    pub fn sync(&self) -> anyhow::Result<()> {
        self.wallet.sync(&self.blockchain, SyncOptions::default())?;
        Ok(())
    }

    /// 獲取餘額
    pub fn balance(&self) -> anyhow::Result<Balance> {
        let b = self.wallet.get_balance()?;
        Ok(Balance {
            confirmed: b.confirmed,
            pending: b.untrusted_pending + b.trusted_pending,
        })
    }

    /// 獲取新地址
    pub fn new_address(&self) -> anyhow::Result<Address> {
        Ok(self.wallet.get_address(AddressIndex::New)?.address)
    }

    /// 發送交易
    pub fn send(
        &self,
        recipient: &Address,
        amount: Amount,
        fee_rate: f32,
    ) -> anyhow::Result<Txid> {
        // 驗證
        if !recipient.is_valid_for_network(self.network) {
            anyhow::bail!("地址網路不匹配");
        }

        // 構建交易
        let mut builder = self.wallet.build_tx();
        builder
            .add_recipient(recipient.script_pubkey(), amount.to_sat())
            .fee_rate(FeeRate::from_sat_per_vb(fee_rate))
            .enable_rbf();

        let (mut psbt, _) = builder.finish()?;

        // 簽名
        self.wallet.sign(&mut psbt, SignOptions::default())?;

        // 廣播
        let tx = psbt.extract_tx();
        self.blockchain.broadcast(&tx)?;

        Ok(tx.compute_txid())
    }
}

pub struct Balance {
    pub confirmed: u64,
    pub pending: u64,
}

4.2 命令列介面

使用 clap crate 可以輕鬆創建命令列介面:

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
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "btc-wallet")]
#[command(about = "簡單的 Bitcoin 錢包")]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    #[arg(long, default_value = "testnet")]
    network: String,

    #[arg(long, default_value = "~/.btc-wallet")]
    data_dir: String,
}

#[derive(Subcommand)]
enum Commands {
    /// 創建新錢包
    Create { name: String },

    /// 從助記詞恢復
    Restore { name: String },

    /// 顯示餘額
    Balance { name: String },

    /// 獲取新地址
    Address { name: String },

    /// 發送 Bitcoin
    Send {
        name: String,
        to: String,
        amount: u64,
        #[arg(default_value = "10")]
        fee_rate: f32,
    },

    /// 列出交易
    History { name: String },
}

fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Create { name } => {
            // 生成助記詞並創建錢包
            let mnemonic = Mnemonic::generate(12)?;
            println!("您的助記詞(請安全備份):\n{}", mnemonic);

            let wallet = WalletApp::create(
                &name,
                &mnemonic.to_string(),
                "",
                parse_network(&cli.network)?,
                Path::new(&cli.data_dir),
            )?;

            let address = wallet.new_address()?;
            println!("\n錢包已創建");
            println!("您的第一個地址: {}", address);
        }

        Commands::Balance { name } => {
            let wallet = load_wallet(&name, &cli)?;
            wallet.sync()?;
            let balance = wallet.balance()?;
            println!("已確認: {} sat", balance.confirmed);
            println!("待確認: {} sat", balance.pending);
        }

        // ... 其他命令 ...
    }

    Ok(())
}

5. 實戰專案:支付處理器

讓我們構建一個簡單的支付處理器,展示如何將所學知識應用到實際場景。

5.1 設計

支付處理器需要:

  1. 為每筆訂單生成唯一的支付地址
  2. 監控地址的入帳
  3. 確認付款後通知商家
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub struct PaymentProcessor {
    wallet: WalletApp,
    pending_payments: HashMap<String, PaymentRequest>,
}

pub struct PaymentRequest {
    pub order_id: String,
    pub address: Address,
    pub amount: Amount,
    pub expires_at: u64,
    pub callback_url: String,
}

pub struct PaymentConfirmation {
    pub order_id: String,
    pub txid: Txid,
    pub amount: Amount,
    pub confirmations: u32,
}

5.2 創建支付請求

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
impl PaymentProcessor {
    pub fn create_payment(
        &mut self,
        order_id: String,
        amount: Amount,
        callback_url: String,
        ttl_seconds: u64,
    ) -> anyhow::Result<PaymentRequest> {
        // 生成新地址
        let address = self.wallet.new_address()?;

        // 計算過期時間
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)?
            .as_secs();
        let expires_at = now + ttl_seconds;

        let request = PaymentRequest {
            order_id: order_id.clone(),
            address: address.clone(),
            amount,
            expires_at,
            callback_url,
        };

        // 存儲待處理支付
        self.pending_payments.insert(order_id, request.clone());

        Ok(request)
    }
}

5.3 監控支付

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
impl PaymentProcessor {
    pub async fn monitor_payments(&mut self) {
        let mut interval = tokio::time::interval(Duration::from_secs(30));

        loop {
            interval.tick().await;

            // 同步錢包
            if let Err(e) = self.wallet.sync() {
                eprintln!("同步錯誤: {}", e);
                continue;
            }

            // 檢查每個待處理支付
            let now = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs();

            let mut completed = Vec::new();
            let mut expired = Vec::new();

            for (order_id, request) in &self.pending_payments {
                // 檢查過期
                if now > request.expires_at {
                    expired.push(order_id.clone());
                    continue;
                }

                // 檢查是否收到付款
                if let Some(confirmation) = self.check_payment(request) {
                    if confirmation.confirmations >= 1 {
                        self.notify_payment(&request.callback_url, &confirmation).await;
                        completed.push(order_id.clone());
                    }
                }
            }

            // 清理已完成和過期的支付
            for id in completed {
                self.pending_payments.remove(&id);
            }
            for id in expired {
                self.pending_payments.remove(&id);
            }
        }
    }

    fn check_payment(&self, request: &PaymentRequest) -> Option<PaymentConfirmation> {
        // 查詢地址的交易
        // 檢查是否有符合金額的入帳
        // 返回確認信息
        None
    }

    async fn notify_payment(&self, url: &str, confirmation: &PaymentConfirmation) {
        // 發送 HTTP 回調通知商家
    }
}

6. 測試策略

6.1 單元測試

單元測試應該覆蓋所有核心邏輯,不依賴外部服務。

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
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_address_generation() {
        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
        // 這是一個已知的測試助記詞,可以驗證派生結果

        let seed = Mnemonic::parse(mnemonic).unwrap().to_seed("");
        let xprv = Xpriv::new_master(Network::Testnet, &seed).unwrap();

        // 驗證派生的地址符合預期
    }

    #[test]
    fn test_amount_validation() {
        // 有效金額
        assert!(validate_amount(1000, 546, 1_000_000).is_ok());

        // 低於粉塵限制
        assert!(validate_amount(100, 546, 1_000_000).is_err());

        // 超過上限
        assert!(validate_amount(10_000_000, 546, 1_000_000).is_err());
    }
}

6.2 Regtest 整合測試

對於需要實際區塊鏈互動的測試,使用 regtest 網路。Regtest 是一個本地測試網路,你可以隨時生成區塊,非常適合測試。

1
2
3
4
5
# 啟動 regtest 節點
bitcoind -regtest -daemon

# 生成區塊
bitcoin-cli -regtest generatetoaddress 101 <your-address>
1
2
3
4
5
6
7
8
9
10
11
12
#[cfg(test)]
mod integration_tests {
    #[test]
    #[ignore]  // 需要 regtest 環境
    fn test_full_transaction_flow() {
        // 1. 連接到 regtest 節點
        // 2. 生成一些區塊以獲得可花費的 UTXO
        // 3. 創建並發送交易
        // 4. 生成區塊確認交易
        // 5. 驗證結果
    }
}

7. 總結

本系列帶你從零開始學習使用 Rust 開發 Bitcoin 應用。我們涵蓋了:

第一篇:Rust 環境設置、rust-bitcoin 基礎類型、密碼學原語

第二篇:HD 錢包、地址生成、UTXO 模型、交易構建

第三篇:Bitcoin Script、多簽、ECDSA 和 Schnorr 簽名、Miniscript

第四篇:節點互動、BDK 錢包開發、安全最佳實踐

進一步學習

掌握了這些基礎後,你可以繼續探索:

  • Taproot 深入:MAST、腳本樹、MuSig2
  • 閃電網路:使用 LDK(Lightning Development Kit)
  • 隱私技術:CoinJoin、PayJoin、Silent Payments
  • Layer 2:狀態通道、Rollups

Bitcoin 是一個不斷發展的生態系統。保持學習,關注 BIP 提案和社區討論,你會發現這個領域有無限的可能性。


參考資源

官方文檔

學習資源

社群


恭喜完成 Rust Bitcoin 開發入門系列!你現在有了構建 Bitcoin 應用的堅實基礎。實踐是最好的學習方式——開始構建你的第一個專案吧!

// 分享

CP

Cypherpunks Taiwan

密碼學使自由和隱私再次偉大。Cryptography makes freedom and privacy great again.

// 留言