经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 区块链 » 查看文章
从第一行代码开始开发区块链(二)
来源:cnblogs  作者:通证派大本营  时间:2019/3/6 9:02:08  对本文有异议


传送门: 柏链项目学院



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

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

我们要支持哪些功能?

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

1. 有区块的链表

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

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

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

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

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

  1. type Blockchain struct {
  2. blocks []*Block
  3. }

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

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

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

  1. func main() {
  2. bc := NewBlockchain()
  3. bc.AddBlock("Send 1 BTC to Ivan")
  4. bc.AddBlock("Send 2 more BTC to Ivan")
  5. for _, block := range bc.blocks {
  6. fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
  7. fmt.Printf("Data: %s\n", block.Data)
  8. fmt.Printf("Hash: %x\n", block.Hash)
  9. fmt.Println()
  10. }
  11. }

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

全部代码如下:

  • main.go
  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. bc := NewBlockchain()
  7. bc.AddBlock("Send 1 BTC to Ivan")
  8. bc.AddBlock("Send 2 more BTC to Ivan")
  9. for _, block := range bc.blocks {
  10. fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
  11. fmt.Printf("Data: %s\n", block.Data)
  12. fmt.Printf("Hash: %x\n", block.Hash)
  13. fmt.Println()
  14. }
  15. }
  • block.go
  1. package main
  2. import (
  3. "bytes"
  4. "crypto/sha256"
  5. "strconv"
  6. "time"
  7. )
  8. // Block keeps block headers
  9. type Block struct {
  10. Timestamp int64
  11. Data []byte
  12. PrevBlockHash []byte
  13. Hash []byte
  14. }
  15. // SetHash calculates and sets block hash
  16. func (b *Block) SetHash() {
  17. timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
  18. headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
  19. hash := sha256.Sum256(headers)
  20. b.Hash = hash[:]
  21. }
  22. // NewBlock creates and returns Block
  23. func NewBlock(data string, prevBlockHash []byte) *Block {
  24. block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
  25. block.SetHash()
  26. return block
  27. }
  28. // NewGenesisBlock creates and returns genesis Block
  29. func NewGenesisBlock() *Block {
  30. return NewBlock("Genesis Block", []byte{})
  31. }
  • blockchain.go
  1. package main
  2. // Blockchain keeps a sequence of Blocks
  3. type Blockchain struct {
  4. blocks []*Block
  5. }
  6. // AddBlock saves provided data as a block in the blockchain
  7. func (bc *Blockchain) AddBlock(data string) {
  8. prevBlock := bc.blocks[len(bc.blocks)-1]
  9. newBlock := NewBlock(data, prevBlock.Hash)
  10. bc.blocks = append(bc.blocks, newBlock)
  11. }
  12. // NewBlockchain creates a new Blockchain with genesis Block
  13. func NewBlockchain() *Blockchain {
  14. return &Blockchain{[]*Block{NewGenesisBlock()}}
  15. }

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

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

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

  1. var (
  2. maxNonce = math.MaxInt64
  3. )
  4. const targetBits = 24
  5. // ProofOfWork represents a proof-of-work
  6. type ProofOfWork struct {
  7. block *Block
  8. target *big.Int
  9. }

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

  1. MaxInt64 = 1<<63 - 1

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

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

  1. func NewProofOfWork(b *Block) *ProofOfWork {
  2. target := big.NewInt(1)
  3. target.Lsh(target, uint(256-targetBits))
  4. fmt.Println("target======", target)
  5. pow := &ProofOfWork{b, target}
  6. return pow
  7. }

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

  1. func (pow *ProofOfWork) prepareData(nonce int) []byte {
  2. data := bytes.Join(
  3. [][]byte{
  4. pow.block.PrevBlockHash,
  5. pow.block.Data,
  6. IntToHex(pow.block.Timestamp),
  7. IntToHex(int64(targetBits)),
  8. IntToHex(int64(nonce)),
  9. },
  10. []byte{},
  11. )
  12. return data
  13. }

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

  1. func (pow *ProofOfWork) Run() (int, []byte) {
  2. var hashInt big.Int
  3. var hash [32]byte
  4. nonce := 0
  5. fmt.Printf("Mining the block containing \"%s\"maxNonce=%d\n", pow.block.Data, maxNonce)
  6. for nonce < maxNonce {
  7. data := pow.prepareData(nonce)
  8. hash = sha256.Sum256(data)
  9. fmt.Printf("\r%x", hash)
  10. hashInt.SetBytes(hash[:])
  11. if hashInt.Cmp(pow.target) == -1 {
  12. break
  13. } else {
  14. nonce++
  15. }
  16. }
  17. fmt.Print("\n\n")
  18. return nonce, hash[:]
  19. }

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

  1. func (pow *ProofOfWork) Validate() bool {
  2. var hashInt big.Int
  3. data := pow.prepareData(pow.block.Nonce)
  4. hash := sha256.Sum256(data)
  5. hashInt.SetBytes(hash[:])
  6. isValid := hashInt.Cmp(pow.target) == -1
  7. return isValid
  8. }



原文链接:http://www.cnblogs.com/tokenpai/p/10478389.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号