1 /*------------------------------------------
  2 Code your own blockchain in 200 lines of Go!
  3 --------------------------------------------
  4 https://github.com/mycoralhealth/blockchain-tutorial
  5 
  6 Blockchain (Wikipedia): A distributed database that
  7 maintains a continuously-growing list of records called blocks
  8 secured from tampering and revision.
  9 
 10 $  go get github.com/davecgh/go-spew/spew # to view structs and slices
 11 $  go get github.com/gorilla/mux          # to write web handlers
 12 #  ------------------------------------------
 13 $  go run ~/ma/sofia/blockchain/blockchain.go
 14 #  ------------------------------------------
 15 #  curl http://localhost:2703
 16 #  (1) install Postman API workflow on Chrome
 17 #  (2) modify Postman => POST :: Body :: raw :: JSON :: {"BPM":66}
 18 #  (3) send POST requests to add blocks
 19 #  curl http://localhost:2703
 20 #  ----------------------------------------
 21 #  # advanced topics after this fundamental:
 22 #    - Networking
 23 #    - Consensus: Proof of Work
 24 #    - Consensus: Proof of Stake
 25 #    - P2P
 26 #    - IPFS
 27 #  --------------------------------------*/
 28 
 29 
 30 package main
 31 
 32 import (
 33   "crypto/sha256"
 34   "encoding/hex"
 35   "encoding/json"
 36   "io"
 37   "log"
 38   "net/http"
 39 //"os"
 40   "strconv"
 41   "sync"
 42   "time"
 43   "github.com/davecgh/go-spew/spew"
 44   "github.com/gorilla/mux"
 45 //"github.com/joho/godotenv"
 46 )
 47 
 48 // Block represents each 'item' in the blockchain
 49 // - Index is the position of the data record in the blockchain
 50 // - Timestamp is the time the data is written
 51 // - Data: BPM or beats per minute, is your pulse rate
 52 // - Hash is a SHA256 identifier representing this data record
 53 // - PrevHash is the SHA256 identifier of the previous record
 54 type Block struct {
 55   Index     int
 56   Timestamp string
 57   BPM       int     // Beats_Per_Minute
 58   Hash      string  // to save space and preserve integrity
 59   PrevHash  string  // to build chain
 60 }
 61 
 62 // Blockchain is a series of validated Blocks
 63 var Blockchain []Block  // dynamical array
 64 
 65 // Message takes incoming JSON payload for writing BPM
 66 type Message struct {
 67   BPM int
 68 }
 69 
 70 var mutex = &sync.Mutex{}
 71 
 72 func main() {
 73 
 74 //err := godotenv.Load()
 75 //if err != nil {
 76 //  log.Fatal(err)
 77 //}
 78 
 79   go func() {
 80     t := time.Now()
 81     genesisBlock := Block{}
 82     genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""}
 83     spew.Dump(genesisBlock)
 84     mutex.Lock()
 85     // to isolate the genesis block into its own go routine
 86     // to have a Separation of Concerns design pattern
 87     //    from blockchain logic and web server logic
 88     Blockchain = append(Blockchain, genesisBlock)
 89     mutex.Unlock()
 90   }()
 91   log.Fatal(run())
 92 
 93 }
 94 
 95 
 96 // Web Server from GoLand
 97 func run() error {
 98   mux := makeMuxRouter()
 99 //httpPort := os.Getenv("PORT")
100   const httpPort = "2703" // hard-coded
101   log.Println("HTTP Server Listening on port :", httpPort)
102   s := &http.Server{
103     Addr:           ":" + httpPort,
104     Handler:        mux,
105     ReadTimeout:    10 * time.Second,
106     WriteTimeout:   10 * time.Second,
107     MaxHeaderBytes: 1 << 20,  // 1048576
108   }
109   if err := s.ListenAndServe(); err != nil {
110     return err
111   }
112   return nil
113 }
114 
115 // create handlers for Web Server
116 func makeMuxRouter() http.Handler {
117   muxRouter := mux.NewRouter()
118   // [GET]  $ curl http://localhost:2703
119   muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
120   // [POST] # use Postman to Submit POST
121   muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
122   return muxRouter
123 }
124 
125 // write blockchain when we receive an http request
126 func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
127   bytes, err := json.MarshalIndent(Blockchain, "", "  ")
128   if err != nil {
129     http.Error(w, err.Error(), http.StatusInternalServerError)
130     return
131   }
132   io.WriteString(w, string(bytes))
133 }
134 
135 // takes JSON payload as an input for BPM, using Postman
136 func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
137   w.Header().Set("Content-Type", "application/json")
138   var m Message
139 
140   decoder := json.NewDecoder(r.Body)
141   if err := decoder.Decode(&m); err != nil {
142     respondWithJSON(w, r, http.StatusBadRequest, r.Body)
143     return
144   }
145   defer r.Body.Close()
146 
147   mutex.Lock()
148   newBlock := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
149   mutex.Unlock()
150 
151   if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
152     Blockchain = append(Blockchain, newBlock)
153     spew.Dump(Blockchain)
154   }
155   respondWithJSON(w, r, http.StatusCreated, newBlock)
156 }
157 
158 func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
159   response, err := json.MarshalIndent(payload, "", "  ")
160   if err != nil {
161     w.WriteHeader(http.StatusInternalServerError)
162     w.Write([]byte("HTTP 500: Internal Server Error"))
163     return
164   }
165   w.WriteHeader(code)
166   w.Write(response)
167 }
168 
169 // validate block by checking index, and comparing the previous hash
170 func isBlockValid(newBlock, oldBlock Block) bool {
171   if oldBlock.Index+1 != newBlock.Index {
172     return false
173   }
174   if oldBlock.Hash != newBlock.PrevHash {
175     return false
176   }
177   if calculateHash(newBlock) != newBlock.Hash {
178     return false
179   }
180   return true
181 }
182 
183 // SHA256 hasing
184 func calculateHash(block Block) string {
185   record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash
186   h := sha256.New()
187   h.Write([]byte(record))
188   hashed := h.Sum(nil)
189   return hex.EncodeToString(hashed)
190 }
191 
192 // create a new block using previous block's hash
193 func generateBlock(oldBlock Block, BPM int) Block {
194   var newBlock Block
195   t := time.Now()
196   newBlock.Index = oldBlock.Index + 1
197   newBlock.PrevHash = oldBlock.Hash
198   newBlock.Timestamp = t.String()
199   newBlock.BPM = BPM
200   newBlock.Hash = calculateHash(newBlock)
201   return newBlock
202 }
203 
204