Skip to content

Commit

Permalink
add proof of stake tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
nosequeldeebee committed Mar 25, 2018
1 parent 738cb1c commit d61bb70
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 0 deletions.
15 changes: 15 additions & 0 deletions proof-stake/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Code your own Proof of Stake blockchain in Go!

### Deployment steps:
- clone this repo
- navigate to this directory and rename the example file `mv example.env .env`
- `go run main.go`
- open a new terminal window and `nc localhost 9000`
- input a token amount to set stake
- input a BPM
- wait a few seconds to see which of the two terminals won
- open as many terminal windows as you like and `nc localhost 9000` and watch Proof of Stake in action!

### Ask us anything!

Join our [Telegram](https://t.me/joinchat/FX6A7UThIZ1WOUNirDS_Ew) chat and follow us on [Twitter](https://twitter.com/myCoralHealth)!
1 change: 1 addition & 0 deletions proof-stake/example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ADDR=9000
282 changes: 282 additions & 0 deletions proof-stake/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
package main

import (
"bufio"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"net"
"os"
"strconv"
"sync"
"time"

"github.com/davecgh/go-spew/spew"
"github.com/joho/godotenv"
)

// Block represents each 'item' in the blockchain
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
Validator string
}

// Blockchain is a series of validated Blocks
var Blockchain []Block
var tempBlocks []Block

// candidateBlocks handles incoming blocks for validation
var candidateBlocks = make(chan Block)

// announcements broadcasts winning validator to all nodes
var announcements = make(chan string)

var mutex = &sync.Mutex{}

// validators keeps track of open validators and balances
var validators = make(map[string]int)

func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}

// create genesis block
t := time.Now()
genesisBlock := Block{}
genesisBlock = Block{0, t.String(), 0, calculateBlockHash(genesisBlock), "", ""}
spew.Dump(genesisBlock)
Blockchain = append(Blockchain, genesisBlock)

// start TCP and serve TCP server
server, err := net.Listen("tcp", ":"+os.Getenv("ADDR"))
if err != nil {
log.Fatal(err)
}
defer server.Close()

go func() {
for candidate := range candidateBlocks {
mutex.Lock()
tempBlocks = append(tempBlocks, candidate)
mutex.Unlock()
}
}()

go func() {
for {
pickWinner()
}
}()

for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}
}

// pickWinner creates a lottery pool of validators and chooses the validator who gets to forge a block to the blockchain
// by random selecting from the pool, weighted by amount of tokens staked
func pickWinner() {
time.Sleep(15 * time.Second)
mutex.Lock()
temp := tempBlocks
mutex.Unlock()

lotteryPool := []string{}
if len(temp) > 0 {

// slightly modified traditional proof of stake algorithm
// from all validators who submitted a block, weight them by the number of staked tokens
// in traditional proof of stake, validators can participate without submitting a block to be forged
OUTER:
for _, block := range temp {
// if already in lottery pool, skip
for _, node := range lotteryPool {
if block.Validator == node {
continue OUTER
}
}

// lock list of validators to prevent data race
mutex.Lock()
setValidators := validators
mutex.Unlock()

k, ok := setValidators[block.Validator]
if ok {
for i := 0; i < k; i++ {
lotteryPool = append(lotteryPool, block.Validator)
}
}
}

// randomly pick winner from lottery pool
s := rand.NewSource(time.Now().Unix())
r := rand.New(s)
lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))]

// add block of winner to blockchain and let all the other nodes know
for _, block := range temp {
if block.Validator == lotteryWinner {
mutex.Lock()
Blockchain = append(Blockchain, block)
mutex.Unlock()
for _ = range validators {
announcements <- "\nwinning validator: " + lotteryWinner + "\n"
}
break
}
}
}

mutex.Lock()
tempBlocks = []Block{}
mutex.Unlock()
}

func handleConn(conn net.Conn) {
defer conn.Close()

go func() {
for {
msg := <-announcements
io.WriteString(conn, msg)
}
}()
// validator address
var address string

// allow user to allocate number of tokens to stake
// the greater the number of tokens, the greater chance to forging a new block
io.WriteString(conn, "Enter token balance:")
scanBalance := bufio.NewScanner(conn)
for scanBalance.Scan() {
balance, err := strconv.Atoi(scanBalance.Text())
if err != nil {
log.Printf("%v not a number: %v", scanBalance.Text(), err)
return
}
t := time.Now()
address = calculateHash(t.String())
validators[address] = balance
fmt.Println(validators)
break
}

io.WriteString(conn, "\nEnter a new BPM:")

scanBPM := bufio.NewScanner(conn)

go func() {
for {
// take in BPM from stdin and add it to blockchain after conducting necessary validation
for scanBPM.Scan() {
bpm, err := strconv.Atoi(scanBPM.Text())
// if malicious party tries to mutate the chain with a bad input, delete them as a validator and they lose their staked tokens
if err != nil {
log.Printf("%v not a number: %v", scanBPM.Text(), err)
delete(validators, address)
conn.Close()
}

mutex.Lock()
oldLastIndex := Blockchain[len(Blockchain)-1]
mutex.Unlock()

// create newBlock for consideration to be forged
newBlock, err := generateBlock(oldLastIndex, bpm, address)
if err != nil {
log.Println(err)
continue
}
if isBlockValid(newBlock, oldLastIndex) {
candidateBlocks <- newBlock
}
io.WriteString(conn, "\nEnter a new BPM:")
}
}
}()

// simulate receiving broadcast
for {
time.Sleep(20 * time.Second)
mutex.Lock()
output, err := json.Marshal(Blockchain)
mutex.Unlock()
if err != nil {
log.Fatal(err)
}
io.WriteString(conn, string(output)+"\n")
}

}

// isBlockValid makes sure block is valid by checking index
// and comparing the hash of the previous block
func isBlockValid(newBlock, oldBlock Block) bool {
if oldBlock.Index+1 != newBlock.Index {
return false
}

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

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

return true
}

// replaceChain supplants old blockchain with new, longer chain
func replaceChain(newBlocks []Block) {
mutex.Lock()
Blockchain = newBlocks
mutex.Unlock()
}

// SHA256 hasing
// calculateHash is a simple SHA256 hashing function
func calculateHash(s string) string {
h := sha256.New()
h.Write([]byte(s))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}

//calculateBlockHash returns the hash of all block information
func calculateBlockHash(block Block) string {
record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
return calculateHash(record)
}

// generateBlock creates a new block using previous block's hash
func generateBlock(oldBlock Block, BPM int, address string) (Block, error) {

var newBlock Block

t := time.Now()

newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.BPM = BPM
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = calculateBlockHash(newBlock)
newBlock.Validator = address

return newBlock, nil
}

0 comments on commit d61bb70

Please sign in to comment.