带你玩转区块链--基于GoLang创建区块链、重构BTC-第一章【比特币篇】

一、意义:

ps:基于bolt的v3版本已上传github,点击链接下载源码:

https://github.com/lsy-zhaoshuaiji/boltBlockChain.git

V4、V5版皆已上传,点击链接下载源码(请注意分支):

https://github.com/lsy-zhaoshuaiji/pow.git

区块链是什么?(个人理解非百度..)

       区块链,就是一个分布式的超级账本。是比特币的底层技术。在区块链中每一个节点都是数据校验者和守护者,数据的真实性,由所有节点共同守护,一旦出现了某个节点作弊的行为,就会被踢出节点。而新数据验证存储的过程就叫做工作量证明(pow),也就是挖矿。说到分布式,我们会想起以前比较火的p2p平台p2p也是分布式,但缺少校验机制,所以是缺乏第三方监管和担保的点对点交易平台,所以容易发生跑路现象。依赖中心数据库来处理信息的平台往往在安全性方面存在弊端。区块链上的p2p因为全网无特殊节点,每个节点都可以提供全网所需的全部服务,任何一个节点垮掉,都不会对整个网络的稳定性构成威胁,所以是非常安全的。


区块的构成?

区块的构成分为区块头和区块体,区块头中记录了版本号、上一个区块的Hash地址、merkle根、区块创建时间戳、区块的工作量难度目标以及用于计算目标的参数值。区块体中记录了该区块存储的交易数量以及交易数据。

区块头:

字段  大小   描述
version 4字节 版本号,⽤于跟踪软件/协议的更新
prevBlockHash  32字节 上一个区块的Hash地址
merkleRoot 32字节 该区块中交易的merkl e树根的哈希值
time  4字节 该区块的创建时间戳
difficultyTarget 4字节 该区块链工作量证明难度目标(稍后讲解工作量证明)
nonce  4字节  用于证明工作量的计算参数(挖矿要求出的值)

 区块体:

字段 大小 描述
numTransactionsBytes 1字节 交易数量占用的字节数
numTransactions 0-8个字节 区块内存储的交易数量
transactions 不确定 区块内存的多个交易数据

 

 二、最简单的区块链的创建:

    通过上述描述,我们知道了区块链是由区块头加区块体组成,区块体存放了各种其他数据,我们暂不做考虑。我们先从创建一个区块头开始学习。

1.首先我们定义一个结构体并起名为block,里面存放区块头字段,为了简单起见,字段中只包含前哈希,当前哈希和某些字符串数据(可以理解为区块链中的转账数据)。

2.我们创建一个newblock方法,此方法的作用是为block结构体赋值,并返回区块地址。

3.创建一个setHash方法,模拟挖矿生成当前哈希。

4.定义区块链结构体,blockChain,存放区块地址。

5.创建一个genesisBlock方法,此方法的目的是创建一个创世块(第一个区块),

5.创建一个newBlockChain方法,此方法的目的是将创世块地址放入结构体变量blockChain.blockchain中,初始化区块链。

6.创建一个AddBlockChain方法,此方法的目的是将新增区块的地址,存放在blockChain.blockchain中,将新区块连接到区块链中。

ps:在区块链中区块生成当前哈希的过程就是pow(工作量证明/挖矿),为了简单起见,我们将pow放到后面详细讲,此部分我们利用前哈希和data拼接后的数据做sha256得到当前哈希

 程序分为三个文件,分别是负责区块的block.go文件,负责区块链的blockchain模块,负责程序运行的main.go文件

main.go:

package main

import (
	"fmt"
)
/*
区块的创建分为7步
1.定义结构体
2.创建区块
3.生成哈希(pow/工作量证明/)
4.定义区块链结构体
5.生成区块链并添加创世
6.生成创世块块
7.添加其他区块
*/


func main(){
	BC:=NewBlockChain()
	BC.AddBlockChain("A向laughing转账580个BTC")
	for i,block:=range BC.blockChain{
		fmt.Printf("当前区块高度:%d\n",i)
		fmt.Printf("当前哈希:%x\n",block.Hash)
		fmt.Printf("上一级哈希:%x\n",block.prevHash)
		fmt.Printf("交易信息:%s\n",block.data)
	}
}

block .go

package main

import "crypto/sha256"
//1.定义区块结构体

type Block struct {
	prevHash []byte
	Hash []byte
	data []byte
}
//2.创建区块
func NewBlock(data string,prevHash []byte) *Block{
	block:=Block{
		prevHash:prevHash,
		Hash:[]byte{},
		data:[]byte(data),
	}
	block.SetHash()
	return &block
}
//3.生成哈希
func (this *Block)SetHash (){
	blockInfo:=append(this.prevHash,this.data...)
	//func Sum256(data []byte) [Size]byte {
	Hash:=sha256.Sum256(blockInfo)
	this.Hash=Hash[:]
}

blockChain.go

package main
//4.定义区块链结构体
type BlockChain struct {
	blockChain []*Block
}
//5.生成创世块
func GenesisBlock()*Block{
	return NewBlock("laughing 转账给 fance 1BTC",[]byte{})
}
//6.生成区块链
func NewBlockChain()*BlockChain{
	genesisBlock:=GenesisBlock()
	return &BlockChain{
		blockChain:[]*Block{genesisBlock},
	}
}
//添加其他区块
func (this *BlockChain)AddBlockChain(data string){
	//1.找到上一个区块,并获得其哈希
	lastBlock:=this.blockChain[len(this.blockChain)-1]
	prevHash:=lastBlock.Hash
	//2.生成新的区块
	block:=NewBlock(data,prevHash)
	//3.将新的区块添加到区块链中
	this.blockChain=append(this.blockChain,block)
}

三、完整字段的区块链的创建:

          以上代码,区块头中字段过于简单。而在真实的区块头中,是存在很多字段的。所以现在我们将区块头完整字段加入到代码中。修改如下:

1.修改了区块头block结构体,新增了version等区块字段。

2.修改了哈希生成函数,利用bytes.join()方法,将区块头字段拼接起来。

ps:链上(blockchain)的代码是没有进行修改的。完整字段包含版本号、前哈希,markleRoot、时间戳、难度、目标值、当前哈希和某些字符串数据。 代码如下:

