微信号:iOSDevTip

介绍:最新iOS、iPhone资讯,万名iOS开发者、swift开发、果粉聚集,参与技术讨论,整理开发技巧,分享创业经验!享受生活、热爱编程!

使用 Swift 和 Vapor 构建区块链服务器

2018-02-11 19:01 iOS开发

关键时刻,第一时间送达!


本文参考


  • http://www.wanbizu.com/baike/201408081395.html


我在上一篇文章中讨论了如何用 Swift 语言实现基本的区块链。在这篇文章里会使用服务器端 Swift 框架 Vapor 在云端实现区块链。通过 HTTP 协议来构建区块链 Web API,使用不同的路由来提供必要的功能。阅读本文需要在电脑上安装 https://vapor.codes/ 框架,还需要对 Swift 语言有基本的了解。


实现模型


第一步是为区块链 Web API 创建必要的模型,如下所示。


Block:Block(区块)类表示一个区块,包含交易的输入和输出。


class Block: Codable {

    var index: Int = 0

    var dateCreated: String

    var previousHash: String!

    var hash: String!

    var nonce: Int

    var message: String = ""

    private (set) var transactions: [Transaction] = [Transaction]()


    var key: String {

        get {

            let transactionsData = try! JSONEncoder().encode(self.transactions)

            let transactionsJSONString = String(data: transactionsData, encoding: .utf8)


            return String(self.index) + self.dateCreated + self.previousHash + transactionsJSONString! + String(self.nonce)

        }

    }


    func addTransaction(transaction: Transaction) {

        self.transactions.append(transaction)

    }


    init() {

        self.dateCreated = Date().toString()

        self.nonce = 0

        self.message = "挖出新的区块"

    }


    init(transaction: Transaction) {

        self.dateCreated = Date().toString()

        self.nonce = 0

        self.addTransaction(transaction: transaction)

    }

}


Block 类的属性解释如下:


  • index——区块位于区块链中的位置。index 为 0 则表示该区块是区块链中的第一个区块。index 为 1 则表示区块链中的第二个区块……以此类推!

  • dateCreated——区块创建的日期

  • previousHash——前一个区块的哈希值

  • hash——当前区块的散列值

  • message——每个区块的备忘说明。只是为了例子使用

  • nonce——递增的数字,对生成哈希值很关键

  • transactions——一系列交易。每笔交易都代表货物/价值的转移

  • key——计算属性,提供给产生哈希值的函数


Transaction:Transaction(交易)由 sender(发送者)、recipient(接收者)和被转移的 amount(金额)组成。实现如下:


class Transaction: Codable {

    var from: String

    var to: String

    var amount: Double


    init(from: String, to: String, amount: Double) {

        self.from = from

        self.to = to

        self.amount = amount

    }


    init?(request: Request) {

        guard let from = request.data["from"]?.string, let to = request.data["to"]?.string, let amount = request.data["amount"]?.double else {

            return nil

        }

        self.from = from

        self.to = to

        self.amount = amount

    }

}


Transaction 类的实现很直观。由 from、to 和 amount 字段组成。为了简单起见,from 和 to 字段会用虚拟名字来表示,在实际中这两个字段还会包含包(wallet)ID 。


Blockchain:Blockchain(区块链)类是表示区块列表的主类。每个区块都指向链中的前一个区块。每个区块可以包含多笔交易,表示信贷或借记。


class Blockchain: Codable {

    var blocks: [Block] = [Block]()


    init() {


    }


    init(_ genesisBlock: Block) {

        self.addBlock(genesisBlock)

    }


    func addBlock(_ block: Block) {

        if self.blocks.isEmpty {

            // 添加创世区块

            // 第一个区块没有 previous hash

            block.previousHash = "0"

        } else {

            let previousBlock = getPreviousBlock()

            block.previousHash = previousBlock.hash

            block.index = self.blocks.count

        }


        block.hash = generateHash(for: block)

        self.blocks.append(block)

        block.message = "此区块已添加至区块链"

    }


    private func getPreviousBlock() -> Block {

        return self.blocks[self.blocks.count - 1]

    }


    private func displayBlock(_ block: Block) {

        print("------ 第 (block.index) 个区块 --------")

        print("创建日期:(block.dateCreated)")

        // print("数据:(block.data)")

        print("Nonce:(block.nonce)")

        print("前一个区块的哈希值:(block.previousHash!)")

        print("哈希值:(block.hash!)")

    }


