區塊鏈開發(三)

前面在代碼中增長了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

相關文章
相關標籤/搜索