Go coded language interpretation from the perspective of a simple block chain to achieve - to achieve trade

Achieved in the public chain block chain transaction basis

The purpose of the block chain, is capable of safe and reliable storage transactions, such as our common Bitcoin transaction, where we will achieve universal transaction Bitcoin for example on the block chain. On completion of a well-chain block chain with a simple data structure, to achieve this Section block chain portion of the transaction on this basis. Achieve public chain

Trading mechanism

In the block chain, the transaction once it is created, no one can go to modify or delete it, this section will be the basic framework of a transaction, the specific transaction details will be given later.

Bits currency, for example, different from the general concept of the account model, which is used in UTXO transaction model. The information we need, are indirectly included in each transaction, including the user's balance information.

For each transaction, you can imagine into a channel, left channel has a number of input information, the right end of the channel there will be a number of output information. Meaning the input information represents, with the exchange of money is coming from, a transaction may be zero or more currency source (0 is a special case, namely excavated mine, because there is no user source, so there is no input information). Meaning output information represents that after the transaction, digital currency movements gone. Thus, the number of inputs and outputs of a monetary transaction information should be equivalent to the sum of the source of digital money, the digital money equal to the sum of the output. Not difficult to imagine, compared to the traditional account model, the model in UTXO user account balance is the output part of the transaction record.

To give a simple example, suppose A coin pay a bit to B, the following process:

  1. View the current existing transaction information, transaction output point to find their own trading and included in the total balance
  2. Is there enough money of their own digital information to determine the current transaction output
  3. When the balance is insufficient, the balance tips insufficient information
  4. When sufficient balance, a new transaction, namely a UTXO
  5. The output of consumer UTXO user's part of the balance (the balance of all consumer users do not need to, as long as enough on the line), and the user's balance is recorded in the conventional output before UTXO in, so enter a new transaction, they before the output of certain transactions.
  6. When the number of users to find the balance needed for the transaction are not equal, the user can output the rest of the money themselves again, that the change to ensure equal input and output transactions

So we implemented a simple deal with the sources of money in the deal, the currency has a clear destination, while carrying our ongoing transaction information.

Then we will combine the code, so this logic become clearer picture below is a brief description of the UTXO model:
Here Insert Picture Description
Coinbase trading is a special kind of transaction, which represents the miners dig new mines, new role excavated ore and outputs the added point well chain mining miners.

This example shows, 12.5 Zhang mining bit credits and pay 2.5 to John Doe, their credits remaining 10 bits, then payments of 2.5 bits Zhangsanlisi credits to Wang Wu, Zhang final credits remaining 7.5 bits , John Doe balance is depleted, the remaining five bits king five coins, equal to the total sum of 12.5 credits seating ore excavated.

Coding

Compared with the well-code implementation has been completed before the chain, block chain of transactions need to create a file transaction.go used to implement the business logic. The remaining code in the file, it will be minor adjustments following the addition of trading mechanism.

transaction.go

The following is transaction.go code:

package main

import (
    "bytes"
    "crypto/sha256"
    "encoding/gob"
    "encoding/hex"
    "fmt"
    "log"
)

const subsidy = 10

// Transaction represents a Bitcoin transaction
type Transaction struct {
    ID   []byte
    Vin  []TXInput
    Vout []TXOutput
}

// IsCoinbase checks whether the transaction is coinbase
func (tx Transaction) IsCoinbase() bool {
    return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1
}

// SetID sets ID of a transaction
func (tx *Transaction) SetID() {
    var encoded bytes.Buffer
    var hash [32]byte

    enc := gob.NewEncoder(&encoded)
    err := enc.Encode(tx)
    if err != nil {
        log.Panic(err)
    }
    hash = sha256.Sum256(encoded.Bytes())
    tx.ID = hash[:]
}

// TXInput represents a transaction input
type TXInput struct {
    Txid      []byte
    Vout      int
    ScriptSig string
}

// TXOutput represents a transaction output
type TXOutput struct {
    Value        int
    ScriptPubKey string
}

// CanUnlockOutputWith checks whether the address initiated the transaction
func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {
    return in.ScriptSig == unlockingData
}

// CanBeUnlockedWith checks if the output can be unlocked with the provided data
func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {
    return out.ScriptPubKey == unlockingData
}

// NewCoinbaseTX creates a new coinbase transaction
func NewCoinbaseTX(to, data string) *Transaction {
    if data == "" {
        data = fmt.Sprintf("Reward to '%s'", to)
    }

    txin := TXInput{[]byte{}, -1, data}
    txout := TXOutput{subsidy, to}
    tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
    tx.SetID()

    return &tx
}

