区块链编程go(四)-交易

 

part1: transaction.go

package blockchain

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

type Transaction struct {
    ID      []byte
    Inputs  []TxInput
    Outputs []TxOutput
}

type TxOutput struct {
    Value  int
    PubKey string
}

type TxInput struct {
    ID  []byte
    Out int
    Sig string
}

func (tx *Transaction) SetID() {
    var encoded bytes.Buffer
    var hash [32]byte

    encode := gob.NewEncoder(&encoded)
    err := encode.Encode(tx)
    Handle(err)

    hash = sha256.Sum256(encoded.Bytes())
    tx.ID = hash[:]
}

func CoinbaseTx(to, data string) *Transaction {
    if data == "" {
        data = fmt.Sprintf("Coins to %s", to)
    }

    txin := TxInput{[]byte{}, -1, data}
    txout := TxOutput{100, to}

    tx := Transaction{nil, []TxInput{txin}, []TxOutput{txout}}
    tx.SetID()

    return &tx
}

func NewTransaction(from, to string, amount int, chain *BlockChain) *Transaction {
    var inputs []TxInput
    var outputs []TxOutput

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

    if acc < amount {
        log.Panic("Error: not enough funds")
    }

    for txid, outs := range validOutputs {
        txID, err := hex.DecodeString(txid)
        Handle(err)

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

    outputs = append(outputs, TxOutput{amount, to})

    if acc > amount {
        outputs = append(outputs, TxOutput{acc - amount, from})
    }

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

    return &tx
}

func (tx *Transaction) IsCoinbase() bool {
    return len(tx.Inputs) == 1 && len(tx.Inputs[0].ID) == 0 && tx.Inputs[0].Out == -1
}

func (in *TxInput) CanUnlock(data string) bool {
    return in.Sig == data
}

func (out *TxOutput) CanBeUnlocked(data string) bool {
    return out.PubKey == data
}

 

part2 : blockchain.go

package blockchain

import (
    "encoding/hex"
    "fmt"
    "os"
    "runtime"

    "github.com/dgraph-io/badger"
)

const (
    dbPath      = "./tmp/blocks"
    dbFile      = "./tmp/blocks/MANIFEST"
    genesisData = "First Transaction from Genesis"
)

type BlockChain struct {
    LastHash []byte
    Database *badger.DB
}

type BlockChainIterator struct {
    CurrentHash []byte
    Database    *badger.DB
}

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

    return true
}

func ContinueBlockChain(address string) *BlockChain {
    if DBexists() == false {
        fmt.Println("No existing blockchain found, create one!")
        runtime.Goexit()
    }

    var lastHash []byte

    opts := badger.DefaultOptions
    opts.Dir = dbPath
    opts.ValueDir = dbPath

    db, err := badger.Open(opts)
    Handle(err)

    err = db.Update(func(txn *badger.Txn) error {
        item, err := txn.Get([]byte("lh"))
        Handle(err)
        lastHash, err = item.Value()

        return err
    })
    Handle(err)

    chain := BlockChain{lastHash, db}

    return &chain
}

func InitBlockChain(address string) *BlockChain {
    var lastHash []byte

    if DBexists() {
        fmt.Println("Blockchain already exists")
        runtime.Goexit()
    }

    opts := badger.DefaultOptions
    opts.Dir = dbPath
    opts.ValueDir = dbPath

    db, err := badger.Open(opts)
    Handle(err)

    err = db.Update(func(txn *badger.Txn) error {
        cbtx := CoinbaseTx(address, genesisData)
        genesis := Genesis(cbtx)
        fmt.Println("Genesis created")
        err = txn.Set(genesis.Hash, genesis.Serialize())
        Handle(err)
        err = txn.Set([]byte("lh"), genesis.Hash)

        lastHash = genesis.Hash

        return err

    })

    Handle(err)

    blockchain := BlockChain{lastHash, db}
    return &blockchain
}

func (chain *BlockChain) AddBlock(transactions []*Transaction) {
    var lastHash []byte

    err := chain.Database.View(func(txn *badger.Txn) error {
        item, err := txn.Get([]byte("lh"))
        Handle(err)
        lastHash, err = item.Value()

        return err
    })
    Handle(err)

    newBlock := CreateBlock(transactions, lastHash)

    err = chain.Database.Update(func(txn *badger.Txn) error {
        err := txn.Set(newBlock.Hash, newBlock.Serialize())
        Handle(err)
        err = txn.Set([]byte("lh"), newBlock.Hash)

        chain.LastHash = newBlock.Hash

        return err
    })
    Handle(err)
}

