Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

25 mining proof of work #66

Merged
merged 21 commits into from
Jun 21, 2017
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 72 additions & 3 deletions blockchain/hash.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
package blockchain
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move all the miner stuff into its own package


import "crypto/sha256"
import (
"crypto/sha256"
"encoding/hex"
"log"
)

// HashLen is the length in bytes of a hash.
const HashLen = 32
const (
// HashLen is the length in bytes of a hash.
HashLen = 32
// MaxDifficultyHex is the maximum difficulty value represented as a hex string
MaxDifficultyHex = "000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be replace by using math.big?

// MaxHashHex is the maximum hash value represented as a hex string
MaxHashHex = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
)

// CompareTo comparator constants
const (
// GreaterThan is the value to the CompareTo function returns if h1 is greater than h2
GreaterThan int = 1
// LessThan is the value to the CompareTo function returns if h1 is less than h2
LessThan int = -1
// EqualTo is the value to the CompareTo function returns if h1 is equal to h2
EqualTo int = 0
)

var (
// MaxDifficulty is the maximum difficulty value
MaxDifficulty = HexStringToHash(MaxDifficultyHex)
// MaxHash is the maximum hash value
MaxHash = HexStringToHash(MaxHashHex)
// MinHash is the minimum hash value
MinHash = HexStringToHash("0")
)

// Hash represents a 256-bit hash of a block or transaction
type Hash [HashLen]byte
Expand All @@ -26,3 +55,43 @@ type Marshaller interface {
func HashSum(m Marshaller) Hash {
return sha256.Sum256(m.Marshal())
}

// CompareTo compares two hashes, it returns true if the operation of the first hash on the second hash specified by the comparator is true, and false otherwise
func CompareTo(h1 Hash, h2 Hash, comparator int) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will only ever be checking for hashes being less than right? Can we simplify this accordingly and maybe remove those comparator constants unless we need arbitrary comparisons?

Copy link
Contributor Author

@david-julien david-julien Jun 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we need all of them for miningheader verification purposes before mining, we'll see if i can clean this up when we start using math.big

Copy link
Contributor Author

@david-julien david-julien Jun 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

math.big uses the exact same arbitrary constants for comparison, so if we wanted to compare two hashes we would have to convert to math.big and then use the same constants.

https://golang.org/src/math/big/float.go?s=43528:43561#L1637

for i := HashLen - 1; i >= 0; i-- {
if h1[i] > h2[i] {
return 1 == comparator
} else if h1[i] < h2[i] {
return -1 == comparator
}
}
return 0 == comparator
}

// HexStringToHash converts a big endian hex string to a hash
func HexStringToHash(s string) Hash {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be replaced by math.big?

if len(s)%2 != 0 {
s = "0" + s
}

if len(s)/2 < HashLen {
pre := ""
for i := 0; i < HashLen-len(s)/2; i++ {
pre += "00"
}
s = pre + s
}

var bytes, e = hex.DecodeString(s)

if e != nil {
log.Fatal(e)
}

var hash Hash
for i := 0; i < HashLen; i++ {
hash[i] = bytes[HashLen-1-i]
}

return hash
}
75 changes: 75 additions & 0 deletions blockchain/hash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package blockchain

import "testing"

func TestCompareTo(t *testing.T) {
h1 := HexStringToHash("00000000000008b3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81")
h2 := HexStringToHash("00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81")

if !CompareTo(h1, h2, GreaterThan) {
t.Fail()
}

h1 = HexStringToHash("100000000000000000000000000000000000000000000000000000000000000F")
h2 = HexStringToHash("F000000000000000000000000000000000000000000000000000000000000001")

if !CompareTo(h1, h2, LessThan) {
t.Fail()
}

h1 = HexStringToHash("0000000000000000000000000000000000000000000000000000000000000000")
h2 = HexStringToHash("0000000000000000000000000000000000000000000000000000000000000000")

if !CompareTo(h1, h2, EqualTo) {
t.Fail()
}

h1 = HexStringToHash("0000000000000000000000000000000000000000000000000000000000000000")
h2 = HexStringToHash("0000000000000000000000000000000000000000000000000000000000000001")

if !CompareTo(h1, h2, LessThan) {
t.Fail()
}

h1 = HexStringToHash("0000000000000000000000000000000000000000000000000000000000000001")
h2 = HexStringToHash("0000000000000000000000000000000000000000000000000000000000000000")

if !CompareTo(h1, h2, GreaterThan) {
t.Fail()
}

h1 = HexStringToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
h2 = HexStringToHash("0000000000000000000000000000000000000000000000000000000000000000")

if !CompareTo(h1, h2, GreaterThan) {
t.Fail()
}

h1 = HexStringToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
h2 = HexStringToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")

if !CompareTo(h1, h2, EqualTo) {
t.Fail()
}

h1 = HexStringToHash("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1")
h2 = HexStringToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")

if !CompareTo(h1, h2, LessThan) {
t.Fail()
}

h1 = HexStringToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
h2 = HexStringToHash("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1")

if !CompareTo(h1, h2, GreaterThan) {
t.Fail()
}

h1 = HexStringToHash("fffffffffffffffffffffffffffffff11fffffffffffffffffffffffffffffff")
h2 = HexStringToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")

if !CompareTo(h1, h2, LessThan) {
t.Fail()
}
}
103 changes: 103 additions & 0 deletions blockchain/miner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package blockchain

