Swift 和 Vapor 构建区块链服务器

最近火热的区块链 小编也不禁追赶一下潮流   无所谓什么开发语言 设计思路大同小异。

下面讨论了如何用 Swift 语言实现基本的区块链。在这篇文章里会使用服务器端 Swift 框架 Vapor 在云端实现区块链。通过 HTTP 协议来构建区块链 Web API,使用不同的路由来提供必要的功能。阅读本文需要在电脑上安装 Vapor 框架(这个框架很牛逼 国外服务器不是用这个就是Python),还需要对 Swift 语言有基本的了解。

实现模型

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

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

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
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(金额)组成。实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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(区块链)类是表示区块列表的主类。每个区块都指向链中的前一个区块。每个区块可以包含多笔交易,表示信贷或借记。

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
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 实现 Web API

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

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
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 的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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。

1.png

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

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

1.png

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

1.png

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

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

给区块链添加节点

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 来添加注册新节点功能。如下所示:

1
2
3
4
5
6
7
8
9
10
11
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 以便注册新节点。

1
2
3
4
5
6
7
   func getNodes() -> [BlockchainNode] {
         return  self.blockchain.nodes
     }
     
     func registerNode(_ blockchainNode: BlockchainNode) {
         self.blockchain.addNode(blockchainNode)
     }

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

1.png

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

1.png

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

解决节点间的冲突

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

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

1
2
3
4
5
6
7
8
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 来解决冲突。实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 服务器。

1
vapor run serve -—port= 8081

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

1.png

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

1.png

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

1.png

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

1.png

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值