索引:
理论部分
构建一个自己的区块链
1.创建区块
2.创建区块链
3.http server暴露区块链接口
代码传送门:https://github.com/Bing0514/simpleBlockChain
理论部分
区块链就是一种特殊的分布式数据库,没有中间节点,每个节点都是平等的,可以先任何一个节点写入数据,每个节点都是平等的因此请求的数据会快速同步到整个网络的所有节点,这个网络没有中心,没有管理员,每个节点都会写入交易数据,并且只能增查,不支持改删。
目前区块链主要分为三类:
公有链 – 任何人都能读取、交易等操作,如比特币、以太坊等
私有链 – 一个公司和组织内使用,一般是开发节点、测试节点
联盟链 – 主要针对有竞争有需要合作的场景,其共识过程受到预选节点控制的区块链 如Fabric R3联盟(世界各大银行组成) EEA(以太坊企业联盟) 阳光链等
解决的问题:
价值传递问题 – 交易双方认可的有用的物品进行交互转移,特点是转移的时候转让方必须消失物品,受让方必须接受到物品,在现实世界这是很容易的,但是在互联网中很难实现,互联网中更多的是数据的传递而非真正的价值,因此要引入第三方,比如支付宝等等,这样一来对第三方有很强的依赖性。在区块链中,人们通过区块链记账来解决互联网中一份价值复制多份的问题
特点:
不可篡改:只能增查,不支持改删
可追溯:公开透明(注意,可追溯不代表是数据是真实的)
去中心化
区块链的六层架构
应用层 – 封装了区块链的各种应用场景和案例
合约层 – 封装各类脚本和智能合约,是区块链可编程的基础
激励层 – 主要应用在公有链中,激励遵守规则的节点,惩罚不遵守规则的节点(在某些区块链系统中可能不存在)
共识层 – 网络节点的共识区块算法,决定了记账方式
网络层 – 数据传播、数据验证机制、自动组网功能等
数据层 – 底层数据区块链式结构个公私钥、时间戳技术等(最核心最基础)
应用层通过RPC与区块链其他部分进行交互,分开部署。
区块链的链式结构
最基本的构成单元是区块,一个区块由区块头和区块体构成,区块头中最重要的一个元素是父区块的hash,每个元素都有父区块的hash相当于有了一个父区块的指针,把一个一个的区块连接起来,在链式结构中,第一个区块叫做创世区块,这个区块只有数据没有父区块hash值。每个每个hash值都是通过hash函数计算得来的。
hash函数的特点是:
- 确定性:同样的输入,无论计算多少次都是一定的
- 单向性:反推困难
- 隐秘性:抗暴力破解
- 抗篡改:改变一个比特,其hash值的变化非常大
- 抗碰撞:hash值重复可能性非常小
hash函数的实现:
MD系列
- SHA系列,推荐使用SHA256,SHA3
-
综上区块链其实很简单就是这样的结构:
构建一个自己的区块链
- 实现链式结构
-
实现一个http server,对外暴露读写接口,从而可以通过http请求来读写链上数据
目录结构:
步骤:
1.创建区块 block.go
package demochain
import (
"crypto/sha256"
"encoding/hex"
"time"
)
type Block struct {
//区块头
Index int64 // 区块编号 - 代表区块在区块链中的位置
Timestamp int64 //区块时间戳 - 区块创建的时间
PrevBlockHash string //上一个区块的hash值
Hash string //当前区块的hash值
// 区块体
Data string //区块数据
}
/**
计算hash
*/
func calculateHash(b Block) string{
blockData := string(b.Index) + string(b.Timestamp) + b.PrevBlockHash + b.Data
hashInButes := sha256.Sum256([]byte(blockData)) //blockData是一个字符串,注意这里要转换层字节切片
return hex.EncodeToString(hashInButes[:])
}
/**
生成新的区块 - 数据是基于前一个区块的
*/
func GenerateNewBlock(preBlock Block, data string) Block{
newBlock := Block{}
newBlock.Index = preBlock.Index+1
newBlock.PrevBlockHash = preBlock.Hash
newBlock.Timestamp = time.Now().Unix()
newBlock.Hash = calculateHash(newBlock)
return newBlock
}
/**
生成创世区块
index是0
hash是一个空值
*/
func GenerateGenesesBlock() Block{
preBlock := Block{} //这个父区块是不存在的,是为了函数复用
preBlock.Index = -1
preBlock.Hash = ""
return GenerateNewBlock(preBlock,"Genesis Block")
}
2.创建区块链 blockchain.go
package core
import (
"fmt"
"log"
)
type Blockchain struct {
Blocks []*Block
}
/**
新建一个区块链
*/
func NewBlockchain() *Blockchain {
genesisBlock := GenerateGenesesBlock()
blockchain := Blockchain{}
blockchain.AppendBlock(&genesisBlock)
return &blockchain
}
/**
写入一条数据
*/
func (bc *Blockchain)SendData(data string) {
preBlock := bc.Blocks[len(bc.Blocks) -1 ]
newBlock := GenerateNewBlock(*preBlock,data)
bc.AppendBlock(&newBlock)
}
func (bc *Blockchain) Print(){
for _, block := range bc.Blocks{
fmt.Printf("Index:%d\n", block.Index)
fmt.Printf("Prev.Hash:%s\n", block.PrevBlockHash)
fmt.Printf("Curr.Hash:%s\n", block.Hash)
fmt.Printf("Data:%s\n",block.Data)
fmt.Printf("Timestamp:%d\n",block.Timestamp)
fmt.Println()
}
}
/**
添加新的区块
*/
func (bc *Blockchain) AppendBlock(newBlock *Block) {
if len(bc.Blocks) == 0 {
bc.Blocks = append(bc.Blocks,newBlock)
return
}
if(isValid(*newBlock,*bc.Blocks[len(bc.Blocks) -1])){
bc.Blocks = append(bc.Blocks,newBlock)
}else {
log.Fatal("invalid block")
}
}
func isValid(newBlock Block, oldBlock Block) bool{
if(newBlock.Index - 1 != oldBlock.Index){
return false
}
if newBlock.PrevBlockHash != oldBlock.Hash{
return false;
}
if calculateHash(newBlock) != newBlock.Hash{
return false;
}
return true;
}
接下来创建一个main来运行
package main
import "demochain/core"
func main() {
bc := core.NewBlockchain()
bc.SendData("Send 1 BTC to Jacky")
bc.SendData("Send 1 EOS to Jack")
bc.Print()
}
运行结果
说明(这里前面都提到过):
第一个index为0的区块是创世区块,是没有父区块的hash的
可以看到第一个区块到第二个区块到第三个区块到之后的区块都是通过之前一个区块的hash来生成自己的hash的,这就是为什么叫做区块链的链
3.创建http server
这里我们让这个区块链通过API暴露出来可以被写入数据和读取数据
package main
import (
"demochain/core"
"encoding/json"
"io"
"net/http"
)
var blockchain *core.Blockchain
func run() {
http.HandleFunc("/blockchain/get", blockchainGetHandler)
http.HandleFunc("/blockchain/write",blockchainWriteHandler)
http.ListenAndServe("localhost:8641",nil)
}
func blockchainGetHandler(w http.ResponseWriter, r *http.Request) {
bytes,error := json.Marshal(blockchain)
if error != nil{
http.Error(w, error.Error(),http.StatusInternalServerError)
return
}
io.WriteString(w, string(bytes))
}
func blockchainWriteHandler(w http.ResponseWriter, r *http.Request) {
blockData := r.URL.Query().Get("data")
blockchain.SendData(blockData)
blockchainGetHandler(w,r)
}
func main() {
blockchain = core.NewBlockchain()
run()
}
运行这个main方法,启动一个http server,首先看到的是创世区块,因此这个时候还没有写入数据
然后写入两个区块数据看效果
本文作者熊冰,个人网站Bing的天涯路,转载请注明出处。