import (
"crypto/sha256"
"encoding/binary"
"math"
"time"

log "github.com/Sirupsen/logrus"
)

const (
// MiningHeaderLen is the length of the MiningHeader struct in bytes
MiningHeaderLen = (3 * HashLen) + (1 * (32 / 8)) + (1 * (64 / 8))
)

// MiningHeader contains the metadata required for mining
type MiningHeader struct {
LastBlock Hash
RootHash Hash
Target Hash
Time uint32
Nonce uint64
}

// Marshal converts a Mining Header to a byte slice
func (mh *MiningHeader) Marshal() []byte {
var buf []byte
buf = append(buf, mh.LastBlock.Marshal()...)
buf = append(buf, mh.RootHash.Marshal()...)
buf = append(buf, mh.Target.Marshal()...)
AppendUint32ToSlice(&buf, mh.Time)
AppendUint64ToSlice(&buf, mh.Nonce)
return buf
}

// SetMiningHeader sets the mining header
func (mh *MiningHeader) SetMiningHeader(lastBlock Hash, rootHash Hash, target Hash) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than a setter can we just use composite instantiation? What does the setter buy us?

ie. mh := MiningHeader{lastBlock, rootHash, target}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it sets the time to the time variable to the current time in the miningheader struct, not quite sure how to set default values in golang structs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value is the zero value for the type. You can do something like:

mh := MiningHeader{
  LastBlock: lastBlock,
  ...
  timeField: time.Now(),
}

mh.LastBlock = lastBlock
mh.RootHash = rootHash
mh.Target = target
mh.Time = uint32(time.Now().Unix())
mh.Nonce = 0
}

// VerifyProofOfWork computes the hash of the MiningHeader and returns true if the result is less than the target
func (mh *MiningHeader) VerifyProofOfWork() bool {
if CompareTo(mh.DoubleHashSum(), mh.Target, LessThan) {
return true
}

return false
}

// DoubleHashSum computes the hash 256 of the marshalled mining header twice
func (mh *MiningHeader) DoubleHashSum() Hash {
hash := sha256.Sum256(mh.Marshal())
hash = sha256.Sum256(hash[:])
return hash
}

// Mine continuously increases the nonce and tries to verify the proof of work until the puzzle is solved
func (mh *MiningHeader) Mine() bool {
if mh.VerifyMiningHeader() == false {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use ! rather than comparing to false

return false
}

for mh.VerifyProofOfWork() == false {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

if mh.Nonce == math.MaxUint64 {
mh.Nonce = 0
}
mh.Time = uint32(time.Now().Unix())
mh.Nonce++
}
return true
}

// VerifyMiningHeader confirms that the mining header is properly set
func (mh *MiningHeader) VerifyMiningHeader() bool {
if mh.Time == 0 || mh.Time > uint32(time.Now().Unix()) {
log.Error("Invalid time in mining header")
return false
}
if CompareTo(mh.Target, MinHash, EqualTo) || CompareTo(mh.Target, MaxDifficulty, GreaterThan) {
log.Error("Invalid target in mining header")
return false
}
return true
}

// AppendUint32ToSlice converts a uint32 to a byte slice and appends it to a byte slice
func AppendUint32ToSlice(s *[]byte, num uint32) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this function? (and below) seems like they're only called once

buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, num)
*s = append(*s, buf...)
}

// AppendUint64ToSlice converts a uint64 to a byte slice and appends it to a byte slice
func AppendUint64ToSlice(s *[]byte, num uint64) {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, num)
*s = append(*s, buf...)
}
13 changes: 13 additions & 0 deletions blockchain/miner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package blockchain

import (
"fmt"
"testing"
)

func TestMine(t *testing.T) {
var mh MiningHeader
bc, b := newValidChainAndBlock()
mh.SetMiningHeader(b.LastBlock, bc.Head, MaxDifficulty)
fmt.Println(mh.Mine())
}