    private func generateHash(for block: Block) -> String {

        var hash = block.key.sha256()!


        // 设置工作量证明

        while(!hash.hasPrefix(DIFFICULTY)) {

            block.nonce += 1

            hash = block.key.sha256()!

            print(hash)

        }


        return hash

    }

}


每个模型都遵循 Codable 协议,以便转换为 JSON 对象。如果你看了上一篇文章的话,上面的实现方式就很眼熟了。下一步是为 Web API 配置路由,后面一节会用 Vapor 框架来实现。


使用 Vapor 实现 Web API


有几种不同方式来用 Vapor 实现 Web API 。我在这里会创建一个自定义的控制器来处理所有区块链请求,这样就不用把所有代码都塞进 Routes 类里了。BlockchainController 实现如下:


class BlockchainController {

    private (set) var drop: Droplet

    private (set) var blockchainService: BlockchainService!


    init(drop: Droplet) {

        self.drop = drop

        self.blockchainService = BlockchainService()


        // 为控制器设置路由

        setupRoutes()

    }


    private func setupRoutes() {

        self.drop.get("mine") { request in

            let block = Block()

            self.blockchainService.addBlock(block)

            return try JSONEncoder().encode(block)

        }


        // 添加新交易

        self.drop.post("transaction") { request in

            if let transaction = Transaction(request: request) {

                // 添加交易至区块


                // 获得最后一个挖出的区块

                let block = self.blockchainService.getLastBlock()

                block.addTransaction(transaction: transaction)


                return try JSONEncoder().encode(block)

            }

            return try JSONEncoder().encode(["message": "发生异常!"])

        }


        // 获得链

        self.drop.get("blockchain") { request in

            if let blockchain = self.blockchainService.getBlockchain() {

                return try JSONEncoder().encode(blockchain)

            }


            return try! JSONEncoder().encode(["message":"区块链尚未初始化。请先挖矿"])

        }

    }

}


Web API 从三个基本的 endpoint 开始。


  • Mining(挖矿):这个 endpoint 会启动挖矿程序。挖矿可以让我们达到工作量证明,然后将区块添加到区块链。

  • Transaction:这个 endpoint 用于添加新交易。交易包含有关发送者、接收者和金额的信息。

  • Blockchain:这个 endpoint 返回完整的区块链。


BlockchainController 使用 BlockChainService 来执行所需操作。BlockChainService 的实现如下:


import Foundation

import Vapor


class BlockchainService {

    

    typealias JSONDictionary = [String:String]

    private var blockchain: Blockchain = Blockchain()

    

    init() {


    }


    func addBlock(_ block: Block) {

        self.blockchain.addBlock(block)

    }


    func getLastBlock() -> Block {

        return self.blockchain.blocks.last!

    }


    func getBlockchain() -> Blockchain? {

        return self.blockchain

    }

}


下面我们就来检查一下 Web API  的 endpoint。启动 Vapor 服务器然后发送请求到 “mine” endpoint。



工作量证明算法生成了以“000”开头的散列值。区块被挖出后就立即转换为 JSON 格式返回回来。通过 Swift 4.0 的 Codable 协议实现。

现在给区块链添加一笔简单的交易,从张嘉夫那里转移10美元给马云。



最后一步是检查区块链是否含有新添加的区块。访问 “blockchain” endpoint 来查看完整的链。



完美!我们的区块链 Web API 现在可以正常工作了。


还有一点遗憾的是,区块链应该是去中心化的,但目前我们没有添加新节点的机制。在下一节我们会更新区块链实现以便让其支持多个节点。


给区块链添加节点


在给区块链添加节点之前,首先要定义节点。节点模型的实现如下:


class BlockchainNode :Codable {

    

    var address :String

    

    init(address :String) {

        self.address = address

    }

    

    init?(request :Request) {

        

        guard let address = request.data["address"]?.string else {

            return nil

        }

        

        self.address = address

    }

    

}


BlockChainNode 类很简单,只有一个 address 属性,用于标识节点服务器的 URL。然后更新 BlockchainController 来添加注册新节点功能。如下所示:


self.drop.get("nodes") { request in

            return try JSONEncoder().encode(self.blockchainService.getNodes())

        }


self.drop.post("nodes/register") { request in

            guard let blockchainNode = BlockchainNode(request: request) else {

                return try JSONEncoder().encode(["message": "注册节点出现错误"])

            }

            

            self.blockchainService.registerNode(blockchainNode)

            return try JSONEncoder().encode(blockchainNode)

        }


还要更新 BlockchainService 以便注册新节点。


  func getNodes() -> [BlockchainNode] {

        return self.blockchain.nodes

    }

    

    func registerNode(_ blockchainNode: BlockchainNode) {

        self.blockchain.addNode(blockchainNode)

    }


下面来测试一下。启动新的 Vapor 服务器然后试着注册新节点。



节点注册好后,可以使用 nodes endpoint 来获取它,如下所示:



现在可以注册新节点了,下面要着重解决(resolve)节点间的冲突。如果某个节点上的区块链比其它节点的要大,就会产生冲突。在这种情况下,一般都是获得临近节点并用较大的区块链更新它们。


解决节点间的冲突


为了创建冲突,我们需要第二台服务器或是在另一个端口上运行服务器。本文会用后一种方法,在另一个端口上启动  Vapor 服务器。这两个节点初始化后,各创建一些区块和交易,这些区块会被添加到各自的区块链上。最后,调用 resolve endpoint 来解决节点间的冲突,并将节点更新为较大的那个区块链。

给 BlockchainController 添加新的 endpoint 来解决冲突。


self.drop.get("nodes/resolve") { request in

            return try Response.async { portal in

                self.blockchainService.resolve { blockchain in

                    let blockchain = try! JSONEncoder().encode(blockchain)

                    portal.close(with: blockchain.makeResponse())

                }

            }

        }


上面使用了 Vapor 框架的 async  response 功能来异步处理响应。然后再更新 BlockchainService 来解决冲突。实现如下所示:


func resolve(completion: @escaping(Blockchain) -> ()) {

        //获取节点

        let nodes = self.blockchain.nodes

        

        for node in nodes {

            let url = URL(string: "http://(node.address)/blockchain")!

            URLSession.shared.dataTask(with: url, completionHandler: { (data, _, _) in

                if let data = data {

                    let blockchain = try! JSONDecoder().decode(Blockchain.self, from: data)

                    

                    if self.blockchain.blocks.count > blockchain.blocks.count {

                        completion(self.blockchain)

                    } else {

                        self.blockchain.blocks = blockchain.blocks

                        completion(blockchain)

                    }

                }

            }).resume()

        }

    }


resolve 函数遍历节点列表并获取每个节点的区块链。如果某个区块链比当前区块链要大,则替换当前区块链为更大的那个,否则直接返回当前区块链,因为当前区块链已经是更大的区块链了。

为了测试我们要在不同的端口开启两台服务器,在 8080 端口上添加三笔交易,在 8081 上添加两笔。可以在终端里输入下面的命令来启动 Vapor 服务器。


vapor run serve -—port=8081


在 8080 端口上添加三笔交易,如下所示:



然后在 8081 端口节点上添加两笔交易,如下所示:



确保注册了 8080 地址的节点,如下所示:



最后,来一下测试 resolve endpoint。在 Postman 里访问 “resolve” endpoint,如下所示:



可以看到,resolve endpoint 返回了更大的区块链,同时也更新了节点的区块链。这样解决冲突方案就完工了。


[GitHub](https://github.com/josephchang10/Blockchain-Web-API)



  • 作者:张嘉夫_Joseph

  • 链接:https://juejin.im/post/5a48f645f265da43310e2a30

  • iOS开发整理发布,转载请联系作者授权

【点击成为Android大神】

 
iOS开发 更多文章 iOS 横竖屏旋转总结 在几年后,程序员的薪资会一直这么高么? 做一个有批判性思维的程序员 你真的适配了iPhone X吗? 数据结构 & 算法 in Swift (一):Swift基础和
猜您喜欢 去他妈的乐观,悲观,我只想把事儿做成 今晚九点| go 语言分享 Python进阶 文本文件的输入输出 女生学IT,玩儿转智能硬件拿高薪 Linux Kernel 4.9 分支获得首个维护版本更新