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 a blockchain
// return a blockchain instance
Func NewBlockChain() *BlockChain {
    Var tip []byte
    // Open the file blockchian.db of the blockchain chain, create it if it does not exist
    Db, err := bolt.Open(dbFile, 0600, nil)

    If err != nil {
        log.Panic(err)
    }

    // readable and writable way to access 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 a Genesis block
            Genesis := NewGenesisBlock()

            b, err := tx.CreateBucket([]byte(blockBucket))
            If err != nil {
                log.Panic(err)
            }

            // The key-value pair storage block is in blockchian.db
            Err = b.Put(genesis.Hash, genesis.Serialize())
            If err != nil {
                log.Panic(err)
            }

            // Save the latest block hash with a special key to facilitate the entire chain search
            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)
    }
    // return 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)


// Add a new block to the chain
/ / Parameters: data, the data to be saved in the block
Func (bc *BlockChain) AddBlock(data string) {
    Var lastHash []byte

    // Open blockchian.db in read-only mode to get the hash value of the latest block
    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)
    }

    // Calculate the new block
    newBlock := NewBlock(data, lastHash)
    Bc.tip = newBlock.Hash

    // Read and write to open lockchian.db and write the new block to 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)
        }

        / / Update the hash of the latest block
        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


/ / Serialize a block instance to a byte array
Func (b *Block)Serialize()[]byte {
     Var result bytes.Buffer
     // instantiate an encoding instance encoder with a byte of buf
     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


// Deserialize the byte array to generate 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
}


4. Command line flag use


// command line executor
Func (cli *CLI) Run() {
     cli.validateArgs()

     // Create a command line object
     addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)
     printChianCmd := flag.NewFlagSet("printchain", flag.ExitOnError)

     // Command line object adds parameters,
     addBlockData := addBlockCmd.String("data", "", "The block data 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()
         //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 main

Import (
     "core"
)

Func main() {

     // Create a blockchain
     Bc := core.NewBlockChain()
     // close the local library
     Defer bc.Db.Close()
     // instance command line object
     Cli := core.CLI{bc}

     cli.Run()
}


Block.go


Package core

Import (
    "time"
    "strconv"
    "bytes"
    "crypto/sha256"
    "encoding/gob"
    "log"
)

Type block struct {
    TimeStamp int64
    Data []byte
    PrevBlockHash []byte
    Hash []byte
    Nonce int
}

Func NewBlock(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, 10))
    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 block instance to a byte array
Func (b *Block)Serialize()[]byte {
    Var result bytes.Buffer
    // instantiate an encoding instance encoder with a byte of buf
    Encoder:=gob.NewEncoder(&result)

    Err:=encoder.Encode(b)
    If err!=nil {
        log.Panic(err)
    }
    Return result.Bytes()
}

// Deserialize the byte array to generate 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 core

Import (
    "fmt"
    "log"
    "github.com/boltdb/bolt"
)

Const dbFile = "blockchian.db"
Const blockBucket = "blocks"

Type BlockChain struct {
    Tip []byte
    Db *bolt.DB
}

Type BLockchainIterator struct {
    currentHash []byte
    Db *bolt.DB
}

// Add a new block to the chain
/ / Parameters: data, the data to be saved in the block
Func (bc *BlockChain) AddBlock(data string) {
    Var lastHash []byte

    // Open lockchian.db in read-only mode to get the hash value of the latest block
    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)
    }

    // Calculate the new block
    newBlock := NewBlock(data, lastHash)
    Bc.tip = newBlock.Hash

    // Read and write to lockchian.db and write the new block to 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 block
        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(byteBlock)
    bci.currentHash = block.PrevBlockHash
    Return block
}

// Create a blockchain
// return a blockchain instance
Func NewBlockChain() *BlockChain {
    Var tip []byte
    // Open the file blockchian.db of the blockchain chain, create it if it does not exist
    Db, err := bolt.Open(dbFile, 0600, nil)

    If err != nil {
        log.Panic(err)
    }

    // readable and writable way to access 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 a Genesis block
            Genesis := NewGenesisBlock()

            b, err := tx.CreateBucket([]byte(blockBucket))
            If err != nil {
                log.Panic(err)
            }

            // The key-value pair storage block is in blockchian.db
            Err = b.Put(genesis.Hash, genesis.Serialize())
            If err != nil {
                log.Panic(err)
            }

            // Save the latest block hash with a special key to facilitate the entire chain search
            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)
    }
    // return an instance of the blockchain
    Bc := &BlockChain{tip, db}
    Return bc
}


Cli.go


Package core

Import (
    "fmt"
    "os"
    "flag"
    "log"
    "strconv"
)

Type CLI struct {
    Bc *BlockChain
}

Func (cli *CLI) printUsage() {
    fmt.Println("Usage:")
    fmt.Println(" addblock -data(block data) - Add a block to the blockchain.")
    fmt.Println(" printchain - print all blocks on the blockchain")
}

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 a command line object
    addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)
    printChianCmd := flag.NewFlagSet("printchain", flag.ExitOnError)

    // Command line object adds parameters,
    addBlockData := addBlockCmd.String("data", "", "The block data 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()
        //cli.printUsage()
    }
}


Proofofwork.go


package core

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

var (
    maxNonce = math.MaxInt64
)

const targetBits = 12

type 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),
        IntToHex(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\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 core

import (
    "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.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.