├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── example.env ├── main.go ├── networking ├── README.md ├── example.env └── main.go ├── p2p ├── README.md └── main.go ├── proof-stake ├── README.md ├── example.env └── main.go └── proof-work ├── README.md ├── example.env └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.10" 5 | 6 | after_success: 7 | - go build 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 1999 nosequeldeebee 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code your own blockchain in less than 200 lines of Go! 2 | 3 | ### Tutorial 4 | 5 | [Read](https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc) our blog post first to see a walkthrough of the code. 6 | 7 | #### Check out our follow-up tutorials: 8 | - [Networking](https://github.com/mycoralhealth/blockchain-tutorial/tree/master/networking) 9 | - [Proof of Work](https://github.com/mycoralhealth/blockchain-tutorial/tree/master/proof-work) 10 | - [Proof of Stake](https://github.com/mycoralhealth/blockchain-tutorial/tree/master/proof-stake) 11 | - [IPFS](https://medium.com/@mycoralhealth/learn-to-securely-share-files-on-the-blockchain-with-ipfs-219ee47df54c) 12 | - [P2P](https://medium.com/coinmonks/code-a-simple-p2p-blockchain-in-go-46662601f417) 13 | - [Advanced Concepts for Beginners](https://medium.com/@mycoralhealth/advanced-blockchain-concepts-for-beginners-32887202afad) 14 | - [Start your own Hyperledger blockchain the Easy Way!](https://medium.com/@mycoralhealth/start-your-own-hyperledger-blockchain-the-easy-way-5758cb4ed2d1) 15 | - [Build a DApp on Hyperledger the Easy Way!](https://medium.com/@mycoralhealth/build-a-dapp-on-hyperledger-the-easy-way-178c39e503fa) 16 | - [Build your own Blockchain Twitter recorder](https://github.com/mycoralhealth/twitter-blockchain) 17 | 18 | ### Deployment steps: 19 | - `git clone https://github.com/mycoralhealth/blockchain-tutorial.git` 20 | - navigate to this directory and rename the example file `mv example.env .env` 21 | - `go run main.go` 22 | - open a web browser and visit `http://localhost:8080/` 23 | - to write new blocks, send a `POST` request (I like to use [Postman](https://www.getpostman.com/apps)) to `http://localhost:8080/` with a JSON payload with `BPM` as the key and an integer as the value. For example: 24 | ``` 25 | {"BPM":50} 26 | ``` 27 | - Send as many requests as you like and refresh your browser to see your blocks grow! Use your actual heart rate (Beats Per Minute) to track it over time. 28 | 29 | ### Screenshot 30 | 31 | ![screen](https://user-images.githubusercontent.com/15616604/35492333-2829f690-0461-11e8-8c1f-8a0258d370e8.png) 32 | 33 | 34 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | "io" 8 | "log" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | "sync" 13 | "time" 14 | 15 | "github.com/davecgh/go-spew/spew" 16 | "github.com/gorilla/mux" 17 | "github.com/joho/godotenv" 18 | ) 19 | 20 | // Block represents each 'item' in the blockchain 21 | type Block struct { 22 | Index int 23 | Timestamp string 24 | BPM int 25 | Hash string 26 | PrevHash string 27 | } 28 | 29 | // Blockchain is a series of validated Blocks 30 | var Blockchain []Block 31 | 32 | // Message takes incoming JSON payload for writing heart rate 33 | type Message struct { 34 | BPM int 35 | } 36 | 37 | var mutex = &sync.Mutex{} 38 | 39 | func main() { 40 | err := godotenv.Load() 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | go func() { 46 | t := time.Now() 47 | genesisBlock := Block{} 48 | genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""} 49 | spew.Dump(genesisBlock) 50 | 51 | mutex.Lock() 52 | Blockchain = append(Blockchain, genesisBlock) 53 | mutex.Unlock() 54 | }() 55 | log.Fatal(run()) 56 | 57 | } 58 | 59 | // web server 60 | func run() error { 61 | mux := makeMuxRouter() 62 | httpPort := os.Getenv("PORT") 63 | log.Println("HTTP Server Listening on port :", httpPort) 64 | s := &http.Server{ 65 | Addr: ":" + httpPort, 66 | Handler: mux, 67 | ReadTimeout: 10 * time.Second, 68 | WriteTimeout: 10 * time.Second, 69 | MaxHeaderBytes: 1 << 20, 70 | } 71 | 72 | if err := s.ListenAndServe(); err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | 79 | // create handlers 80 | func makeMuxRouter() http.Handler { 81 | muxRouter := mux.NewRouter() 82 | muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET") 83 | muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST") 84 | return muxRouter 85 | } 86 | 87 | // write blockchain when we receive an http request 88 | func handleGetBlockchain(w http.ResponseWriter, r *http.Request) { 89 | bytes, err := json.MarshalIndent(Blockchain, "", " ") 90 | if err != nil { 91 | http.Error(w, err.Error(), http.StatusInternalServerError) 92 | return 93 | } 94 | io.WriteString(w, string(bytes)) 95 | } 96 | 97 | // takes JSON payload as an input for heart rate (BPM) 98 | func handleWriteBlock(w http.ResponseWriter, r *http.Request) { 99 | w.Header().Set("Content-Type", "application/json") 100 | var msg Message 101 | 102 | decoder := json.NewDecoder(r.Body) 103 | if err := decoder.Decode(&msg); err != nil { 104 | respondWithJSON(w, r, http.StatusBadRequest, r.Body) 105 | return 106 | } 107 | defer r.Body.Close() 108 | 109 | mutex.Lock() 110 | prevBlock := Blockchain[len(Blockchain)-1] 111 | newBlock := generateBlock(prevBlock, msg.BPM) 112 | 113 | if isBlockValid(newBlock, prevBlock) { 114 | Blockchain = append(Blockchain, newBlock) 115 | spew.Dump(Blockchain) 116 | } 117 | mutex.Unlock() 118 | 119 | respondWithJSON(w, r, http.StatusCreated, newBlock) 120 | 121 | } 122 | 123 | func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) { 124 | response, err := json.MarshalIndent(payload, "", " ") 125 | if err != nil { 126 | w.WriteHeader(http.StatusInternalServerError) 127 | w.Write([]byte("HTTP 500: Internal Server Error")) 128 | return 129 | } 130 | w.WriteHeader(code) 131 | w.Write(response) 132 | } 133 | 134 | // make sure block is valid by checking index, and comparing the hash of the previous block 135 | func isBlockValid(newBlock, oldBlock Block) bool { 136 | if oldBlock.Index+1 != newBlock.Index { 137 | return false 138 | } 139 | 140 | if oldBlock.Hash != newBlock.PrevHash { 141 | return false 142 | } 143 | 144 | if calculateHash(newBlock) != newBlock.Hash { 145 | return false 146 | } 147 | 148 | return true 149 | } 150 | 151 | // SHA256 hasing 152 | func calculateHash(block Block) string { 153 | record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash 154 | h := sha256.New() 155 | h.Write([]byte(record)) 156 | hashed := h.Sum(nil) 157 | return hex.EncodeToString(hashed) 158 | } 159 | 160 | // create a new block using previous block's hash 161 | func generateBlock(oldBlock Block, BPM int) Block { 162 | 163 | var newBlock Block 164 | 165 | t := time.Now() 166 | 167 | newBlock.Index = oldBlock.Index + 1 168 | newBlock.Timestamp = t.String() 169 | newBlock.BPM = BPM 170 | newBlock.PrevHash = oldBlock.Hash 171 | newBlock.Hash = calculateHash(newBlock) 172 | 173 | return newBlock 174 | } 175 | -------------------------------------------------------------------------------- /networking/README.md: -------------------------------------------------------------------------------- 1 | # Part 2: Networking 2 | ### Code your own blockchain in less than 200 lines of Go! 3 | 4 | ### Tutorial 5 | 6 | [Read Part 1 first!](https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc) 7 | 8 | [Read the Part 2 Tutorial](https://medium.com/@mycoralhealth/part-2-networking-code-your-own-blockchain-in-less-than-200-lines-of-go-17fe1dad46e1) to see a walkthrough of the code. 9 | 10 | ### Deployment steps: 11 | - clone this repo 12 | - navigate to this directory and rename the example file `mv example.env .env` 13 | - `go run main.go` 14 | - open a new terminal window and `nc localhost 9000` 15 | - input a BPM and watch the blockchain in the first terminal window update 16 | - wait a few seconds to see the second terminal window also print the blockchain 17 | - open as many terminal windows as you like and `nc localhost 9000` and watch blockchain networking in action! 18 | 19 | ### Screenshot 20 | 21 | ![screen](https://user-images.githubusercontent.com/15616604/35776154-16ea5c9a-094c-11e8-836b-e7e27c1d557f.png) 22 | 23 | ### Ask us anything! 24 | 25 | Join our [Telegram](https://t.me/joinchat/FX6A7UThIZ1WOUNirDS_Ew) chat and follow us on [Twitter](https://twitter.com/myCoralHealth)! 26 | -------------------------------------------------------------------------------- /networking/example.env: -------------------------------------------------------------------------------- 1 | PORT=9000 2 | -------------------------------------------------------------------------------- /networking/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "encoding/json" 8 | "io" 9 | "log" 10 | "net" 11 | "os" 12 | "strconv" 13 | "sync" 14 | "time" 15 | 16 | "github.com/davecgh/go-spew/spew" 17 | "github.com/joho/godotenv" 18 | ) 19 | 20 | // Block represents each 'item' in the blockchain 21 | type Block struct { 22 | Index int 23 | Timestamp string 24 | BPM int 25 | Hash string 26 | PrevHash string 27 | } 28 | 29 | // Blockchain is a series of validated Blocks 30 | var Blockchain []Block 31 | 32 | // bcServer handles incoming concurrent Blocks 33 | var bcServer chan []Block 34 | var mutex = &sync.Mutex{} 35 | 36 | func main() { 37 | err := godotenv.Load() 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | bcServer = make(chan []Block) 43 | 44 | // create genesis block 45 | t := time.Now() 46 | genesisBlock := Block{0, t.String(), 0, "", ""} 47 | spew.Dump(genesisBlock) 48 | Blockchain = append(Blockchain, genesisBlock) 49 | 50 | tcpPort := os.Getenv("PORT") 51 | 52 | // start TCP and serve TCP server 53 | server, err := net.Listen("tcp", ":"+tcpPort) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | log.Println("TCP Server Listening on port :", tcpPort) 58 | defer server.Close() 59 | 60 | for { 61 | conn, err := server.Accept() 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | go handleConn(conn) 66 | } 67 | 68 | } 69 | 70 | func handleConn(conn net.Conn) { 71 | 72 | defer conn.Close() 73 | 74 | io.WriteString(conn, "Enter a new BPM:") 75 | 76 | scanner := bufio.NewScanner(conn) 77 | 78 | // take in BPM from stdin and add it to blockchain after conducting necessary validation 79 | go func() { 80 | for scanner.Scan() { 81 | bpm, err := strconv.Atoi(scanner.Text()) 82 | if err != nil { 83 | log.Printf("%v not a number: %v", scanner.Text(), err) 84 | continue 85 | } 86 | newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], bpm) 87 | if err != nil { 88 | log.Println(err) 89 | continue 90 | } 91 | if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) { 92 | newBlockchain := append(Blockchain, newBlock) 93 | replaceChain(newBlockchain) 94 | } 95 | 96 | bcServer <- Blockchain 97 | io.WriteString(conn, "\nEnter a new BPM:") 98 | } 99 | }() 100 | 101 | // simulate receiving broadcast 102 | go func() { 103 | for { 104 | time.Sleep(30 * time.Second) 105 | mutex.Lock() 106 | output, err := json.Marshal(Blockchain) 107 | if err != nil { 108 | log.Fatal(err) 109 | } 110 | mutex.Unlock() 111 | io.WriteString(conn, string(output)) 112 | } 113 | }() 114 | 115 | for _ = range bcServer { 116 | spew.Dump(Blockchain) 117 | } 118 | 119 | } 120 | 121 | // make sure block is valid by checking index, and comparing the hash of the previous block 122 | func isBlockValid(newBlock, oldBlock Block) bool { 123 | if oldBlock.Index+1 != newBlock.Index { 124 | return false 125 | } 126 | 127 | if oldBlock.Hash != newBlock.PrevHash { 128 | return false 129 | } 130 | 131 | if calculateHash(newBlock) != newBlock.Hash { 132 | return false 133 | } 134 | 135 | return true 136 | } 137 | 138 | // make sure the chain we're checking is longer than the current blockchain 139 | func replaceChain(newBlocks []Block) { 140 | mutex.Lock() 141 | if len(newBlocks) > len(Blockchain) { 142 | Blockchain = newBlocks 143 | } 144 | mutex.Unlock() 145 | } 146 | 147 | // SHA256 hasing 148 | func calculateHash(block Block) string { 149 | record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash 150 | h := sha256.New() 151 | h.Write([]byte(record)) 152 | hashed := h.Sum(nil) 153 | return hex.EncodeToString(hashed) 154 | } 155 | 156 | // create a new block using previous block's hash 157 | func generateBlock(oldBlock Block, BPM int) (Block, error) { 158 | 159 | var newBlock Block 160 | 161 | t := time.Now() 162 | 163 | newBlock.Index = oldBlock.Index + 1 164 | newBlock.Timestamp = t.String() 165 | newBlock.BPM = BPM 166 | newBlock.PrevHash = oldBlock.Hash 167 | newBlock.Hash = calculateHash(newBlock) 168 | 169 | return newBlock, nil 170 | } 171 | -------------------------------------------------------------------------------- /p2p/README.md: -------------------------------------------------------------------------------- 1 | # Peer 2 Peer Blockchain Tutorial 2 | 3 | ### Getting started 4 | 5 | You'll need to set up the `gx` package manager. If you don't know how to do that, don't worry. Hang tight and read the upcoming blog post. 6 | 7 | In your 1st terminal `go run main.go -secio -l 10000` 8 | 9 | Follow the instructions in your 1st terminal and copy and paste the given command into your 2nd terminal e.g. `go run main.go -l 10001 -d /ip4/127.0.0.1/tcp/10000/ipfs/QmZ8NayvdXc2U2A1cwh9qGaHK7uxXXVrZQEYwDqbfFydfj -secio` 10 | 11 | Follow the instructions in your 2nd terminal and copy and paste the given command into your 3rd terminal e.g. `go run main.go -l 10002 -d /ip4/127.0.0.1/tcp/10001/ipfs/QmRAj9JJVKRJmWHbDKzvzKDVVFPWxuWYio3bPym4SgGPgF -secio` 12 | 13 | Type a BPM into any of your terminals and watch your blockchain be broadcast to all terminals! 14 | 15 | 16 | -------------------------------------------------------------------------------- /p2p/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "encoding/hex" 9 | "encoding/json" 10 | "flag" 11 | "fmt" 12 | "io" 13 | "log" 14 | mrand "math/rand" 15 | "os" 16 | "strconv" 17 | "strings" 18 | "sync" 19 | "time" 20 | 21 | "github.com/davecgh/go-spew/spew" 22 | golog "github.com/ipfs/go-log" 23 | libp2p "github.com/libp2p/go-libp2p" 24 | crypto "github.com/libp2p/go-libp2p-crypto" 25 | host "github.com/libp2p/go-libp2p-host" 26 | net "github.com/libp2p/go-libp2p-net" 27 | peer "github.com/libp2p/go-libp2p-peer" 28 | pstore "github.com/libp2p/go-libp2p-peerstore" 29 | ma "github.com/multiformats/go-multiaddr" 30 | gologging "github.com/whyrusleeping/go-logging" 31 | ) 32 | 33 | // Block represents each 'item' in the blockchain 34 | type Block struct { 35 | Index int 36 | Timestamp string 37 | BPM int 38 | Hash string 39 | PrevHash string 40 | } 41 | 42 | // Blockchain is a series of validated Blocks 43 | var Blockchain []Block 44 | 45 | var mutex = &sync.Mutex{} 46 | 47 | // makeBasicHost creates a LibP2P host with a random peer ID listening on the 48 | // given multiaddress. It will use secio if secio is true. 49 | func makeBasicHost(listenPort int, secio bool, randseed int64) (host.Host, error) { 50 | 51 | // If the seed is zero, use real cryptographic randomness. Otherwise, use a 52 | // deterministic randomness source to make generated keys stay the same 53 | // across multiple runs 54 | var r io.Reader 55 | if randseed == 0 { 56 | r = rand.Reader 57 | } else { 58 | r = mrand.New(mrand.NewSource(randseed)) 59 | } 60 | 61 | // Generate a key pair for this host. We will use it 62 | // to obtain a valid host ID. 63 | priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | opts := []libp2p.Option{ 69 | libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", listenPort)), 70 | libp2p.Identity(priv), 71 | } 72 | 73 | basicHost, err := libp2p.New(context.Background(), opts...) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | // Build host multiaddress 79 | hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", basicHost.ID().Pretty())) 80 | 81 | // Now we can build a full multiaddress to reach this host 82 | // by encapsulating both addresses: 83 | addrs := basicHost.Addrs() 84 | var addr ma.Multiaddr 85 | // select the address starting with "ip4" 86 | for _, i := range addrs { 87 | if strings.HasPrefix(i.String(), "/ip4") { 88 | addr = i 89 | break 90 | } 91 | } 92 | fullAddr := addr.Encapsulate(hostAddr) 93 | log.Printf("I am %s\n", fullAddr) 94 | if secio { 95 | log.Printf("Now run \"go run main.go -l %d -d %s -secio\" on a different terminal\n", listenPort+1, fullAddr) 96 | } else { 97 | log.Printf("Now run \"go run main.go -l %d -d %s\" on a different terminal\n", listenPort+1, fullAddr) 98 | } 99 | 100 | return basicHost, nil 101 | } 102 | 103 | func handleStream(s net.Stream) { 104 | 105 | log.Println("Got a new stream!") 106 | 107 | // Create a buffer stream for non blocking read and write. 108 | rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) 109 | 110 | go readData(rw) 111 | go writeData(rw) 112 | 113 | // stream 's' will stay open until you close it (or the other side closes it). 114 | } 115 | 116 | func readData(rw *bufio.ReadWriter) { 117 | 118 | for { 119 | str, err := rw.ReadString('\n') 120 | if err != nil { 121 | log.Fatal(err) 122 | } 123 | 124 | if str == "" { 125 | return 126 | } 127 | if str != "\n" { 128 | 129 | chain := make([]Block, 0) 130 | if err := json.Unmarshal([]byte(str), &chain); err != nil { 131 | log.Fatal(err) 132 | } 133 | 134 | mutex.Lock() 135 | if len(chain) > len(Blockchain) { 136 | Blockchain = chain 137 | bytes, err := json.MarshalIndent(Blockchain, "", " ") 138 | if err != nil { 139 | 140 | log.Fatal(err) 141 | } 142 | // Green console color: \x1b[32m 143 | // Reset console color: \x1b[0m 144 | fmt.Printf("\x1b[32m%s\x1b[0m> ", string(bytes)) 145 | } 146 | mutex.Unlock() 147 | } 148 | } 149 | } 150 | 151 | func writeData(rw *bufio.ReadWriter) { 152 | 153 | go func() { 154 | for { 155 | time.Sleep(5 * time.Second) 156 | mutex.Lock() 157 | bytes, err := json.Marshal(Blockchain) 158 | if err != nil { 159 | log.Println(err) 160 | } 161 | mutex.Unlock() 162 | 163 | mutex.Lock() 164 | rw.WriteString(fmt.Sprintf("%s\n", string(bytes))) 165 | rw.Flush() 166 | mutex.Unlock() 167 | 168 | } 169 | }() 170 | 171 | stdReader := bufio.NewReader(os.Stdin) 172 | 173 | for { 174 | fmt.Print("> ") 175 | sendData, err := stdReader.ReadString('\n') 176 | if err != nil { 177 | log.Fatal(err) 178 | } 179 | 180 | sendData = strings.Replace(sendData, "\n", "", -1) 181 | bpm, err := strconv.Atoi(sendData) 182 | if err != nil { 183 | log.Fatal(err) 184 | } 185 | newBlock := generateBlock(Blockchain[len(Blockchain)-1], bpm) 186 | 187 | if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) { 188 | mutex.Lock() 189 | Blockchain = append(Blockchain, newBlock) 190 | mutex.Unlock() 191 | } 192 | 193 | bytes, err := json.Marshal(Blockchain) 194 | if err != nil { 195 | log.Println(err) 196 | } 197 | 198 | spew.Dump(Blockchain) 199 | 200 | mutex.Lock() 201 | rw.WriteString(fmt.Sprintf("%s\n", string(bytes))) 202 | rw.Flush() 203 | mutex.Unlock() 204 | } 205 | 206 | } 207 | 208 | func main() { 209 | t := time.Now() 210 | genesisBlock := Block{} 211 | genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""} 212 | 213 | Blockchain = append(Blockchain, genesisBlock) 214 | 215 | // LibP2P code uses golog to log messages. They log with different 216 | // string IDs (i.e. "swarm"). We can control the verbosity level for 217 | // all loggers with: 218 | golog.SetAllLoggers(gologging.INFO) // Change to DEBUG for extra info 219 | 220 | // Parse options from the command line 221 | listenF := flag.Int("l", 0, "wait for incoming connections") 222 | target := flag.String("d", "", "target peer to dial") 223 | secio := flag.Bool("secio", false, "enable secio") 224 | seed := flag.Int64("seed", 0, "set random seed for id generation") 225 | flag.Parse() 226 | 227 | if *listenF == 0 { 228 | log.Fatal("Please provide a port to bind on with -l") 229 | } 230 | 231 | // Make a host that listens on the given multiaddress 232 | ha, err := makeBasicHost(*listenF, *secio, *seed) 233 | if err != nil { 234 | log.Fatal(err) 235 | } 236 | 237 | if *target == "" { 238 | log.Println("listening for connections") 239 | // Set a stream handler on host A. /p2p/1.0.0 is 240 | // a user-defined protocol name. 241 | ha.SetStreamHandler("/p2p/1.0.0", handleStream) 242 | 243 | select {} // hang forever 244 | /**** This is where the listener code ends ****/ 245 | } else { 246 | ha.SetStreamHandler("/p2p/1.0.0", handleStream) 247 | 248 | // The following code extracts target's peer ID from the 249 | // given multiaddress 250 | ipfsaddr, err := ma.NewMultiaddr(*target) 251 | if err != nil { 252 | log.Fatalln(err) 253 | } 254 | 255 | pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS) 256 | if err != nil { 257 | log.Fatalln(err) 258 | } 259 | 260 | peerid, err := peer.IDB58Decode(pid) 261 | if err != nil { 262 | log.Fatalln(err) 263 | } 264 | 265 | // Decapsulate the /ipfs/ part from the target 266 | // /ip4//ipfs/ becomes /ip4/ 267 | targetPeerAddr, _ := ma.NewMultiaddr( 268 | fmt.Sprintf("/ipfs/%s", peer.IDB58Encode(peerid))) 269 | targetAddr := ipfsaddr.Decapsulate(targetPeerAddr) 270 | 271 | // We have a peer ID and a targetAddr so we add it to the peerstore 272 | // so LibP2P knows how to contact it 273 | ha.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL) 274 | 275 | log.Println("opening stream") 276 | // make a new stream from host B to host A 277 | // it should be handled on host A by the handler we set above because 278 | // we use the same /p2p/1.0.0 protocol 279 | s, err := ha.NewStream(context.Background(), peerid, "/p2p/1.0.0") 280 | if err != nil { 281 | log.Fatalln(err) 282 | } 283 | // Create a buffered stream so that read and writes are non blocking. 284 | rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) 285 | 286 | // Create a thread to read and write data. 287 | go writeData(rw) 288 | go readData(rw) 289 | 290 | select {} // hang forever 291 | 292 | } 293 | } 294 | 295 | // make sure block is valid by checking index, and comparing the hash of the previous block 296 | func isBlockValid(newBlock, oldBlock Block) bool { 297 | if oldBlock.Index+1 != newBlock.Index { 298 | return false 299 | } 300 | 301 | if oldBlock.Hash != newBlock.PrevHash { 302 | return false 303 | } 304 | 305 | if calculateHash(newBlock) != newBlock.Hash { 306 | return false 307 | } 308 | 309 | return true 310 | } 311 | 312 | // SHA256 hashing 313 | func calculateHash(block Block) string { 314 | record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash 315 | h := sha256.New() 316 | h.Write([]byte(record)) 317 | hashed := h.Sum(nil) 318 | return hex.EncodeToString(hashed) 319 | } 320 | 321 | // create a new block using previous block's hash 322 | func generateBlock(oldBlock Block, BPM int) Block { 323 | 324 | var newBlock Block 325 | 326 | t := time.Now() 327 | 328 | newBlock.Index = oldBlock.Index + 1 329 | newBlock.Timestamp = t.String() 330 | newBlock.BPM = BPM 331 | newBlock.PrevHash = oldBlock.Hash 332 | newBlock.Hash = calculateHash(newBlock) 333 | 334 | return newBlock 335 | } 336 | -------------------------------------------------------------------------------- /proof-stake/README.md: -------------------------------------------------------------------------------- 1 | # Code your own Proof of Stake blockchain in Go! 2 | 3 | [Read](https://medium.com/@mycoralhealth/code-your-own-proof-of-stake-blockchain-in-go-610cd99aa658) the blog post for this tutorial first! 4 | 5 | ### Ask us anything! 6 | 7 | Join our [Telegram](https://t.me/joinchat/FX6A7UThIZ1WOUNirDS_Ew) community and follow us on [Twitter](https://twitter.com/myCoralHealth)! 8 | 9 | ### Deployment steps: 10 | - clone this repo 11 | - navigate to this directory and rename the example file `mv example.env .env` 12 | - `go run main.go` 13 | - open a new terminal window and `nc localhost 9000` 14 | - input a token amount to set stake 15 | - input a BPM 16 | - wait a few seconds to see which of the two terminals won 17 | - open as many terminal windows as you like and `nc localhost 9000` and watch Proof of Stake in action! 18 | 19 | ### Screenshots 20 | 21 | ![screen](https://user-images.githubusercontent.com/15616604/37950306-e97dfc0c-314c-11e8-93b7-7cce0689d421.png) 22 | 23 | ![screen](https://user-images.githubusercontent.com/15616604/37950314-f26e5f78-314c-11e8-9697-0d95e0c687d0.png) 24 | 25 | ![screen](https://user-images.githubusercontent.com/15616604/37950331-fef438bc-314c-11e8-94ae-9ea09b9b5276.png) 26 | 27 | ![screen](https://user-images.githubusercontent.com/15616604/37950343-061a309c-314d-11e8-84a2-3e5712524fc2.png) 28 | 29 | 30 | -------------------------------------------------------------------------------- /proof-stake/example.env: -------------------------------------------------------------------------------- 1 | PORT=9000 2 | -------------------------------------------------------------------------------- /proof-stake/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "log" 11 | "math/rand" 12 | "net" 13 | "os" 14 | "strconv" 15 | "sync" 16 | "time" 17 | 18 | "github.com/davecgh/go-spew/spew" 19 | "github.com/joho/godotenv" 20 | ) 21 | 22 | // Block represents each 'item' in the blockchain 23 | type Block struct { 24 | Index int 25 | Timestamp string 26 | BPM int 27 | Hash string 28 | PrevHash string 29 | Validator string 30 | } 31 | 32 | // Blockchain is a series of validated Blocks 33 | var Blockchain []Block 34 | var tempBlocks []Block 35 | 36 | // candidateBlocks handles incoming blocks for validation 37 | var candidateBlocks = make(chan Block) 38 | 39 | // announcements broadcasts winning validator to all nodes 40 | var announcements = make(chan string) 41 | 42 | var mutex = &sync.Mutex{} 43 | 44 | // validators keeps track of open validators and balances 45 | var validators = make(map[string]int) 46 | 47 | func main() { 48 | err := godotenv.Load() 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | // create genesis block 54 | t := time.Now() 55 | genesisBlock := Block{} 56 | genesisBlock = Block{0, t.String(), 0, calculateBlockHash(genesisBlock), "", ""} 57 | spew.Dump(genesisBlock) 58 | Blockchain = append(Blockchain, genesisBlock) 59 | 60 | tcpPort := os.Getenv("PORT") 61 | 62 | // start TCP and serve TCP server 63 | server, err := net.Listen("tcp", ":"+tcpPort) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | log.Println("TCP Server Listening on port :", tcpPort) 68 | defer server.Close() 69 | 70 | go func() { 71 | for candidate := range candidateBlocks { 72 | mutex.Lock() 73 | tempBlocks = append(tempBlocks, candidate) 74 | mutex.Unlock() 75 | } 76 | }() 77 | 78 | go func() { 79 | for { 80 | pickWinner() 81 | } 82 | }() 83 | 84 | for { 85 | conn, err := server.Accept() 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | go handleConn(conn) 90 | } 91 | } 92 | 93 | // pickWinner creates a lottery pool of validators and chooses the validator who gets to forge a block to the blockchain 94 | // by random selecting from the pool, weighted by amount of tokens staked 95 | func pickWinner() { 96 | time.Sleep(30 * time.Second) 97 | mutex.Lock() 98 | temp := tempBlocks 99 | mutex.Unlock() 100 | 101 | lotteryPool := []string{} 102 | if len(temp) > 0 { 103 | 104 | // slightly modified traditional proof of stake algorithm 105 | // from all validators who submitted a block, weight them by the number of staked tokens 106 | // in traditional proof of stake, validators can participate without submitting a block to be forged 107 | OUTER: 108 | for _, block := range temp { 109 | // if already in lottery pool, skip 110 | for _, node := range lotteryPool { 111 | if block.Validator == node { 112 | continue OUTER 113 | } 114 | } 115 | 116 | // lock list of validators to prevent data race 117 | mutex.Lock() 118 | setValidators := validators 119 | mutex.Unlock() 120 | 121 | k, ok := setValidators[block.Validator] 122 | if ok { 123 | for i := 0; i < k; i++ { 124 | lotteryPool = append(lotteryPool, block.Validator) 125 | } 126 | } 127 | } 128 | 129 | // randomly pick winner from lottery pool 130 | s := rand.NewSource(time.Now().Unix()) 131 | r := rand.New(s) 132 | lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))] 133 | 134 | // add block of winner to blockchain and let all the other nodes know 135 | for _, block := range temp { 136 | if block.Validator == lotteryWinner { 137 | mutex.Lock() 138 | Blockchain = append(Blockchain, block) 139 | mutex.Unlock() 140 | for _ = range validators { 141 | announcements <- "\nwinning validator: " + lotteryWinner + "\n" 142 | } 143 | break 144 | } 145 | } 146 | } 147 | 148 | mutex.Lock() 149 | tempBlocks = []Block{} 150 | mutex.Unlock() 151 | } 152 | 153 | func handleConn(conn net.Conn) { 154 | defer conn.Close() 155 | 156 | go func() { 157 | for { 158 | msg := <-announcements 159 | io.WriteString(conn, msg) 160 | } 161 | }() 162 | // validator address 163 | var address string 164 | 165 | // allow user to allocate number of tokens to stake 166 | // the greater the number of tokens, the greater chance to forging a new block 167 | io.WriteString(conn, "Enter token balance:") 168 | scanBalance := bufio.NewScanner(conn) 169 | for scanBalance.Scan() { 170 | balance, err := strconv.Atoi(scanBalance.Text()) 171 | if err != nil { 172 | log.Printf("%v not a number: %v", scanBalance.Text(), err) 173 | return 174 | } 175 | t := time.Now() 176 | address = calculateHash(t.String()) 177 | validators[address] = balance 178 | fmt.Println(validators) 179 | break 180 | } 181 | 182 | io.WriteString(conn, "\nEnter a new BPM:") 183 | 184 | scanBPM := bufio.NewScanner(conn) 185 | 186 | go func() { 187 | for { 188 | // take in BPM from stdin and add it to blockchain after conducting necessary validation 189 | for scanBPM.Scan() { 190 | bpm, err := strconv.Atoi(scanBPM.Text()) 191 | // if malicious party tries to mutate the chain with a bad input, delete them as a validator and they lose their staked tokens 192 | if err != nil { 193 | log.Printf("%v not a number: %v", scanBPM.Text(), err) 194 | delete(validators, address) 195 | conn.Close() 196 | } 197 | 198 | mutex.Lock() 199 | oldLastIndex := Blockchain[len(Blockchain)-1] 200 | mutex.Unlock() 201 | 202 | // create newBlock for consideration to be forged 203 | newBlock, err := generateBlock(oldLastIndex, bpm, address) 204 | if err != nil { 205 | log.Println(err) 206 | continue 207 | } 208 | if isBlockValid(newBlock, oldLastIndex) { 209 | candidateBlocks <- newBlock 210 | } 211 | io.WriteString(conn, "\nEnter a new BPM:") 212 | } 213 | } 214 | }() 215 | 216 | // simulate receiving broadcast 217 | for { 218 | time.Sleep(time.Minute) 219 | mutex.Lock() 220 | output, err := json.Marshal(Blockchain) 221 | mutex.Unlock() 222 | if err != nil { 223 | log.Fatal(err) 224 | } 225 | io.WriteString(conn, string(output)+"\n") 226 | } 227 | 228 | } 229 | 230 | // isBlockValid makes sure block is valid by checking index 231 | // and comparing the hash of the previous block 232 | func isBlockValid(newBlock, oldBlock Block) bool { 233 | if oldBlock.Index+1 != newBlock.Index { 234 | return false 235 | } 236 | 237 | if oldBlock.Hash != newBlock.PrevHash { 238 | return false 239 | } 240 | 241 | if calculateBlockHash(newBlock) != newBlock.Hash { 242 | return false 243 | } 244 | 245 | return true 246 | } 247 | 248 | // SHA256 hasing 249 | // calculateHash is a simple SHA256 hashing function 250 | func calculateHash(s string) string { 251 | h := sha256.New() 252 | h.Write([]byte(s)) 253 | hashed := h.Sum(nil) 254 | return hex.EncodeToString(hashed) 255 | } 256 | 257 | //calculateBlockHash returns the hash of all block information 258 | func calculateBlockHash(block Block) string { 259 | record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash 260 | return calculateHash(record) 261 | } 262 | 263 | // generateBlock creates a new block using previous block's hash 264 | func generateBlock(oldBlock Block, BPM int, address string) (Block, error) { 265 | 266 | var newBlock Block 267 | 268 | t := time.Now() 269 | 270 | newBlock.Index = oldBlock.Index + 1 271 | newBlock.Timestamp = t.String() 272 | newBlock.BPM = BPM 273 | newBlock.PrevHash = oldBlock.Hash 274 | newBlock.Hash = calculateBlockHash(newBlock) 275 | newBlock.Validator = address 276 | 277 | return newBlock, nil 278 | } 279 | -------------------------------------------------------------------------------- /proof-work/README.md: -------------------------------------------------------------------------------- 1 | # Code your own Proof of Work algorithm! 2 | 3 | [Read](https://medium.com/@mycoralhealth/code-your-own-blockchain-mining-algorithm-in-go-82c6a71aba1f) our blog post first to see a walkthrough of the code. 4 | 5 | ### Ask us anything! 6 | 7 | Join our [Telegram](https://t.me/joinchat/FX6A7UThIZ1WOUNirDS_Ew) chat and follow us on [Twitter](https://twitter.com/myCoralHealth)! 8 | 9 | ### Deployment steps: 10 | - clone this repo 11 | - navigate to this directory and rename the example file `mv example.env .env` 12 | - `go run main.go` 13 | - open a web browser and visit `http://localhost:8080/` 14 | - to write new blocks, send a `POST` request (I like to use [Postman](https://www.getpostman.com/apps)) to `http://localhost:8080/` with a JSON payload with `BPM` as the key and an integer as the value. For example: 15 | ``` 16 | {"BPM":50} 17 | ``` 18 | - **watch your Terminal to see Proof of Work in action!** 19 | 20 | ### Screenshot 21 | 22 | ![screen](https://user-images.githubusercontent.com/15616604/36948221-7ce7f9c0-1f8c-11e8-9d1b-0769233a6abc.png) 23 | -------------------------------------------------------------------------------- /proof-work/example.env: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | -------------------------------------------------------------------------------- /proof-work/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net/http" 11 | "os" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | "time" 16 | 17 | "github.com/davecgh/go-spew/spew" 18 | "github.com/gorilla/mux" 19 | "github.com/joho/godotenv" 20 | ) 21 | 22 | const difficulty = 1 23 | 24 | // Block represents each 'item' in the blockchain 25 | type Block struct { 26 | Index int 27 | Timestamp string 28 | BPM int 29 | Hash string 30 | PrevHash string 31 | Difficulty int 32 | Nonce string 33 | } 34 | 35 | // Blockchain is a series of validated Blocks 36 | var Blockchain []Block 37 | 38 | // Message takes incoming JSON payload for writing heart rate 39 | type Message struct { 40 | BPM int 41 | } 42 | 43 | var mutex = &sync.Mutex{} 44 | 45 | func main() { 46 | err := godotenv.Load() 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | go func() { 52 | t := time.Now() 53 | genesisBlock := Block{} 54 | genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), "", difficulty, ""} 55 | spew.Dump(genesisBlock) 56 | 57 | mutex.Lock() 58 | Blockchain = append(Blockchain, genesisBlock) 59 | mutex.Unlock() 60 | }() 61 | log.Fatal(run()) 62 | 63 | } 64 | 65 | // web server 66 | func run() error { 67 | mux := makeMuxRouter() 68 | httpPort := os.Getenv("PORT") 69 | log.Println("HTTP Server Listening on port :", httpPort) 70 | s := &http.Server{ 71 | Addr: ":" + httpPort, 72 | Handler: mux, 73 | ReadTimeout: 10 * time.Second, 74 | WriteTimeout: 10 * time.Second, 75 | MaxHeaderBytes: 1 << 20, 76 | } 77 | 78 | if err := s.ListenAndServe(); err != nil { 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | 85 | // create handlers 86 | func makeMuxRouter() http.Handler { 87 | muxRouter := mux.NewRouter() 88 | muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET") 89 | muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST") 90 | return muxRouter 91 | } 92 | 93 | // write blockchain when we receive an http request 94 | func handleGetBlockchain(w http.ResponseWriter, r *http.Request) { 95 | bytes, err := json.MarshalIndent(Blockchain, "", " ") 96 | if err != nil { 97 | http.Error(w, err.Error(), http.StatusInternalServerError) 98 | return 99 | } 100 | io.WriteString(w, string(bytes)) 101 | } 102 | 103 | // takes JSON payload as an input for heart rate (BPM) 104 | func handleWriteBlock(w http.ResponseWriter, r *http.Request) { 105 | w.Header().Set("Content-Type", "application/json") 106 | var m Message 107 | 108 | decoder := json.NewDecoder(r.Body) 109 | if err := decoder.Decode(&m); err != nil { 110 | respondWithJSON(w, r, http.StatusBadRequest, r.Body) 111 | return 112 | } 113 | defer r.Body.Close() 114 | 115 | //ensure atomicity when creating new block 116 | mutex.Lock() 117 | newBlock := generateBlock(Blockchain[len(Blockchain)-1], m.BPM) 118 | mutex.Unlock() 119 | 120 | if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) { 121 | Blockchain = append(Blockchain, newBlock) 122 | spew.Dump(Blockchain) 123 | } 124 | 125 | respondWithJSON(w, r, http.StatusCreated, newBlock) 126 | 127 | } 128 | 129 | func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) { 130 | w.Header().Set("Content-Type", "application/json") 131 | response, err := json.MarshalIndent(payload, "", " ") 132 | if err != nil { 133 | w.WriteHeader(http.StatusInternalServerError) 134 | w.Write([]byte("HTTP 500: Internal Server Error")) 135 | return 136 | } 137 | w.WriteHeader(code) 138 | w.Write(response) 139 | } 140 | 141 | // make sure block is valid by checking index, and comparing the hash of the previous block 142 | func isBlockValid(newBlock, oldBlock Block) bool { 143 | if oldBlock.Index+1 != newBlock.Index { 144 | return false 145 | } 146 | 147 | if oldBlock.Hash != newBlock.PrevHash { 148 | return false 149 | } 150 | 151 | if calculateHash(newBlock) != newBlock.Hash { 152 | return false 153 | } 154 | 155 | return true 156 | } 157 | 158 | // SHA256 hasing 159 | func calculateHash(block Block) string { 160 | record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash + block.Nonce 161 | h := sha256.New() 162 | h.Write([]byte(record)) 163 | hashed := h.Sum(nil) 164 | return hex.EncodeToString(hashed) 165 | } 166 | 167 | // create a new block using previous block's hash 168 | func generateBlock(oldBlock Block, BPM int) Block { 169 | var newBlock Block 170 | 171 | t := time.Now() 172 | 173 | newBlock.Index = oldBlock.Index + 1 174 | newBlock.Timestamp = t.String() 175 | newBlock.BPM = BPM 176 | newBlock.PrevHash = oldBlock.Hash 177 | newBlock.Difficulty = difficulty 178 | 179 | for i := 0; ; i++ { 180 | hex := fmt.Sprintf("%x", i) 181 | newBlock.Nonce = hex 182 | if !isHashValid(calculateHash(newBlock), newBlock.Difficulty) { 183 | fmt.Println(calculateHash(newBlock), " do more work!") 184 | time.Sleep(time.Second) 185 | continue 186 | } else { 187 | fmt.Println(calculateHash(newBlock), " work done!") 188 | newBlock.Hash = calculateHash(newBlock) 189 | break 190 | } 191 | 192 | } 193 | return newBlock 194 | } 195 | 196 | func isHashValid(hash string, difficulty int) bool { 197 | prefix := strings.Repeat("0", difficulty) 198 | return strings.HasPrefix(hash, prefix) 199 | } 200 | --------------------------------------------------------------------------------