package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"time"
)
//1.定义区块结构体

type Block struct {
	//这里进行了修改,新增了变量
	version uint64
	prevHash []byte
	markleRoot []byte
	timeStamp uint64
	diffculty uint64
	nonce uint64
	Hash []byte
	data []byte
}
//2.创建区块
func NewBlock(data string,prevHash []byte) *Block{
	block:=Block{
		version:00,
		prevHash:prevHash,
		markleRoot:[]byte{},
		timeStamp:uint64(time.Now().Unix()),
		diffculty:0,
		nonce:0,
		Hash:[]byte{},
		data:[]byte(data),
	}
	block.SetHash()
	return &block
}
func IntToByte(n uint64)[]byte{
	x:=int64(n)
	bytesBuffer:=bytes.NewBuffer([]byte{})
	err:=binary.Write(bytesBuffer,binary.BigEndian,x)
	if err!=nil{
		fmt.Println(err)
		return []byte{}
	}
	return bytesBuffer.Bytes()
}
//3.生成哈希
func (this *Block)SetHash (){
	byteList:=[][]byte{
		IntToByte(this.version),
		this.prevHash,
		this.markleRoot,
		IntToByte(this.timeStamp),
		IntToByte(this.diffculty),
		IntToByte(this.nonce),
		this.Hash,
		this.data,
	}
	//利用bytes.join()方法将[]byte拼接起来,不会的同学请自行百度
	blockInfo:=bytes.Join(byteList,[]byte{})
	Hash:=sha256.Sum256(blockInfo)
	this.Hash=Hash[:]
}

四、实现pow(工作量证明/挖矿):

        通过上述的描述,相信大家也意识到了pow其实就是生成当前哈希的过程。我们将pow有关的信息存放到proofofwork.go文件中。文件中定义了pow的结构体,有一个创建pow的方法,和一个挖矿(工作量证明)的run方法。现在我们详细分析pow的整个过程。如下:

 1.创建pow结构体,并在结构体中定义变量--“挖矿难度值”(diffculty),由于挖矿竞争很大,int存储可能不够,所以我们使用big.int型作为diffculty的基础类型。

 2.创建run方法,实现当前区块的数据拼接(包括版本号、前哈希、难度值、一个随机数(nonce)等....)

 3.将拼接后的数据做哈希sha256,得到一个哈希值

4.将此哈希值转化为big.int类型

5.将转换后的数据与diffculty进行比较,

   5.1 若小于diffculty则,代表挖矿成功,继而将此区块添加进区块链中,并返回随机数nonce。

   5.2若大于diffculty则表达,挖矿失败,nonce++,继续循环,直到满足5.1条件。如图:

带你玩转区块链--基于GoLang创建区块链、重构BTC-第一章【比特币篇】_第1张图片

 实现代码如下:

1.首先,我们创建一个proofofwork.go(pow.go)文件:

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"math/big"
)

type ProofOfWork struct {
	block *Block
	target *big.Int
}
func NewProofOfWork(block *Block)*ProofOfWork{
	pow:=ProofOfWork{
		block:block,
	}
	//挖矿难度值
	diffculty:="0000100000000000000000000000000000000000000000000000000000000000"
	tmp:=big.Int{}
	tmp.SetString(diffculty,16)
	pow.target=&tmp
	pow.block.diffculty=pow.target.Uint64()
	return &pow
}
func (this *ProofOfWork)run()([]byte,uint64){
	var nonce uint64
	var hash [32]byte
	for {
		//1.拼接区块数据
		byteList:=[][]byte{
			IntToByte(this.block.version),
			this.block.prevHash,
			this.block.markleRoot,
			IntToByte(this.block.timeStamp),
			IntToByte(this.block.diffculty),
			IntToByte(nonce),
			this.block.data,
		}
		blockinfo:=bytes.Join(byteList,[]byte{})
		//2.将拼接后的数据进行哈希256运算
		hash=sha256.Sum256(blockinfo)
		//3.将哈希数据转为big.int类型
		tmp:=big.Int{}
		tmp.SetBytes(hash[:])
		//4.比较
		status:=tmp.Cmp(this.target)
		if status==-1{
			fmt.Printf("挖矿成功,难度为:%d,   当前哈希为:%x,  nonce为:%d\n",this.block.diffculty,hash,nonce)
			break;
		}else {
			//fmt.Println(nonce)
			nonce++
		}
	}
	return hash[:],nonce
}

2.修改block.go文件,将原生成哈希的方法修改为pow的方式:

package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"time"
)
//1.定义区块结构体

type Block struct {
	version uint64
	prevHash []byte
	markleRoot []byte
	timeStamp uint64
	diffculty uint64
	nonce uint64
	Hash []byte
	data []byte
}
//2.创建区块
func NewBlock(data string,prevHash []byte) *Block{
	block:=Block{
		version:00,
		prevHash:prevHash,
		markleRoot:[]byte{},
		timeStamp:uint64(time.Now().Unix()),
		diffculty:0,
		nonce:0,
		Hash:[]byte{},
		data:[]byte(data),
	}
	//这里注释了原生成哈希的方法,并通过NewProofOfWork方法创建了pow结构体,最后再调用pow的run方法,开启挖矿,生成hash
	//block.SetHash()
	pow:=NewProofOfWork(&block)
	hash,nonce:=pow.run()
	block.Hash=hash
	block.nonce=nonce
	return &block
}
func IntToByte(n uint64)[]byte{
	x:=int64(n)
	bytesBuffer:=bytes.NewBuffer([]byte{})
	err:=binary.Write(bytesBuffer,binary.BigEndian,x)
	if err!=nil{
		fmt.Println(err)
		return []byte{}
	}
	return bytesBuffer.Bytes()
}
//3.生成哈希
func (this *Block)SetHash (){
	byteList:=[][]byte{
		IntToByte(this.version),
		this.prevHash,
		this.markleRoot,
		IntToByte(this.timeStamp),
		IntToByte(this.diffculty),
		IntToByte(this.nonce),
		this.Hash,
		this.data,
	}
	//利用bytes.join()方法将[]byte拼接起来,不会的同学请自行百度
	blockInfo:=bytes.Join(byteList,[]byte{})
	Hash:=sha256.Sum256(blockInfo)
	this.Hash=Hash[:]
}

