- Added Persistence (boltdb storage)
- Added CLI
- Added PoW Validation
This commit is contained in:
Brian Buller 2017-09-22 12:38:21 -05:00
parent 72004f2396
commit a198d9da89
6 changed files with 221 additions and 15 deletions

2
.gitignore vendored
View File

@ -26,3 +26,5 @@ _testmain.go
# And the binary
blockchain-poc
# And the database
blockchain.db

View File

@ -1,6 +1,9 @@
package main
import (
"bytes"
"encoding/gob"
"log"
"math"
"time"
)
@ -33,3 +36,21 @@ func NewBlock(data string, prevBlockHash []byte) *Block {
return block
}
func (b *Block) Serialize() []byte {
var result bytes.Buffer
encoder := gob.NewEncoder(&result)
if err := encoder.Encode(b); err != nil {
log.Panic(err)
}
return result.Bytes()
}
func DeserializeBlock(d []byte) *Block {
var block Block
decoder := gob.NewDecoder(bytes.NewReader(d))
if err := decoder.Decode(&block); err != nil {
log.Panic(err)
}
return &block
}

View File

@ -1,11 +1,64 @@
package main
import (
"fmt"
"log"
"github.com/boltdb/bolt"
)
var dbFile = "blockchain.db"
const blocksBucket = "blocks"
type Blockchain struct {
blocks []*Block
tip []byte
db *bolt.DB
}
type BlockchainIterator struct {
currentHash []byte
db *bolt.DB
}
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
var tip []byte
db, err := bolt.Open(dbFile, 0600, nil)
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
if b == nil {
fmt.Println("No existing blockchain found. Creating a new one...")
genesis := NewGenesisBlock()
b, err := tx.CreateBucket([]byte(blocksBucket))
if err != nil {
log.Panic(err)
}
err = b.Put(genesis.Hash, genesis.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("1"), genesis.Hash)
if err != nil {
log.Panic(err)
}
tip = genesis.Hash
} else {
tip = b.Get([]byte("1"))
}
return nil
})
if err != nil {
log.Panic(err)
}
bc := Blockchain{tip, db}
return &bc
}
// NewGenesisBlock creates a new Genesis Block to start the blockchain
@ -15,7 +68,54 @@ func NewGenesisBlock() *Block {
// AddBlock adds a block to 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)
var lastHash []byte
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash = b.Get([]byte("1"))
return nil
})
if err != nil {
log.Panic(err)
}
newBlock := NewBlock(data, lastHash)
err = bc.db.Update(func(tx *bolt.Tx) error {
var err error
b := tx.Bucket([]byte(blocksBucket))
if err = b.Put(newBlock.Hash, newBlock.Serialize()); err != nil {
log.Panic(err)
}
if err = b.Put([]byte("1"), newBlock.Hash); err != nil {
log.Panic(err)
}
bc.tip = newBlock.Hash
return nil
})
if err != nil {
log.Panic(err)
}
}
func (bc *Blockchain) Iterator() *BlockchainIterator {
bci := &BlockchainIterator{bc.tip, bc.db}
return bci
}
func (bci *BlockchainIterator) Next() *Block {
var block *Block
err := bci.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
encodedBlock := b.Get(bci.currentHash)
block = DeserializeBlock(encodedBlock)
return nil
})
if err != nil {
log.Panic(err)
}
bci.currentHash = block.PrevBlockHash
return block
}

79
cli.go Normal file
View File

@ -0,0 +1,79 @@
package main
import (
"flag"
"fmt"
"os"
"strconv"
)
type CLI struct {
bc *Blockchain
}
func (cli *CLI) printUsage() {
fmt.Println("Usage:")
fmt.Println("\taddblock -data BLOCK_DATA\tadd a block to the blockchain")
fmt.Println("\tprintchain\t\t\tprint all the blocks of the blockchain")
}
func (cli *CLI) validateArgs() {
if len(os.Args) < 2 {
cli.printUsage()
os.Exit(1)
}
}
func (cli *CLI) Run() {
cli.validateArgs()
// Valid CLI flags
addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
// CLI Flag Data
addBlockData := addBlockCmd.String("data", "", "Block data")
switch os.Args[1] {
case "addblock":
addBlockCmd.Parse(os.Args[2:])
case "printchain":
printChainCmd.Parse(os.Args[2:])
default:
cli.printUsage()
os.Exit(1)
}
if addBlockCmd.Parsed() {
if *addBlockData == "" {
addBlockCmd.Usage()
os.Exit(1)
}
cli.addBlock(*addBlockData)
}
if printChainCmd.Parsed() {
cli.printChain()
}
}
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("Prev. 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
}
}
}

13
main.go
View File

@ -1,16 +1,9 @@
package main
import "fmt"
func main() {
bc := NewBlockchain()
bc.AddBlock("Data 1")
bc.AddBlock("Data 2")
defer bc.db.Close()
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()
}
cli := CLI{bc}
cli.Run()
}

View File

@ -58,3 +58,14 @@ func (pow *ProofOfWork) Run() (int, []byte) {
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
}