[Go] Blockchain 區塊鏈實作

程式語言:Go
Package:
flag
net/http
crypto/sha256
encoding/json
[Python] 區塊鏈實作
GitHub

簡介:用 go 重新實現

程式碼

資料夾架構
|--web.go
|--core
     |---blockchain.go

web.go
  1. package main
  2.  
  3. import (
  4. "encoding/json"
  5. "flag"
  6. "fmt"
  7. "log"
  8. "net/http"
  9. "strconv"
  10.  
  11. "./core"
  12. "github.com/pkg/errors"
  13. )
  14.  
  15. // Instantiate the Blockchain
  16. var blockchain = core.NewBlockchain()
  17.  
  18. func main() {
  19. host := flag.String("host", "127.0.0.1", "port to listen on")
  20. port := flag.String("port", "6060", "port number")
  21. flag.Parse()
  22.  
  23. mux := http.NewServeMux()
  24.  
  25. mux.HandleFunc("/chain/", fullChain)
  26. mux.HandleFunc("/transactions/new", newTransactions)
  27. mux.HandleFunc("/mine/", mine)
  28. mux.HandleFunc("/nodes/register", registerNodes)
  29. mux.HandleFunc("/nodes/resolve", consensus)
  30.  
  31. addr := *host + ":" + *port
  32. log.Println("Listening on ", addr)
  33. err := http.ListenAndServe(addr, mux)
  34. if err != nil {
  35. log.Fatal("ListenAndServe: ", err)
  36. }
  37. }
  38.  
  39. func fullChain(w http.ResponseWriter, req *http.Request) {
  40. if req.Method != http.MethodGet {
  41. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  42. return
  43. }
  44.  
  45. w.Header().Set("Content-Type", "application/json")
  46. json.NewEncoder(w).Encode(blockchain)
  47. }
  48.  
  49. func newTransactions(w http.ResponseWriter, req *http.Request) {
  50. if req.Method != http.MethodPost {
  51. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  52. return
  53. }
  54.  
  55. if err := req.ParseForm(); err != nil {
  56. fmt.Fprintf(w, "error:%s", err)
  57. return
  58. }
  59.  
  60. form := make(map[string]string)
  61. for _, k := range []string{"sender", "recipient", "amount"} {
  62. v, ok := req.Form[k]
  63. if !ok {
  64. fmt.Fprintf(w, "Missing %s\n", k)
  65. continue
  66. }
  67. form[k] = v[0]
  68. }
  69.  
  70. amount, err := strconv.ParseFloat(form["amount"], 64)
  71. if err != nil {
  72. fmt.Fprintf(w, "amount:%s is not a number", form["amount"])
  73. return
  74. }
  75.  
  76. blockchain.NewTransaction(form["sender"], form["recipient"], amount)
  77. fmt.Fprintf(w, "CurrentTransactions: %s", blockchain.CurrentTransactions)
  78. }
  79.  
  80. func mine(w http.ResponseWriter, req *http.Request) {
  81. if req.Method != http.MethodGet {
  82. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  83. return
  84. }
  85.  
  86. blockchain.NewBlock()
  87. fmt.Fprintf(w, "Chain: %s", blockchain.Chain)
  88. }
  89.  
  90. func registerNodes(w http.ResponseWriter, req *http.Request) {
  91. if req.Method != http.MethodPost {
  92. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  93. return
  94. }
  95.  
  96. if err := req.ParseForm(); err != nil {
  97. fmt.Fprintf(w, "error:%s", err)
  98. return
  99. }
  100.  
  101. addrs, ok := req.Form["addr"]
  102. if !ok {
  103. fmt.Fprintln(w, "Missing addr")
  104. return
  105. }
  106. for _, addr := range addrs {
  107. blockchain.RegisterNode(addr)
  108. }
  109.  
  110. for node := range blockchain.Nodes {
  111. fmt.Fprintf(w, "%s,\n", node)
  112. }
  113. }
  114.  
  115. func consensus(w http.ResponseWriter, req *http.Request) {
  116. if req.Method != http.MethodGet {
  117. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  118. return
  119. }
  120.  
  121. newBlockchain := &core.Blockchain{}
  122. for k := range blockchain.Nodes {
  123. s := k + "/chain/"
  124. log.Printf("update by %s\n", s)
  125. err := getJSON(s, newBlockchain)
  126. if err != nil {
  127. log.Printf("getJSON Error: %s\n", err)
  128. continue
  129. }
  130.  
  131. changed, err := blockchain.ResolveConflicts(newBlockchain)
  132. if err != nil {
  133. log.Printf("blockchain.ResolveConflicts Error: %s\n", err)
  134. continue
  135. }
  136.  
  137. if changed {
  138. fmt.Fprintln(w, "updated")
  139. }
  140.  
  141. }
  142.  
  143. fmt.Fprintf(w, "chain: %s", blockchain.Chain)
  144. }
  145.  
  146. func getJSON(url string, target interface{}) error {
  147. r, err := http.Get(url)
  148. if err != nil {
  149. return errors.Wrapf(err, "http.Get(%s)", url)
  150. }
  151. defer r.Body.Close()
  152.  
  153. return json.NewDecoder(r.Body).Decode(target)
  154. }