blockchain和main文件为进行修改,可以继续使用。

五、基于Bolt数据库实现可持续化的区块链

           上文中,我们已经可以实现一个最基本的区块链结构了。但是我们在运行过程中发现,目前的程序数据都存在内存中,换句话来说就是,程序一关闭数据就不见了,所以我们需要引入数据库。这时轻量级数据库Bolt就映入眼帘了

什么是Bolt数据库呢?

Bolt是一个用Go编写的键值数据库。其目标是为了给程序提供一个简单、快捷、稳定的数据库。

安装:

go get github.com/boltdb/bolt/...

基于BOLT的读写、

func main() {
	//打开数据库

	// func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
	db, err := bolt.Open("testDb.db", 0600, nil)//第二个参数为权限
	
	if err != nil {
		fmt.Println(" bolt Open err :", err)
		return
	}

	defer db.Close()

	//创建bucket
    //Updata参数为一个函数类型,是一个事务
	err = db.Update(func(tx *bolt.Tx) error {
		//打开一个bucket
		b1 := tx.Bucket([]byte("bucket1"))

		//没有这个bucket
		if b1 == nil {
			//创建一个bucket
			b1, err = tx.CreateBucket([]byte("bucket1"))
			if err != nil {
				fmt.Printf("tx.CreateBucket err:", err)
				return err
			}

			//写入数据
			b1.Put([]byte("key1"), []byte("hello"))
			b1.Put([]byte("key2"), []byte("world"))

			//读取数据
			v1 := b1.Get([]byte("key1"))
			v2 := b1.Get([]byte("key2"))
			v3 := b1.Get([]byte("key3"))

			fmt.Printf("v1:%s\n", string(v1))
			fmt.Printf("v2:%s\n", string(v2))
			fmt.Printf("v3:%s\n", string(v3))
		}
		return nil
	})

	if err != nil {
		fmt.Printf("db.Update err:", err)
	}

	return
}

只读:

func main() {
	//打开数据库

	// func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
	db, err := bolt.Open("testDb.db", 0400, nil)//第二个参数为权限
	
	if err != nil {
		fmt.Println(" bolt Open err :", err)
		return
	}

	defer db.Close()

	//创建bucket
    //View参数为一个函数类型,是一个事务
	err = db.View(func(tx *bolt.Tx) error {
		//打开一个bucket
		b1 := tx.Bucket([]byte("bucket1"))

		//没有这个bucket
		if b1 == nil {
			return errors.New("bucket do not exist!")
		}
       		v1 := b1.Get([]byte("key1"))
			v2 := b1.Get([]byte("key2"))
			v3 := b1.Get([]byte("key3"))

			fmt.Printf("v1:%s\n", string(v1))
			fmt.Printf("v2:%s\n", string(v2))
			fmt.Printf("v3:%s\n", string(v3))
        
		return nil
	})

	if err != nil {
		fmt.Printf("db.View err:", err)
	}

	return
}

 

特别注意--血坑,博主在因为此原因排查了一下午(~~):

切记结构体中的变量要大写:!!!

 结构体中参数首字母没有大写时,别的包虽然可以调用这个结构体,但是找不到这个结构体中没有首字母大写的参数。

附上block.go中修改结构体变量后的代码:

package main

import (
	"bytes"
	"encoding/binary"
	"encoding/gob"
	"fmt"
	"log"
	"time"
)
//1.定义区块结构体

type Block struct {
	//1.版本号
	Version uint64
	//2. 前区块哈希
	PrevHash []byte
	//3. Merkel根(梅克尔根,这就是一个哈希值,我们先不管,我们后面v4再介绍)
	MerkelRoot []byte
	//4. 时间戳
	TimeStamp uint64
	//5. 难度值
	Difficulty uint64
	//6. 随机数,也就是挖矿要找的数据
	Nonce uint64

	//a. 当前区块哈希,正常比特币区块中没有当前区块的哈希,我们为了是方便做了简化!
	Hash []byte
	//b. 数据
	Data []byte
}
//2.创建区块
func NewBlock(data string, prevBlockHash []byte) *Block {
	block := Block{
		Version:    00,
		PrevHash:   prevBlockHash,
		MerkelRoot: []byte{},
		TimeStamp:  uint64(time.Now().Unix()),
		Difficulty: 0, //随便填写的无效值
		Nonce:      0, //同上
		Hash:       []byte{},
		Data:       []byte(data),
	}

	//block.SetHash()
	//创建一个pow对象
	pow := NewProofOfWork(&block)
	//查找随机数,不停的进行哈希运算
	hash, nonce := pow.run()

	//根据挖矿结果对区块数据进行更新(补充)
	block.Hash = hash
	block.Nonce = nonce

	return &block
}

func IntToByte(n uint64)[]byte{
	x:=int64(n)
	bytesBuffer:=bytes.NewBuffer([]byte{})
	err:=binary.Write(bytesBuffer,binary.BigEndian,x)
	if err!=nil{
		fmt.Println(err)
		return []byte{}
	}
	return bytesBuffer.Bytes()
}
//3.生成哈希,目前不再使用此方法,而是使用pow
//序列化
func (block *Block) Serialize() []byte {
	var buffer bytes.Buffer

	//- 使用gob进行序列化(编码)得到字节流
	//1. 定义一个编码器
	//2. 使用编码器进行编码
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(&block)
	if err != nil {
		log.Panic("编码出错!")
	}

	//fmt.Printf("编码后的小明:%v\n", buffer.Bytes())

	return buffer.Bytes()
}

//反序列化
func Deserialize(data []byte) Block {

	decoder := gob.NewDecoder(bytes.NewReader(data))

	var block Block
	//2. 使用解码器进行解码
	err := decoder.Decode(&block)
	if err != nil {
		log.Panic("解码出错!")
	}
	return block
}

基于Bolt数据库实现可持续化的区块链

1.重构blockChain结构体,结构体变量为:1. bolt数据库对象db,2.存放最后一块区块的哈希tail。

type BlockChain struct {
	db *bolt.DB
	tail []byte
}

2.在block.go中实现能将结构体block系列化为字节的Serialize方法,和将字节反序列化为结构体的Deserialize方法。