func (chain *BlockChain) Iterator() *BlockChainIterator {
    iter := &BlockChainIterator{chain.LastHash, chain.Database}

    return iter
}

func (iter *BlockChainIterator) Next() *Block {
    var block *Block

    err := iter.Database.View(func(txn *badger.Txn) error {
        item, err := txn.Get(iter.CurrentHash)
        Handle(err)
        encodedBlock, err := item.Value()
        block = Deserialize(encodedBlock)

        return err
    })
    Handle(err)

    iter.CurrentHash = block.PrevHash

    return block
}

func (chain *BlockChain) FindUnspentTransactions(address string) []Transaction {
    var unspentTxs []Transaction

    spentTXOs := make(map[string][]int)

    iter := chain.Iterator()

    for {
        block := iter.Next()

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

        Outputs:
            for outIdx, out := range tx.Outputs {
                if spentTXOs[txID] != nil {
                    for _, spentOut := range spentTXOs[txID] {
                        if spentOut == outIdx {
                            continue Outputs
                        }
                    }
                }
                if out.CanBeUnlocked(address) {
                    unspentTxs = append(unspentTxs, *tx)
                }
            }
            if tx.IsCoinbase() == false {
                for _, in := range tx.Inputs {
                    if in.CanUnlock(address) {
                        inTxID := hex.EncodeToString(in.ID)
                        spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Out)
                    }
                }
            }
        }

        if len(block.PrevHash) == 0 {
            break
        }
    }
    return unspentTxs
}

func (chain *BlockChain) FindUTXO(address string) []TxOutput {
    var UTXOs []TxOutput
    unspentTransactions := chain.FindUnspentTransactions(address)

    for _, tx := range unspentTransactions {
        for _, out := range tx.Outputs {
            if out.CanBeUnlocked(address) {
                UTXOs = append(UTXOs, out)
            }
        }
    }
    return UTXOs
}

func (chain *BlockChain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
    unspentOuts := make(map[string][]int)
    unspentTxs := chain.FindUnspentTransactions(address)
    accumulated := 0

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

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

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

    return accumulated, unspentOuts
}

 

block.go

package blockchain

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

type Block struct {
    Hash         []byte
    Transactions []*Transaction
    PrevHash     []byte
    Nonce        int
}

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[:]
}

func CreateBlock(txs []*Transaction, prevHash []byte) *Block {
    block := &Block{[]byte{}, txs, prevHash, 0}
    pow := NewProof(block)
    nonce, hash := pow.Run()

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

    return block
}

func Genesis(coinbase *Transaction) *Block {
    return CreateBlock([]*Transaction{coinbase}, []byte{})
}

func (b *Block) Serialize() []byte {
    var res bytes.Buffer
    encoder := gob.NewEncoder(&res)

    err := encoder.Encode(b)

    Handle(err)

    return res.Bytes()
}

func Deserialize(data []byte) *Block {
    var block Block

    decoder := gob.NewDecoder(bytes.NewReader(data))

    err := decoder.Decode(&block)

    Handle(err)

    return &block
}

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

part4: proof.go

package blockchain

import (
    "bytes"
    "crypto/sha256"
    "encoding/binary"
    "fmt"
    "log"
    "math"
    "math/big"
)

// Take the data from the block

// create a counter (nonce) which starts at 0

// create a hash of the data plus the counter

// check the hash to see if it meets a set of requirements

// Requirements:
// The First few bytes must contain 0s

const Difficulty = 12

type ProofOfWork struct {
    Block  *Block
    Target *big.Int
}

func NewProof(b *Block) *ProofOfWork {
    target := big.NewInt(1)
    target.Lsh(target, uint(256-Difficulty))

    pow := &ProofOfWork{b, target}

    return pow
}

func (pow *ProofOfWork) InitData(nonce int) []byte {
    data := bytes.Join(
        [][]byte{
            pow.Block.PrevHash,
            pow.Block.HashTransactions(),
            ToHex(int64(nonce)),
            ToHex(int64(Difficulty)),
        },
        []byte{},
    )

    return data
}

func (pow *ProofOfWork) Run() (int, []byte) {
    var intHash big.Int
    var hash [32]byte

    nonce := 0

    for nonce < math.MaxInt64 {
        data := pow.InitData(nonce)
        hash = sha256.Sum256(data)

        fmt.Printf("\r%x", hash)
        intHash.SetBytes(hash[:])

        if intHash.Cmp(pow.Target) == -1 {
            break
        } else {
            nonce++
        }

    }
    fmt.Println()

    return nonce, hash[:]
}

