1. 程式人生 > >200行go語言程式碼自建一個區塊鏈 體驗挖礦樂趣

200行go語言程式碼自建一個區塊鏈 體驗挖礦樂趣

談談區塊鏈:
挖礦的目的:通過挖礦證明算力,防止他人作弊,自己又能獲得獎勵【給自己加錢】。
挖礦的過程:將網上別的合法且最新的使用者交易同步過來,加入到區塊,然後加隨機數雜湊後 與系統給出的值去比較。若符合條件則表示挖礦成功,挖礦成功了還不行,要趕緊告訴別人,我挖成功了,別讓其他人搶風頭。

挖礦更通俗的表現形式:我這裡有編號為 1~1000000000000000個寶箱,其中某些寶箱裡面有金子,獲得這些金子的方法就是一個個去用鑰匙開啟,而這個寶箱鑰匙的鍛造方法就是用 其他人提供的金屬來打造。

挖礦需要實時聯網嗎?
答案:否,只需要把最後的一個區塊的雜湊放進本區塊的PrevHash即可,一般10分鐘就更新最後一個區塊了,所以還是得10分鐘上鍊同步一下,至於別人提交的交易的賬單,可放可不放到區塊上,當然交易的賬單裡面有手續費,若放到區塊可以將裡面的手續費劃到自己賬上, 到時候世界上的礦都挖光了,礦場節點就是通過幫別人打包賺點手續費而已,但一般礦場都幾乎實時線上同步其他節點收到的交易過來。

區塊鏈以最長鏈為主鏈:
若有人搗亂,發一些錯誤的區塊到其他節點上,其他節點不是傻子,肯定不接受,因此無法加入到鏈上。
除非,有一個算力很強的人想搗亂,他能在1秒鐘算出正確的區塊,併發布到其他節點,會出現什麼情況?這個人由於算力太強了,他在算出區塊的時候,不接受任何人的交易記錄就生成區塊了,那麼對於使用者來說,使用者的交易久久不能進鏈,自然而然的放棄使用區塊鏈了,而別的礦工看到如此強勁的對手也紛紛離場。

若同時算出區塊,並提交到鏈上的情況:
有兩個人同時算出答案,並告知其他小夥伴,我已經算出了答案。他們兩個人都告訴了別人。那麼他們誰才是勝利者?
這種情況則以下一個人算出的答案來決定上一次的勝利者。
由於下一個人是最新算出的,所以任何人無異議。

關於浪費
挖礦花掉的電力差不多150多個國家電力的總和,要是把挖礦改成算π,那樣多少都有點貢獻。
呃… 挖礦真的浪費了好多電力,於是乎有人發明一種不需要挖礦的方法,這種方式叫proof of shake,股權證明,大家有興趣可以去研究一下。

比特幣是泡沫嗎?
不是,它是人類共同認可的一種虛擬貨幣,只不過它的價格決定於人民的認可度,而且沒人能憑空造出比特幣,因為比特幣有很強的防偽,若有人偽造,一下子就能瞄出來了,在賬本上別人賬戶上有多少錢都能看到,但是花不了,若要花自己的比特幣之前,需要用一把超級安全的防偽造印章去蓋一個章,就可以使用授權的錢了。
不妨我們改一種說法,美元是泡沫嗎?可它只是一張紙啊,只不過它的價格決定於人民的認可度還有國家的調控,而且國家能憑空造出美元,因為美元由一個美國控制,別人不能通過銀行看別人的賬戶,使用前只需要在銀行輸入密碼取錢出來就可以與人交換貨物或者服務了。

關於空氣幣
有某些黑惡原子份子製作了他們的區塊鏈雖說是真的,但是缺乏人民的認可度,因此不值錢,那麼他們通過什麼方式賺錢?還是挖礦(挖一大部分幣在自己手裡先)----然後大肆宣揚什麼什麼新技術,大家趕緊過來買啊買啊買,哄擡市價,然後將手上的幣賣出,只不過開始挖礦易如反掌,後期人多了,挖不了了,就產生了價值,但挖的人不多,幣還是容易挖,容易挖就不值錢了,這種就叫做空氣幣。

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/gorilla/mux"
)

const difficulty = 1 // 真正的比特幣每兩個星期就更新一次難度,其實是共識更新。

// 區塊鏈的 塊結構
type Block struct {
	Index      int
	Timestamp  string
	BPM        int
	Hash       string
	PrevHash   string
	Difficulty int
	Nonce      string
}