//序列化
func (block *Block) Serialize() []byte {
	var buffer bytes.Buffer

	//- 使用gob进行序列化(编码)得到字节流
	//1. 定义一个编码器
	//2. 使用编码器进行编码
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(&block)
	if err != nil {
		log.Panic("编码出错!")
	}

	//fmt.Printf("编码后的小明:%v\n", buffer.Bytes())

	return buffer.Bytes()
}

//反序列化
func Deserialize(data []byte) Block {

	decoder := gob.NewDecoder(bytes.NewReader(data))

	var block Block
	//2. 使用解码器进行解码
	err := decoder.Decode(&block)
	if err != nil {
		log.Panic("解码出错!")
	}
	return block
}

2.重构newBlockChain方法,将创世块的哈希和序列化后的结构体,存放到boltDB中。并返回db对象,和最后一块区块哈希。

const blockChainDb = "blockChain.db"
const blockBucket = "blockBucket"

//5. 定义一个区块链
func NewBlockChain() *BlockChain {
	//return &BlockChain{
	//	blocks: []*Block{genesisBlock},
	//}

	//最后一个区块的哈希, 从数据库中读出来的
	var lastHash []byte

	//1. 打开数据库
	db, err := bolt.Open(blockChainDb, 0600, nil)
	//defer db.Close()

	if err != nil {
		log.Panic("打开数据库失败!")
	}

	//将要操作数据库(改写)
	_=db.Update(func(tx *bolt.Tx) error {
		//2. 找到抽屉bucket(如果没有,就创建)
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			//没有抽屉,我们需要创建
			bucket, err = tx.CreateBucket([]byte(blockBucket))
			if err != nil {
				log.Panic("创建bucket(b1)失败")
			}

			//创建一个创世块,并作为第一个区块添加到区块链中
			genesisBlock := GenesisBlock()

			//3. 写数据
			//hash作为key, block的字节流作为value,尚未实现
			_=bucket.Put(genesisBlock.Hash, genesisBlock.Serialize())
			_=bucket.Put([]byte("LastHashKey"), genesisBlock.Hash)
			lastHash = genesisBlock.Hash

			////这是为了读数据测试,马上删掉,套路!
			//blockBytes := bucket.Get(genesisBlock.Hash)
			//block := Deserialize(blockBytes)
			//fmt.Printf("block info : %s\n", block)

		} else {
			lastHash = bucket.Get([]byte("LastHashKey"))
		}

		return nil
	})

	return &BlockChain{db, lastHash}
}

3.重构blockChain结构体的AddBlock方法,获取lastHash,然后在函数中执行Newblock方法,并将该block的hash和信息序列化到boltDB中。

func (bc *BlockChain) AddBlock(data string) {
	//如何获取前区块的哈希呢??
	db := bc.db //区块链数据库
	lastHash := bc.tail //最后一个区块的哈希

	_=db.Update(func(tx *bolt.Tx) error {

		//完成数据添加
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("bucket 不应该为空,请检查!")
		}


		//a. 创建新的区块
		block := NewBlock(data, lastHash)

		//b. 添加到区块链db中
		//hash作为key, block的字节流作为value,尚未实现
		_=bucket.Put(block.Hash, block.Serialize())
		_=bucket.Put([]byte("LastHashKey"), block.Hash)

		//c. 更新一下内存中的区块链,指的是把最后的小尾巴tail更新一下
		bc.tail = block.Hash

		return nil
	})
}

4.定义迭代器,方便后续遍历。

package main

import (
	"github.com/boltdb/bolt"
	"log"
)

type BlockChainIterator struct {
	db *bolt.DB
	//游标,用于不断索引
	currentHashPointer []byte
}

//func NewIterator(bc *BlockChain)  {
//
//}

func (bc *BlockChain) NewIterator() *BlockChainIterator {
	return &BlockChainIterator{
		bc.db,
		//最初指向区块链的最后一个区块,随着Next的调用,不断变化
		bc.tail,
	}
}

//迭代器是属于区块链的
//Next方式是属于迭代器的
//1. 返回当前的区块
//2. 指针前移
func (it *BlockChainIterator) Next() *Block {
	var block Block
	_=it.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("迭代器遍历时bucket不应该为空,请检查!")
		}

		blockTmp := bucket.Get(it.currentHashPointer)
		//解码动作
		block = Deserialize(blockTmp)
		//游标哈希左移
		it.currentHashPointer = block.PrevHash

		return nil
	})

	return &block
}
func (it *BlockChainIterator)Restore(){
	//用于将迭代器游标移回初始值位置
	_=it.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("迭代器遍历时bucket不应该为空,请检查!")
		}

		blockTmp := bucket.Get([]byte("LastHashKey"))
		//游标哈希左移
		it.currentHashPointer = blockTmp

		return nil
	})
}

5.新建cli.go文件用于接收外部参数,执行命令。

package main

import (
	"flag"
	"fmt"
	"os"
)

const Usage  = `
	AddBlock --data  	"add block to blockChain" 	example:./block AddBlock {DATA}
	PrintBlockChain     "print all blockChain data"
`
const AddBlockString  ="AddBlock"
const PrintBlockString  = "PrintBlockChain"
type Cli struct {
	Bc *BlockChain
}
func PrintUsage (){
	println(Usage)
}
func (cli *Cli)CheckInputLenth(){
	if len(os.Args)<2{
		fmt.Println("Invalid ARGS")
		PrintUsage()
		os.Exit(1)
	}
}
func (this *Cli)Run(){
	this.CheckInputLenth()
	AddBlocker:=flag.NewFlagSet(AddBlockString,flag.ExitOnError)
	PrintBlockChainer:=flag.NewFlagSet(PrintBlockString,flag.ExitOnError)
	AddBlockerParam:=AddBlocker.String("data","","AddBlock {data}")
	switch os.Args[1] {
	case AddBlockString:
		//AddBlock
		err:=AddBlocker.Parse(os.Args[2:])
		if err!=nil{fmt.Println(err)}
		if AddBlocker.Parsed(){
			if *AddBlockerParam==""{PrintUsage()}else {
				this.AddBlock(*AddBlockerParam)
			}
		}
	case PrintBlockString:
		//PrintBlockChain
		err:=PrintBlockChainer.Parse(os.Args[2:])
		if err!=nil{fmt.Println(err)}
		if PrintBlockChainer.Parsed(){
			this.PrintBlockChain()
		}
	default:
		fmt.Println("Invalid input ")
		PrintUsage()
	}
}