// NewUTXOTransaction creates a new transaction
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
    var inputs []TXInput
    var outputs []TXOutput

    acc, validOutputs := bc.FindSpendableOutputs(from, amount)

    if acc < amount {
        log.Panic("ERROR: Not enough funds")
    }

    // Build a list of inputs
    for txid, outs := range validOutputs {
        txID, err := hex.DecodeString(txid)
        if err != nil {
            log.Panic(err)
        }

        for _, out := range outs {
            input := TXInput{txID, out, from}
            inputs = append(inputs, input)
        }
    }

    // Build a list of outputs
    outputs = append(outputs, TXOutput{amount, to})
    if acc > amount {
        outputs = append(outputs, TXOutput{acc - amount, from}) // a change
    }

    tx := Transaction{nil, inputs, outputs}
    tx.SetID()

    return &tx
}

Code mainly includes the following:

  • ID (transaction requires ID) Transaction structure comprising the current transaction, the input array and an output array
  • IsCoinbase function is used to determine whether the current transaction is Coinbase transaction (mining transaction)
  • SetID function to set the transaction id
  • TXInput structure comprising a strip transaction id input, the amount of the transaction with an address output
  • Amount address TXOutput structure comprising an output current transaction
  • Address CanUnlockOutputWith function to determine whether to provide the transaction record is matched to a input address
  • Address CanBeUnlockedWith function to determine whether to provide the record matched to a transaction output address
  • NewCoinbaseTX function to create a mining deal
  • NewUTXOTransaction function creates a new transaction

关于TXInput与TXOutput中地址的问题,因为目前还没有实现区块链中的地址,所以本节涉及的地址直接用字符串代替,验证地址也只是进行了字符串对比。地址是必要的,它标注了当前的余额属于谁,这里因为刚实现交易机制,还没有引入真正的地址机制,所以是存在漏洞的,用户只要知道有哪些用户就可以直接往自己地址转钱,在下一节会实现地址机制进行完善。

block.go

在transaction.go中实现了交易的结构体,如何创建一条新的交易,以及简单的交易对象判断。在其余文件中,block.go文件做了一些改动,主要是将原本的data字符串换成了Transaction交易。同样的,下一节中我们会将本节的地址字符串换成相应机制的地址,以下是改动后的block.go文件:

package main

import (
    "bytes"
    "crypto/sha256"
    "encoding/gob"
    "log"
    "time"
)

// Block keeps block headers
type Block struct {
    Timestamp     int64
    Transactions  []*Transaction
    PrevBlockHash []byte
    Hash          []byte
    Nonce         int
}

// 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()
}

// HashTransactions returns a hash of the transactions in the block
func (b *Block) HashTransactions() []byte {
    var txHashes [][]byte
    var txHash [32]byte

    for _, tx := range b.Transactions {
        txHashes = append(txHashes, tx.ID)
    }
    txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))

    return txHash[:]
}

// NewBlock creates and returns Block
func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block {
    block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0}
    pow := NewProofOfWork(block)
    nonce, hash := pow.Run()

    block.Hash = hash[:]
    block.Nonce = nonce

    return block
}

// NewGenesisBlock creates and returns genesis Block
func NewGenesisBlock(coinbase *Transaction) *Block {
    return NewBlock([]*Transaction{coinbase}, []byte{})
}

// 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
}

添加了HashTransactions函数,用来将交易转换成哈希值,其余函数随结构体中Data->Transactions的变动相应调整。

blockchain.go

在blockchain.go中,涉及到寻找用户余额(未花费交易输出)操作,需要多做一些调整:

package main

import (
    "encoding/hex"
    "fmt"
    "log"
    "os"
    "bolt-master"
)

const dbFile = "blockchain.db"
const blocksBucket = "blocks"
const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"

// Blockchain implements interactions with a DB
type Blockchain struct {
    tip []byte
    db  *bolt.DB
}

// BlockchainIterator is used to iterate over blockchain blocks
type BlockchainIterator struct {
    currentHash []byte
    db          *bolt.DB
}

// MineBlock mines a new block with the provided transactions
func (bc *Blockchain) MineBlock(transactions []*Transaction) {
    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(transactions, 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)
        if err != nil {
            log.Panic(err)
        }

        bc.tip = newBlock.Hash

        return nil
    })
}

