From a198d9da894ba2c40a145ddfe6edfdc457463d5e Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Fri, 22 Sep 2017 12:38:21 -0500 Subject: [PATCH] Step 3 - Added Persistence (boltdb storage) - Added CLI - Added PoW Validation --- .gitignore | 2 + block.go | 21 ++++++++++ blockchain.go | 110 +++++++++++++++++++++++++++++++++++++++++++++++--- cli.go | 79 ++++++++++++++++++++++++++++++++++++ main.go | 13 ++---- proof.go | 11 +++++ 6 files changed, 221 insertions(+), 15 deletions(-) create mode 100644 cli.go diff --git a/.gitignore b/.gitignore index 7a2c58a..a3903a8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ _testmain.go # And the binary blockchain-poc +# And the database +blockchain.db diff --git a/block.go b/block.go index ec81560..8eec69b 100644 --- a/block.go +++ b/block.go @@ -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 +} diff --git a/blockchain.go b/blockchain.go index 6b7dfb2..a476273 100644 --- a/blockchain.go +++ b/blockchain.go @@ -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 } diff --git a/cli.go b/cli.go new file mode 100644 index 0000000..315b8b9 --- /dev/null +++ b/cli.go @@ -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 + } + } +} diff --git a/main.go b/main.go index fa0f855..5ba110b 100644 --- a/main.go +++ b/main.go @@ -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() } diff --git a/proof.go b/proof.go index 6bb9d88..0542f23 100644 --- a/proof.go +++ b/proof.go @@ -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 +}