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[:]
}