blockchain.go
  1. package core
  2.  
  3. import (
  4. "crypto/sha256"
  5. "encoding/json"
  6. "fmt"
  7. "time"
  8.  
  9. "github.com/pkg/errors"
  10. )
  11.  
  12. // Transaction 交易記錄
  13. type Transaction struct {
  14. Sender string `json:"sender,omitempty"`
  15. Recipient string `json:"recipient,omitempty"`
  16. Amount float64 `json:"amount,omitempty"`
  17. }
  18.  
  19. // Block 區塊
  20. type Block struct {
  21. Index int `json:"index,omitempty"`
  22. Timestamp time.Time `json:"timestamp,omitempty"`
  23. Transactions []*Transaction `json:"transactions,omitempty"`
  24. Proof int `json:"proof,omitempty"`
  25. PreviousHash string `json:"previous_hash,omitempty"`
  26. }
  27.  
  28. // Blockchain 區塊鍊
  29. type Blockchain struct {
  30. Chain []*Block `json:"chain,omitempty"`
  31. Nodes map[string]bool `json:"nodes,omitempty"`
  32. CurrentTransactions []*Transaction `json:"current_transactions,omitempty"`
  33. }
  34.  
  35. // NewBlockchain create Blockchain
  36. func NewBlockchain() *Blockchain {
  37. bc := &Blockchain{}
  38.  
  39. bc.Nodes = make(map[string]bool)
  40.  
  41. // Create the genesis block
  42. bc.NewBlock()
  43.  
  44. return bc
  45. }
  46.  
  47. // NewBlock 建立新的區塊
  48. func (bc *Blockchain) NewBlock() error {
  49. var previousHash string
  50. var err error
  51.  
  52. if len(bc.Chain) == 0 {
  53. previousHash = "1"
  54. } else {
  55. previousHash, err = bc.Chain[len(bc.Chain)-1].hash()
  56. if err != nil {
  57. return errors.Wrap(err, "bc.Chain[len(bc.Chain)-1].hash")
  58. }
  59. }
  60.  
  61. b := &Block{
  62. Index: len(bc.Chain) + 1,
  63. Timestamp: time.Now(),
  64. Transactions: bc.CurrentTransactions,
  65. PreviousHash: previousHash,
  66. }
  67.  
  68. b.proofWork()
  69.  
  70. bc.CurrentTransactions = []*Transaction{}
  71.  
  72. bc.Chain = append(bc.Chain, b)
  73.  
  74. return nil
  75. }
  76.  
  77. //NewTransaction 建立交易
  78. func (bc *Blockchain) NewTransaction(sender, recipient string, amount float64) {
  79. t := &Transaction{
  80. Sender: sender,
  81. Recipient: recipient,
  82. Amount: amount,
  83. }
  84.  
  85. bc.CurrentTransactions = append(bc.CurrentTransactions, t)
  86. }
  87.  
  88. //RegisterNode 註冊節點,節點用來更新區塊鍊
  89. func (bc *Blockchain) RegisterNode(address string) {
  90. node := address
  91. bc.Nodes[node] = true
  92. }
  93.  
  94. //validChain 驗證區塊鍊
  95. func (bc *Blockchain) validChain() (bool, error) {
  96. var previousHash string
  97.  
  98. for i, b := range bc.Chain {
  99. ok, err := b.validProof()
  100. if err != nil {
  101. return false, errors.Wrap(err, "b.validProof")
  102. }
  103. if !ok {
  104. return false, nil
  105. }
  106.  
  107. if i == 0 {
  108. previousHash, err = b.hash()
  109. if err != nil {
  110. return false, errors.Wrap(err, "b.hash")
  111. }
  112. continue
  113. }
  114.  
  115. if previousHash != b.PreviousHash {
  116. return false, nil
  117. }
  118.  
  119. previousHash, err = b.hash()
  120. if err != nil {
  121. return false, errors.Wrap(err, "b.hash")
  122. }
  123. }
  124.  
  125. return true, nil
  126. }
  127.  
  128. //ResolveConflicts 與其他節點比對,確認目前正確的區塊鍊
  129. func (bc *Blockchain) ResolveConflicts(newbc *Blockchain) (bool, error) {
  130. ok, err := newbc.validChain()
  131. if err != nil {
  132. return false, errors.Wrap(err, "newbc.validChain")
  133. }
  134. if ok && len(newbc.Chain) > len(bc.Chain) {
  135. bc.Chain = make([]*Block, len(newbc.Chain))
  136. copy(bc.Chain, newbc.Chain)
  137.  
  138. return true, nil
  139. }
  140.  
  141. return false, nil
  142. }
  143.  
  144. func (bc *Blockchain) String() string {
  145. return fmt.Sprintf("chain:%+v\nnodes:%+v\ncurrentTransactions:%+v", bc.Chain, bc.Nodes, bc.CurrentTransactions)
  146. }
  147.  
  148. // hash sha256
  149. func (b *Block) hash() (string, error) {
  150. s, err := json.Marshal(b)
  151. if err != nil {
  152. return "", errors.Wrap(err, "json.Marshal")
  153. }
  154. sum := sha256.Sum256(s)
  155.  
  156. return fmt.Sprintf("%x", sum), nil
  157. }
  158.  
  159. // 超簡單驗證
  160. func (b *Block) validProof() (bool, error) {
  161. h, err := b.hash()
  162. if err != nil {
  163. return false, errors.Wrap(err, "b.hash")
  164. }
  165.  
  166. return h[:1] == "0", nil
  167. }
  168.  
  169. //proofWork 挖礦工作
  170. func (b *Block) proofWork() error {
  171. b.Proof = 0
  172. for {
  173. ok, err := b.validProof()
  174. if err != nil {
  175. return errors.Wrap(err, "b.validProof")
  176. }
  177. if ok {
  178. break
  179. }
  180. b.Proof++
  181. }
  182. return nil
  183. }
  184.  
  185. func (b *Block) String() string {
  186. return fmt.Sprintf("{\n#%d %s\ntransactions:%+v\nproof:%d\nprevious Hash:%s\n}", b.Index,
  187. b.Timestamp,
  188. b.Transactions,
  189. b.Proof,
  190. b.PreviousHash,
  191. )
  192. }
  193.  
  194. func (t *Transaction) String() string {
  195. return fmt.Sprintf("{s:%s, r:%s, $%.2f}", t.Sender, t.Recipient, t.Amount)
  196. }

參考

My Blockchain in Go
yeasy/blockchain_guide
Code a simple P2P blockchain in Go!
izqui/blockchain
bitcoinbook/bitcoinbook

留言