前言 本文為「Building a Blockchain in Golang 」教學影片的學習筆記。
實作 在 blockchain 資料夾裡建立一個 transaction.go 檔。
1 touch  blockchain/transaction.go
建立一個 Transaction 結構體,代表一個交易紀錄。建立一個 TxInput 結構體,代表一個轉入紀錄。建立一個 TxOutput 結構體,代表一個對應轉入紀錄的轉出紀錄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type  Transaction struct  {	ID      []byte  	Inputs  []TxInput 	Outputs []TxOutput } type  TxInput struct  {	ID  []byte  	Out int      	Sig string   } type  TxOutput struct  {	Value  int      	PubKey string   } 
建立一個 CoinbaseTx 方法,用來建立系統的第一筆交易,並且會生成初始代幣。
1 2 3 4 5 6 7 8 9 10 func  CoinbaseTx (to, data string ) 	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 } 
為 Transaction 結構體建立一個 SetID 方法,用來產生一個唯一的交易 ID。
1 2 3 4 5 6 7 8 9 10 func  (tx *Transaction) 	var  encoded bytes.Buffer 	var  hash [32 ]byte  	encoder := gob.NewEncoder(&encoded) 	if  err := encoder.Encode(tx); err != nil  { 		log.Fatalln(err) 	} 	hash = sha256.Sum256(encoded.Bytes()) 	tx.ID = hash[:] } 
為 Transaction 結構體建立一個 IsCoinbase 方法,判斷交易紀錄是否為系統所生成的代幣。
1 2 3 func  (tx *Transaction) bool  {	return  len (tx.Inputs) == 1  && len (tx.Inputs[0 ].ID) == 0  && tx.Inputs[0 ].Out == -1  } 
為 TxInput 結構體建立一個 CanUnlock 方法,判斷用戶是否擁有存取交易中轉入紀錄的權限。
1 2 3 func  (in *TxInput) string ) bool  {	return  in.Sig == data } 
為 TxOutput 結構體建立一個 CanBeUnlocked 方法,判斷用戶是否擁有存取交易中轉出紀錄的權限。
1 2 3 func  (out *TxOutput) string ) bool  {	return  out.PubKey == data } 
重構 block.go 檔中的 Block 結構體。
1 2 3 4 5 6 type  Block struct  {	Hash         []byte  	Transactions []*Transaction 	PrevHash     []byte  	Nonce        int  } 
重構 block.go 檔中的 CreateBlock 方法。
1 2 3 4 5 6 7 8 func  CreateBlock (txs []*Transaction, prevHash []byte ) 	block := &Block{[]byte {}, txs, prevHash, 0 } 	pow := NewProof(block) 	nonce, hash := pow.Run() 	block.Hash = hash[:] 	block.Nonce = nonce 	return  block } 
重構 block.go 檔中的 Genesis 方法。
1 2 3 func  Genesis (coinbase *Transaction) 	return  CreateBlock([]*Transaction{coinbase}, []byte {}) } 
為 Block 結構體建立一個 HashTransactions 方法,使用區塊中的所有交易 ID 來為區塊建立一個唯一的雜湊值。
1 2 3 4 5 6 7 8 9 func  (b *Block) byte  {	var  txHashIDs [][]byte  	var  txHash [32 ]byte  	for  _, tx := range  b.Transactions { 		txHashIDs = append (txHashIDs, tx.ID) 	} 	txHash = sha256.Sum256(bytes.Join(txHashIDs, []byte {})) 	return  txHash[:] } 
重構 proof.go 檔中的 InitData 方法。
1 2 3 4 5 6 7 8 9 func  (pow *ProofOfWork) int ) []byte  {	return  bytes.Join( 		[][]byte { 			pow.Block.PrevHash, 			pow.Block.HashTransactions(), 			ToHex(int64 (nonce)), 			ToHex(int64 (Difficulty)), 		}, []byte {}) } 
新增一些常數。
1 2 3 4 5 const  (	dbPath = "./tmp/blocks"  	dbFile = "./tmp/blocks/MANIFEST"   	genesisData = "First Transaction from Genesis"   ) 
在 blockchain.go 檔中建立一個 DatabaseExists 方法,用來判斷資料庫是否已被建立。
1 2 3 4 5 6 func  DatabaseExists () bool  {	if  _, err := os.Stat(dbFile); os.IsNotExist(err) { 		return  false  	} 	return  true  } 
重構 InitBlockChain 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func  InitBlockChain (address string ) 	var  lastHash []byte  	if  DatabaseExists() { 		fmt.Println("Blockchain already exists" ) 		runtime.Goexit() 	} 	opts := badger.DefaultOptions(dbPath) 	opts.Logger = nil  	db, err := badger.Open(opts) 	if  err != nil  { 		log.Fatalln(err) 	} 	err = db.Update(func (txn *badger.Txn) error  { 		cbTx := CoinbaseTx(address, genesisData) 		genesis := Genesis(cbTx) 		fmt.Println("Genesis proved" ) 		if  err = txn.Set(genesis.Hash, genesis.Serialize()); err != nil  { 			log.Fatalln(err) 		} 		err = txn.Set([]byte ("lh" ), genesis.Hash) 		lastHash = genesis.Hash 		return  err 	}) 	if  err != nil  { 		log.Fatalln(err) 	} 	return  &BlockChain{lastHash, db} } 
在 blockchain.go 檔中建立一個 ContinueBlockChain 方法,用來取得當前的區塊鏈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func  ContinueBlockChain (address string ) 	if  !DatabaseExists() { 		fmt.Println("No existing blockchain found, create one!" ) 	} 	var  lastHash []byte  	opts := badger.DefaultOptions(dbPath) 	opts.Logger = nil  	db, err := badger.Open(opts) 	err = db.View(func (txn *badger.Txn) error  { 		item, err := txn.Get([]byte ("lh" )) 		if  err != nil  { 			log.Fatalln(err) 		} 		lastHash, err = item.ValueCopy(nil ) 		return  err 	}) 	if  err != nil  { 		log.Fatalln(err) 	} 	return  &BlockChain{lastHash, db} } 
為 BlockChain 結構體建立一個 FindUnspentTransactions 方法,找出未花費的交易。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func  (chain *BlockChain) string ) []Transaction {	var  unspentTxs []Transaction 	spentTxOutputs := 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  spentTxOutputs[txID] != nil  { 					for  _, spentOut := range  spentTxOutputs[txID] { 						if  spentOut == outIdx { 							continue  Outputs 						} 					} 				} 				if  out.CanBeUnlocked(address) { 					unspentTxs = append (unspentTxs, *tx) 				} 			} 			if  !tx.IsCoinbase() { 				for  _, in := range  tx.Inputs { 					if  in.CanUnlock(address) { 						inTxID := hex.EncodeToString(in.ID) 						spentTxOutputs[inTxID] = append (spentTxOutputs[inTxID], in.Out) 					} 				} 			} 		} 		if  len (block.PrevHash) == 0  { 			break  		} 	} 	return  unspentTxs } 
為 BlockChain 結構體建立一個 FindUnspentTxOutputs 方法,找出用戶未花費的輸出紀錄。
1 2 3 4 5 6 7 8 9 10 11 12 func  (chain *BlockChain) string ) []TxOutput {	var  unspentTxOutputs []TxOutput 	unspentTransactions := chain.FindUnspentTransactions(address) 	for  _, tx := range  unspentTransactions { 		for  _, out := range  tx.Outputs { 			if  out.CanBeUnlocked(address) { 				unspentTxOutputs = append (unspentTxOutputs, out) 			} 		} 	} 	return  unspentTxOutputs } 
為 BlockChain 結構體建立一個 FindSpendableOutputs 方法,找出用戶可花費的交易餘額。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func  (chain *BlockChain) string , amount int ) (int , map [string ][]int ) {	unspentOutputs := 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 				unspentOutputs[txID] = append (unspentOutputs[txID], outIdx) 				if  accumulated >= amount { 					break  Work 				} 			} 		} 	} 	return  accumulated, unspentOutputs } 
重構 BlockChain 結構體的 AddBlock 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func  (chain *BlockChain) 	var  lastHash []byte  	err := chain.Database.View(func (txn *badger.Txn) error  { 		item, err := txn.Get([]byte ("lh" )) 		if  err != nil  { 			log.Fatalln(err) 		} 		lastHash, err = item.ValueCopy(nil ) 		return  err 	}) 	if  err != nil  { 		log.Fatalln(err) 	} 	newBlock := CreateBlock(transactions, lastHash) 	err = chain.Database.Update(func (txn *badger.Txn) error  { 		if  err := txn.Set(newBlock.Hash, newBlock.Serialize()); err != nil  { 			log.Fatalln(err) 		} 		err = txn.Set([]byte ("lh" ), newBlock.Hash) 		chain.LastHash = newBlock.Hash 		return  err 	}) 	if  err != nil  { 		log.Fatalln(err) 	} } 
在 transaction.go 檔建立一個 NewTransaction 方法,用來為用戶建立一筆新的交易紀錄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func  NewTransaction (from, to string , amount int , chain *BlockChain) 	var  inputs []TxInput 	var  outputs []TxOutput 	acc, validOutputs := chain.FindSpendableOutputs(from, amount) 	if  acc < amount { 		log.Fatalln("Not enough funds" ) 	} 	for  txIdx, outs := range  validOutputs { 		txID, err := hex.DecodeString(txIdx) 		if  err != nil  { 			log.Fatalln(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 } 
重構有關 CommandLine 結構體的相關方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 type  CommandLine struct {}func  (cli *CommandLine) 	fmt.Println("Usage:" ) 	fmt.Println("  get-balance -address ADDRESS - gets the balance for the address" ) 	fmt.Println("  create-blockchain -address ADDRESS - creates a blockchain and sends genesis reward to address" ) 	fmt.Println("  print-chain - prints the blocks in the chain" ) 	fmt.Println("  send -from FROM -to TO -amount AMOUNT - sends amount of coins" ) } func  (cli *CommandLine) 	if  len (os.Args) < 2  { 		cli.printUsage() 		runtime.Goexit() 	} } func  (cli *CommandLine) 	chain := blockchain.ContinueBlockChain("" ) 	defer  chain.Database.Close() 	iter := chain.Iterator() 	for  { 		block := iter.Next() 		fmt.Printf("Previous 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) string ) {	chain := blockchain.InitBlockChain(address) 	chain.Database.Close() 	fmt.Println("Finished!" ) } func  (cli *CommandLine) string ) {	chain := blockchain.ContinueBlockChain(address) 	defer  chain.Database.Close() 	balance := 0  	unspentTxOutputs := chain.FindUnspentTxOutputs(address) 	for  _, out := range  unspentTxOutputs { 		balance += out.Value 	} 	fmt.Printf("Balance of %s: %d\n" , address, balance) } func  (cli *CommandLine) 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) 	cli.validateArgs() 	getBalanceCmd := flag.NewFlagSet("get-balance" , flag.ExitOnError) 	createBlockchainCmd := flag.NewFlagSet("create-blockchain" , flag.ExitOnError) 	sendCmd := flag.NewFlagSet("send" , flag.ExitOnError) 	printChainCmd := flag.NewFlagSet("print-chain" , 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  "get-balance" : 		err := getBalanceCmd.Parse(os.Args[2 :]) 		if  err != nil  { 			log.Panic(err) 		} 	case  "create-blockchain" : 		err := createBlockchainCmd.Parse(os.Args[2 :]) 		if  err != nil  { 			log.Panic(err) 		} 	case  "print-chain" : 		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) 	} } 
修改 main.go 檔,處理命令列介面的執行。
1 2 3 4 5 func  main () 	defer  os.Exit(0 ) 	cli := CommandLine{} 	cli.run() } 
執行 create-blockchain 指令,創建一個新的區塊鏈。
1 go run main.go create-blockchain -address "Memo Chou"  
結果顯示如下。
1 2 3 00065a1879cf312d59dd2e8dd5e82d6db266b5a3beb0ed43a98067f8db6a6688 Genesis proved Finished! 
執行 print-chain 指令,將區塊鏈印出。
1 go run main.go print-chain 
結果顯示如下。
1 2 3 Previous Hash:  Hash: 00065a1879cf312d59dd2e8dd5e82d6db266b5a3beb0ed43a98067f8db6a6688 Pow: true  
執行 get-balance 指令,取得 Memo Chou 用戶的餘額。
1 go run main.go get-balance -address "Memo Chou"  
結果顯示如下。
執行 send 指令,將 Memo Chou 用戶的 60 個代幣轉給 Tensor 用戶。
1 go run main.go send -from "Memo Chou"  -to "Tensor"  -amount 60 
結果顯示如下。
1 2 000717be0cf982076997a54cbd6988055c097065e03e5b0dc32ad03d72257fec Success! 
執行 print-chain 指令,將區塊鏈印出。
1 go run main.go print-chain 
結果顯示如下,多了一個新的區塊。
1 2 3 4 5 6 7 Previous Hash: 00065a1879cf312d59dd2e8dd5e82d6db266b5a3beb0ed43a98067f8db6a6688 Hash: 000717be0cf982076997a54cbd6988055c097065e03e5b0dc32ad03d72257fec Pow: true  Previous Hash:  Hash: 00065a1879cf312d59dd2e8dd5e82d6db266b5a3beb0ed43a98067f8db6a6688 Pow: true  
執行 get-balance 指令,取得 Tensor 用戶的餘額。
1 go run main.go get-balance -address "Tensor"  
結果顯示如下。
執行 get-balance 指令,取得 Memo Chou 用戶的餘額。
1 go run main.go get-balance -address "Memo Chou"  
結果顯示如下。
程式碼 
參考資料