从0到1简易区块链开发手册V0.3-数据持久化与创世区块

Author: brucefeng

Email: brucefeng@brucefeng.com

编程语言:Golang
从0到1简易区块链开发手册V0.3-数据持久化与创世区块


1.BoltDB简介

Bolt是一个纯粹Key/Value模型的程序。该项目的目标是为不需要完整数据库服务器(如Postgres或MySQL)的项目提供一个简单,快速,可靠的数据库。

BoltDB只需要将其链接到你的应用程序代码中即可使用BoltDB提供的API来高效的存取数据。而且BoltDB支持完全可序列化的ACID事务,让应用程序可以更简单的处理复杂操作。

其源码地址为:https://github.com/boltdb/bolt

2.BoltDB特性

BoltDB设计源于LMDB,具有以下特点:

  • 使用Go语言编写
  • 不需要服务器即可运行
  • 支持数据结构

  • 直接使用API存取数据,没有查询语句;
  • 支持完全可序列化的ACID事务,这个特性比LevelDB强;
  • 数据保存在内存映射的文件里。没有wal、线程压缩和垃圾回收;
  • 通过COW技术,可实现无锁的读写并发,但是无法实现无锁的写写并发,这就注定了读性能超高,但写性能一般,适合与读多写少的场景。

BoltDB是一个Key/Value(键/值)存储,这意味着没有像SQL RDBMS(MySQL,PostgreSQL等)中的表,没有行,没有列。相反,数据作为键值对存储(如在Golang Maps中)。键值对存储在Buckets中,它们旨在对相似的对进行分组(这与RDBMS中的表类似)。因此,为了获得Value(值),需要知道该Value所在的桶和钥匙。

3.BoltDB简单使用

//通过go get下载并import 
import "github.com/boltdb/bolt" 

3.1 打开或创建数据库

db, err := bolt.Open("my.db", 0600, nil)
if err != nil {log.Fatal(err)
}
defer db.Close()
  • 执行注意点

如果通过goland程序运行创建的my.db会保存在

GOPATH /src/Project目录下
如果通过go build main.go ; ./main 执行生成的my.db,会保存在当前目录GOPATH /src/Project/package下

3.2 数据库操作

(1) 创建数据库表与数据写入操作
//1. 调用Update方法进行数据的写入
err = db.Update(func(tx *bolt.Tx) error {
//2.通过CreateBucket()方法创建BlockBucket(表),初次使用创建b, err := tx.CreateBucket([]byte("BlockBucket"))if err != nil {return fmt.Errorf("Create bucket :%s", err)}//3.通过Put()方法往表里面存储一条数据(key,value),注意类型必须为[]byteif b != nil {err := b.Put([]byte("l"), []byte("Send $100 TO Bruce"))if err != nil {log.Panic("数据存储失败..")}}return nil
})//数据Update失败,退出程序
if err != nil {log.Panic(err)
}
(2) 数据写入
//1.打开数据库
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {log.Fatal(err)
}
defer db.Close()err = db.Update(func(tx *bolt.Tx) error {//2.通过Bucket()方法打开BlockBucket表b := tx.Bucket([]byte("BlockBucket"))
//3.通过Put()方法往表里面存储数据if b != nil {err := b.Put([]byte("l"), []byte("Send $200 TO Fengyingcong"))err =  b.Put([]byte("ll"), []byte("Send $100 TO Bruce"))if err != nil {log.Panic("数据存储失败..")}}return nil
})
//更新失败
if err != nil {log.Panic(err)
}
(3) 数据读取
//1.打开数据库
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {log.Fatal(err)
}
defer db.Close()//2.通过View方法获取数据
err = db.View(func(tx *bolt.Tx) error {//3.打开BlockBucket表,获取表对象b := tx.Bucket([]byte("BlockBucket"))//4.Get()方法通过key读取valueif b != nil {data := b.Get([]byte("l"))fmt.Printf("%s\n", data)data = b.Get([]byte("ll"))fmt.Printf("%s\n", data)}return nil
})if err != nil {log.Panic(err)
}

4.通过BoltDB存储区块

