当前位置: 首页 > news >正文

从第一行代码开始开发区块链(二)

1240
传送门: 柏链项目学院



如何通过go语言打造区块链

为什么选择go语言呢?因为个人兴趣爱好,作为后端语言go确实比c++要舒服一些,此外go语言对加密算法,hash函数支持的也非常好。

我们要支持哪些功能?

  • 有区块的链表
  • pow 共识机制
  • UTXO模型

1. 有区块的链表

go语言里借助数组或切片就可以模拟有序链表,所以直接用切片即可,一个区块包含哪些信息呢?

type Block struct {
    Timestamp     int64  // 时间戳 类似 1546590891
    Data          []byte // 打包的交易数据,我们可以随意模拟
    PrevBlockHash []byte // 前一块hash值
    Hash          []byte // 当前hash值
}

那么hash如何计算呢?可以把前一块hash,本块数据,时间戳等进行加工(join)计算,最后借助sha256包的sum256函数获得本块hash值。

    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
    hash := sha256.Sum256(headers)

上述代码中 strconv.FormatInt(b.Timestamp, 10) 就是将时间戳转换为字符串,最后通过[]byte强制转换为[]byte类型。

type Blockchain struct {
    blocks []*Block
}

上述定义就代表我们有了一个区块链,是不是太简单了!接下来编写如何添加区块!

