golang-Block Chain Learning 03 Permanent storage

Source: Internet
Author: User

Objective

The first two articles simply implement blockchain creation and proof of workload, but they are all in memory. The actual blockchain should be stored permanently, which makes sense. A permanent blockchain storage is started below.

Knowledge points

1. GitHub Project Reference
2. Simple use of Github.com/boltdb/bolt project
3, command line use
4, go common data conversion

golang-Block chain Persistent storage

1. Create a block chain

Method: Func newblockchain () *blockchain

Create Blockchain//return a Blockchain instance func Newblockchain () *blockchain {var tip []byte//Open storage Blockchain file blockchian.db, nonexistent then create DB, Err: = Bolt. Open (DBFile, 0600, nil) if err! = Nil {log. Panic (ERR)}//Read-write access to the blockchain file blockchian.db err = db. Update (func (TX *bolt). TX) Error {b: = tx. Bucket ([]byte (Blockbucket)) if b = = nil {fmt. Println ("No existing blockchain. Creating a new one ... ")//Create Genesis block Genesis: = Newgenesisblock () b, err: = Tx. Createbucket ([]byte (Blockbucket)) if err! = Nil {log. Panic (ERR)}//Key-value pairs are stored in chunks in blockchian.db err = B.put (Genesis. Hash, Genesis. Serialize ()) if err! = Nil {log. Panic (ERR)}//With a special key to save the latest chunk hash, easy to retrieve the entire chain 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)}//Returns an instance of the blockchain BC: = &blockchain{tip, db} return BC}

Bolt is a way to store data in a key-value pair. Specific introduction and use of reference Github.com/boltdb/bolt. When the program starts, call the Newblockchain function and open the Blockchian.db file to instantiate a Blockchain object blockchain. For the first time, the program creates a blockchian.db file, generates a Genesis chunk, stores it in a blockchian.db file, and returns a Blockchain object.

2. Add new blocks to the chain

Method: func (BC *blockchain) addblock (data string)

// 添加新区块到链上// 参数:data,区块要保存的数据func (bc *BlockChain) AddBlock(data string) {    var lastHash []byte    // 只读的方式打开blockchian.db,获取最新区块的hash值    err := bc.Db.View(func(tx1 *bolt.Tx) error {        b := tx1.Bucket([]byte(blockBucket))        lastHash = b.Get([]byte("l"))        return nil    })    if err != nil {        log.Panic(err)    }    // 计算新的区块    newBlock := NewBlock(data, lastHash)    bc.tip = newBlock.Hash    // 读写的方式打开lockchian.db,写入新区块到blockchian.db中。    bc.Db.Update(func(tx *bolt.Tx) error {        b := tx.Bucket([]byte(blockBucket))        if b == nil {            log.Panic("bucket is nil !")        }        err := b.Put(newBlock.Hash, newBlock.Serialize())        if err != nil {            log.Panic(err)        }        //更新最新区块的hash        err = b.Put([]byte("l"), newBlock.Hash)        if err != nil {            log.Panic(err)        }        return nil    })}

To add new blocks to the chain, first to obtain the current chain of the latest chunk of the hash, and then calculate the new block, the calculation of the novel block after the storage area blocks data into the blockchian.db.

3. Block data Serialization Conversion

Convert block block instance to byte array method: Func (b *block) Serialize () []byte

// 序列化一个区块实例为byte数组func (b *Block)Serialize()[]byte  {    var result bytes.Buffer    // 以一个byte的buf实例化一个编码实例encoder    encoder:=gob.NewEncoder(&result)        err:=encoder.Encode(b)    if err!=nil {        log.Panic(err)    }    return result.Bytes()}

Convert byte array to Block object method: Func deserialize (b []byte) *block

// 反序列化byte数组,生成block实例。func Deserialize(b []byte)*Block{    var block Block    decoder:=gob.NewDecoder(bytes.NewReader(b))    err:=decoder.Decode(&block)    if err!=nil{        log.Panic(err)    }    return &block}
4. Command line flag use
// 命令行执行程序func (cli *CLI) Run() {    cli.validateArgs()    // 创建命令行对象    addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)    printChianCmd := flag.NewFlagSet("printchain", flag.ExitOnError)    // 命令行对象添加参数,    addBlockData := addBlockCmd.String("data", "", "区块数据不能为空!")    switch os.Args[1] {    case "addblock":        err := addBlockCmd.Parse(os.Args[2:])        if err != nil {            log.Panic(err)        }    case "printchain":        err:=printChianCmd.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 printChianCmd.Parsed(){        cli.printChain()        //cli.printUsage()    }}
5. GitHub Project Reference