// 區塊鏈的鏈(這裡是塊的一個數組,真正的是用kv資料庫儲存的呢!)
var Blockchain []Block

// 心跳值
type Message struct {
	BPM int
}

// 一把鎖
var mutex = &sync.Mutex{}

func main() {
	// 創世塊,啥都沒
	go func() {
		t := time.Now()
		genesisBlock := Block{}
		genesisBlock = Block{
			0,
			t.String(),
			0,
			calculateHash(genesisBlock),
			"",
			difficulty,
			"",
		}

		spew.Dump(genesisBlock) // 顯示創世塊

		// 將創世塊加入鏈
		mutex.Lock()
		Blockchain = append(Blockchain, genesisBlock)
		mutex.Unlock()
	}()

	// 開啟HTTP伺服器
	log.Fatal(run())
}

// 網頁伺服器
func run() error {
	mux := makeMuxRouter()
	httpPort := "8080"
	log.Println("HTTP Server Listening on port :", httpPort)
	s := &http.Server{
		Addr:           ":" + httpPort,
		Handler:        mux,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	if err := s.ListenAndServe(); err != nil {
		return err
	}

	return nil
}

// 建立路由
func makeMuxRouter() http.Handler {
	muxRouter := mux.NewRouter()
	muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
	muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
	return muxRouter
}

// http 方法,得到當前所有區塊
func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
	bytes, err := json.MarshalIndent(Blockchain, "", "  ")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	io.WriteString(w, string(bytes))
}

// 上傳心跳值,並且在挖出礦後將此心跳值加入區塊。
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	var m Message

	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&m); err != nil {
		respondWithJSON(w, r, http.StatusBadRequest, r.Body)
		return
	}
	defer r.Body.Close()

	//ensure atomicity when creating new block
	mutex.Lock()
	newBlock := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
	mutex.Unlock()

	if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
		Blockchain = append(Blockchain, newBlock)
		spew.Dump(Blockchain)
	}

	respondWithJSON(w, r, http.StatusCreated, newBlock)
}

// 顯示出json資料
func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
	w.Header().Set("Content-Type", "application/json")
	response, err := json.MarshalIndent(payload, "", "  ")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("HTTP 500: Internal Server Error"))
		return
	}
	w.WriteHeader(code)
	w.Write(response)
}

// 驗證區塊是否正確
func isBlockValid(newBlock, oldBlock Block) bool {
	if oldBlock.Index+1 != newBlock.Index {
		return false
	}

	if oldBlock.Hash != newBlock.PrevHash {
		return false
	}

	if calculateHash(newBlock) != newBlock.Hash {
		return false
	}

	return true
}

// 計算區塊雜湊
func calculateHash(block Block) string {
	record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash + block.Nonce
	h := sha256.New()
	h.Write([]byte(record))
	hashed := h.Sum(nil)
	return hex.EncodeToString(hashed)
}

// 挖礦
func generateBlock(oldBlock Block, BPM int) Block {
	var newBlock Block

	t := time.Now()

	newBlock.Index = oldBlock.Index + 1
	newBlock.Timestamp = t.String()
	newBlock.BPM = BPM
	newBlock.PrevHash = oldBlock.Hash
	newBlock.Difficulty = difficulty

	for i := 0; ; i++ {
		hex := fmt.Sprintf("%x", i)
		newBlock.Nonce = hex
		if !isHashValid(calculateHash(newBlock), newBlock.Difficulty) {
			fmt.Println(calculateHash(newBlock), " do more work!")
			time.Sleep(time.Second) // 此處延時為了模擬真實
			continue
		} else {
			fmt.Println(calculateHash(newBlock), " work done!")
			newBlock.Hash = calculateHash(newBlock)
			break
		}

	}
	return newBlock
}

// 區塊驗證
func isHashValid(hash string, difficulty int) bool {
	prefix := strings.Repeat("0", difficulty)
	return strings.HasPrefix(hash, prefix)
}

以上程式碼的原理:
該區塊鏈的區塊用於儲存的【人類心跳】,好處是別人想存必須先通過挖礦。
實際的區塊鏈將【人類心跳】換成 交易記錄。
本區塊鏈由於是單節點,因此沒有 幣的地址。
只闡明挖礦原理。

程式碼詳細解析:
https://medium.com/@mycoralhealth/code-your-own-blockchain-mining-algorithm-in-go-82c6a71aba1f

請在啟動服務後,安裝postman,向http://127.0.0.1:8080, 提交原生資料{“BPM”: 50}

在這裡插入圖片描述