Skip to content

Instantly share code, notes, and snippets.

@erikreppel
Last active March 26, 2019 01:42
Show Gist options
  • Save erikreppel/51ae3f266d7ad2ac6ba2f128f1b480d0 to your computer and use it in GitHub Desktop.
Save erikreppel/51ae3f266d7ad2ac6ba2f128f1b480d0 to your computer and use it in GitHub Desktop.
Simple example of mining with proof of work
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