交易及记账

Posted coder-886

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了交易及记账相关的知识,希望对你有一定的参考价值。

交易机制:
1、区块链能够安全可靠地存储交易结果
2、在区块链中,交易一旦被创建,就没有任何人能够再去修改或删除
3、交易由一些输入与一些输出组合而来
4、对于一笔新的交易,它的输入会引用之前一笔交易的输出
5、交易的输出,也就是比特币实际存储的地方
6、例外:
  1、有一些输出并没有被关联到某个输入上(尚未使用)
  2、一笔交易的输入可以引用之前多笔交易的输出
  3、一个输入必须引用一个输出

 

挖矿->A通过挖矿获得10个比特币

  -> 输入为空, 输出为A获得10个比特币

转账->A向B支付3个比特币:

  找出A未用的交易(总额够支付即可)

  那么A被找出来的这些交易就作为输入列表,如果有剩余的会一个找零过程(A会有一个7比特币的输出)

  而B会有一个3比特币的输出

  这些输入与输出会作为新区块的数据存储到链中

找出某人未用交易算法:

  从链的最新头开始找,找出属于某人的输入,过滤掉与这些输入挂钩的输出就是这个人的未用的交易(余额)

transaction.go

package core

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

const subsidy = 10

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

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

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

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

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

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

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

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

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

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

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

	return &tx
}

 cli.go

package core

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

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

func (cli *CLI) createBlockChain(address string) {
	bc := CreateBlockchain(address)
	defer 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‘", address, balance)

}

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")
}

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" +
		" coin from FROM address to TO")
}

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

func (cli *CLI) printChain() {

	bc := NewBlockChain("")
	defer bc.Db.Close()

	bci := bc.Iterator()

	for {
		block := bci.Next()

		fmt.Printf("Prev hash: %x
", block.PrevBlockHash)
		fmt.Printf("Hash: %x
", block.Hash)
		pow := NewProofOfWork(block)
		fmt.Printf("Pow: %s
", strconv.FormatBool(pow.Validate()))

		fmt.Println()

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

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

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

	getBalanceAddress := getBalanceCmd.String("address","", "the address" +
		"to get balance for")
	createBlockchainAddress := createBlockChainCmd.String("address","",
		"the address to send genesis block award 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 "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 "getbalance":
		err := getBalanceCmd.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 sendCmd.Parsed() {
		if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
			sendCmd.Usage()
			os.Exit(1)
		}
		cli.send(*sendFrom, *sendTo, *sendAmount)
	}

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

 blockchain.go

package core

import (
	"github.com/boltdb/bolt"
	"log"
	"fmt"
	"os"
	"encoding/hex"
)

const dbFile = "blockchain.db"
const blockBucket = "blocks"
const genesisCoinbaseData = "The Time 03/Jan/2009 Chancellor on brink of second bailout for bank"

//BlockChain keeps a sequence of Blocks
type BlockChain struct {
	tip []byte
	Db  *bolt.DB
}

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

//Iterator
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(blockBucket))
		encoderBlock := b.Get(i.currentHash)
		block = DeserializeBlock(encoderBlock)
		return nil
	})

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

	i.currentHash = block.PrevBlockHash

	return block
}

func NewBlockChain(address string) *BlockChain {
	if dbExists() == false {
		fmt.Println("No Blockchain exists, 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 {
		db := tx.Bucket([]byte(blockBucket))
		tip = db.Get([]byte("l"))

		return nil
	})

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

	bc := BlockChain{tip, db}

	return &bc
}

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

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

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

//CreateBlockchain create a new blockchain DB
func CreateBlockchain(address string) *BlockChain {

	if dbExists() {
		fmt.Println("Blockchain already exsits.")
		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(blockBucket))
		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
}

//MineBlock mines a new block with the provided transactions
func (bc *BlockChain) MineBlock(transaction []*Transaction) {

	var lastHash []byte

	err := bc.Db.View(func(tx *bolt.Tx) error {

		b := tx.Bucket([]byte(blockBucket))
		lastHash = b.Get([]byte("l"))
		return nil
	})

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

	newBlock := NewBlock(transaction,lastHash)

	err = bc.Db.Update(func(tx *bolt.Tx) error {

		b := tx.Bucket([]byte(blockBucket))
		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
	})

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

 main.go

package main

import "core"

func main() {

	cli := core.CLI{}

	cli.Run()

}

 block.go

package core

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

//Block keeps block header
type Block struct {
	Timestamp     int64          //区块创建的时间
	Transactions  []*Transaction //区块包含的数据
	PrevBlockHash []byte         //前一个区块的哈希值
	Hash          []byte         //区块自身的哈希值,用于校验区块数据有效
	Nonce         int            //记录工作量证明用到的数字
}

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

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
}

//NewBlock create and returns Block
func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block {
	block := &Block{
		Timestamp:     time.Now().Unix(),
		Transactions:  transactions,
		PrevBlockHash: prevBlockHash,
		Hash:          []byte{},
		Nonce:         0,
	}

	pow := NewProofOfWork(block) //新建工作量证明
	nonce,hash := pow.Run()  //执行工作量证明(挖矿)
	block.Hash = hash
	block.Nonce = nonce
	return block
}

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

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

	for _,tx := range b.Transactions {
		txHashs = append(txHashs, tx.ID)
	}

	txHash = sha256.Sum256(bytes.Join(txHashs, []byte{}))

	return txHash[:]
}

 proofofwork.go

package core

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

var (
	maxNonce = math.MaxInt64
)

const targetBits  = 16

//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(int64(pow.block.Timestamp)),
			IntToHex(int64(targetBits)),
			IntToHex(int64(nonce)),
		},
		[]byte{},
	)
	return data
}

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("
%x", hash)
		hashInt.SetBytes(hash[:])

		if hashInt.Cmp(pow.target) == -1 {
			break
		}else{
			nonce++
		}
	}
	fmt.Print("

")
	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
}

  

以上是关于交易及记账的主要内容,如果未能解决你的问题,请参考以下文章

区块链及技术栈概述

SQL 时间比较

区块链记账流程

2.3 区块链工作过程

自己动手写区块链之交易中继

自己动手写区块链之交易中继