6.新建command.go文件,用于实现cli的方法。

package main

import "fmt"

func (this *Cli)AddBlock(data string){
	this.Bc.AddBlock(data)
}
func (this *Cli)PrintBlockChain(){
	//TODO
	Iterator:=this.Bc.NewIterator()
	HeightblockChain:=0
	for{
		HeightblockChain++
		block:=Iterator.Next()
		if len(block.PrevHash)==0{
			Iterator.Restore()
			break
		}

	}

	for{
		block:=Iterator.Next()
		fmt.Printf("=======当前区块高度:%d======\n",HeightblockChain)
		fmt.Printf("当前哈希:%x\n",block.Hash)
		fmt.Printf("上一级哈希:%x\n",block.PrevHash)
		fmt.Printf("交易信息:%s\n",block.Data)
		HeightblockChain--
		if len(block.PrevHash)==0{
			break
		}
	}
}

7.修改main函数,

package main

/*
区块的创建分为7步
1.定义结构体
2.创建区块
3.生成哈希
4.定义区块链结构体
5.生成区块链并添加创世
6.生成创世块块
7.添加其他区块
*/

func main(){
	bc:=NewBlockChain()
	cli:=Cli{bc}
	cli.Run()
}

 

六、基于UTXO实现区块链交易(V版本)

UTXO(Unspent Transaction Outputs)是未花费的交易输出,它是比特币交易生成及验证的一个核心概念。交易构成了一组链式结构,所有合法的比特币交易都可以追溯到前向一个或多个交易的输出,这些链条的源头都是挖矿奖励,末尾则是当前未花费的交易输出。

一、新建Trancastions.go文件,并定义结构体

type Transaction struct {
	TXID []byte           //交易ID
	TXinputs []TXinput   //交易输入数组
	TXoutputs []TXoutput //交易输出数组
}
type TXinput struct {
	TXid []byte//引用的交易ID
	index int64//引用的output索引值
	Sig string//解锁脚本
}
type TXoutput struct {
	value float64//转账金额
	PubKeyHash string//锁定脚本
}
//设置交易id

二、序列化Transcation结构体,并设置TXID

func (tx *Transaction)SetHash(){
	var buffer bytes.Buffer
	encoder:=gob.NewEncoder(&buffer)
	err:=encoder.Encode(tx)
	if err!=nil {
		fmt.Println(err)
	}
	hash:=sha256.Sum256(buffer.Bytes())
	tx.TXID=hash[:]
}

三、提供创建Transcation的挖矿方法

func NewCoinBaseTX(address string,data string) *Transaction{
	//1.挖矿交易只有一个Input和一个output
	//2.在input时,TXid为空,index为-1,解锁脚本为:矿池地址
	input:=TXinput{[]byte{},-1,address}
	//3.在output中,金额为btc常量,reward{12.5},锁定脚本为address
	output:=TXoutput{reward,address}
	//4.创建Transcation交易,并设置TXid
	tx:=Transaction{[]byte{},[]TXinput{input},[]TXoutput{output}}
	//通过SetHash方法创建交易ID
	tx.SetHash()
	return &tx
}


四、修改block.go和blockChain.go(略,报错的文件都需要修改,可以下载源码等比)

五、修改cli.go新建getbalance命令

