Last active
March 26, 2019 01:42
-
-
Save erikreppel/51ae3f266d7ad2ac6ba2f128f1b480d0 to your computer and use it in GitHub Desktop.
Simple example of mining with proof of work
This file contains hidden or 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
package main | |
import ( | |
"crypto/sha256" | |
"encoding/base64" | |
"fmt" | |
"math" | |
"math/rand" | |
"strconv" | |
"time" | |
) | |
// MinedCondition takes a hash and returns if it meets a condition | |
type MinedCondition func(string) bool | |
// BlockHasher returns a string to hash | |
type BlockHasher func(uint64) string | |
// PendingBlock is a block that has not yet been mined | |
type PendingBlock struct { | |
Index uint64 `json:"index"` | |
PrevHash string `json:"prev_hash"` | |
Data string `json:"data"` // this would txs in a currency | |
Timestamp int64 `json:"timestamp"` | |
Nonce uint64 `json:"nonce"` | |
} | |
// MinedBlock is like a pending block, but also has the block hash | |
type MinedBlock struct { | |
PendingBlock | |
Hash string `json:"hash"` | |
} | |
// MakeHasher returns a hash function for a block | |
// (doing less string mangling per hash check ~3x hash rate) | |
func MakeHasher(b *PendingBlock) BlockHasher { | |
ts := strconv.Itoa(int(b.Timestamp)) | |
i := strconv.Itoa(int(b.Index)) | |
str := "timestamp:" + ts + "index:" + i + "data:" + b.Data + "prev_hash:" + b.PrevHash + "nonce:%d" | |
return func(nonce uint64) string { | |
s := fmt.Sprintf(str, nonce) | |
buf := []byte(s) | |
h := sha256.New() | |
h.Write(buf) | |
sum := h.Sum(nil) | |
strSum := base64.URLEncoding.EncodeToString(sum) | |
return strSum | |
} | |
} | |
// MakeMinedCond takes a difficulty (ex: 4) and returns a function that can act | |
// as a mining condition | |
func MakeMinedCond(difficulty int) MinedCondition { | |
cond := "" | |
for i := 0; i < difficulty; i++ { | |
cond += "0" | |
} | |
return func(hash string) bool { | |
return hash[0:difficulty] == cond | |
} | |
} | |
// Mine chooses random nonces until the mining condition is met | |
func Mine(b PendingBlock, mined MinedCondition) MinedBlock { | |
hashCount := 1 | |
var hash string | |
start := time.Now().UnixNano() | |
hasher := MakeHasher(&b) | |
logInterval := 100000 | |
for { | |
nonce := uint64(rand.Intn(math.MaxInt64)) | |
hash = hasher(nonce) | |
hashCount++ | |
// Logging | |
if hashCount%logInterval == 0 { | |
period := time.Now().UnixNano() | |
l := (float64(period) - float64(start)) * 1e-9 | |
fmt.Printf("\r%.2f hashes/s Last nonce: %d Next hash: %s", | |
float64(logInterval)/l, nonce, hash) | |
start = period | |
} | |
if mined(hash) == true { | |
fmt.Printf("\n\nTook %d hashes\n", hashCount) | |
fmt.Printf("Mined PendingBlock: %+v with hash: %s\n", b, hash) | |
b.Nonce = nonce | |
return MinedBlock{ | |
PendingBlock: b, | |
Hash: hash, | |
} | |
} | |
} | |
} | |
func randomString(n int) string { | |
letterBytes := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
b := make([]byte, n) | |
for i := range b { | |
b[i] = letterBytes[rand.Intn(len(letterBytes))] | |
} | |
return string(b) | |
} | |
// BlockWithRandomData returns PendingBlock with random Data | |
func BlockWithRandomData(prevHash string, index uint64) PendingBlock { | |
return PendingBlock{ | |
Index: index, | |
PrevHash: prevHash, | |
Data: randomString(20), | |
Timestamp: time.Now().Unix(), | |
} | |
} | |
func printBlockchain(blocks []MinedBlock) { | |
fmt.Printf("\nBlockchain:\n") | |
for _, block := range blocks { | |
fmt.Printf("%+v\n\n", block) | |
} | |
} | |
const difficulty = 3 | |
const nBlocks = 10 | |
func main() { | |
// Simulate mining a blockchain | |
blockchain := []MinedBlock{} | |
minedCond := MakeMinedCond(difficulty) | |
// Create a genesis block | |
pb := PendingBlock{ | |
Index: 0, | |
PrevHash: "12345", | |
Data: "Hello, world!", | |
Timestamp: time.Now().Unix(), | |
} | |
for i := 1; i < nBlocks; i++ { | |
mb := Mine(pb, minedCond) | |
blockchain = append(blockchain, mb) | |
nextIndex := blockchain[len(blockchain)-1].Index + 1 | |
pb = BlockWithRandomData(mb.Hash, nextIndex) | |
} | |
printBlockchain(blockchain) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment