原文連接node
最近身邊的許多人都開始玩比特幣,雖然本人不炒可是想稍微瞭解一下其中的原理,因此就練手寫了一個簡易版的區塊鏈系統。python
這裏引用一下Google的結果git
所謂區塊鏈技術 , 簡稱BT(Blockchain technology),也被稱之爲分佈式帳本技術,是一種互聯網數據庫技術,其特色是去中心化、公開透明,讓每一個人都可參與數據庫記錄。github
若是把區塊鏈做爲一個狀態機,則每次交易就是試圖改變一次狀態,而每次共識生成的區塊,就是參與者對於區塊中全部交易內容致使狀態改變的結果進行確認。sql
簡單理解就是:數據庫
若是咱們把數據庫假設成一本帳本,讀寫數據庫就能夠看作一種記帳的行爲,區塊鏈技術的原理就是在一段時間內找出記帳最快最好的人,由這我的來記帳,而後將帳本的這一頁信息發給整個系統裏的其餘全部人。這也就至關於改變數據庫全部的記錄,發給全網的其餘每一個節點,因此區塊鏈技術也稱爲分佈式帳本(distributed ledger)。json
既然要用swift實現 , 我在這裏就選擇vapor做爲服務端框架來使用 , vapor裏面有意思的東西不少 , 這裏只介紹基本的操做而不深究其原理 。swift
前置條件 , 這裏咱們使用macOS進行開發部署 , 如下是須要軟件和版本。api
接着使用homeBrew安裝xcode
brew install vapor/tap/vapor
複製代碼
若是一切輸出正常的話咱們就能夠繼續啦。
如今vapor已經裝好了 , 咱們能夠先把基本的準備工做弄好 使用vapor初始化工程
vapor new blockChainServer
複製代碼
生成工程文件
vapor xcode
複製代碼
接着打開 blockChainServer.xcodeproj 文件 , 在導航上的schema上選擇 run ,接着按下 Command
+ R
這時你應可以在控制檯看到輸出的server地址了
到如今咱們一切準備工做都就緒了 , 那麼開始鼓搗個區塊鏈api出來吧 。
區塊鏈的基本概念上面介紹了,包含如下幾類,咱們使用oop能夠抽象出如下一些class
排列由下向上存在集合關係。
其實這裏面最後應該加上一個區塊網絡不過這裏咱們暫時不須要實現所有網絡,這裏咱們先搭建一個內網環境的來練練手
下面分別來看下幾個model的代碼 (ps:這裏的框架添加的協議和擴展不少,不在此過多介紹,請把關注點放在class自己)
Transaction.swift
import Foundation
import FluentSQLite
import Vapor
final class Transaction: Codable,SQLiteModel {
var id: Int?
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
}
}
extension Transaction: Content { }
extension Transaction: Migration { }
extension Transaction: Parameter { }
複製代碼
這裏咱們能夠看到定義了幾個property ,
Block.swift
import FluentSQLite
import Vapor
final class Block: Codable,SQLiteModel {
var id: Int?
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(transactions)
let transactionsJSONString = String(data: transactionsData, encoding: .utf8)
return String(index) + dateCreated + previousHash + transactionsJSONString! + String(nonce)
}
}
@discardableResult
func addTransaction(transaction: Transaction) -> Block{
transactions.append(transaction)
return self
}
init() {
dateCreated = Date().toString()
nonce = 0
message = "挖出新的區塊"
}
init(transaction: Transaction) {
dateCreated = Date().toString()
nonce = 0
addTransaction(transaction: transaction)
}
}
extension Block: Content { }
extension Block: Migration { }
extension Block: Parameter { }
複製代碼
Blockchain.swift
import Foundation
import FluentSQLite
import Vapor
final class Blockchain: Codable,SQLiteModel {
var id: Int?
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.sha1Hash()
// 設置工做量證實
while(!hash.hasPrefix("11")) {
block.nonce += 1
hash = block.key.sha1Hash()
print(hash)
}
return hash
}
}
extension Blockchain: Content { }
extension Blockchain: Migration { }
extension Blockchain: Parameter { }
複製代碼
BlockChainNode.swift
import FluentSQLite
import Vapor
final class BlockChainNode: Codable,SQLiteModel {
var id: Int?
var address :String
init(addr:String) {
address = addr
}
}
extension BlockChainNode: Content { }
extension BlockChainNode: Migration { }
extension BlockChainNode: Parameter { }
複製代碼
這裏涉及到的事件主要是計算hash , 咱們在這裏面給String 添加一個extension
extension String {
func sha1Hash() -> String {
let task = Process()
task.launchPath = "/usr/bin/shasum"
task.arguments = []
let inputPipe = Pipe()
inputPipe.fileHandleForWriting.write(self.data(using: .utf8)!)
inputPipe.fileHandleForWriting.closeFile()
let outputPipe = Pipe()
task.standardOutput = outputPipe
task.standardInput = inputPipe
task.launch()
let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
let hash = String(data: data, encoding: .utf8)!
return hash.replacingOccurrences(of: " -\n", with: "")
}
}
複製代碼
給date也添加一個便於咱們輸出區塊建立時間
extension Date {
func toString() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter.string(from: self)
}
}
複製代碼
在這裏咱們把全部對區塊鏈的操做抽象爲一個service類
BlockchainService.swift
import Foundation
import Vapor
class BlockchainService {
private var blockchain: Blockchain = Blockchain()
private var nodes = [BlockChainNode]()
init() {
}
func addBlock(_ block: Block) -> Block {
self.blockchain.addBlock(block)
return block
}
func getLastBlock() -> Block {
guard let lastB = self.blockchain.blocks.last else {
return addBlock(Block())
}
return lastB;
}
func getBlockchain() -> Blockchain {
return self.blockchain
}
func registerNode(_ node:BlockChainNode) -> BlockChainNode {
self.nodes.append(node)
return node
}
func getAllNodes() -> [BlockChainNode] {
return nodes
}
}
複製代碼
這裏咱們基本完成了全部基本模型的搭建 , 如今須要對咱們的server進行操做 , 讓咱們能夠方便的經過curl調用咱們的區塊鏈系統 。
BlockChainController.swift
import Foundation
import Vapor
struct BCError:LocalizedError {
let name:String
}
final class BlockChainController {
let bcService = BlockchainService()
func addBlock(_ req: Request) throws -> Future<Block> {
return bcService.addBlock(Block()).save(on: req)
}
func addTransaction(_ req: Request) throws -> Future<Block> {
return try req.content.decode(Transaction.self).flatMap{ transation in
return self.bcService.getLastBlock().addTransaction(transaction: transation).save(on: req)
}
}
func findBlockChain(_ req: Request) throws -> Future<Blockchain> {
return bcService.getBlockchain().save(on: req)
}
func registeNode(_ req: Request) throws -> Future<BlockChainNode> {
return try req.content.decode(BlockChainNode.self).flatMap{ node in
return self.bcService.registerNode(node).save(on: req)
}
}
func allNodes(_ req: Request) throws -> Future<[BlockChainNode]> {
return BlockChainNode.query(on: req).all()
}
func resolve(_ req: Request) throws -> Future<Blockchain> {
let promise = req.eventLoop.newPromise(Blockchain.self)
bcService.getAllNodes().forEach { node in
guard let url = URL(string: "http://\(node.address)/blockchain") else {return promise.fail(error: BCError(name: "node error"))}
URLSession.shared.dataTask(with: url, completionHandler: { (data, _, _) in
if let data = data {
guard let bc = try? JSONDecoder().decode(Blockchain.self, from: data) else {return promise.fail(error: BCError(name: "json error"))}
if self.bcService.getBlockchain().blocks.count < bc.blocks.count {
self.bcService.getBlockchain().blocks = bc.blocks
}
promise.succeed(result: self.bcService.getBlockchain())
} else {
promise.fail(error: BCError(name: "data Error"))
}
}).resume()
}
return promise.futureResult
}
}
複製代碼
routes.swift
import Vapor
/// Register your application's routes here.
public func routes(_ router: Router) throws {
// Basic "Hello, world!" example
router.get("hello") { req in
return "Hello, world!"
}
let bcc = BlockChainController()
router.post("block", use: bcc.addBlock)
router.post("transaction", use: bcc.addTransaction)
router.get("blockchain", use: bcc.findBlockChain)
router.post("node", use: bcc.registeNode)
router.get("node", use: bcc.allNodes)
router.post("resolve", use: bcc.resolve)
}
複製代碼
如今解釋一下咱們註冊的api都是幹啥的
ps:API層遵照restfull
下面咱們可讓服務運行起來 , 而後經過curl命令行來調用區塊鏈系統 ,
進入咱們的工程目錄 , 執行命令
vapor build
複製代碼
你應能看到如下輸出
Building Project [Done]
複製代碼
這說明咱們的工程能夠運行了
vapor run serve --port=8080
複製代碼
咱們在本地8080端口上開啓咱們的服務
這時應能看到以下輸出
Running blockChainServer ...
[ INFO ] Migrating 'sqlite' database (/Users/felix/Documents/TestFolder/blockChainServer/.build/checkouts/fluent.git-6251908308727715749/Sources/Fluent/Migration/MigrationConfig.swift:69)
[ INFO ] Preparing migration 'Todo' (/Users/felix/Documents/TestFolder/blockChainServer/.build/checkouts/fluent.git-6251908308727715749/Sources/Fluent/Migration/Migrations.swift:111)
[ INFO ] Preparing migration 'Block' (/Users/felix/Documents/TestFolder/blockChainServer/.build/checkouts/fluent.git-6251908308727715749/Sources/Fluent/Migration/Migrations.swift:111)
[ INFO ] Preparing migration 'Blockchain' (/Users/felix/Documents/TestFolder/blockChainServer/.build/checkouts/fluent.git-6251908308727715749/Sources/Fluent/Migration/Migrations.swift:111)
[ INFO ] Preparing migration 'BlockChainNode' (/Users/felix/Documents/TestFolder/blockChainServer/.build/checkouts/fluent.git-6251908308727715749/Sources/Fluent/Migration/Migrations.swift:111)
[ INFO ] Migrations complete (/Users/felix/Documents/TestFolder/blockChainServer/.build/checkouts/fluent.git-6251908308727715749/Sources/Fluent/Migration/MigrationConfig.swift:73)
[Deprecated] --option=value syntax is deprecated. Please use --option value (with no =) instead.
Server starting on http://localhost:8080
複製代碼
如今咱們只要用api調用如下 , 本機的區塊鏈系統就會響應了 , 讓咱們試一下吧
curl -s -X POST localhost:8080/block
複製代碼
你會在一段時間後看到響應
{
"dateCreated": "2018-08-11 17:19:01",
"hash": "1124aa2a5867abee8b9cc3a3f4051b6665f89e26",
"id": 1,
"index": 0,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 193,
"previousHash": "0",
"transactions": []
}
複製代碼
咱們的第一個block已經建立成功而且被加入區塊鏈裏了 , 他的previousHash爲「0」是由於他是創世區塊 。
咱們能夠在這裏添加幾筆交易看看
curl -s -X POST localhost:8080/transaction --data "from=Felix&to=mayun&amount=100" | python -m json.tool
複製代碼
data裏面表示 Felix向mayun轉帳100 每次添加後你將會獲得區塊的最新信息
{
"dateCreated": "2018-08-11 17:19:01",
"hash": "1124aa2a5867abee8b9cc3a3f4051b6665f89e26",
"id": 1,
"index": 0,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 193,
"previousHash": "0",
"transactions": [
{
"amount": 100,
"from": "Felix",
"to": "mayun"
}
]
}
複製代碼
咱們能夠重複以上操做幾回,而後查看整個區塊鏈信息
curl -s -X GET localhost:8080/blockchain | python -m json.tool
複製代碼
會看到以下輸出
{
"blocks": [
{
"dateCreated": "2018-08-11 17:19:01",
"hash": "1124aa2a5867abee8b9cc3a3f4051b6665f89e26",
"id": 1,
"index": 0,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 193,
"previousHash": "0",
"transactions": [
{
"amount": 100,
"from": "Felix",
"to": "mayun"
},
{
"amount": 100,
"from": "Felix",
"to": "mayun"
}
]
},
{
"dateCreated": "2018-08-11 17:27:39",
"hash": "11bec5f7bf8226c62119adfbb03ad37d24267092",
"id": 2,
"index": 1,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 277,
"previousHash": "1124aa2a5867abee8b9cc3a3f4051b6665f89e26",
"transactions": [
{
"amount": 100,
"from": "Felix",
"to": "mayun"
},
{
"amount": 100,
"from": "Felix",
"to": "mayun"
}
]
}
],
"id": 1
}
複製代碼
咱們能夠看到Felix不停的向mayun轉100塊 , 真不要臉 。
區塊鏈同時存在與多個主機上,也就是說會有不少的block-chain-server運行,而對於不一樣的運算會有多個解產生,這就是衝突問題了,那麼咱們看下衝突解決的過程
vapor run serve --port=8081
複製代碼
curl -s -X POST localhost:8081/block | python -m json.tool
複製代碼
{
"blocks": [
{
"dateCreated": "2018-08-11 17:35:15",
"hash": "1152cb1aac50abd803a4589f28c7e054db207e23",
"id": 1,
"index": 0,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 215,
"previousHash": "0",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
},
{
"dateCreated": "2018-08-11 17:37:18",
"hash": "1127f8c712ae3205ccbab9788392bcd190b8b6b1",
"id": 2,
"index": 1,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 380,
"previousHash": "1152cb1aac50abd803a4589f28c7e054db207e23",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
},
{
"dateCreated": "2018-08-11 17:37:39",
"hash": "11b5d293c3068081f1771f14f96a3e450f282171",
"id": 3,
"index": 2,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 64,
"previousHash": "1127f8c712ae3205ccbab9788392bcd190b8b6b1",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
},
{
"dateCreated": "2018-08-11 17:37:45",
"hash": "11d97dfca7cc7a67c22b9df06017768fca0a193f",
"id": 4,
"index": 3,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 125,
"previousHash": "11b5d293c3068081f1771f14f96a3e450f282171",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
},
{
"dateCreated": "2018-08-11 17:38:03",
"hash": "115a991bd5d2ebe6e232b421965ab852b97a4202",
"id": 5,
"index": 4,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 220,
"previousHash": "11d97dfca7cc7a67c22b9df06017768fca0a193f",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
}
],
"id": 1
}
複製代碼
curl -s -X POST localhost:8080/node --data "address=localhost:8081" | python -m json.tool
複製代碼
{
"address": "localhost:8081",
"id": 1
}
複製代碼
curl -s -X POST localhost:8080/resolve | python -m json.tool
複製代碼
{
"blocks": [
{
"dateCreated": "2018-08-11 17:35:15",
"hash": "1152cb1aac50abd803a4589f28c7e054db207e23",
"id": 1,
"index": 0,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 215,
"previousHash": "0",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
},
{
"dateCreated": "2018-08-11 17:37:18",
"hash": "1127f8c712ae3205ccbab9788392bcd190b8b6b1",
"id": 2,
"index": 1,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 380,
"previousHash": "1152cb1aac50abd803a4589f28c7e054db207e23",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
},
{
"dateCreated": "2018-08-11 17:37:39",
"hash": "11b5d293c3068081f1771f14f96a3e450f282171",
"id": 3,
"index": 2,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 64,
"previousHash": "1127f8c712ae3205ccbab9788392bcd190b8b6b1",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
},
{
"dateCreated": "2018-08-11 17:37:45",
"hash": "11d97dfca7cc7a67c22b9df06017768fca0a193f",
"id": 4,
"index": 3,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 125,
"previousHash": "11b5d293c3068081f1771f14f96a3e450f282171",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
},
{
"dateCreated": "2018-08-11 17:38:03",
"hash": "115a991bd5d2ebe6e232b421965ab852b97a4202",
"id": 5,
"index": 4,
"message": "\u6b64\u533a\u5757\u5df2\u6dfb\u52a0\u81f3\u533a\u5757\u94fe",
"nonce": 220,
"previousHash": "11d97dfca7cc7a67c22b9df06017768fca0a193f",
"transactions": [
{
"amount": 200,
"from": "mayun",
"to": "Felix"
},
{
"amount": 200,
"from": "mayun",
"to": "Felix"
}
]
}
],
"id": 1
}
複製代碼
能夠看到大概區塊鏈的設計仍是比較有意思的,細節部分請不要在乎(好比sha1和prefix「11」 😄) 基本的介紹就到這裏,建議本身動手實踐一遍,仍是蠻有意思的。