// FindUnspentTransactions returns a list of transactions containing unspent outputs
func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction {
    var unspentTXs []Transaction
    spentTXOs := make(map[string][]int)
    bci := bc.Iterator()

    for {
        block := bci.Next()

        for _, tx := range block.Transactions {
            txID := hex.EncodeToString(tx.ID)

        Outputs:
            for outIdx, out := range tx.Vout {
                // Was the output spent?
                if spentTXOs[txID] != nil {
                    for _, spentOut := range spentTXOs[txID] {
                        if spentOut == outIdx {
                            continue Outputs
                        }
                    }
                }

                if out.CanBeUnlockedWith(address) {
                    unspentTXs = append(unspentTXs, *tx)
                }
            }

            if tx.IsCoinbase() == false {
                for _, in := range tx.Vin {
                    if in.CanUnlockOutputWith(address) {
                        inTxID := hex.EncodeToString(in.Txid)
                        spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
                    }
                }
            }
        }

        if len(block.PrevBlockHash) == 0 {
            break
        }
    }

    return unspentTXs
}

// FindUTXO finds and returns all unspent transaction outputs
func (bc *Blockchain) FindUTXO(address string) []TXOutput {
    var UTXOs []TXOutput
    unspentTransactions := bc.FindUnspentTransactions(address)

    for _, tx := range unspentTransactions {
        for _, out := range tx.Vout {
            if out.CanBeUnlockedWith(address) {
                UTXOs = append(UTXOs, out)
            }
        }
    }

    return UTXOs
}

// FindSpendableOutputs finds and returns unspent outputs to reference in inputs
func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
    unspentOutputs := make(map[string][]int)
    unspentTXs := bc.FindUnspentTransactions(address)
    accumulated := 0

Work:
    for _, tx := range unspentTXs {
        txID := hex.EncodeToString(tx.ID)

        for outIdx, out := range tx.Vout {
            if out.CanBeUnlockedWith(address) && accumulated < amount {
                accumulated += out.Value
                unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)

                if accumulated >= amount {
                    break Work
                }
            }
        }
    }

    return accumulated, unspentOutputs
}

// Iterator returns a BlockchainIterat
func (bc *Blockchain) Iterator() *BlockchainIterator {
    bci := &BlockchainIterator{bc.tip, bc.db}

    return bci
}

// 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
}

func dbExists() bool {
    if _, err := os.Stat(dbFile); os.IsNotExist(err) {
        return false
    }

    return true
}

// NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain(address string) *Blockchain {
    if dbExists() == false {
        fmt.Println("No existing blockchain found. Create one first.")
        os.Exit(1)
    }

    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))
        tip = b.Get([]byte("l"))

        return nil
    })

    if err != nil {
        log.Panic(err)
    }

    bc := Blockchain{tip, db}

    return &bc
}

// CreateBlockchain creates a new blockchain DB
func CreateBlockchain(address string) *Blockchain {
    if dbExists() {
        fmt.Println("Blockchain already exists.")
        os.Exit(1)
    }

    var tip []byte
    db, err := bolt.Open(dbFile, 0600, nil)
    if err != nil {
        log.Panic(err)
    }

    err = db.Update(func(tx *bolt.Tx) error {
        cbtx := NewCoinbaseTX(address, genesisCoinbaseData)
        genesis := NewGenesisBlock(cbtx)

        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("l"), genesis.Hash)
        if err != nil {
            log.Panic(err)
        }
        tip = genesis.Hash

        return nil
    })

    if err != nil {
        log.Panic(err)
    }

    bc := Blockchain{tip, db}

    return &bc
}

代码的主要变动是新增了三个关于交易的函数:

  • FindUnspendTransactions遍历公链,寻找交易信息中没有被使用过输出的交易,即未被花费过的余额。当一条交易中的余额被其他交易用做过输入,该余额也就不在具有余额的属性,不能再次被交易
  • FindUTXO在内部调用了FindUnspendTransactions函数,与FindUnspendTransactions不同的是它用于查询用户的余额信息,即所有有效未花费余额的总和
  • FindSpendableOutputs在内部调用了FindUnspendTransactions函数,用于找出哪些余额是可用的

其次,原本的Addblock被改成了更具体的Mineblock挖矿函数,新增了Createblockchain函数和dbExists函数,用来判断数据库是否存在,只有当数据库中没有公链时才能创建新的区块链。

proofofwork.go

In proofofwork file, only when the Data prepareData replaced HashTransactions, when no printed portion Data mining, proofofwork.go complete code as follows:

package main

import (
    "bytes"
    "crypto/sha256"
    "fmt"
    "math"
    "math/big"
)

var (
    maxNonce = math.MaxInt64
)

const targetBits = 24

// ProofOfWork represents a proof-of-work
type ProofOfWork struct {
    block  *Block
    target *big.Int
}

// NewProofOfWork builds and returns a ProofOfWork
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.HashTransactions(),
            IntToHex(pow.block.Timestamp),
            IntToHex(int64(targetBits)),
            IntToHex(int64(nonce)),
        },
        []byte{},
    )

    return data
}

// Run performs a proof-of-work
func (pow *ProofOfWork) Run() (int, []byte) {
    var hashInt big.Int
    var hash [32]byte
    nonce := 0

    fmt.Printf("Mining a new block")
    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.Print("\n\n")

    return nonce, hash[:]
}

// Validate validates block's PoW
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
}

cli.go

cli.go file with some changes underlying business logic and make the appropriate changes, variations mainly used to implement the command line, the chain does not involve logic blocks:

package main

import (
    "flag"
    "fmt"
    "log"
    "os"
    "strconv"
)

// CLI responsible for processing command line arguments
type CLI struct{}

func (cli *CLI) createBlockchain(address string) {
    bc := CreateBlockchain(address)
    bc.db.Close()
    fmt.Println("Done!")
}

func (cli *CLI) getBalance(address string) {
    bc := NewBlockchain(address)
    defer bc.db.Close()

    balance := 0
    UTXOs := bc.FindUTXO(address)

    for _, out := range UTXOs {
        balance += out.Value
    }

    fmt.Printf("Balance of '%s': %d\n", address, balance)
}

func (cli *CLI) printUsage() {
    fmt.Println("Usage:")
    fmt.Println("  getbalance -address ADDRESS - Get balance of ADDRESS")
    fmt.Println("  createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS")
    fmt.Println("  printchain - Print all the blocks of the blockchain")
    fmt.Println("  send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO")
}

func (cli *CLI) validateArgs() {
    if len(os.Args) < 2 {
        cli.printUsage()
        os.Exit(1)
    }
}

func (cli *CLI) printChain() {
    // TODO: Fix this
    bc := NewBlockchain("")
    defer bc.db.Close()

    bci := bc.Iterator()

    for {
        block := bci.Next()

        fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
        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 (cli *CLI) send(from, to string, amount int) {
    bc := NewBlockchain(from)
    defer bc.db.Close()

    tx := NewUTXOTransaction(from, to, amount, bc)
    bc.MineBlock([]*Transaction{tx})
    fmt.Println("Success!")
}

// Run parses command line arguments and processes commands
func (cli *CLI) Run() {
    cli.validateArgs()

    getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError)
    createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)
    sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
    printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)

    getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for")
    createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to")
    sendFrom := sendCmd.String("from", "", "Source wallet address")
    sendTo := sendCmd.String("to", "", "Destination wallet address")
    sendAmount := sendCmd.Int("amount", 0, "Amount to send")

    switch os.Args[1] {
    case "getbalance":
        err := getBalanceCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "createblockchain":
        err := createBlockchainCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "printchain":
        err := printChainCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "send":
        err := sendCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    default:
        cli.printUsage()
        os.Exit(1)
    }

    if getBalanceCmd.Parsed() {
        if *getBalanceAddress == "" {
            getBalanceCmd.Usage()
            os.Exit(1)
        }
        cli.getBalance(*getBalanceAddress)
    }

    if createBlockchainCmd.Parsed() {
        if *createBlockchainAddress == "" {
            createBlockchainCmd.Usage()
            os.Exit(1)
        }
        cli.createBlockchain(*createBlockchainAddress)
    }

    if printChainCmd.Parsed() {
        cli.printChain()
    }

    if sendCmd.Parsed() {
        if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
            sendCmd.Usage()
            os.Exit(1)
        }

        cli.send(*sendFrom, *sendTo, *sendAmount)
    }
}

main.go

In main.go, we will all have to operate cli objects, originally the old main.go creation of new block operation, also put cli.go of logic, so only the following code:

package main

func main() {
    bc := NewBlockchain()
    defer bc.db.Close()

    cli := CLI{bc}
    cli.Run()
}

utils.go

No new utility functions introduced, utils.go files intact.

In the next section, we will achieve address block chain mechanism, and gradually improve the entire block chain.

Guess you like

Origin www.cnblogs.com/ustca/p/11765349.html