该代码包含对BoltDB的数据库创建,表创建,区块添加,区块查询操作

//1.创建一个区块对象block
block := BLC.NewBlock("Send $500 to Tom", 1, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})//2. 打印区块对象相关信息
fmt.Printf("区块的Hash信息为:\t%x\n", block.Hash)
fmt.Printf("区块的数据信息为:\t%v\n", string(block.Data))
fmt.Printf("区块的随机数为:\t%d\n", block.Nonce)//3. 打开数据库
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {log.Fatal(err)
}
defer db.Close()//4. 更新数据
err = db.Update(func(tx *bolt.Tx) error {//4.1 打开BlockBucket表对象b := tx.Bucket([]byte("blocks"))
//4.2 如果表对象不存在,创建表对象if b == nil {b, err = tx.CreateBucket([]byte("blocks"))if err != nil {log.Panic("Block Table Create Failed")}}//4.3 往表里面存储一条数据(key,value)err = b.Put([]byte("l"), block.Serialize())if err != nil {log.Panic("数据存储失败..")}return nil
})//更新失败,返回错误
if err != nil {log.Panic("数据更新失败")
}//5. 查看数据
err = db.View(func(tx *bolt.Tx) error {//5.1打开BlockBucket表对象b := tx.Bucket([]byte("blocks"))if b != nil {//5.2 取出key=“l”对应的valueblockData := b.Get([]byte("l"))//5.3反序列化   block := BLC.DeserializeBlock(blockData)//6. 打印区块对象相关信息fmt.Printf("区块的Hash信息为:\t%x\n", block.Hash)fmt.Printf("区块的数据信息为:\t%v\n", string(block.Data))fmt.Printf("区块的随机数为:\t%d\n", block.Nonce)}return nil
})
//数据查看失败
if err != nil {log.Panic("数据更新失败")
}

五.创建创世区块

从0到1简易区块链开发手册V0.3-数据持久化与创世区块

1.概念

北京时间2009年1月4日2时15分5秒,比特币的第一个区块诞生了。随着时间往后推移,不断有新的区块被添加到链上,所有后续区块都可以追溯到第一个区块。第一个区块就被人们称为创世区块。

2. 工作量证明

在比特币世界中,获取区块记账权的过程称之为挖矿,一个矿工成功后,他会把之前打包好的网络上的交易记录到一页账本上,同步给其他人。因为这个矿工能够最先计算出超难数学题的正确答案,说明这个矿工付出了工作量,是一个有权利记账的人,因此其他人也会同意这一页账单。这种依靠工作量来证明记账权,大家来达成共识的机制叫做“工作量证明”,简而言之结果可以证明你付出了多少工作量。Proof Of Work简称“PoW”,关于其原理跟代码实现,我们在后面的代码分析中进行讲解说明。

2.1 定义结构体

type ProofOfWork struct {Block  *Block   //要验证的blockTarget *big.Int //目标hash
}

2.2 创建工作量证明对象

