前面在代碼中增長了pow共識,這一步則把信息持久化而且對外提供cli,先看代碼,我命名爲dbblock.go:node
package main import ( "fmt" "time" "crypto/sha256" "bytes" "math/big" "math" "strconv" "github.com/boltdb/bolt" "encoding/gob" "log" "flag" "os" ) const targetBits = 24 const maxNonce int = math.MaxInt64 const dbFile = "blockchain_%s.db" const blocksBucket = "blocks" type Block struct { Timestamp int64 Data []byte PrevBlockHash []byte Hash []byte Nonce int } type Blockchain struct { tip []byte db *bolt.DB } type ProofOfWork struct { block *Block target *big.Int } type BlockchainIterator struct { currentHash []byte db *bolt.DB } type CLI struct { bc *Blockchain } func (cli *CLI) printUsage() { fmt.Println("Usage:") fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS") fmt.Println(" createwallet - Generates a new key-pair and saves it into the wallet file") fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS") fmt.Println(" listaddresses - Lists all addresses from the wallet file") fmt.Println(" printchain - Print all the blocks of the blockchain") fmt.Println(" reindexutxo - Rebuilds the UTXO set") fmt.Println(" send -from FROM -to TO -amount AMOUNT -mine - Send AMOUNT of coins from FROM address to TO. Mine on the same node, when -mine is set.") fmt.Println(" startnode -miner ADDRESS - Start a node with ID specified in NODE_ID env. var. -miner enables mining") } func (cli *CLI) validateArgs() { if len(os.Args) < 2 { cli.printUsage() os.Exit(1) } } // Serialize serializes the block func (b *Block) Serialize() []byte { var result bytes.Buffer encoder := gob.NewEncoder(&result) err := encoder.Encode(b) if err != nil { log.Panic(err) } return result.Bytes() } // DeserializeBlock deserializes a block func DeserializeBlock(d []byte) *Block { var block Block decoder := gob.NewDecoder(bytes.NewReader(d)) err := decoder.Decode(&block) if err != nil { log.Panic(err) } return &block } 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(strconv.FormatInt((pow.block.Timestamp),16)), []byte(strconv.FormatInt((int64(targetBits)),16)), []byte(strconv.FormatInt((int64(nonce)),16)), }, []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) hashInt.SetBytes(hash[:]) if hashInt.Cmp(pow.target) == -1 { fmt.Printf("\r%x", hash) break } else { nonce++ } } fmt.Print("\n\n") return nonce, hash[:] } func NewBlock(data string, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} pow := NewProofOfWork(block) nonce, hash := pow.Run() block.Hash = hash[:] block.Nonce = nonce return block } func NewGenesisBlock() *Block { return NewBlock("Genesis Block", []byte{}) } func NewBlockchain() *Blockchain { var tip []byte db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Panic(err) } err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) if b == nil { genesis := NewGenesisBlock() b, err := tx.CreateBucket([]byte(blocksBucket)) if err != nil { log.Panic(err) } err = b.Put(genesis.Hash, genesis.Serialize()) err = b.Put([]byte("l"), genesis.Hash) tip = genesis.Hash } else { tip = b.Get([]byte("l")) } return nil }) bc := Blockchain{tip, db} return &bc } func (bc *Blockchain) AddBlock(data string) { var lastHash []byte err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) return nil }) if err != nil { log.Panic(err) } newBlock := NewBlock(data, lastHash) err = bc.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { log.Panic(err) } err = b.Put([]byte("l"), newBlock.Hash) bc.tip = newBlock.Hash return nil }) } 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 } func (cli *CLI) Run() { cli.validateArgs() addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) addBlockData := addBlockCmd.String("data", "", "Block data") switch os.Args[1] { case "addblock": err := addBlockCmd.Parse(os.Args[2:]) if err != nil { log.Panic(err) } case "printchain": err := printChainCmd.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 printChainCmd.Parsed() { cli.printChain() } } func (cli *CLI) addBlock(data string) { cli.bc.AddBlock(data) fmt.Println("Success!") } // Next returns next block starting from the tip func (i *BlockchainIterator) Next() *Block { var block *Block err := i.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) encodedBlock := b.Get(i.currentHash) block = DeserializeBlock(encodedBlock) return nil }) if err != nil { log.Panic(err) } i.currentHash = block.PrevBlockHash return block } // Iterator returns a BlockchainIterat func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} return bci } 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 } } } func main() { bc := NewBlockchain() defer bc.db.Close() cli := CLI{bc} cli.Run() }
採用了boltdb數據庫,由於很簡單且使用go開發,git
安裝boltdb的方法:github
go get github.com/boltdb/bolt/...數據庫
先go build,會生成dbblock.exe,可以下執行ui