交易及记账
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 }
以上是关于交易及记账的主要内容,如果未能解决你的问题,请参考以下文章