const AddBlockString  ="addblock"
const PrintBlockString  = "print"
const GetBlanceString  = "getbalance"
type Cli struct {
	Bc *BlockChain
}
func PrintUsage (){
	println(Usage)
}
func (cli *Cli)CheckInputLenth(){
	if len(os.Args)<2{
		fmt.Println("Invalid ARGS")
		PrintUsage()
		os.Exit(1)
	}
}
func (this *Cli)Run(){
	this.CheckInputLenth()
	AddBlocker:=flag.NewFlagSet(AddBlockString,flag.ExitOnError)
	PrintBlockChainer:=flag.NewFlagSet(PrintBlockString,flag.ExitOnError)
	getBalancer:=flag.NewFlagSet(GetBlanceString,flag.ExitOnError)
	AddBlockerParam:=AddBlocker.String("data","","AddBlock {data}")
	getBalancerParam:=getBalancer.String("address","","打印余额")
	switch os.Args[1] {
	case AddBlockString:
		//AddBlock
		err:=AddBlocker.Parse(os.Args[2:])
		if err!=nil{fmt.Println(err)}
		if AddBlocker.Parsed(){
			if *AddBlockerParam==""{PrintUsage()}else {
				this.AddBlock(*AddBlockerParam)
			}
		}
	case GetBlanceString:
		err:=getBalancer.Parse(os.Args[2:])
		if err!=nil{
			PrintUsage()
			log.Panic(err)
		}
		if getBalancer.Parsed(){
			if *getBalancerParam==""{fmt.Println(PrintUsage)}else {
				this.getBalance(*getBalancerParam)
			}
		}

六、修改command.go

func (this *Cli)getBalance(address string){
	utxos:=this.Bc.FindUTXOs(address)
	total:=0.0
	for _,utxo:=range utxos{
		total+=utxo.value
	}
	fmt.Printf("/%s/的余额为:%f\n",address,total)
}

七、在blockChain.go中新建FindCoinbaseUTXOs方法,获取UTXOs

func (this *BlockChain)FindUTXOs(address string)[]TXoutput{
	var UTXO []TXoutput
	spentOutputs := make(map[string][]int64)
	it := this.NewIterator()
	for {
		block := it.Next()
		for _, tx := range block.Transactions {
		OUTPUT:
			for i, output := range tx.TXoutputs {
				if spentOutputs[string(tx.TXID)] != nil {
					fmt.Println(spentOutputs[string(tx.TXID)])
					for _, j := range spentOutputs[string(tx.TXID)] {
						if int64(i) == j {
							continue OUTPUT
						}
					}
				}
				if output.PubKeyHash == address {
					UTXO=append(UTXO, output)
				}
			}
			if !tx.IsCoinBaseTX(tx) {
				for _, input := range tx.TXinputs {
					if input.Sig == address {
						spentOutputs[string(input.TXid)] = append(spentOutputs[string(input.TXid)], input.Index)
					}
				}
			}
		}

		if len(block.PrevHash) == 0 {
			fmt.Printf("区块遍历完成退出!")
			break
		}
	}

	return UTXO
	//
}

八、在Trancations.go中新增判断是否为挖矿交易的方法,如果是则跳过input记录前output

func (tx *Transaction)IsCoinBaseTX(txs *Transaction)bool{
	//1.TXid为空
	//2.index为-1
	//3.只有一个Input
	if len(txs.TXinputs) ==1{
		if len(txs.TXinputs[0].TXid)==0 && txs.TXinputs[0].Index==-1{
			return true
		}
	}
	return false
}


/*个人认为没有这个方法,也不会影响收益查看,因为余额虽然会在多个交易中查询UTXOS,但是挖矿交易始终是收益的源头,也就是第一条数据,而当遍历到第一条交易数据时,遍历output的循环不会在下一次执行了,所以大家可以发现就算没有添加此函数,余额也不会受影响,
但添加此函数有也添加的好处,比如会提交代码运行效率等

*/

九、创建普通交易

思路

[创建普通交易逻辑上要实现以下:(var utxos []output)

1.找到匹配的utxos,遍历余额,返回map[tanscation.TXid][outputIndex],账户余额(resvalue)

2.判断余额与转账金额的大小,若余额小于转账金额,则返回nil,若余额大于转账金额则:

2.1新建TXinputs,记录output的ID和索引

2.2.新建txoutput,进行找零,output[amount,to]   output[resValuea - mount,from],且返回*Transcation

实现:

1.在Transcation中实现普通交易的NewTranscations方法

func NewTranscations(from, to string,amount float64,bc *BlockChain )*Transaction{
	utxos,resValue:=bc.FindNeedUtxos(from,amount)
	if resValue

 2.在blockChain中实现查找所需余额的FindNeedUTXOS方法

func (bc *BlockChain)FindNeedUtxos(from string,amount float64)(map[string][]uint64,float64){
	it:=bc.NewIterator()
	utxos:=make(map[string][]uint64)
	resValue:=float64(0.0)
	spentoutput :=make(map[string][]uint64)
	for {
		block:=it.Next()
		for _,transcation :=range block.Transactions{
			outputList:=[]uint64{}
			//output获取
		OUTPUT:
			for index,output :=range transcation.TXoutputs{
				if resValue>=amount{
					return utxos,resValue
				}
				if spentoutput[string(transcation.TXID)]!=nil{
					for _,value :=range spentoutput[string(transcation.TXID)]{
						if value==uint64(index){
							continue OUTPUT
						}
					}
				}
				if output.PubKeyHash==from{
					outputList=append(outputList, uint64(index))
					utxos[string(transcation.TXID)]=outputList
					resValue+=output.Value
					fmt.Printf("找到满足的金额:%f\n",output.Value)
				}
			}
			//input筛选
			if !transcation.IsCoinBaseTX(transcation){
				inputList:=[]uint64{}
				for _,input :=range transcation.TXinputs{
					if input.Sig==from{
						inputList=append(inputList, uint64(input.Index))
						spentoutput[string(input.TXid)]=inputList
					}
				}
			}
		}
		if len(block.PrevHash)==0{
			break
		}
	}
	fmt.Printf("转账结束\n")
	return utxos,resValue
}

至此,我们已经实现了UTXO的转账,特别提醒一点,一个交易中是不会存在两个同地址output(其中一个已经用过,另一个没有用过。)两个同地output,要么同时没有被用过,要么都被用过。因为区块链转账时实时,是全部转完的,即使自己有剩余,也会先拿出来,最后转给自己。所以,我们可以通过这一点,将两个utxo函数,高聚合化,但是为了节约时候,这里我就不再详细说明。

十、补充makerkleRoot生成函数,以及优化时间戳

func (this *Block)MakeMakerkleRoot()[]byte{
	//我们进行哈希拼接
	final:=[]byte{}
	for _,j :=range this.Transactions{
		final=append(final, j.TXID...)
	}
	hash:=sha256.Sum256(final)
	return hash[:]
}


//时间戳
fmt.Printf("时间戳:%s\n",time.Unix(int64(block.TimeStamp),0).Format("2006-1-2 15:04:05"))

七、实现钱包地址和交易加密(V5版本)

在V4版本中,我们已经模拟实现了比特币UTXO,但是我们的钱包还是字符串,亟需修改。我们知道比特币中使用的数字签名算法是椭圆曲线数字签。所以我们有必要开发一个新的版本。

一、非对称加密签名算法--(椭圆曲线加密/ECDSA)的实现

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"math/big"
)

func main(){
	//创建曲线
	curve:=elliptic.P256()
	//生成私匙
	privateKey,err:=ecdsa.GenerateKey(curve,rand.Reader)
	if err!=nil{
		fmt.Println(err)
	}
	//生成公钥
	publicKey:=privateKey.PublicKey
	//对数据进行哈希运算
	data:="666666666"
	hash:=sha256.Sum256([]byte(data))
	//数据签名
	//func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
	r,s,er:=ecdsa.Sign(rand.Reader,privateKey,hash[:])
	if er!=nil{
		fmt.Println(err)
	}
	//把r、s进行序列化传输
	//1.传输
	signature:=append(r.Bytes(),s.Bytes()...)
	//2.获取、定义两个辅助的BIG.INT
	r1:=big.Int{}
	s1:=big.Int{}
	//3.拆分并赋值
	r1.SetBytes(signature[:len(signature)/2])
	s1.SetBytes(signature[len(signature)/2:])


	//数据校验
	//func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
	status:=ecdsa.Verify(&publicKey,hash[:],&r1,&s1)
	fmt.Println(status)

}

二、创建公钥、私匙,以及实现NewWallet命令

1.新建Wallet.go

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"
)

type Wallet struct {
	PrivateKey *ecdsa.PrivateKey
	PublicKey []byte
}
func NewWallet()*Wallet{
	curve:=elliptic.P256()
	privateKey,err:=ecdsa.GenerateKey(curve,rand.Reader)
	if err!=nil{fmt.Println(err)}
	publicKeyOrign:=privateKey.PublicKey
	publicKey:=append(publicKeyOrign.X.Bytes(),publicKeyOrign.Y.Bytes()...)
	return &Wallet{privateKey,publicKey}
}

2.修改cl.go,并添加命令(略)

3.地址生成

3.1下载 ripemd160源码包

由于伟大的墙,我们需要手动下载源码包
1.在{GOPATH}中的golang.org\x 进行gitlone

git clone https://github.com/golang/crypto.git

生成地址分为几个过程

1.将publicKey进行哈希运算,生成hash,并将生成的hash进行ripemd160运算,生成哈希

2.将生成的哈希与version拼接,并进行哈希运算,生成hash1

3.拷贝hash1,并再对hash1进行哈希运算生成hash2,截取hash2的前四个字节,并命名为checkCode

4.将hash1和checkCode拼接,并进行base58编码,得到钱包地址

流程如图:

带你玩转区块链--基于GoLang创建区块链、重构BTC-第一章【比特币篇】_第2张图片

实现:

func (this *Wallet)NewAdress()string{
	//1.获取pubKey
	pubKey:=this.PublicKey
	//2.获取ripemd160哈希
	ripeHash:=Newripe160Hash(pubKey)
	//3.将ripeHash与version进行拼接
	version:=byte(00)
	payload:=append(ripeHash,[]byte{version}...)
	//4.拷贝一份payload做hash后截取前4个字节
	hash1:=sha256.Sum256(payload)
	CheckCode:=CheckCode(hash1[:])
	//5.再次拼接
	payload=append(payload,CheckCode...)
	//6.做base58
	address:=base58.Encode(payload)
	return address
}
func CheckCode(hash1 []byte)[]byte{
	//再做一次hash
	hash2:=sha256.Sum256(hash1)
	return hash2[:4]
}
func Newripe160Hash(pubKey []byte)[]byte{
	hash:=sha256.Sum256(pubKey)
	//创建编码器
	ripe:=ripemd160.New()
	//写入
	_,err:=ripe.Write(hash[:])
	if err!=nil {
		fmt.Println(err)
	}
	//生成哈希
	ripehash:=ripe.Sum(nil)
	return ripehash

}

三、创建wallets,实现本地存储地址

老问题,上面创建的地址,在关闭程序后就会消失,这时候我们需要把地址存在某个地方。因为地址是加密处理过的,所以我们选择最简单的方式--存在本地。

一、创建Wallets结构体,保存[address]*wallet

二、创建NewWallets方法,从本地获取反序列化的结构体,对对Wallets赋值,(在赋值之前,要先判断本地文件是否存在)

三、创建CreateWallets方法,生成的address的生成和对Wallets结构体通过map,序列化到本地文件中。

代码如下:

package main

import (
	"btcutil/base58"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"golang.org/x/crypto/ripemd160"
)

type Wallet struct {
	PrivateKey *ecdsa.PrivateKey
	PublicKey []byte
}
func NewWallet()*Wallet{
	curve:=elliptic.P256()
	privateKey,err:=ecdsa.GenerateKey(curve,rand.Reader)
	if err!=nil{fmt.Println(err)}
	publicKeyOrign:=privateKey.PublicKey
	publicKey:=append(publicKeyOrign.X.Bytes(),publicKeyOrign.Y.Bytes()...)
	return &Wallet{privateKey,publicKey}
}
func (this *Wallet)NewAdress()string{
	//1.获取pubKey
	pubKey:=this.PublicKey
	//2.获取ripemd160哈希
	ripeHash:=Newripe160Hash(pubKey)
	//3.将ripeHash与version进行拼接
	version:=byte(00)
	payload:=append(ripeHash,[]byte{version}...)
	//4.拷贝一份payload做hash后截取前4个字节
	hash1:=sha256.Sum256(payload)
	CheckCode:=CheckCode(hash1[:])
	//5.再次拼接
	payload=append(payload,CheckCode...)
	//6.做base58
	address:=base58.Encode(payload)
	return address
}
func CheckCode(hash1 []byte)[]byte{
	//再做一次hash
	hash2:=sha256.Sum256(hash1)
	return hash2[:4]
}
func Newripe160Hash(pubKey []byte)[]byte{
	hash:=sha256.Sum256(pubKey)
	//创建编码器
	ripe:=ripemd160.New()
	//写入
	_,err:=ripe.Write(hash[:])
	if err!=nil {
		fmt.Println(err)
	}
	//生成哈希
	ripehash:=ripe.Sum(nil)
	return ripehash

}

四、添加Cli命令,编写PrintAddressList方法,对钱包地址进行遍历

func (this *Cli)PrintAddressList(){
	wallets:=NewWallets()
	addressList:=wallets.PrintAdress()
	for _,address :=range addressList{
		fmt.Printf("地址为:%s\n",address)
	}

}

四、修改Transcation.go等文件,将钱包地址与转账融合

1.修改TXinput和TXoutput结构体

type TXinput struct {
	TXid []byte//引用的交易ID
	Index int64//引用的output索引值
	//Sig string//解锁脚本
	//签名,由r、s拼接成的hash
	Sigrnature []byte
	//公钥,由X Y拼接的公钥
	PublicKey  []byte
}
type TXoutput struct {
	Value float64//转账金额
	//PubKeyHash string//锁定脚本
	//公钥的哈希
	PublickHash []byte
}

2.创建TXout结构体的Lock函数,实现反解到PublicHash,

func (TX *TXoutput)Lock(address string){
	//1.base58解码
	payLoad:=base58.Decode(address)
	PublicHash:=payLoad[1:len(payLoad)-4]
	TX.PublickHash=PublicHash
}

3.创建NewTXoutput,实现对TX的赋值

func NewTXoutput(value float64,address string)*TXoutput{
	TX:=TXoutput{
		Value:value,
	}
	TX.Lock(address)
	return &TX
}

五、实现交易验证

交易加密:

1.拷贝一份新生成的Transcation并命名为txCopy,并txCopy进行trimmed修剪,txCopy的所有TXnput的sinature和publik赋值为[]byte

2.循环整个区块链,找到与TXinput的Tid相同Transcation,并生成为map[string(TXinput.Tid)]Transcation,

3.循环txCopy.TXinput,通过TXid和索引找到map中的Transcation的TXoutput,并将TXoutput的publicHash赋值给TXinput.public

4.对txCopy进行setHash,并赋值给Signature

5.将signature做ecdsa.sign签名,得到R/S/ERR ,并将r、s进行append拼接,得到最终的signature并赋值给交易生成的Transcation.TXinput.signature

6.将txCopy的signature和public赋值为[]byte,方便下一次循环

实现:

func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey *ecdsa.PrivateKey){
	prevTXs := make(map[string]Transaction)
	for _,input :=range tx.TXinputs{
		Tid:=input.TXid
		tx,err:=bc.FindTransactionByTXid(Tid)
		if err!=nil{
			continue
		}
		prevTXs[string(input.TXid)] = tx
	}
	tx.Sign(privateKey, prevTXs)
}
func (bc *BlockChain) FindTransactionByTXid(TXid []byte)(Transaction,error){
	it:=bc.NewIterator()
	for {
		block:=it.Next()
		for _,tx :=range block.Transactions{
			if bytes.Equal(tx.TXID,TXid){
				return *tx,nil
			}
		}
		if len(block.PrevHash)==0{
			break
		}
	}
	return Transaction{},errors.New("not find ")
}


func (tx *Transaction)Sign(privateKey *ecdsa.PrivateKey, prevTXs map[string]Transaction){
	//1.循环tx交易,获取TXinput,并赋值output的publickHash
	txCopy:=tx.TrimmedCopy()
	for index,input :=range txCopy.TXinputs{
		prevTranscation:=prevTXs[string(input.TXid)]
		if len(prevTranscation.TXID)==0{
			log.Panic("交易错误,..........")
		}
		txCopy.TXinputs[index].PublicKey=prevTranscation.TXinputs[input.Index].PublicKey
		txCopy.SetHash()
		txCopy.TXinputs[index].PublicKey=[]byte{}
		r,s,err:=ecdsa.Sign(rand.Reader,privateKey,txCopy.TXID)
		if err!=nil{log.Panic(err)}
		signature:=append(r.Bytes(),s.Bytes()...)
		tx.TXinputs[index].Sigrnature=signature
	}
}
func (tx *Transaction)TrimmedCopy()Transaction{
	var inputs[]TXinput
	var outputs[]TXoutput
	for _,input :=range tx.TXinputs{
		inputs=append(inputs, TXinput{input.TXid,input.Index,nil,nil})
	}
	for _,output :=range tx.TXoutputs{
		outputs=append(outputs, output)
	}
	return Transaction{tx.TXID,inputs,outputs}
}

校验:

1.检验函数在AddBlock时执行,属于TX的方法

2.得到交易的签名前的数据signature

3.得到rs 

4.通过public得到X  Y从而  得到originPublicKey、

5.进行ecdsa.verify

实现:

func (tx *Transaction)Verify(prevTXs map[string]Transaction)bool{
	if !tx.IsCoinBaseTX(tx){
		return true
	}
	txCopy:=tx.TrimmedCopy()
	for index,input :=range tx.TXinputs{
		prevTranscation:=prevTXs[string(input.TXid)]
		if len(prevTranscation.TXID)==0{
			log.Panic("error......")
		}
		txCopy.TXinputs[index].PublicKey=prevTranscation.TXoutputs[input.Index].PublickHash
		txCopy.SetHash()
		txCopy.TXinputs[index].PublicKey=nil
		originSignature:=txCopy.TXID
		signature:=input.Sigrnature
		publicKey:=input.PublicKey
		r :=big.Int{}
		s :=big.Int{}
		r.SetBytes(signature[:len(signature)/2])
		s.SetBytes(signature[len(signature)/2:])
		x :=big.Int{}
		y :=big.Int{}
		x.SetBytes(publicKey[:len(publicKey)/2])
		y.SetBytes(publicKey[len(publicKey)/2:])
		originPublicKey:=ecdsa.PublicKey{elliptic.P256(),&x,&y}
		//func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
		if !ecdsa.Verify(&originPublicKey,originSignature,&r,&s){
			return false
		}
	}
	return true
}



//以下在blockChain.go中实现
func (bc *BlockChain) VerifyTranscation(tx *Transaction)bool{
	if tx.IsCoinBaseTX(tx){
		return true
	}
	prevTXs := make(map[string]Transaction)
	for _,input :=range tx.TXinputs{
		Tid:=input.TXid
		tx,err:=bc.FindTransactionByTXid(Tid)
		if err!=nil{
			continue
		}
		prevTXs[string(input.TXid)] = tx
	}
	return tx.Verify(prevTXs)
}

对交易添加Sting方法进行遍历:

func (tx Transaction) String() string {
	var lines []string

	lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TXID))

	for i, input := range tx.TXinputs {

		lines = append(lines, fmt.Sprintf("     Input %d:", i))
		lines = append(lines, fmt.Sprintf("       TXID:      %x", input.TXid))
		lines = append(lines, fmt.Sprintf("       Out:       %d", input.Index))
		lines = append(lines, fmt.Sprintf("       Signature: %x", input.Sigrnature))
		lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PublicKey))
	}

	for i, output := range tx.TXoutputs{
		lines = append(lines, fmt.Sprintf("     Output %d:", i))
		lines = append(lines, fmt.Sprintf("       Value:  %f", output.Value))
		lines = append(lines, fmt.Sprintf("       Script: %x", output.PublickHash))
	}

	return strings.Join(lines, "\n")
}

          至此,go重构BTC源码结束。反过来回顾的时候,我发现区块链也不是用很难的知识点实现的,没有复杂的算法,也没用复杂的结构。仅是简单的密码学基础和结构体的反复调用。区块链让21世纪的我们眼前一亮。究其原因、是中本聪先生将密码学和去中心化的思想融会贯通了。对于我们普通的开发人员来说,并不是一定要用多牛逼的技术实现某些程序,而是要将最简单的理论识融会贯通。非精不是明其理,非博不能制其约。我想大概就是区块链诞生的名言警句吧~~

小伙伴们,下一节,我们将继续学习区块链知识。剖析--以太坊(ETH)源码

                                                                                                                                                 您身边喜欢绿色的朋友:

                                                                                                                                                      wechat:laughing_jk

你可能感兴趣的:(golang知识,区块链开发,挖矿教程)