社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  区块链

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

Cocoa开发者社区 • 7 年前 • 771 次点击  


本文参考



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


作者:张嘉夫_Joseph

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



今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/cmTHFS2liW
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/6747
 
771 次点击