func (bc *Blockchain) AddBlock(data string) {
    prevBlock := bc.blocks[len(bc.blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.blocks = append(bc.blocks, newBlock)
}

我们这样测试,并且打印区块链结果

func main() {
    bc := NewBlockchain()

    bc.AddBlock("Send 1 BTC to Ivan")
    bc.AddBlock("Send 2 more BTC to Ivan")

    for _, block := range bc.blocks {
        fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
        fmt.Printf("Data: %s\n", block.Data)
        fmt.Printf("Hash: %x\n", block.Hash)
        fmt.Println()
    }
}

ok,到这里我们完成了一个最初版本的开发。

全部代码如下:

  • main.go
package main

import (
    "fmt"
)

func main() {
    bc := NewBlockchain()

    bc.AddBlock("Send 1 BTC to Ivan")
    bc.AddBlock("Send 2 more BTC to Ivan")

    for _, block := range bc.blocks {
        fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
        fmt.Printf("Data: %s\n", block.Data)
        fmt.Printf("Hash: %x\n", block.Hash)
        fmt.Println()
    }
}
  • block.go
package main

import (
    "bytes"
    "crypto/sha256"
    "strconv"
    "time"
)

// Block keeps block headers
type Block struct {
    Timestamp     int64
    Data          []byte
    PrevBlockHash []byte
    Hash          []byte
}

// SetHash calculates and sets block hash
func (b *Block) SetHash() {
    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
    hash := sha256.Sum256(headers)

    b.Hash = hash[:]
}

// NewBlock creates and returns Block
func NewBlock(data string, prevBlockHash []byte) *Block {
    block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
    block.SetHash()
    return block
}

// NewGenesisBlock creates and returns genesis Block
func NewGenesisBlock() *Block {
    return NewBlock("Genesis Block", []byte{})
}
  • blockchain.go
package main

// Blockchain keeps a sequence of Blocks
type Blockchain struct {
    blocks []*Block
}

// AddBlock saves provided data as a block in the blockchain
func (bc *Blockchain) AddBlock(data string) {
    prevBlock := bc.blocks[len(bc.blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.blocks = append(bc.blocks, newBlock)
}

// NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain() *Blockchain {
    return &Blockchain{[]*Block{NewGenesisBlock()}}
}

2. 产生区块的工作量证明

之前我们的代码已经可以生成区块了,但是产生区块太容易了,这样任何人都可以添加一个区块,给记账也带来混乱。前面我们也提到了,想要在链表中增加区块,那么必须做一个数学难题,这个难题就是找到一个适合的数字能让它产生一个符合条件的hash值,而且这个hash值必须小于某个数。计算出这个hash值对应的数字没有投机取巧的办法,只能自己尝试,谁先拿到了,谁就中奖了。当然,这个事儿可以靠计算能力来作弊,举个简单的例子,假设一共有256个值需要尝试,你的计算能力如果是别人的4倍,比如你有四台机器同时计算,那么你中奖的机会也就是别人的4倍。下面还是来说代码,需要对之前的代码改造。

在产生区块时需要经过一个hash计算,而且这个值必须小于一个数,习惯上把它成为挖矿难度。

var (
    maxNonce = math.MaxInt64
)

const targetBits = 24

// ProofOfWork represents a proof-of-work
type ProofOfWork struct {
    block  *Block
    target *big.Int
}

math.MaxInt64 实际上是1左移63位后-1

MaxInt64  = 1<<63 - 1

targetBits 实际上就是挖矿难度了,需要通过这个挖矿难度最后再计算出一个数。

接下来我们实现生成ProofOfWork结构体的函数

func NewProofOfWork(b *Block) *ProofOfWork {
    target := big.NewInt(1)
    target.Lsh(target, uint(256-targetBits))
    fmt.Println("target======", target)

    pow := &ProofOfWork{b, target}

    return pow
}

将前一块的内容加上本块数据结合起来,准备去挖矿

func (pow *ProofOfWork) prepareData(nonce int) []byte {
    data := bytes.Join(
        [][]byte{
            pow.block.PrevBlockHash,
            pow.block.Data,
            IntToHex(pow.block.Timestamp),
            IntToHex(int64(targetBits)),
            IntToHex(int64(nonce)),
        },
        []byte{},
    )

    return data
}

循环实验,也就是挖矿,hashInt.Cmp 是如果hashInt小于target则返回-1,这样就ok了。

func (pow *ProofOfWork) Run() (int, []byte) {
    var hashInt big.Int
    var hash [32]byte
    nonce := 0

    fmt.Printf("Mining the block containing \"%s\"maxNonce=%d\n", pow.block.Data, maxNonce)
    for nonce < maxNonce {
        data := pow.prepareData(nonce)

        hash = sha256.Sum256(data)
        fmt.Printf("\r%x", hash)
        hashInt.SetBytes(hash[:])

        if hashInt.Cmp(pow.target) == -1 {
            break
        } else {
            nonce++
        }
    }
    fmt.Print("\n\n")

    return nonce, hash[:]
}

顺便我们再增加一个验证的函数,验证是否挖到矿

func (pow *ProofOfWork) Validate() bool {
    var hashInt big.Int

    data := pow.prepareData(pow.block.Nonce)
    hash := sha256.Sum256(data)
    hashInt.SetBytes(hash[:])

    isValid := hashInt.Cmp(pow.target) == -1

    return isValid
}



%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20190103092604.jpg

转载于:https://www.cnblogs.com/tokenpai/p/10478389.html

相关文章:

  • 函数组件与类有什么不同?
  • 通过find文件并对大小求和统计目录大小
  • elasticsearch 占CPU过高
  • Windows本地代码仓库使用连接教程
  • Redis 安装加集群配置
  • 带你快速了解ES1D-E361T
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • dependencies与devDependencies的区别
  • ale.js2.0 更新计划正式发布
  • 代码管理podfile.lock报错
  • 2019年3月11日 [950] Reveal Cards In Increasing Order
  • bzoj2395 [Balkan 2011]Timeismoney(最小乘积生成树+计算几何)
  • UAV心跳机制与容器、进程数据采集
  • [Codeforces1137D]Cooperative Game
  • 数论 欧拉线性素数筛
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • canvas 绘制双线技巧
  • JavaScript类型识别
  • PHP CLI应用的调试原理
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Vue组件定义
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • 翻译 | The Principles of OOD 面向对象设计原则
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • 正则表达式-基础知识Review
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​力扣解法汇总946-验证栈序列
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (ibm)Java 语言的 XPath API
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (Python第六天)文件处理
  • (二)丶RabbitMQ的六大核心
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (一)kafka实战——kafka源码编译启动
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • .NET 2.0中新增的一些TryGet,TryParse等方法
  • .NET 8.0 中有哪些新的变化?
  • .net CHARTING图表控件下载地址
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .NET的数据绑定
  • .sh
  • @DependsOn:解析 Spring 中的依赖关系之艺术
  • @RequestMapping处理请求异常
  • [20161101]rman备份与数据文件变化7.txt