Download the Github.com/boltdb/bolt project to the project catalog, as shown in the attachment project structure.
Note Set the project path to the Gopath path.

Attachment

1. Project Structure project directory structure

2. Code
Main.go

package mainimport (    "core")func main() {    // 创建区块链    bc := core.NewBlockChain()    // 关闭本地库    defer bc.Db.Close()    // 实例命令行对象    cli := core.CLI{bc}    cli.Run()}

Block.go

Package Coreimport ("Time" "StrConv" "bytes" "crypto/sha256" "Encoding/gob" "Log") type Block struct { TimeStamp Int64 Data []byte prevblockhash []byte Hash []byte Nonce int}func Ne WBlock (data string, Prevblockhash []byte) *block {Block: = &block{time. Now (). Unix (), []byte (data), Prevblockhash, []byte{}, 0} POW: = Newproofofwork (block) block. Nonce, block. Hash = Pow. Run () return Block}func (b *block) Sethash () {strtimestamp: = []byte (StrConv. Formatint (B.timestamp, Ten)) Headers: = bytes. Join ([][]byte{b.prevblockhash, B.data, Strtimestamp}, []byte{}) Hash: = sha256. Sum256 (headers) B.hash = Hash[:]}func Newgenesisblock () *block {return Newblock ("Genesis Block", []byte{})}//serialize a The chunk instance is a byte array, func (b *block) Serialize () []byte {var result bytes. Buffer//Instantiate an instance of encoding with a byte BUF encoder encoder:=gob. Newencoder (&result) Err:=encoder. Encode (b) if Err!=nil {log.   Panic (ERR)} return result. Bytes ()}//deserializes a byte array, generating a block instance. Func deserialize (b []byte) *block{var block block Decoder:=gob. Newdecoder (bytes. Newreader (b)) Err:=decoder. Decode (&block) if err!=nil{log. Panic (Err)} return &block}

Blockchain.go

Package Coreimport ("FMT" "Log" "Github.com/boltdb/bolt") const DBFile = "Blockchian.db" Const Blockbucket = "bloc KS "type BlockChain struct {tip []byte Db *bolt. Db}type blockchainiterator struct {currenthash []byte Db *bolt. db}//Add a new zone block to the chain//parameter: data, chunk to be saved by func (BC *blockchain) addblock (data string) {var lasthash []byte//read-only mode open Lockchi An.db, get the hash value of the latest chunk err: = BC. Db.view (func (tx1 *bolt. Tx) Error {b: = tx1. Bucket ([]byte (blockbucket)) Lasthash = B.get ([]byte ("L")) return nil}) if err! = Nil {log. Panic (ERR)}//Calculates a new chunk newblock: = Newblock (data, lasthash) Bc.tip = newblock.hash//Read and write mode to open lockchian.db, write    Into the new block into the lockchian.db. Bc. Db.update (func (TX *bolt). TX) Error {b: = tx. Bucket ([]byte (Blockbucket)) if b = = Nil {log.        Panic ("Bucket is nil!") } ERR: = B.put (Newblock.hash, Newblock.serialize ()) if err! = Nil {log. Panic (ERR)}//Update the Hash of the latest chunk err = B.put ([]byte ("L"), Newblock.hash) if err! = Nil {log.    Panic (ERR)} return nil})}func (BC *blockchain) Iterator () *blockchainiterator {var lasthash []byte Bc. Db.view (func (TX *bolt). TX) Error {//assume bucket exists and has keys B: = TX. Bucket ([]byte (blockbucket)) Lasthash = B.get ([]byte ("L"))//c: = B.cursor ()//for k, V: = Cursor. First (); K! = Nil; K, v = cursor. Next () {//FMT. Printf ("key=%s, value=%s\n", K, V)//} return nil}) return &blockchainiterator{lasthash, BC. Db}}func (BCI *blockchainiterator) Next () *block {var byteblock []byte BCI. Db.view (func (TX *bolt). TX) Error {b: = tx. Bucket ([]byte (blockbucket)) Byteblock = B.get (Bci.currenthash) return nil}) Block: = Deserialize (byte Block) Bci.currenthash = block. Prevblockhash return block}//Create a blockchain//return a Blockchain instance func Newblockchain () *blockchain {var tip []byte    Open the storage blockchain file blockchian.db, do not exist, create db, err: = Bolt. Open (DBFile, 0600, nil) if err! = Nil {log. Panic (ERR)}//Read-write access to the blockchain file blockchian.db err = db. Update (func (TX *bolt). TX) Error {b: = tx. Bucket ([]byte (Blockbucket)) if b = = nil {fmt. Println ("No existing blockchain. Creating a new one ... ")//Create Genesis block Genesis: = Newgenesisblock () b, err: = Tx. Createbucket ([]byte (Blockbucket)) if err! = Nil {log. Panic (ERR)}//Key-value pairs are stored in chunks in blockchian.db err = B.put (Genesis. Hash, Genesis. Serialize ()) if err! = Nil {log. Panic (ERR)}//With a special key to save the latest chunk hash, easy to retrieve the entire chain 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)}//Returns an instance of the blockchain BC: = &blockchain{tip, db} return BC}

Cli.go

Package Coreimport ("FMT" "OS" "Flag" "Log" "StrConv") type cli struct {Bc *blockchain}func (CLI *cli) Printusage () {fmt. Println ("Usage:") fmt. Println ("Addblock-data (chunk data)-add a chunk to the block chain." ") Fmt. Println ("Printchain-all blocks on the print Block chain")}func (CLI *cli) Validateargs () {If Len (OS). Args) <2{cli.printusage () os. Exit (1)}}func (CLI *cli) addblock (data string) {CLI. Bc.addblock (data) fmt. Println ("success!")} Func (CLI *cli) Printchain () {bci:=cli. Bc.iterator () for{BLOCK:=BCI. Next () fmt. Printf ("Prive Hash:%x\n", block. Prevblockhash) fmt. Printf ("Data:%s\n", block. Data) fmt. Printf ("Hash:%x\n", block. Hash) Pow: = newproofofwork (block) fmt. Printf ("pow:%s\n", StrConv. Formatbool (POW. Validate ())) Fmt. Println () If Len (block.    Prevblockhash) ==0{Break}}}//command-line executor func (CLI *cli) Run () {Cli.validateargs ()//create command line Object Addblockcmd: = flag. Newflagset ("Addblock", Flag. Exitonerror) Printchiancmd: = flag. Newflagset ("Printchain", flag. EXITONERROR)//command-line object to add parameters, Addblockdata: = addblockcmd.string ("Data", "", "chunks" cannot be empty!) ") Switch OS. ARGS[1] {case "Addblock": Err: = Addblockcmd.parse (OS. Args[2:]) if err! = Nil {log. Panic (ERR)} case "Printchain": Err:=printchiancmd.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 printchiancmd.parsed () {cli.printchain ()//cl I.printusage ()}}

Proofofwork.go

Package Coreimport ("math" "Math/big" "FMT" "crypto/sha256" "bytes") var (maxnonce = math). MaxInt64) Const Targetbits = 12type proofofwork struct {block *block target *big. Int}func Newproofofwork (b *block) *proofofwork {target: = big. Newint (1) target. Lsh (target, UINT (256-targetbits)) 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), Inttohe X (Int64 (targetbits)), Inttohex (Int64 (Nonce)),}, []byte{}) return Data}func (Pow *proofofwork) Run () (int, [] byte) {var hashint big. Int var hash [32]byte nonce: = 0 fmt. Printf ("Mining The block containing \"%s\ "\ n", Pow.block.Data) 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. Printf ("\ 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}

Utils.go

package coreimport (    "bytes"    "encoding/binary"    "log"    "crypto/sha256")func IntToHex(num int64) []byte {    buff := new(bytes.Buffer)    err := binary.Write(buff, binary.BigEndian, num)    if err != nil {        log.Panic(err)    }    return buff.Bytes()}func DataToHash(data []byte) []byte {    hash := sha256.Sum256(data)    return hash[:]}
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

Tags Index: