幣圈實戰:用 Swift 打造一條區塊鏈( Server + iOS App)

連續爆倉之後,只好靠 Coding 找回成就感

最近跑到幣圈玩樂,完全打開了新世界,一切都太新鮮、太有趣了(除了開空單爆倉好多次之外)!

也讓我開始好奇,區塊鏈背後的運作原理到底是什麼?常常聽到的「去中心化」又是什麼?所以手癢用 Swift 來動手實作,順便把 input 記錄下來,好好整理自己的思緒,所以這也算是我的個人筆記!歡迎各位糾正、討論。


以下內容以 Swift + Vapor 實作 API Server、SwiftUI 實作 Demo App,歡迎 clone 完整專案服用:

區塊鏈 Server:https://github.com/xiaohsun/Swift-Blockchain

區塊鏈 App:https://github.com/xiaohsun/Blockchain_Demo_App

使用方式:先用 Command line 將 Server 專案 run 起來、確定能連線到 8080 之後,就可以打開 App 專案互動了。

詳情請見 Repo 的 README,有需要協助也歡迎詢問!

部分實作參考了 Daniel van Flymen 超讚的文章,歡迎懂 Python 的各位前去朝聖。




區塊鏈是怎麼運作的?

區塊鏈是按順序排列的 Blocks(區塊),是由 Hash Value(雜湊值)所串接起來。Hash Value 就像「數位指紋」一樣,任何種類的資料,都能產生一串獨一無二、長度固定的數值。

/// 計算 Block 的 Hash Value
static func hash(block: Block) -> String {
    let encoder = JSONEncoder()
    encoder.outputFormatting = .sortedKeys
        
    guard let blockData = try? encoder.encode(block) else {
        return ""
    }
        
    let hash = SHA256.hash(data: blockData)
    return hash.compactMap { String(format: "%02x", $0) }.joined()
}

一個 Block 裡面有什麼呢?

根據不同鏈有所不同,而大多數鏈上的 Block 包含了:

  • Index(序號)
  • Proof(工作量證明)
  • Timestamp(時間戳)
  • Transactions(交易)
  • PreviousHash(前一個 Block 的 Hash Value)

先從 PreviousHash 說起,PreviousHash 代表每一個 Block 都記著前一個 Block 的數位指紋。

想想看,若有人竄改某個 Block 的數值,會導致該 Block 的 Hash Value 改變,那下一個 Block 紀錄的 PreviousHash 就會對不上,導致後續一連串 Block 全部失效。

因此,各種竄改都很容易被發現,這是區塊鏈信任的基石,也是形成「去中心化」的要點之一。

當然,鏈上的第一個 Block 不會有 PreviousHash,被稱為 Block 0(創世區塊)。

/// 創建新 Block 並加入鏈的尾巴
func newBlock(proof: Int, previousHash: String? = nil) -> Block {
    let block = Block(
        index: chain.count + 1,
        timestamp: Date().timeIntervalSince1970,
        transactions: currentTransactions,
        proof: proof,
        previousHash: previousHash ?? Blockchain.hash(block: chain.last!)
    )
        
    // 清空 Transactions 等待區
    currentTransactions = []
        
    // 將新 Block 接在鏈的尾巴
    chain.append(block)
        
    return block
}
    
       
init() {
    // 創建第一個 Block
    _ = newBlock(proof: 100, previousHash: "1")
}

Transactions 是買賣的交易紀錄,交易產生時,會被集中到「等待區」。等下一個 Block 產生時,蓋上當下的 Timestamp,與 PreviousHash 一同被打包進 Block ,並算出 Hash Value,將 Block 接到鏈的尾巴。

/// 創建新交易並添加到等待區
func newTransaction(sender: String, recipient: String, amount: Int) -> Int {
    let transaction = Transaction(
        sender: sender,
        recipient: recipient,
        amount: amount
    )
        
    currentTransactions.append(transaction)
        
    // 欲打包此交易的區塊 index(下一個要被挖掘的)
    return lastBlock.index + 1
}

那新的 block 是怎麼被挖掘出來的?

