传送门: 柏链项目学院
3. 增加数据库和客户端
之前我们的区块链已经产生了,但是程序终止后,数据就丢失了!为了下次启动后能够继续,我们需要引入一个小型数据库。这一次,我们选择与短跑世界记录保持者同名的数据库-bolt。
bolt的内容不详细在这里介绍了,大家可以参考文章:与短跑名将同名的数据库Bolt。
定义数据库文件和bucket
const dbFile = "blockchain.db"
const blocksBucket = "blocks"
重新定义blockchain
// Blockchain keeps a sequence of Blocks
type Blockchain struct {
tip []byte
db *bolt.DB
}
定义blockchain的迭代器
// BlockchainIterator is used to iterate over blockchain blocks
type BlockchainIterator struct {
currentHash []byte
db *bolt.DB
}
处理创世块,使用boltdb
func NewBlockchain() *Blockchain {
var tip []byte
db, err := bolt.Open(dbFile, 0600, nil)
if err != nil {
log.Panic(err)
}
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
if b == nil {
fmt.Println("No existing blockchain found. Creating a new one...")
genesis := NewGenesisBlock()
b, err := tx.CreateBucket([]byte(blocksBucket))
if err != nil {
log.Panic(err)
}
err = b.Put(genesis.Hash, genesis.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"), genesis.Hash)
if err != nil {
log.Panic(err)
}
tip = genesis.Hash
} else {
tip = b.Get([]byte("l"))
}
return nil
})
if err != nil {
log.Panic(err)
}
bc := Blockchain{tip, db}
return &bc
}
上述代码将创世块的数据写入到数据库中,并将创世块返回,注意此块的生成仍然需要pow工作量证明才可以。
接下来我们来写一个客户端,创建一个cli.go文件
cli肯定要记录区块链的数据
type CLI struct {
bc *Blockchain
}
提供一个命令行的启动帮助
func (cli *CLI) printUsage() {
fmt.Println("Usage:")
fmt.Println(" addblock -data BLOCK_DATA - add a block to the blockchain")
fmt.Println(" printchain - print all the blocks of the blockchain")
}
校验一下参数是否正确
func (cli *CLI) validateArgs() {
if len(os.Args) < 2 {
cli.printUsage()
os.Exit(1)
}
}
需要将之前blockchain添加一个AddBlock函数,需要将前一块的hash取出来
// AddBlock saves provided data as a block in the blockchain
func (bc *Blockchain) AddBlock(data string) {
var lastHash []byte
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash = b.Get([]byte("l"))
return nil
})
if err != nil {
log.Panic(err)
}
newBlock := NewBlock(data, lastHash)
err = bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
err := b.Put(newBlock.Hash, newBlock.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"), newBlock.Hash)
if err != nil {
log.Panic(err)
}
bc.tip = newBlock.Hash
return nil
})
}
打印一下区块链信息
func (cli *CLI) printChain() {
bci := cli.bc.Iterator()
for {
block := bci.Next()
fmt.Printf("Prev. hash: %xn", block.PrevBlockHash)
fmt.Printf("Data: %sn", block.Data)
fmt.Printf("Hash: %xn", block.Hash)
pow := NewProofOfWork(block)
fmt.Printf("PoW: %sn", strconv.FormatBool(pow.Validate()))
fmt.Println()
if len(block.PrevBlockHash) == 0 {
break
}
}
}
上述代码需要区块链迭代器支持next函数以可以遍历
// Next returns next block starting from the tip
func (i *BlockchainIterator) Next() *Block {
var block *Block
err := i.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
encodedBlock := b.Get(i.currentHash)
block = DeserializeBlock(encodedBlock)
return nil
})
if err != nil {
log.Panic(err)
}
i.currentHash = block.PrevBlockHash
return block
}
DeserializeBlock是对区块链的数据进行读取和解码
// DeserializeBlock deserializes a block
func DeserializeBlock(d []byte) *Block {
var block Block
decoder := gob.NewDecoder(bytes.NewReader(d))
err := decoder.Decode(&block)
if err != nil {
log.Panic(err)
}
return &block
}
接下来核心部分,为cli增加一个运行函数
// Run parses command line arguments and processes commands
func (cli *CLI) Run() {
cli.validateArgs()
addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
addBlockData := addBlockCmd.String("data", "", "Block data")
switch os.Args[1] {
case "addblock":
err := addBlockCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "printchain":
err := printChainCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
default:
cli.printUsage()
os.Exit(1)
}
if addBlockCmd.Parsed() {
if *addBlockData == "" {
addBlockCmd.Usage()
os.Exit(1)
}
cli.addBlock(*addBlockData)
}
if printChainCmd.Parsed() {
cli.printChain()
}
}
根据命令行输入的不同,执行不同的功能。
内容来源于网络如有侵权请私信删除
- 还没有人评论,欢迎说说您的想法!