func (pow *ProofOfWork) Validate() bool {
    var intHash big.Int

    data := pow.InitData(pow.Block.Nonce)

    hash := sha256.Sum256(data)
    intHash.SetBytes(hash[:])

    return intHash.Cmp(pow.Target) == -1
}

func ToHex(num int64) []byte {
    buff := new(bytes.Buffer)
    err := binary.Write(buff, binary.BigEndian, num)
    if err != nil {
        log.Panic(err)

    }

    return buff.Bytes()
}

 

main.go

package main

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

    "github.com/tensor-programming/golang-blockchain/blockchain"
)

type CommandLine struct{}

func (cli *CommandLine) printUsage() {
    fmt.Println("Usage:")
    fmt.Println(" getbalance -address ADDRESS - get the balance for an address")
    fmt.Println(" createblockchain -address ADDRESS creates a blockchain and sends genesis reward to address")
    fmt.Println(" printchain - Prints the blocks in the chain")
    fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send amount of coins")
}

func (cli *CommandLine) validateArgs() {
    if len(os.Args) < 2 {
        cli.printUsage()
        runtime.Goexit()
    }
}

func (cli *CommandLine) printChain() {
    chain := blockchain.ContinueBlockChain("")
    defer chain.Database.Close()
    iter := chain.Iterator()

    for {
        block := iter.Next()

        fmt.Printf("Prev. hash: %x\n", block.PrevHash)
        fmt.Printf("Hash: %x\n", block.Hash)
        pow := blockchain.NewProof(block)
        fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
        fmt.Println()

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

func (cli *CommandLine) createBlockChain(address string) {
    chain := blockchain.InitBlockChain(address)
    chain.Database.Close()
    fmt.Println("Finished!")
}

func (cli *CommandLine) getBalance(address string) {
    chain := blockchain.ContinueBlockChain(address)
    defer chain.Database.Close()

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

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

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

func (cli *CommandLine) send(from, to string, amount int) {
    chain := blockchain.ContinueBlockChain(from)
    defer chain.Database.Close()

    tx := blockchain.NewTransaction(from, to, amount, chain)
    chain.AddBlock([]*blockchain.Transaction{tx})
    fmt.Println("Success!")
}

func (cli *CommandLine) 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()
        runtime.Goexit()
    }

    if getBalanceCmd.Parsed() {
        if *getBalanceAddress == "" {
            getBalanceCmd.Usage()
            runtime.Goexit()
        }
        cli.getBalance(*getBalanceAddress)
    }

    if createBlockchainCmd.Parsed() {
        if *createBlockchainAddress == "" {
            createBlockchainCmd.Usage()
            runtime.Goexit()
        }
        cli.createBlockChain(*createBlockchainAddress)
    }

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

    if sendCmd.Parsed() {
        if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
            sendCmd.Usage()
            runtime.Goexit()
        }

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

func main() {
    defer os.Exit(0)
    cli := CommandLine{}
    cli.run()
}

 

go.mod 和 go.sum 和之前一样

 go run main.go createblockchain -address "john"

2024/08/17 13:58:20 Replaying from value pointer: {Fid:0 Len:0 Offset:0}
2024/08/17 13:58:20 Iterating file id: 0
2024/08/17 13:58:20 Iteration took: 0s
000d04e55453192e5210ef22ac1f66a50496a958ea36c5dea69942963acc9ed6
Genesis created
Finished!

 

go run main.go getbalance -address "john"

2024/08/17 13:59:16 Replaying from value pointer: {Fid:0 Len:42 Offset:584}
2024/08/17 13:59:16 Iterating file id: 0
2024/08/17 13:59:16 Iteration took: 0s
Balance of john: 100

 

 go run main.go send -from "john" -to "fred" -amount 50

2024/08/17 14:06:25 Replaying from value pointer: {Fid:0 Len:42 Offset:584}
2024/08/17 14:06:25 Iterating file id: 0
2024/08/17 14:06:25 Iteration took: 0s
0002e5673d5

 

go run main.go getbalance -address "fred"

2024/08/17 14:06:36 Replaying from value pointer: {Fid:0 Len:42 Offset:1259}
2024/08/17 14:06:36 Iterating file id: 0
2024/08/17 14:06:36 Iteration took: 0s
Balance of fred: 50

 

posted @ 2024-08-17 12:50  apeNote  阅读(27)  评论(0编辑  收藏  举报