const TargetBit = 16 //目标哈希的0个个数,16,20,24,28
func NewProofOfWork(block *Block) *ProofOfWork {//1.创建pow对象pow := &ProofOfWork{}//2.设置属性值pow.Block = blocktarget := big.NewInt(1)           // 目标hash,初始值为1target.Lsh(target, 256-TargetBit) //左移256-16pow.Target = targetreturn pow}

我们首先设定一个难度系数值为16,即目标哈希前导0的个数,0的个数越多,挖矿难度越大,此处我们创建一个函数NewProofOfWork用于返回Pow对象。

目标Hash的长度为256bit,通过64个16进制byte进行展示,如下所示为前导0为16/4=4的哈希

0000c01d342fc51cb030f93979343de70ab771855dd8ca28e6f5888737759747
  • 通过big.NewInt创建一个BigInt对象target
  • 对target进行通过左移(256-TargetBit)位操作

2.3 将int64类型转[]byte

func IntToHex(num int64) []byte {buff := new(bytes.Buffer)//将二进制数据写入w//err := binary.Write(buff, binary.BigEndian, num)if err != nil {log.Panic(err)}//转为[]byte并返回return buff.Bytes()
}

通过func Write(w io.Writer, order ByteOrder, data interface{}) error方法将一个int64的整数转为二进制后,每8bit一个byte,转为[]byte

2.4 拼接区块属性数据

func (pow *ProofOfWork) prepareData(nonce int64) []byte {data := bytes.Join([][]byte{IntToHex(pow.Block.Height),pow.Block.PrevBlockHash,IntToHex(pow.Block.TimeStamp),pow.Block.HashTransactions(),IntToHex(nonce),IntToHex(TargetBit),}, []byte{})return data}

通过bytes.Join方法将区块相关属性进行拼接成字节数组

2.5 "挖矿"方法

func (pow *ProofOfWork) Run() ([]byte, int64) {var nonce int64 = 0var hash [32]bytefor {//1.根据nonce获取数据data := pow.prepareData(nonce)//2.生成hashhash = sha256.Sum256(data) //[32]bytefmt.Printf("\r%d,%x", nonce, hash)//3.验证:和目标hash比较/*func (x *Int) Cmp(y *Int) (r int)Cmp compares x and y and returns:-1 if x <  y0 if x == y+1 if x >  y目的:target > hashInt,成功*/hashInt := new(big.Int)hashInt.SetBytes(hash[:])if pow.Target.Cmp(hashInt) == 1 {break}nonce++}fmt.Println()return hash[:], nonce}

代码思路

  • 设置nonce值:0,1,2.......
  • block-->拼接数组,产生hash
  • 比较实际hash和pow的目标hash

不断更改nonce的值,计算hash,直到小于目标hash。

2.6 验证区块

func (pow *ProofOfWork) IsValid() bool {hashInt := new(big.Int)hashInt.SetBytes(pow.Block.Hash)return pow.Target.Cmp(hashInt) == 1
}

判断方式同挖矿中的策略

3.区块创建

3.1 定义结构体

type Block struct {//字段属性//1.高度:区块在区块链中的编号,第一个区块也叫创世区块,一般设定为0Height int64//2.上一个区块的Hash值PrevBlockHash []byte//3.数据:Txs,交易数据Txs []*Transaction//4.时间戳TimeStamp int64//5.自己的hashHash []byte//6.NonceNonce int64
}

关于属性的定义,在代码的注释中比较清晰了,需要提一下的就是创世区块的PrevBlockHash一般设定为0 ,高度也一般设定为0

3.2 创建创世区块

func CreateGenesisBlock(txs []*Transaction) *Block{return NewBlock(txs,make([]byte,32,32),0)
}

设定创世区块的PrevBlockHash为0,区块高度为0

3.3 序列化区块对象

func (block *Block) Serialize()[]byte{//1.创建一个buffvar buf bytes.Buffer//2.创建一个编码器encoder:=gob.NewEncoder(&buf)//3.编码err:=encoder.Encode(block)if err != nil{log.Panic(err)}return buf.Bytes()
}

通过gob库的Encode方法将Block对象序列化成字节数组,用于持久化存储

3.4 字节数组反序列化

func DeserializeBlock(blockBytes [] byte) *Block{var block Block//1.先创建一个readerreader:=bytes.NewReader(blockBytes)//2.创建×××decoder:=gob.NewDecoder(reader)//3.解码err:=decoder.Decode(&block)if err != nil{log.Panic(err)}return &block
}

定义一个函数,用于将[]byte反序列化为block对象

4.区块链创建

4.1 定义结构体

type BlockChain struct {DB  *bolt.DB //对应的数据库对象Tip [] byte  //存储区块中最后一个块的hash值
}

定义区块链结构体属性DB用于存储对应的数据库对象,Tip用于存储区块中最后一个块的Hash值

4.2 判断数据库是否存在

const DBName  = "blockchain.db" //数据库的名字
const BlockBucketName = "blocks" //定义bucket

定义数据库名字以及定义用于存储区块数据的bucket(表)名

func dbExists() bool {if _, err := os.Stat(DBName); os.IsNotExist(err) {  return false //表示文件不存在}return true //表示文件存在
}

需要注意IsNotExist返回true,则表示不存在成立,返回值为true,则dbExists函数的返回值则需要返回false,否则,返回true

4.3 创建带有创世区块的区块链

func CreateBlockChainWithGenesisBlock(address string) {/*1.判断数据库如果存在,直接结束方法2.数据库不存在,创建创世区块,并存入到数据库中*/if dbExists() {fmt.Println("数据库已经存在,无法创建创世区块")return}//数据库不存在fmt.Println("数据库不存在")fmt.Println("正在创建创世区块")/*1.创建创世区块2.存入到数据库中*///创建一个txs--->CoinBasetxCoinBase := NewCoinBaseTransaction(address)genesisBlock := CreateGenesisBlock([]*Transaction{txCoinBase})db, err := bolt.Open(DBName, 0600, nil)if err != nil {log.Panic(err)}defer db.Close()err = db.Update(func(tx *bolt.Tx) error {//创世区块序列化后,存入到数据库中b, err := tx.CreateBucketIfNotExists([]byte(BlockBucketName))if err != nil {log.Panic(err)}if b != nil {err = b.Put(genesisBlock.Hash, genesisBlock.Serialize())if err != nil {log.Panic(err)}b.Put([]byte("l"), genesisBlock.Hash)}return nil})if err != nil {log.Panic(err)}//return &BlockChain{db, genesisBlock.Hash}
}

代码分析

(1) 判断数据库是否存在,如果不存在,证明还没有创建创世区块,如果存在,则提示创世区块已存在,直接返回

    if dbExists() {fmt.Println("数据库已经存在,无法创建创世区块")return}

(2) 如果数据库不存在,则提示开始调用相关函数跟方法创建创世区块

    fmt.Println("数据库不存在")fmt.Println("正在创建创世区块")

(3) 创建一个交易数组Txs

关于交易这一部分内容,将在后面一个章节中进行详细说明,篇幅会非常长,这也是整个课程体系中最为繁琐,知识点最广的地方,届时慢慢分析

txCoinBase := NewCoinBaseTransaction(address)

通过函数NewCoinBaseTransaction创建一个CoinBase交易

func NewCoinBaseTransaction(address string) *Transaction {txInput := &TxInput{[]byte{}, -1, nil, nil}txOutput := NewTxOutput(10, address)txCoinBaseTransaction := &Transaction{[]byte{}, []*TxInput{txInput}, []*TxOutput{txOutput}}//设置交易IDtxCoinBaseTransaction.SetID()return txCoinBaseTransaction
}

(4) 生成创世区块

genesisBlock := CreateGenesisBlock([]*Transaction{txCoinBase})

(5) 打开/创建数据库

    db, err := bolt.Open(DBName, 0600, nil)if err != nil {log.Panic(err)}defer db.Close()

通过bolt.Open方法打开(如果不存在则创建)数据库文件,注意数据库关闭操作不能少,用defer实现延迟关闭。

(6) 将数据写入数据库

    err = db.Update(func(tx *bolt.Tx) error {b, err := tx.CreateBucketIfNotExists([]byte(BlockBucketName))if err != nil {log.Panic(err)}if b != nil {err = b.Put(genesisBlock.Hash, genesisBlock.Serialize())if err != nil {log.Panic(err)}b.Put([]byte("l"), genesisBlock.Hash)}return nil})if err != nil {log.Panic(err)}

通过db.Upadate方法进行数据更新操作

  • 创建/打开存储区块的Bucket:BlockBucketName
  • 将创世区块序列化后存入Bucket中
    • 通过Put方法更新K/V值(Key:区块哈希,Value:区块序列化后的字节数组)
    • 通过Put方法更新Key为“l”的Value为最新区块哈希值,此处即genesisBlock.Hash

5.命令行调用

func (cli *CLI) CreateBlockChain(address string) {CreateBlockChainWithGenesisBlock(address)
}

测试命令

$ ./mybtc  createblockchain -address 1DHPNHKfk9uUdog2f2xBvx9dq4NxpF5Q4Q

返回结果

数据库不存在
正在创建创世区块
32325,00005c7b4246aa88bd1f9664c665d6424d1522f569d981691ac2b5b5d15dd8d9

本章节介绍了如何创建一个带有创世区块的区块链,并持久化存储至数据库blockchain.db

$ ls
BLC             Wallets.dat     blockchain.db   main.go         mybtc

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/282379.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ELK之elasticsearch5.6的安装和head插件的安装

这里选择的elasticsearch为5.6的新版本&#xff0c;根据官方文档有几种暗装方式&#xff1a; https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html 这里选择rpm包安装https://www.elastic.co/guide/en/elasticsearch/reference/curre…

Nginx 基础(一)

一 、Nginx简述 Nginx是一个开源、高性能、可靠的HTTP中间件、代理服务。二 、常见的HTTP服务 1. HTTPD-Apache基金会 2. IIS-微软 3. GWS-Google 4. Nginx三、为什么选择Nginx 原因一&#xff1a;IO多路复用epoll &#xff08;主要解决了并发性的问题&#xff09; 注1&#xf…

ASP.NET Core高性能服务器HTTP.SYS

如果我们只需要将ASP.NET CORE应用部署到Windows环境下&#xff0c;并且希望获得更好的性能&#xff0c;那么我们选择的服务器类型应该是HTTP.SYS。Windows环境下任何针对HTTP的网络监听器/服务器在性能上都无法与HTTP.SYS比肩。[本文节选《ASP.NET Core 6框架揭秘》第18章]一、…

Nginx 基础 ( 二)

一、HTTP请求 http请求包括客户端请求服务端 以及 服务端响应数据回客户端&#xff0c;如下 请求&#xff1a;包括请求行、请求头部、请求数据 响应&#xff1a;包括状态行、消息报头、响应正文 比如在Linux中curl请求网站获取请求信息和响应信息 curl -v http://www.kugou.com…

《金融行业应用解决方案白皮书》发布,金融自主创新未来可期!

日前&#xff0c;以“聚势赋能 行业共创”为主题的金融行业解决方案发布会在线上举行。麒麟软件发布《金融行业应用解决方案白皮书》&#xff0c;并发起成立“金融机具生态圈俱乐部”&#xff0c;助力金融行业用户高质量发展。金融信息系统曾经被国外厂商垄断金融信息系统作为国…

leetcode53 Maximum Subarray 最大连续子数组

题目要求 Find the contiguous subarray within an array (containing at least one number) which has the largest sum.For example, given the array [-2,1,-3,4,-1,2,1,-5,4], the contiguous subarray [4,-1,2,1] has the largest sum 6.即&#xff1a;寻找数列中的一个子…

详解go语言的array和slice 【二】

上一篇 详解go语言的array和slice 【一】已经讲解过,array和slice的一些基本用法&#xff0c;使用array和slice时需要注意的地方&#xff0c;特别是slice需要注意的地方比较多。上一篇的最后讲解到创建新的slice时使用第三个索引来限制slice的容量&#xff0c;在操作新slice时…

详解Objective-C的meta-class

2019独角兽企业重金招聘Python工程师标准>>> 比较简单的一篇英文&#xff0c;重点是讲解meta-class。翻译下&#xff0c;加深理解。 原文标题&#xff1a;What is a meta-class in Objective-C? 原文地址&#xff1a;http://www.cocoawithlove.com/2010/01/what-is…

十倍程序员 | 使用 Source Generator 将 JSON 转换成 C# 类

前言有时候&#xff0c;我们需要将通过 WebAPI 接收 JSON 字符串转换成 C# 代码。Visual Studio 提供了一个功能菜单可以轻松实现&#xff1a;执行完成后&#xff0c;它会将生成的代码放在打开的的代码窗口中。但是&#xff0c;如果有多个 JSON 字符串需要转换&#xff0c;这个…

微软Microsoft Azure 机器学习工作室的案例之Image Classification using DenseNet

点击上方蓝字关注我们&#xff08;本文阅读时间&#xff1a;10分钟)Microsoft Azure Machine Learning Studio是微软强大的机器学习平台&#xff0c;在设计器中&#xff0c;微软内置了15个场景案例&#xff0c;但网上似乎没有对这15个案例深度刨析的分析资料&#xff0c;所以我…

音乐分类

代码&#xff1a; 1 import numpy as np2 from scipy import fft3 from scipy.io import wavfile4 from sklearn.linear_model import LogisticRegression5 import random6 """7 使用logistic regression处理音乐数据&#xff0c;音乐数据训练样本的获得是使…

不管对不对,先把闹钟关了再说

小榆提前关闭早上闹钟&#xff0c;几乎工作日的早晨都是被这魔怔的铃声给拉扯醒&#xff0c;无论有多么不愿还是痛苦&#xff0c;可对这闹钟也无可奈何&#xff0c;就算一时果断掐掉接下来是另一回麻烦事。最后一天&#xff0c;已经顾不得多少&#xff0c;没什么令人惧怕的人或…

pycharm(windows)安装及其设置中文菜单

pycharm&#xff08;windows&#xff09;安装及其设置中文菜单 1.下载 在官网&#xff08;http://www.jetbrains.com/pycharm/download/#sectionwindows&#xff09;进行下载 或者到百度云进行下载 专业版&#xff1a;链接&#xff1a;http://pan.baidu.com/s/1bSSRds 密码&…

Tomcat定义虚拟主机案例

Tomcat定义虚拟主机案例 作者&#xff1a;尹正杰 版权声明&#xff1a;原创作品&#xff0c;谢绝转载&#xff01;否则将追究法律责任。 一.准备环境 1>.创建web程序的根目录 [rootyinzhengjie ~]# mkdir -pv /home/yinzhengjie/data/www/webapps/ROOT mkdir: created direc…

将域名绑定到ip上,并实现访问不同二级子域名对应不同目录

一、将域名绑定到ip上1、环境介绍&#xff1a;阿里云服务器ESC&#xff08;美国硅谷&#xff09; 2、购买域名 3、备案 注&#xff1a;由于我买的是美国地区服务器&#xff0c;所以不用备案&#xff0c;如果买的国内服务器&#xff0c;这里需要添加一个备案操作。 4、域名实名认…

ABP vNext微服务架构详细教程(补充篇)——单层模板(中)

框架搭建2聚合服务这里我们将聚合服务命名为Domain.Core和基础服务层一致&#xff0c;我们先通过命令创建单层模板项目Domain.Core&#xff0c;这里我们删除wwwroot、Data、Entities、Localization、ObjectMapping文件夹及其所有子文件&#xff0c;并删除package.json文件和Ser…

谈一谈synchronized关键词

1.使用 java中的每一个对象都可以作为synchronized的锁进行代码同步&#xff0c;常见的形式 同步代码块锁是synchronized括号内的对象普通成员方法上&#xff0c;锁是当前的对象&#xff0c;synchronized(this)静态方法上&#xff0c;锁是当前类的Class对象2. 原理 synchronize…

系统学习redis之二——redis集群搭建

redis单点部署&#xff1a; 安装命令&#xff1a; # cd /usr/local/ # wget http://download.redis.io/releases/redis-4.0.1.tar.gz #下载安装包 # yum -y install gcc psmisc #安装依赖包 # tar xf redis-4.0.1.tar.gz # cd /usr/lo…

业务技术协同线上化的研发管理实战

摘要&#xff1a;2017年1月13日举办的【云栖计算之旅】线下沙龙第4期研发管理专场&#xff0c;阿里巴巴B2B事业群产品专家代平为大家带来了题为业务技术协同线上化的研发管理实战的演讲。本文主要从管理产品研发的理念开始谈起&#xff0c;着重说明了云效指挥部的六大步骤&…

Linux中写脚本,同时去开启我们自己设定的多个服务(含定时脚本实现)

场景介绍&#xff1a; 在Linux中&#xff0c;我们通常开启服务需要使用systemctl start 服务名 命令&#xff0c;这样&#xff0c;如果开启一个服务还好&#xff0c;但是如果同时开启多个服务&#xff0c;难免会感到麻烦&#xff0c;这时&#xff0c;我们可以自定义一个脚本&a…