區塊鏈錢包技術實現理論 - 企業級系統架構解析
📚 從實際生產環境專案學習區塊鏈錢包技術與理論
來源專案:blockchain-listener-modify 整理日期:2025-11-13
本文檔解析企業級區塊鏈錢包系統的技術架構與設計理念
📑 目錄
- 1. 系統概述
- 2. 錢包管理架構理論
- 3. 錢包地址存儲與查詢設計
- 4. ERC20 代幣互動原理
- 5. 區塊鏈 RPC 互動機制
- 6. 交易監聽與處理流程
- 7. 錢包餘額管理機制
- 8. 加密與安全理論
- 9. 多鏈支援架構設計
- 10. 系統整合與協作
1. 系統概述
1.1 系統定位
Blockchain Listener 是一個企業級的區塊鏈監聽與錢包管理系統,專注於:
- 大規模錢包管理:同時管理數萬個錢包地址
- 即時交易監聽:毫秒級響應區塊鏈上的轉帳事件
- 多鏈資產追蹤:統一管理不同區塊鏈的資產
- 資金自動化整合:智能歸集錢包資金
- 審計與對帳:提供完整的歷史追蹤
1.2 核心功能模組
┌────────────────────────────────────────────────────────────┐
│ Blockchain Listener 核心架構 │
├────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 錢包管理 │ │ 交易監聽 │ │ 餘額追蹤 │ │
│ │ 模組 │ │ 模組 │ │ 模組 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 資金整合 │ │ 多鏈支援 │ │ 審計對帳 │ │
│ │ 模組 │ │ 模組 │ │ 模組 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
1.3 支援的區塊鏈網路
| 網路名稱 | 符號 | Chain ID | 用途 |
|---|---|---|---|
| Ethereum | ETH | 1 | 主網,支援多種 ERC20 代幣 |
| Polygon | POL | 137 | Layer 2,低手續費 |
| BNB Smart Chain | BSC | 56 | 幣安生態,交易快速 |
1.4 支援的代幣類型
Ethereum 網路
- 原生代幣:ETH
- ERC20 代幣:USDT、USDC、APE、SHIB、BITO、TON
Polygon 網路
- 原生代幣:POL (原 MATIC)
- ERC20 代幣:USDT、USDC、MV
BSC 網路
- 原生代幣:BNB
- BEP20 代幣:USDT、USDC
1.5 技術架構總覽
┌──────────────────────────────────────────────────────────────┐
│ 技術架構分層圖 │
└──────────────────────────────────────────────────────────────┘
應用層 (Application Layer)
┌────────────────────────────────────┐
│ REST API │ Schedulers │ Consumers │
└────────────────────────────────────┘
↓
業務邏輯層 (Service Layer)
┌────────────────────────────────────┐
│ 錢包服務 │ 監聽服務 │ 整合服務 │
└────────────────────────────────────┘
↓
資料訪問層 (Data Access Layer)
┌────────────────────────────────────┐
│ DAO 模式 │ 資料庫連線池 │
└────────────────────────────────────┘
↓
儲存層 (Storage Layer)
┌────────────────────────────────────┐
│ MySQL │ Kafka │ Cache │
└────────────────────────────────────┘
↓
區塊鏈層 (Blockchain Layer)
┌────────────────────────────────────┐
│ Ethereum │ Polygon │ BSC │
└────────────────────────────────────┘
2. 錢包管理架構理論
2.1 單例模式 (Singleton Pattern)
設計理念
在整個系統生命週期中,錢包管理器只存在一個實例,提供:
- 全局統一訪問點:所有模組使用同一份錢包數據
- 記憶體效率:避免重複載入相同數據
- 狀態一致性:確保所有查詢使用最新的錢包列表
架構圖
┌─────────────────────────────────────────────────────────┐
│ 錢包單例模式架構 │
└─────────────────────────────────────────────────────────┘
系統啟動
↓
┌─────────────────┐
│ 載入所有錢包地址 │
│ (一次性從資料庫) │
└────────┬────────┘
↓
┌─────────────────┐
│ 存入記憶體結構 │
│ • Set (快速查找)│
│ • Map (屬性映射)│
└────────┬────────┘
↓
┌─────────────────────────────────┐
│ 錢包單例 (全局唯一) │
│ │
│ worldpayWalletSet │
│ ├─ 0x123... │
│ ├─ 0x456... │
│ └─ 0x789... │
│ │
│ bitoproWalletSet │
│ ├─ 0xabc... │
│ ├─ 0xdef... │
│ └─ 0xghi... │
│ │
│ worldpayGatewayMap │
│ ├─ 0x123... → gatewayId: 5 │
│ ├─ 0x456... → gatewayId: 3 │
│ └─ 0x789... → gatewayId: 7 │
└─────────────────────────────────┘
↓
┌─────────────────┐
│ 應用各模組使用 │
│ • 交易監聽 │
│ • 餘額查詢 │
│ • 資金整合 │
└─────────────────┘
2.2 資料結構選擇理論
為什麼使用 Set 和 Map?
Set(集合)特性
集合 (Set) 的特點:
┌─────────────────────────────────────┐
│ 特性 │ 說明 │
├─────────────────────────────────────┤
│ 唯一性 │ 自動去重 │
│ 查找速度 │ O(1) 時間複雜度 │
│ 記憶體效率 │ 僅存儲值,無鍵值對 │
│ 適用場景 │ 快速判斷存在性 │
└─────────────────────────────────────┘
Map(映射)特性
映射 (Map) 的特點:
┌─────────────────────────────────────┐
│ 特性 │ 說明 │
├─────────────────────────────────────┤
│ 鍵值對存儲 │ 關聯額外屬性 │
│ 查找速度 │ O(1) 時間複雜度 │
│ 動態更新 │ 易於修改屬性值 │
│ 適用場景 │ 需要關聯數據的查找 │
└─────────────────────────────────────┘
效能對比分析
假設系統有 10,000 個錢包地址:
| 操作 | 陣列 (Array) | Set | Map |
|---|---|---|---|
| 查找地址是否存在 | O(n) ≈ 10,000 次比對 | O(1) ≈ 1 次哈希查找 | O(1) ≈ 1 次哈希查找 |
| 記憶體佔用 | ~420 KB | ~420 KB | ~460 KB |
| 新增地址 | O(1) | O(1) | O(1) |
| 刪除地址 | O(n) | O(1) | O(1) |
結論:Set/Map 在大規模錢包管理中效能優勢明顯
2.3 記憶體管理策略
載入策略:分批載入 (Pagination)
分批載入流程:
┌─────────────────────────────────────┐
│ 資料庫總數:50,000 個錢包地址 │
└─────────────────────────────────────┘
↓
┌─────────────────┐
│ 批次 1: 載入 │
│ Offset: 0 │
│ Limit: 10,000 │
└────────┬────────┘
↓
┌─────────────────┐
│ 批次 2: 載入 │
│ Offset: 10,000 │
│ Limit: 10,000 │
└────────┬────────┘
↓
┌─────────────────┐
│ 批次 3: 載入 │
│ Offset: 20,000 │
│ Limit: 10,000 │
└────────┬────────┘
↓
......
↓
┌─────────────────┐
│ 全部載入完成 │
│ 總數:50,000 │
└─────────────────┘
優點:
- ✅ 避免單次查詢過大導致資料庫壓力
- ✅ 防止記憶體瞬間暴增
- ✅ 可顯示載入進度
2.4 錢包分類管理
雙錢包系統設計
系統將錢包分為兩類,分別管理:
錢包分類架構:
┌─────────────────────────────────────────────┐
│ Worldpay 錢包 (業務錢包) │
│ • 用途:處理客戶入金 │
│ • 特性:需要 gatewayId 映射 │
│ • 數量:~30,000 個 │
│ • 管理:worldpayWalletSet │
│ worldpayGatewayMap │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Bitopro 錢包 (交易所錢包) │
│ • 用途:交易所充提幣 │
│ • 特性:無需額外屬性 │
│ • 數量:~20,000 個 │
│ • 管理:bitoproWalletSet │
└─────────────────────────────────────────────┘
查詢邏輯
地址查詢流程:
輸入:某個地址 (例如 0x123...)
↓
┌──────────────────┐
│ 檢查是否在 │ 是 → 返回 true (Bitopro 錢包)
│ bitoproWalletSet│
└────────┬─────────┘
↓ 否
┌──────────────────┐
│ 檢查是否在 │ 是 → 返回 true (Worldpay 錢包)
│ worldpayWalletSet│ + 取得 gatewayId
└────────┬─────────┘
↓ 否
返回 false (非系統錢包)
3. 錢包地址存儲與查詢設計
3.1 資料庫設計理論
錢包資料表結構
┌──────────────────────────────────────────────┐
│ wallets 資料表 │
├──────────────────────────────────────────────┤
│ 欄位名 │ 類型 │ 說明 │
├──────────────────────────────────────────────┤
│ id │ INT │ 主鍵 │
│ address │ VARCHAR(42) │ 錢包地址 │
│ gateway_id │ INT │ 閘道 ID │
│ created_at │ INT │ 建立時間 │
│ deleted_at │ INT │ 刪除時間 │
└──────────────────────────────────────────────┘
索引設計:
• PRIMARY KEY (id)
• UNIQUE KEY (address) ← 確保地址唯一
• INDEX (deleted_at) ← 軟刪除查詢優化
3.2 軟刪除機制
為什麼使用軟刪除?
硬刪除 vs 軟刪除對比:
硬刪除 (DELETE)
┌─────────────────────────────────┐
│ ✗ 資料永久丟失 │
│ ✗ 無法恢復誤刪 │
│ ✗ 失去歷史審計記錄 │
│ ✓ 節省儲存空間 │
└─────────────────────────────────┘
軟刪除 (UPDATE deleted_at)
┌─────────────────────────────────┐
│ ✓ 資料可恢復 │
│ ✓ 保留完整歷史 │
│ ✓ 支援審計追蹤 │
│ ✓ 符合金融業規範 │
│ ✗ 需要額外儲存空間 │
└─────────────────────────────────┘
軟刪除實現邏輯
正常查詢:
SELECT * FROM wallets WHERE deleted_at IS NULL
軟刪除操作:
UPDATE wallets SET deleted_at = UNIX_TIMESTAMP() WHERE address = ?
查詢已刪除:
SELECT * FROM wallets WHERE deleted_at IS NOT NULL
恢復錢包:
UPDATE wallets SET deleted_at = NULL WHERE address = ?
3.3 地址格式標準化
以太坊地址特性
地址格式:
┌─────────────────────────────────────────┐
│ 標準格式:0x + 40 個十六進制字元 │
│ 範例:0x742d35Cc6634C0532925a3b844Bc... │
│ │
│ 關鍵特性: │
│ • 不區分大小寫 │
│ • Checksum 大小寫混合 (EIP-55) │
│ • 總長度固定 42 字元 │
└─────────────────────────────────────────┘
地址統一化處理
由於地址不區分大小寫,系統需要統一處理:
統一化流程:
輸入地址: 0xAbC123...DEF789
↓
┌──────────────────┐
│ 轉換為小寫 │
└────────┬─────────┘
↓
輸出地址: 0xabc123...def789
↓
存入資料庫 / Set / Map
為什麼選擇小寫?
- ✅ 簡化查詢邏輯
- ✅ 避免重複存儲(0xABC... 和 0xabc... 被視為同一地址)
- ✅ 減少比對錯誤
3.4 批次操作設計
批次新增錢包
批次新增流程:
┌──────────────────────────────────┐
│ 輸入:1,000 個錢包地址列表 │
└───────────┬──────────────────────┘
↓
┌───────────────┐
│ 地址驗證 │
│ • 格式檢查 │
│ • 重複檢查 │
└───────┬───────┘
↓
┌───────────────┐
│ 統一化處理 │
│ • 轉小寫 │
└───────┬───────┘
↓
┌───────────────┐
│ 批次 INSERT │
│ (單次 SQL) │
└───────┬───────┘
↓
┌───────────────┐
│ 更新記憶體 │
│ (加入 Set) │
└───────────────┘
效能優勢
| 操作方式 | 1,000 個地址耗時 | 資料庫連線數 |
|---|---|---|
| 逐筆 INSERT | ~5,000 ms | 1,000 次 |
| 批次 INSERT | ~200 ms | 1 次 |
效能提升:~25 倍
4. ERC20 代幣互動原理
4.1 ERC20 標準介面
什麼是 ERC20?
ERC20 (Ethereum Request for Comment 20) 是以太坊上的代幣標準,定義了一組統一的智能合約介面。
核心功能
┌────────────────────────────────────────────┐
│ ERC20 標準介面 │
├────────────────────────────────────────────┤
│ 功能 │ 說明 │
├────────────────────────────────────────────┤
│ balanceOf │ 查詢錢包代幣餘額 │
│ transfer │ 轉帳代幣給他人 │
│ approve │ 授權他人使用代幣 │
│ transferFrom │ 從授權帳戶轉帳 │
│ allowance │ 查詢授權額度 │
│ │ │
│ 事件 │ 說明 │
├────────────────────────────────────────────┤
│ Transfer │ 轉帳事件(from, to, value)│
│ Approval │ 授權事件 │
└────────────────────────────────────────────┘
4.2 智能合約 ABI (Application Binary Interface)
ABI 的作用
ABI 是什麼?
┌─────────────────────────────────────────┐
│ 智能合約的「使用說明書」 │
│ │
│ 定義了: │
│ • 函數名稱 │
│ • 參數類型 │
│ • 返回值類型 │
│ • 事件結構 │
└─────────────────────────────────────────┘
Transfer 事件結構
Transfer 事件定義:
┌──────────────────────────────────────┐
│ event Transfer( │
│ address indexed from, ← 轉出方 │
│ address indexed to, ← 接收方 │
│ uint256 value ← 金額 │
│ ) │
└──────────────────────────────────────┘
為什麼 from 和 to 是 indexed?
• indexed 參數可作為過濾條件
• 快速查詢特定地址的轉帳記錄
• 最多 3 個 indexed 參數
4.3 代幣餘額查詢機制
查詢流程
查詢 USDT 餘額流程:
┌──────────────────────────────────────┐
│ 1. 準備查詢參數 │
│ • 合約地址:USDT 合約 │
│ • 錢包地址:要查詢的地址 │
└───────────┬──────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 2. 呼叫 balanceOf(address) │
│ • 這是一個 view 函數 │
│ • 不消耗 Gas │
│ • 直接從區塊鏈狀態讀取 │
└───────────┬──────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 3. 取得原始餘額 (uint256) │
│ 範例:1000000 (6 個零) │
└───────────┬──────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 4. 根據 decimals 格式化 │
│ • USDT decimals = 6 │
│ • 1000000 / 10^6 = 1.0 USDT │
└──────────────────────────────────────┘
Decimals 概念
不同代幣的 Decimals:
┌──────────────────────────────────────┐
│ 代幣 │ Decimals │ 範例 │
├──────────────────────────────────────┤
│ ETH │ 18 │ 1 ETH = 10^18 Wei │
│ USDT │ 6 │ 1 USDT = 10^6 單位 │
│ USDC │ 6 │ 1 USDC = 10^6 單位 │
│ WBTC │ 8 │ 1 WBTC = 10^8 Satoshi │
└──────────────────────────────────────┘
為什麼需要 Decimals?
• 區塊鏈只能處理整數
• Decimals 定義「小數點位置」
• 1.5 USDT 實際存為 1500000
4.4 Transfer 事件監聽
事件監聽原理
事件監聽流程:
┌──────────────────────────────────────┐
│ 1. 定義監聽範圍 │
│ • 合約地址:USDT │
│ • 起始區塊:100000 │
│ • 結束區塊:100100 │
└───────────┬──────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 2. 查詢 Transfer 事件 │
│ • 使用 getPastEvents │
│ • 或直接查詢 Logs │
└───────────┬──────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 3. 解析事件參數 │
│ Event { │
│ from: 0x123... │
│ to: 0x456... │
│ value: 1000000 │
│ transactionHash: 0xabc... │
│ blockNumber: 100050 │
│ } │
└───────────┬──────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 4. 過濾系統關心的事件 │
│ • 檢查 to 是否為系統錢包 │
│ • 是 → 記錄並處理 │
│ • 否 → 忽略 │
└──────────────────────────────────────┘
分批查詢策略
由於 RPC 節點對單次查詢的區塊範圍有限制(通常 1000-5000 個區塊),需要分批查詢:
分批查詢示意:
目標:查詢區塊 10000 到 15000 的 Transfer 事件
分批大小:100 區塊
查詢 1: 10000 → 10099
查詢 2: 10100 → 10199
查詢 3: 10200 → 10299
...
查詢 50: 14900 → 14999
查詢 51: 15000 → 15000
合併所有結果 → 完整事件列表
5. 區塊鏈 RPC 互動機制
5.1 RPC (Remote Procedure Call) 原理
什麼是 RPC?
RPC 通信模型:
┌────────────────┐ ┌────────────────┐
│ 客戶端 │ │ 區塊鏈節點 │
│ (你的應用) │ │ (Ethereum) │
│ │ │ │
│ 1. 發送請求 │───────>│ 3. 執行操作 │
│ (JSON-RPC) │ │ │
│ │ │ 4. 準備回應 │
│ 2. 等待回應 │<───────│ │
│ │ │ │
│ 5. 處理結果 │ │ │
└────────────────┘ └────────────────┘
JSON-RPC 格式
請求範例:查詢區塊高度
{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}
回應範例:
{
"jsonrpc": "2.0",
"result": "0x10d4f", // 十六進制區塊號
"id": 1
}
5.2 常用 RPC 方法
┌──────────────────────────────────────────────┐
│ 常用 RPC 方法分類 │
├──────────────────────────────────────────────┤
│ 區塊相關 │
│ • eth_blockNumber 取得最新區塊號 │
│ • eth_getBlockByNumber 取得區塊詳情 │
│ │
│ 交易相關 │
│ • eth_getTransactionByHash 查詢交易 │
│ • eth_getTransactionReceipt 查詢交易收據 │
│ • eth_sendRawTransaction 廣播交易 │
│ │
│ 帳戶相關 │
│ • eth_getBalance 查詢餘額 │
│ • eth_getTransactionCount 查詢 nonce │
│ │
│ 合約相關 │
│ • eth_call 呼叫合約函數 │
│ • eth_getLogs 查詢事件日誌 │
│ │
│ 進階功能 │
│ • debug_traceTransaction 追蹤內部調用 │
└──────────────────────────────────────────────┘
5.3 多 RPC 節點備援機制
為什麼需要多節點?
單節點風險:
┌─────────────────────────────────┐
│ ✗ 節點故障 → 系統停擺 │
│ ✗ 速率限制 → 請求失敗 │
│ ✗ 網路延遲 → 響應緩慢 │
│ ✗ 節點維護 → 服務中斷 │
└─────────────────────────────────┘
多節點優勢:
┌─────────────────────────────────┐
│ ✓ 故障轉移 │
│ ✓ 負載均衡 │
│ ✓ 速率限制分散 │
│ ✓ 高可用性 │
└─────────────────────────────────┘
多節點架構
多 RPC 節點配置:
┌──────────────────────────────────────┐
│ Ethereum 網路 (networkId: 1) │
├──────────────────────────────────────┤
│ 節點 1 (Priority: 1) │
│ • Alchemy │
│ • 速度快、穩定性高 │
│ • 有速率限制 │
├──────────────────────────────────────┤
│ 節點 2 (Priority: 2) │
│ • LlamaRPC │
│ • 免費、無速率限制 │
│ • 可能較慢 │
├──────────────────────────────────────┤
│ 節點 3 (Priority: 3) │
│ • Ankr │
│ • 備用節點 │
│ • 故障時使用 │
└──────────────────────────────────────┘
節點切換邏輯
節點切換流程:
┌─────────────────┐
│ 使用節點 1 │
│ (最高優先級) │
└────────┬────────┘
↓
┌────────┐
│ 請求成功│ ─ 是 → 返回結果
└───┬────┘
↓ 否
┌───────────────┐
│ 錯誤類型? │
└───┬───────────┘
↓
┌─────────────────┐ 是 ┌─────────────┐
│ 速率限制? │ ────→ │ 切換節點 2 │
└─────────────────┘ └─────────────┘
↓ 否
┌─────────────────┐ 是 ┌─────────────┐
│ 連線超時? │ ────→ │ 切換節點 2 │
└─────────────────┘ └─────────────┘
↓ 否
┌─────────────────┐ 是 ┌─────────────┐
│ 節點維護? │ ────→ │ 切換節點 2 │
└─────────────────┘ └─────────────┘
↓ 否
拋出錯誤
5.4 Web3.js vs Ethers.js 選擇
兩個函式庫的定位
┌─────────────────────────────────────────────┐
│ Web3.js │
├─────────────────────────────────────────────┤
│ 優勢: │
│ • 更早期,生態成熟 │
│ • 事件監聽功能完善 │
│ • getPastEvents 方法方便 │
│ │
│ 適用場景: │
│ • 合約事件監聽 │
│ • 需要向下相容舊專案 │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Ethers.js │
├─────────────────────────────────────────────┤
│ 優勢: │
│ • 更輕量 (~88 KB vs ~200 KB) │
│ • 錯誤處理更友善 │
│ • TypeScript 原生支援 │
│ • 交易簽名更安全 │
│ │
│ 適用場景: │
│ • 交易廣播與簽名 │
│ • 新專案開發 │
│ • TypeScript 專案 │
└─────────────────────────────────────────────┘
專案中的使用策略
功能分工:
┌────────────────────────────────────┐
│ 監聽 ERC20 Transfer 事件 │
│ → 使用 Web3.js │
│ 理由:getPastEvents 更直觀 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 廣播簽名交易 │
│ → 使用 Ethers.js │
│ 理由:錯誤處理更完善 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 查詢區塊、交易資訊 │
│ → 兩者皆可 │
│ 根據專案習慣選擇 │
└────────────────────────────────────┘
6. 交易監聽與處理流程
6.1 交易監聽架構設計
雙軌監聽系統
系統使用兩種方式監聽交易:
┌──────────────────────────────────────────┐
│ ERC20 代幣轉帳監聽 │
│ (Token Transfer Listener) │
├──────────────────────────────────────────┤
│ 監聽方式:查詢 Transfer 事件 │
│ 監聽對象:USDT、USDC、APE 等 │
│ 監聽頻率:每 30 秒 │
│ 資料來源:eth_getLogs │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ 原生代幣轉帳監聽 │
│ (Native Transfer Listener) │
├──────────────────────────────────────────┤
│ 監聽方式:逐區塊掃描交易 │
│ 監聽對象:ETH、POL、BNB │
│ 監聽頻率:每 15 秒 │
│ 資料來源:eth_getBlockByNumber │
│ + debug_traceTransaction │
└──────────────────────────────────────────┘
6.2 ERC20 監聽流程詳解
完整監聽流程
ERC20 Transfer 監聽週期:
┌─────────────────────────────────────┐
│ 1. 讀取上次掃描位置 │
│ • 從 last_block_numbers 表 │
│ • 取得 networkId + tokenId │
│ • 範例:上次掃描到區塊 10000 │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. 查詢當前區塊高度 │
│ • 呼叫 eth_blockNumber │
│ • 範例:當前區塊 10100 │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. 計算掃描範圍 │
│ • fromBlock: 10001 │
│ • toBlock: 10100 │
│ • 共 100 個區塊 │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. 查詢 Transfer 事件 │
│ • 使用 getPastEvents('Transfer')│
│ • 過濾合約地址 │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 5. 逐個事件處理 │
│ For each event: │
│ • 解析 from、to、value │
│ • 檢查 to 是否為系統錢包 │
│ • 是 → 發送到 Kafka │
│ • 否 → 忽略 │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 6. 更新掃描位置 │
│ • 更新 last_block_numbers │
│ • 設為 10100 │
└───────────┬─────────────────────────┘
↓
等待 30 秒,重複流程
6.3 原生代幣監聽流程
為什麼更複雜?
原生代幣(ETH/POL/BNB)的轉帳不會產生事件,需要:
- 掃描每個區塊的所有交易
- 使用
debug_traceTransaction追蹤內部轉帳
內部轉帳 (Internal Transaction) 概念
什麼是內部轉帳?
┌──────────────────────────────────────┐
│ 外部交易 (External Transaction) │
│ • 用戶直接發起的轉帳 │
│ • 在交易列表中可見 │
│ • 範例:Alice 轉 1 ETH 給 Bob │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 內部交易 (Internal Transaction) │
│ • 智能合約執行過程中的轉帳 │
│ • 不在交易列表中顯示 │
│ • 需要 trace 才能發現 │
│ • 範例:DeFi 合約分配收益給用戶 │
└──────────────────────────────────────┘
內部轉帳追蹤流程
debug_traceTransaction 流程:
┌─────────────────────────────────────┐
│ 1. 發現一筆交易 │
│ txHash: 0xabc123... │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. 呼叫 debug_traceTransaction │
│ • 重新執行交易 │
│ • 記錄每一步操作 │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. 分析 trace 結果 │
│ Trace { │
│ type: "CALL", │
│ from: "0x123...", │
│ to: "0x456...", │
│ value: "1000000000000000000", │
│ gas: 21000, │
│ gasUsed: 21000 │
│ } │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. 提取所有轉帳操作 │
│ • 可能有多層調用 │
│ • 合約 A → 合約 B → 用戶 │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 5. 檢查是否涉及系統錢包 │
│ • 檢查每個 to 地址 │
│ • 匹配 → 記錄 │
└─────────────────────────────────────┘
6.4 Kafka 異步處理架構
為什麼使用 Kafka?
同步處理 vs 異步處理:
同步處理 (不推薦)
┌─────────────────────────────────┐
│ 監聽 → 處理 → 儲存 → 更新餘額 │
│ ✗ 阻塞監聽流程 │
│ ✗ 處理慢導致漏掉區塊 │
│ ✗ 無法擴展 │
└─────────────────────────────────┘
異步處理 (Kafka)
┌─────────────────────────────────┐
│ 監聽 → 發送到 Kafka │
│ ↓ │
│ Consumer 處理 (獨立進程) │
│ ✓ 監聽不阻塞 │
│ ✓ 可以延遲處理 │
│ ✓ 可以水平擴展 Consumer │
│ ✓ 訊息不會丟失 │
└─────────────────────────────────┘
Kafka 訊息流
Kafka 訊息處理流程:
┌─────────────────────────────────────────┐
│ Scheduler (生產者) │
│ • ERC20 Transfer Scheduler │
│ • Native Transfer Scheduler │
└───────────┬─────────────────────────────┘
↓ 發送訊息
┌─────────────────────────────────────────┐
│ Kafka Topic: blockchain-listener-rescan│
│ │
│ 訊息格式: │
│ { │
│ networkId: 1, │
│ tokenId: 2, │
│ transactionHash: "0xabc...", │
│ to: "0x123...", │
│ value: "1000000" │
│ } │
└───────────┬─────────────────────────────┘
↓ 消費訊息
┌─────────────────────────────────────────┐
│ Rescan Consumer (消費者) │
│ • 查詢完整交易資料 │
│ • 儲存到 received_transactions │
│ • 發送餘額更新訊息 │
└───────────┬─────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Kafka Topic: blockchain-listener-balance│
└───────────┬─────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Balance Consumer (消費者) │
│ • 查詢鏈上餘額 │
│ • 更新 wallet_balances │
└─────────────────────────────────────────┘
6.5 區塊掃描狀態管理
last_block_numbers 資料表
掃描狀態記錄:
┌──────────────────────────────────────┐
│ network_id │ token_id │ block_number│
├──────────────────────────────────────┤
│ 1 │ 2 │ 10000 │ ← ETH USDT
│ 1 │ 3 │ 10050 │ ← ETH USDC
│ 1 │ 0 │ 9950 │ ← ETH 原生
│ 2 │ 0 │ 5000 │ ← Polygon 原生
│ 2 │ 5 │ 5100 │ ← Polygon USDT
└──────────────────────────────────────┘
作用:
• 系統重啟後從上次位置繼續
• 避免重複掃描
• 支援多 Scheduler 並行
7. 錢包餘額管理機制
7.1 餘額資料模型
雙層儲存架構
┌─────────────────────────────────────────┐
│ 即時餘額表 (wallet_balances) │
├─────────────────────────────────────────┤
│ 用途:儲存最新的錢包餘額 │
│ 更新:每次收到轉帳後更新 │
│ 查詢:提供給前端 API 快速查詢 │
│ │
│ 結構: │
│ • wallet_id (錢包 ID) │
│ • network_id (網路 ID) │
│ • token_id (代幣 ID) │
│ • balance (餘額,Wei 格式) │
│ • updated_at (更新時間) │
│ │
│ 唯一約束:(wallet_id, network_id, token_id)│
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ 歷史快照表 (wallet_balance_snapshots) │
├─────────────────────────────────────────┤
│ 用途:審計與對帳 │
│ 更新:定時(每日)建立快照 │
│ 查詢:用於歷史資料分析 │
│ │
│ 結構: │
│ • wallet_id │
│ • network_id │
│ • token_id │
│ • balance │
│ • snapshot_at (快照時間戳) │
│ • created_at (記錄建立時間) │
└─────────────────────────────────────────┘
7.2 餘額更新流程
觸發時機
什麼時候更新餘額?
┌─────────────────────────────────────┐
│ 1. 接收到轉帳 │
│ • Rescan Consumer 處理完交易 │
│ • 發送餘額更新訊息到 Kafka │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 2. 手動觸發查詢 │
│ • 管理員透過 API 請求 │
│ • 對帳時批次更新 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 3. 定時全量更新 │
│ • 每日凌晨 3:00 │
│ • 確保數據準確性 │
└─────────────────────────────────────┘
更新流程詳解
餘額更新完整流程:
┌─────────────────────────────────────┐
│ 1. 接收更新請求 │
│ From: Kafka balance topic │
│ Data: { │
│ walletAddress: "0x123...", │
│ networkId: 1, │
│ tokenId: 2 │
│ } │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. 查詢鏈上餘額 │
│ IF tokenId == 0: │
│ → 呼叫 eth_getBalance │
│ ELSE: │
│ → 呼叫 ERC20 balanceOf │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. 取得原始餘額 │
│ 範例: │
│ • USDT: "1500000" (6 decimals) │
│ • 代表 1.5 USDT │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. 更新資料庫 │
│ • 查找現有記錄 │
│ • 存在 → UPDATE │
│ • 不存在 → INSERT │
│ • 更新 updated_at │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 5. 記錄變更日誌 │
│ • 舊餘額 vs 新餘額 │
│ • 變化量 │
│ • 變化原因(txHash) │
└─────────────────────────────────────┘
7.3 餘額格式化處理
Wei / 原始單位 概念
為什麼鏈上存儲的是「大整數」?
┌─────────────────────────────────────┐
│ 區塊鏈特性: │
│ • 只能處理整數運算 │
│ • 浮點數會有精度問題 │
│ • 使用「最小單位」存儲 │
└─────────────────────────────────────┘
單位轉換範例:
┌─────────────────────────────────────┐
│ ETH (18 decimals) │
│ • 鏈上:1000000000000000000 Wei │
│ • 顯示:1.0 ETH │
│ • 計算:value / 10^18 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ USDT (6 decimals) │
│ • 鏈上:1500000 │
│ • 顯示:1.5 USDT │
│ • 計算:value / 10^6 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ WBTC (8 decimals) │
│ • 鏈上:150000000 │
│ • 顯示:1.5 WBTC │
│ • 計算:value / 10^8 │
└─────────────────────────────────────┘
數值精度處理
大數值問題:
┌─────────────────────────────────────┐
│ JavaScript Number 類型限制: │
│ • 最大安全整數:2^53 - 1 │
│ • ETH 最小單位:10^18 │
│ • 會發生精度丟失! │
└─────────────────────────────────────┘
解決方案:使用 BigInt 或字串
┌─────────────────────────────────────┐
│ 資料庫存儲:VARCHAR(78) │
│ • 足夠存儲 uint256 最大值 │
│ • 避免數值溢出 │
│ │
│ 計算時:使用 BigInt 或 BigNumber │
│ • Ethers.js: BigNumber │
│ • JavaScript: BigInt │
│ │
│ 顯示時:格式化為 decimal string │
│ • 使用 formatUnits(value, decimals)│
└─────────────────────────────────────┘
7.4 餘額快照機制
為什麼需要快照?
快照的用途:
┌─────────────────────────────────────┐
│ 1. 審計追蹤 │
│ • 每日記錄所有錢包餘額 │
│ • 可追溯任意時間點的資產狀況 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 2. 對帳驗證 │
│ • 鏈上餘額 vs 資料庫餘額 │
│ • 發現數據不一致 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 3. 財務報表 │
│ • 每日資產總量統計 │
│ • 資產變動趨勢分析 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 4. 合規要求 │
│ • 金融業務需要完整記錄 │
│ • 監管機構可能要求提供 │
└─────────────────────────────────────┘
快照生成流程
每日快照生成流程:
┌─────────────────────────────────────┐
│ 時間:每日 03:00 (UTC) │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 1. 查詢所有活躍錢包 │
│ • 從 wallets 表 │
│ • WHERE deleted_at IS NULL │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. 批次查詢鏈上餘額 │
│ For each wallet: │
│ • 查詢所有支援的 token │
│ • ETH, USDT, USDC... │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. 記錄快照 │
│ INSERT INTO │
│ wallet_balance_snapshots │
│ • snapshot_at = UNIX_TIMESTAMP()│
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. 生成對帳報告 │
│ • 總資產統計 │
│ • 與昨日比較 │
│ • 異常告警 │
└─────────────────────────────────────┘
8. 加密與安全理論
8.1 AES 加密原理
AES (Advanced Encryption Standard)
AES-128-ECB 模式:
┌─────────────────────────────────────┐
│ 演算法:AES │
│ 金鑰長度:128 bits (16 bytes) │
│ 加密模式:ECB (Electronic Codebook) │
│ 區塊大小:128 bits │
└─────────────────────────────────────┘
加密流程
加密過程:
明文 (Plaintext)
↓
┌─────────────────┐
│ 轉換為 bytes │
└────────┬────────┘
↓
┌─────────────────┐
│ 金鑰擴展 │
│ • 128 bit key │
│ • 生成輪密鑰 │
└────────┬────────┘
↓
┌─────────────────┐
│ AES 加密 │
│ • 10 輪運算 │
│ • SubBytes │
│ • ShiftRows │
│ • MixColumns │
│ • AddRoundKey │
└────────┬────────┘
↓
密文 (Ciphertext)
↓
轉為十六進制字串
金鑰轉換機制
專案使用自定義的金鑰轉換演算法:
金鑰轉換流程:
輸入金鑰:"MySecretPassword"
↓
┌─────────────────────────────────────┐
│ 1. 初始化 16 bytes 緩衝區 │
│ [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]│
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. 將輸入金鑰轉為 bytes │
│ "MySecretPassword" → bytes[] │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. XOR 運算 │
│ For i in keyBytes: │
│ buffer[i % 16] ^= keyBytes[i] │
│ │
│ 效果:任意長度密碼 → 16 bytes │
└───────────┬─────────────────────────┘
↓
最終 16 bytes 金鑰
為什麼使用 XOR?
XOR 特性:
┌─────────────────────────────────────┐
│ 可逆性: │
│ • A XOR B = C │
│ • C XOR B = A │
│ • 加密和解密使用相同運算 │
│ │
│ 混淆性: │
│ • 密碼每個字元影響多個位置 │
│ • 增加破解難度 │
└─────────────────────────────────────┘
8.2 敏感資料保護策略
分層保護架構
┌─────────────────────────────────────┐
│ 第一層:環境變數隔離 │
│ • 私鑰、API Key 不寫入程式碼 │
│ • 使用 .env 檔案 │
│ • .env 加入 .gitignore │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 第二層:資料庫加密存儲 │
│ • 私鑰使用 AES 加密後存儲 │
│ • 加密金鑰存在環境變數 │
│ • 資料庫洩露也無法直接使用 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 第三層:記憶體內使用 │
│ • 需要時才解密到記憶體 │
│ • 使用後立即清除 │
│ • 避免記憶體 dump 洩露 │
└─────────────────────────────────────┘
私鑰管理最佳實踐
私鑰生命週期管理:
┌─────────────────────────────────────┐
│ 1. 生成階段 │
│ • 使用硬體隨機數生成器 │
│ • 立即加密 │
│ • 安全傳輸給管理員 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 2. 存儲階段 │
│ • 加密後存入資料庫 │
│ • 或使用 HSM (硬體安全模組) │
│ • 設定存取權限 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 3. 使用階段 │
│ • 臨時解密到記憶體 │
│ • 簽名交易 │
│ • 立即從記憶體清除 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 4. 輪換階段 │
│ • 定期生成新錢包 │
│ • 遷移資金 │
│ • 銷毀舊私鑰 │
└─────────────────────────────────────┘
8.3 交易簽名安全
本地簽名 vs 遠程簽名
本地簽名 (推薦)
┌─────────────────────────────────────┐
│ 流程: │
│ 1. 建構交易參數 │
│ 2. 本地使用私鑰簽名 │
│ 3. 廣播已簽名交易 │
│ │
│ 優點: │
│ ✓ 私鑰不離開服務器 │
│ ✓ 完全控制簽名過程 │
│ ✓ 可離線簽名 │
└─────────────────────────────────────┘
遠程簽名 (不推薦)
┌─────────────────────────────────────┐
│ 流程: │
│ 1. 發送未簽名交易到節點 │
│ 2. 節點使用私鑰簽名 │
│ 3. 節點廣播交易 │
│ │
│ 風險: │
│ ✗ 私鑰存在遠程節點 │
│ ✗ 依賴第三方安全性 │
│ ✗ 網路攔截風險 │
└─────────────────────────────────────┘
簽名流程詳解
交易簽名完整流程:
┌─────────────────────────────────────┐
│ 1. 準備交易參數 │
│ { │
│ to: "0x456...", │
│ value: "1000000000000000000", │
│ gasLimit: 21000, │
│ gasPrice: "20000000000", │
│ nonce: 5, │
│ chainId: 1 │
│ } │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. RLP 編碼 │
│ • Recursive Length Prefix │
│ • 以太坊標準序列化格式 │
│ • 將交易轉為 bytes │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. Keccak-256 哈希 │
│ • 計算交易哈希 │
│ • txHash = keccak256(rlpEncode) │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. ECDSA 簽名 │
│ • 使用私鑰簽名 txHash │
│ • 產生 (r, s, v) 簽名 │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 5. 組合簽名交易 │
│ • RLP(tx + signature) │
│ • 產生 rawTransaction │
└───────────┬─────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 6. 廣播交易 │
│ • eth_sendRawTransaction │
│ • 無需私鑰 │
└─────────────────────────────────────┘
8.4 API 安全防護
認證與授權
多層認證機制:
┌─────────────────────────────────────┐
│ 第一層:Session 認證 │
│ • 用戶登入後建立 session │
│ • 每次請求檢查 session 有效性 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 第二層:OTP (一次性密碼) │
│ • 敏感操作需要 OTP 驗證 │
│ • 例如:刪除錢包、轉帳 │
│ • 使用 TOTP (Time-based OTP) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 第三層:IP 白名單 │
│ • 限制允許訪問的 IP │
│ • 防止未授權訪問 │
└─────────────────────────────────────┘
輸入驗證
完整的輸入驗證流程:
┌─────────────────────────────────────┐
│ 1. 格式驗證 │
│ • 地址必須是有效的 Ethereum 地址 │
│ • 金額必須是正數 │
│ • 欄位長度限制 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 2. 類型驗證 │
│ • 字串 vs 數字 │
│ • 防止類型混淆攻擊 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 3. 範圍驗證 │
│ • 金額不能超過餘額 │
│ • nonce 合理性檢查 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 4. 業務邏輯驗證 │
│ • 錢包是否存在 │
│ • 是否有操作權限 │
└─────────────────────────────────────┘
9. 多鏈支援架構設計
9.1 統一抽象層設計
為什麼需要統一介面?
多鏈差異:
┌─────────────────────────────────────┐
│ Ethereum │
│ • Chain ID: 1 │
│ • 區塊時間:~12 秒 │
│ • Gas 單位:Gwei │
│ • 原生代幣:ETH (18 decimals) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Polygon │
│ • Chain ID: 137 │
│ • 區塊時間:~2 秒 │
│ • Gas 單位:Gwei │
│ • 原生代幣:POL (18 decimals) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ BSC │
│ • Chain ID: 56 │
│ • 區塊時間:~3 秒 │
│ • Gas 單位:Gwei │
│ • 原生代幣:BNB (18 decimals) │
└─────────────────────────────────────┘
統一抽象後:
所有鏈使用相同的介面,差異由配置決定
統一介面設計
統一的區塊鏈操作介面:
┌─────────────────────────────────────┐
│ getBalance(network, wallet, token) │
│ • network 參數決定使用哪個 RPC │
│ • token 參數決定查詢原生或 ERC20 │
│ • 返回格式統一 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ monitorTransfers(network, token) │
│ • 根據 network 選擇 RPC │
│ • 根據 token 決定監聽方式 │
│ • 統一的事件處理邏輯 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ sendTransaction(network, tx) │
│ • 自動選擇對應的 RPC │
│ • 統一的簽名流程 │
│ • 統一的錯誤處理 │
└─────────────────────────────────────┘
9.2 配置驅動架構
網路配置表
網路配置資料模型:
┌──────────────────────────────────────┐
│ networks 資料表 │
├──────────────────────────────────────┤
│ id │ name │ symbol │ chain_id│
│ 1 │ Ethereum │ ETH │ 1 │
│ 2 │ Polygon │ POL │ 137 │
│ 3 │ BSC │ BSC │ 56 │
└──────────────────────────────────────┘
新增網路步驟:
1. INSERT INTO networks
2. INSERT INTO network_rpcs (配置 RPC 節點)
3. 重啟 Scheduler
4. 無需修改程式碼!
代幣配置表
代幣配置資料模型:
┌───────────────────────────────────────────────┐
│ tokens 資料表 │
├───────────────────────────────────────────────┤
│ network_id │ symbol │ contract │ decimals │
│ 1 │ ETH │ NULL │ 18 │ ← 原生
│ 1 │ USDT │ 0xdac... │ 6 │ ← ERC20
│ 1 │ USDC │ 0xa0b... │ 6 │
│ 2 │ POL │ NULL │ 18 │ ← 原生
│ 2 │ USDT │ 0xc21... │ 6 │ ← ERC20
└───────────────────────────────────────────────┘
判斷邏輯:
IF contract_address IS NULL:
→ 原生代幣,使用 eth_getBalance
ELSE:
→ ERC20 代幣,使用 balanceOf
9.3 多鏈並行處理
獨立 Scheduler 設計
多 Scheduler 並行架構:
┌─────────────────────────────────────┐
│ Scheduler 1: ETH USDT │
│ • networkId: 1, tokenId: 2 │
│ • 獨立進程 │
│ • 掃描進度獨立 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Scheduler 2: ETH USDC │
│ • networkId: 1, tokenId: 3 │
│ • 獨立進程 │
│ • 與 Scheduler 1 不互相影響 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Scheduler 3: Polygon USDT │
│ • networkId: 2, tokenId: 5 │
│ • 獨立進程 │
│ • 可部署在不同服務器 │
└─────────────────────────────────────┘
優點:
✓ 一個鏈故障不影響其他鏈
✓ 可根據負載調整資源
✓ 易於水平擴展
進程管理策略
進程分配建議:
┌─────────────────────────────────────┐
│ 高頻代幣 (ETH USDT, ETH USDC) │
│ • 獨立服務器 │
│ • 更多 CPU 和記憶體資源 │
│ • 低掃描延遲 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 中頻代幣 (Polygon USDT) │
│ • 共享服務器 │
│ • 適中資源配置 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 低頻代幣 (小眾 ERC20) │
│ • 多個 Scheduler 共享一臺服務器 │
│ • 較長掃描間隔 │
└─────────────────────────────────────┘
9.4 跨鏈統一查詢
統一餘額查詢 API
API 設計:
GET /api/wallet/balance?address=0x123...
回應格式:
{
"success": true,
"address": "0x123...",
"balances": [
{
"network": "ETH",
"token": "ETH",
"balance": "1.5",
"valueUSD": "3000"
},
{
"network": "ETH",
"token": "USDT",
"balance": "500",
"valueUSD": "500"
},
{
"network": "Polygon",
"token": "POL",
"balance": "100",
"valueUSD": "50"
}
],
"totalUSD": "3550"
}
特點:
• 一次請求查詢所有鏈
• 統一格式化
• 自動計算美元價值
10. 系統整合與協作
10.1 整體架構回顧
完整資料流
端到端資料流程:
┌──────────────────────────────────────────────┐
│ 區塊鏈層 (Blockchain Layer) │
│ • Ethereum Mainnet │
│ • Polygon Mainnet │
│ • BSC Mainnet │
└────────────┬─────────────────────────────────┘
↓ RPC 查詢
┌──────────────────────────────────────────────┐
│ 監聽層 (Listener Layer) │
│ • ERC20 Transfer Schedulers (11 個進程) │
│ • Native Transfer Schedulers (3 個進程) │
│ • Consolidate Scheduler (1 個進程) │
└────────────┬─────────────────────────────────┘
↓ 發送到 Kafka
┌──────────────────────────────────────────────┐
│ 消息隊列層 (Message Queue Layer) │
│ • Kafka Topic: rescan │
│ • Kafka Topic: balance │
│ • Kafka Topic: priority-rescan │
└────────────┬─────────────────────────────────┘
↓ Consumer 消費
┌──────────────────────────────────────────────┐
│ 處理層 (Processing Layer) │
│ • Rescan Consumer (2-4 個進程) │
│ • Balance Consumer (1-2 個進程) │
│ • Priority Rescan Consumer (1 個進程) │
└────────────┬─────────────────────────────────┘
↓ 寫入資料庫
┌──────────────────────────────────────────────┐
│ 儲存層 (Storage Layer) │
│ • MySQL: wallets, balances, transactions │
│ • Cache: 熱點數據快取 │
└────────────┬─────────────────────────────────┘
↓ API 查詢
┌──────────────────────────────────────────────┐
│ 服務層 (Service Layer) │
│ • REST API (Express.js) │
│ • Swagger 文件 │
└────────────┬─────────────────────────────────┘
↓
外部系統 / 前端
10.2 錯誤處理與容錯
分層錯誤處理
錯誤處理策略:
┌─────────────────────────────────────┐
│ RPC 層錯誤 │
│ • 連線超時 → 切換備用節點 │
│ • 速率限制 → 延遲重試 │
│ • 節點同步中 → 等待或切換 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Kafka 層錯誤 │
│ • 連線失敗 → 自動重連 │
│ • 消息處理失敗 → 重試 3 次 │
│ • 超過重試 → 記錄到死信隊列 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 資料庫層錯誤 │
│ • 連線池耗盡 → 等待可用連線 │
│ • 死鎖 → 自動重試 │
│ • 主鍵衝突 → 忽略(冪等性設計) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 業務層錯誤 │
│ • 餘額不足 → 返回明確錯誤 │
│ • 錢包不存在 → 404 錯誤 │
│ • 權限不足 → 403 錯誤 │
└─────────────────────────────────────┘
重試機制
指數退避重試:
嘗試 1: 立即重試
↓ 失敗
嘗試 2: 等待 1 秒
↓ 失敗
嘗試 3: 等待 2 秒
↓ 失敗
嘗試 4: 等待 4 秒
↓ 失敗
嘗試 5: 等待 8 秒
↓ 失敗
放棄,記錄錯誤
10.3 效能監控
關鍵指標
監控指標分類:
┌─────────────────────────────────────┐
│ Scheduler 指標 │
│ • 掃描區塊速度 (blocks/min) │
│ • 發現事件數量 │
│ • RPC 請求延遲 │
│ • 錯誤率 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Consumer 指標 │
│ • 消息處理速度 (msgs/sec) │
│ • 消息積壓數量 (lag) │
│ • 處理成功率 │
│ • 平均處理時間 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 資料庫指標 │
│ • 查詢響應時間 │
│ • 連線池使用率 │
│ • 慢查詢數量 │
│ • 錯誤率 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ API 指標 │
│ • 請求數 (QPS) │
│ • 響應時間 (P50, P95, P99) │
│ • 錯誤率 │
│ • 並發連線數 │
└─────────────────────────────────────┘
10.4 擴展性設計
水平擴展策略
擴展點:
┌─────────────────────────────────────┐
│ 1. Scheduler 擴展 │
│ • 每個代幣獨立進程 │
│ • 可部署到不同服務器 │
│ • 互不影響 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 2. Consumer 擴展 │
│ • Kafka Consumer Group │
│ • 自動負載均衡 │
│ • 增加進程即可提升吞吐量 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 3. API 擴展 │
│ • 無狀態設計 │
│ • 使用負載均衡器 │
│ • Session 存儲外部化 (Redis) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 4. 資料庫擴展 │
│ • 讀寫分離 │
│ • 分庫分表(按網路 ID) │
│ • 冷熱數據分離 │
└─────────────────────────────────────┘
效能瓶頸與優化
常見瓶頸與解決方案:
┌─────────────────────────────────────┐
│ 瓶頸:RPC 請求慢 │
│ 解決: │
│ • 使用付費節點(Alchemy, Infura) │
│ • 多節點負載均衡 │
│ • 批次查詢優化 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 瓶頸:資料庫查詢慢 │
│ 解決: │
│ • 建立適當索引 │
│ • 使用查詢快取 │
│ • 讀寫分離 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 瓶頸:Kafka 消息積壓 │
│ 解決: │
│ • 增加 Consumer 數量 │
│ • 增加 Partition 數量 │
│ • 優化處理邏輯 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 瓶頸:記憶體佔用高 │
│ 解決: │
│ • 分批載入錢包地址 │
│ • 定期清理快取 │
│ • 使用流式處理 │
└─────────────────────────────────────┘
總結
核心技術要點
本文從實際生產環境的企業級區塊鏈監聽系統中,提煉出以下核心技術理論:
1. 錢包管理
- 單例模式確保全局唯一性
- Set/Map 資料結構提供 O(1) 查詢效能
- 軟刪除機制保留審計記錄
- 分批載入策略避免記憶體暴增
2. ERC20 互動
- ABI 定義智能合約介面
- Transfer 事件監聽機制
- Decimals 處理數值精度
- 批次查詢優化 RPC 請求
3. 區塊鏈 RPC
- JSON-RPC 通信協議
- 多節點備援提升可用性
- Web3.js vs Ethers.js 功能分工
- debug_traceTransaction 追蹤內部轉帳
4. 交易監聽
- 雙軌監聽系統(ERC20 + 原生)
- Kafka 異步處理解耦流程
- 區塊掃描狀態管理
- 事件過濾與處理
5. 餘額管理
- 即時餘額 + 歷史快照雙層架構
- Wei/原始單位格式化
- BigInt 處理大數值
- 定時快照支援審計
6. 加密安全
- AES-128-ECB 加密演算法
- 環境變數隔離敏感資料
- 本地簽名保護私鑰
- 多層認證與授權
7. 多鏈支援
- 統一抽象層設計
- 配置驅動架構
- 獨立 Scheduler 並行處理
- 跨鏈統一查詢介面
8. 系統協作
- 分層架構清晰職責
- 錯誤處理與容錯機制
- 效能監控與告警
- 水平擴展策略
適用場景
本系統架構適合以下場景:
- ✅ 加密貨幣交易所(充提幣管理)
- ✅ 支付閘道(多鏈收款)
- ✅ DeFi 平臺(資產追蹤)
- ✅ NFT 市場(交易監聽)
- ✅ 錢包服務(多鏈支援)
- ✅ 審計系統(資產追蹤)
學習建議
理論 → 實踐路徑
階段 1:理解基礎概念
• 區塊鏈原理
• 以太坊架構
• 智能合約基礎
階段 2:學習開發工具
• Web3.js / Ethers.js 文檔
• RPC 方法實驗
• 測試網實作
階段 3:架構設計
• 單例模式應用
• 異步處理設計
• 資料庫設計
階段 4:安全實踐
• 私鑰管理
• 交易簽名
• API 防護
階段 5:生產部署
• 監控告警
• 擴展策略
• 災難恢復
參考專案: blockchain-listener-modify 文檔版本: 2.0.0 最後更新: 2025-11-13