- 取得連結
- X
- 以電子郵件傳送
- 其他應用程式
程式語言:Go
GitHub
簡介:用 go 重新實現
yeasy/blockchain_guide
Code a simple P2P blockchain in Go!
izqui/blockchain
bitcoinbook/bitcoinbook
- Package:
- flag
- net/http
- crypto/sha256
- encoding/json
GitHub
簡介:用 go 重新實現
程式碼
資料夾架構
web.go
blockchain.go
|--web.go |--core |---blockchain.go
web.go
package main import ( "encoding/json" "flag" "fmt" "log" "net/http" "strconv" "./core" "github.com/pkg/errors" ) // Instantiate the Blockchain var blockchain = core.NewBlockchain() func main() { host := flag.String("host", "127.0.0.1", "port to listen on") port := flag.String("port", "6060", "port number") flag.Parse() mux := http.NewServeMux() mux.HandleFunc("/chain/", fullChain) mux.HandleFunc("/transactions/new", newTransactions) mux.HandleFunc("/mine/", mine) mux.HandleFunc("/nodes/register", registerNodes) mux.HandleFunc("/nodes/resolve", consensus) addr := *host + ":" + *port log.Println("Listening on ", addr) err := http.ListenAndServe(addr, mux) if err != nil { log.Fatal("ListenAndServe: ", err) } } func fullChain(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodGet { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(blockchain) } func newTransactions(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodPost { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if err := req.ParseForm(); err != nil { fmt.Fprintf(w, "error:%s", err) return } form := make(map[string]string) for _, k := range []string{"sender", "recipient", "amount"} { v, ok := req.Form[k] if !ok { fmt.Fprintf(w, "Missing %s\n", k) continue } form[k] = v[0] } amount, err := strconv.ParseFloat(form["amount"], 64) if err != nil { fmt.Fprintf(w, "amount:%s is not a number", form["amount"]) return } blockchain.NewTransaction(form["sender"], form["recipient"], amount) fmt.Fprintf(w, "CurrentTransactions: %s", blockchain.CurrentTransactions) } func mine(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodGet { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } blockchain.NewBlock() fmt.Fprintf(w, "Chain: %s", blockchain.Chain) } func registerNodes(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodPost { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if err := req.ParseForm(); err != nil { fmt.Fprintf(w, "error:%s", err) return } addrs, ok := req.Form["addr"] if !ok { fmt.Fprintln(w, "Missing addr") return } for _, addr := range addrs { blockchain.RegisterNode(addr) } for node := range blockchain.Nodes { fmt.Fprintf(w, "%s,\n", node) } } func consensus(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodGet { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } newBlockchain := &core.Blockchain{} for k := range blockchain.Nodes { s := k + "/chain/" log.Printf("update by %s\n", s) err := getJSON(s, newBlockchain) if err != nil { log.Printf("getJSON Error: %s\n", err) continue } changed, err := blockchain.ResolveConflicts(newBlockchain) if err != nil { log.Printf("blockchain.ResolveConflicts Error: %s\n", err) continue } if changed { fmt.Fprintln(w, "updated") } } fmt.Fprintf(w, "chain: %s", blockchain.Chain) } func getJSON(url string, target interface{}) error { r, err := http.Get(url) if err != nil { return errors.Wrapf(err, "http.Get(%s)", url) } defer r.Body.Close() return json.NewDecoder(r.Body).Decode(target) }
blockchain.go
package core import ( "crypto/sha256" "encoding/json" "fmt" "time" "github.com/pkg/errors" ) // Transaction 交易記錄 type Transaction struct { Sender string `json:"sender,omitempty"` Recipient string `json:"recipient,omitempty"` Amount float64 `json:"amount,omitempty"` } // Block 區塊 type Block struct { Index int `json:"index,omitempty"` Timestamp time.Time `json:"timestamp,omitempty"` Transactions []*Transaction `json:"transactions,omitempty"` Proof int `json:"proof,omitempty"` PreviousHash string `json:"previous_hash,omitempty"` } // Blockchain 區塊鍊 type Blockchain struct { Chain []*Block `json:"chain,omitempty"` Nodes map[string]bool `json:"nodes,omitempty"` CurrentTransactions []*Transaction `json:"current_transactions,omitempty"` } // NewBlockchain create Blockchain func NewBlockchain() *Blockchain { bc := &Blockchain{} bc.Nodes = make(map[string]bool) // Create the genesis block bc.NewBlock() return bc } // NewBlock 建立新的區塊 func (bc *Blockchain) NewBlock() error { var previousHash string var err error if len(bc.Chain) == 0 { previousHash = "1" } else { previousHash, err = bc.Chain[len(bc.Chain)-1].hash() if err != nil { return errors.Wrap(err, "bc.Chain[len(bc.Chain)-1].hash") } } b := &Block{ Index: len(bc.Chain) + 1, Timestamp: time.Now(), Transactions: bc.CurrentTransactions, PreviousHash: previousHash, } b.proofWork() bc.CurrentTransactions = []*Transaction{} bc.Chain = append(bc.Chain, b) return nil } //NewTransaction 建立交易 func (bc *Blockchain) NewTransaction(sender, recipient string, amount float64) { t := &Transaction{ Sender: sender, Recipient: recipient, Amount: amount, } bc.CurrentTransactions = append(bc.CurrentTransactions, t) } //RegisterNode 註冊節點,節點用來更新區塊鍊 func (bc *Blockchain) RegisterNode(address string) { node := address bc.Nodes[node] = true } //validChain 驗證區塊鍊 func (bc *Blockchain) validChain() (bool, error) { var previousHash string for i, b := range bc.Chain { ok, err := b.validProof() if err != nil { return false, errors.Wrap(err, "b.validProof") } if !ok { return false, nil } if i == 0 { previousHash, err = b.hash() if err != nil { return false, errors.Wrap(err, "b.hash") } continue } if previousHash != b.PreviousHash { return false, nil } previousHash, err = b.hash() if err != nil { return false, errors.Wrap(err, "b.hash") } } return true, nil } //ResolveConflicts 與其他節點比對,確認目前正確的區塊鍊 func (bc *Blockchain) ResolveConflicts(newbc *Blockchain) (bool, error) { ok, err := newbc.validChain() if err != nil { return false, errors.Wrap(err, "newbc.validChain") } if ok && len(newbc.Chain) > len(bc.Chain) { bc.Chain = make([]*Block, len(newbc.Chain)) copy(bc.Chain, newbc.Chain) return true, nil } return false, nil } func (bc *Blockchain) String() string { return fmt.Sprintf("chain:%+v\nnodes:%+v\ncurrentTransactions:%+v", bc.Chain, bc.Nodes, bc.CurrentTransactions) } // hash sha256 func (b *Block) hash() (string, error) { s, err := json.Marshal(b) if err != nil { return "", errors.Wrap(err, "json.Marshal") } sum := sha256.Sum256(s) return fmt.Sprintf("%x", sum), nil } // 超簡單驗證 func (b *Block) validProof() (bool, error) { h, err := b.hash() if err != nil { return false, errors.Wrap(err, "b.hash") } return h[:1] == "0", nil } //proofWork 挖礦工作 func (b *Block) proofWork() error { b.Proof = 0 for { ok, err := b.validProof() if err != nil { return errors.Wrap(err, "b.validProof") } if ok { break } b.Proof++ } return nil } func (b *Block) String() string { return fmt.Sprintf("{\n#%d %s\ntransactions:%+v\nproof:%d\nprevious Hash:%s\n}", b.Index, b.Timestamp, b.Transactions, b.Proof, b.PreviousHash, ) } func (t *Transaction) String() string { return fmt.Sprintf("{s:%s, r:%s, $%.2f}", t.Sender, t.Recipient, t.Amount) }
參考
My Blockchain in Goyeasy/blockchain_guide
Code a simple P2P blockchain in Go!
izqui/blockchain
bitcoinbook/bitcoinbook
留言
張貼留言