這就要講到 PoW( Proof of Work,工作量證明),PoW 能夠確保一個 Block 的產生是需要付出相應的努力,我們可以把它想像成一個數學考題:

Q: 假設我有一個數字 10,10 + y = 100,試問 y 為何?

若用區塊鏈的角度來出題,則是:

Q: 假設我有一個數字 10,10y 的 Hash Value 為 0000 開頭,試問 y 為何?

如果成功得出 y,就能夠建立 Block,這個過程就是俗稱的「挖礦」,礦工也會因此得到獎勵(代幣)。

/// 計算 PoW
func proofOfWork(lastProof: Int) -> Int {
    var proof = 0
        
    while !Blockchain.validProof(lastProof: lastProof, proof: proof) {
        proof += 1
    }
        
    return proof
 }
    
/// 檢查 PoW 是否正確
static func validProof(lastProof: Int, proof: Int) -> Bool {
    let guess = "\(lastProof)\(proof)".data(using: .utf8)!
    let hash = SHA256.hash(data: guess)
    let hashString = hash.compactMap { String(format: "%02x", $0) }.joined()
        
    // 檢查 Hash Value 是否以 4 個 0 開頭
    return hashString.prefix(4) == "0000"
}

然而,算出 PoW 的過程許多的運算(如上方的 Loop )雖然能確保 Block 得來不易,卻也消耗許多時間與能源。

因此,也有鏈採用不同的驗證方式,如 PoS( Proof of Stake,權益證明)。

但,PoW 的解答可能不只一個

在成千上萬個電腦同時在解謎時,可能會找到不同的解答,因而建立不同的 Block,產生了分歧。

這時候就要依靠區塊鏈的「最長鏈共識」來解決。

最長鏈共識

在區塊鏈中,每個參與的電腦(個體)都要註冊節點,讓網路知道你的存在。我們可以透過訪問別人的節點,下載別人的鏈來檢查是否比自己的鏈最長、最有效(所有 PoW 都符合規範)。

若成功驗證,很不幸地,我們要拋棄自己的鏈,去採用上述那條鏈。

因此,就算區塊鏈一度出現分歧,透過最長鏈共識,最終還是會慢慢同步到那條最長的鏈上。這就是「去中心化」網路獲得共識的方式,不需要中心化的單位做裁決。

(口語雖常說「最長鏈」,但實際規則是累積工作量 (total work) 最大的鏈才被採納,不一定是最長。)

/// 註冊新節點
/// - Parameter address: 節點地址,例如 "http://192.168.0.5:5000"
func registerNode(address: String) {
    if let url = URL(string: address), let host = url.host {
       let node = url.port != nil ? "\(host):\(url.port!)" : host
       nodes.insert(node)
    }
}
/// 解決節點間的衝突
/// 若自己的鏈被替換,返回 true
func resolveConflicts() async throws -> Bool {
    var newChain: [Block]? = nil
    var maxLength = chain.count
        
    // 獲取並驗證網路中所有節點的鏈
    for node in nodes {
        let urlString = "http://\(node)/chain"
        guard let url = URL(string: urlString) else { continue }
            
        let (data, response) = try await URLSession.shared.data(from: url)
            
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            continue
        }
            
        let decoder = JSONDecoder()
        let chainResponse = try decoder.decode(ChainResponse.self, from: data)
            
        // 檢查鏈的長度是否更長且有效
        if chainResponse.length > maxLength && Blockchain.validChain(chain: chainResponse.chain) {
            maxLength = chainResponse.length
            newChain = chainResponse.chain
        }
    }
        
    // 如果找到一個更長且有效的鏈,則替換自己的鏈
    if let newChain = newChain {
        chain = newChain
        return true
    }
        
    return false
}

以上~是目前研究到的,之後再跟大家分享更多!

區塊鏈看似很神秘,但搞懂後發現,透過數字的環環相扣,居然真的能夠解決部分傳統金融機構中心化的痛點,不得不佩服第一個想出這個點子的大神。

身為幣圈的菜鳥,我非常期待未來區塊鏈的發展會如何改變人們的生活,讓我們繼續看下去吧!