forked from nosequeldeebee/blockchain-tutorial
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
nosequeldeebee
committed
Mar 25, 2018
1 parent
738cb1c
commit d61bb70
Showing
3 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ADDR=9000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |