-
Notifications
You must be signed in to change notification settings - Fork 2
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
Changes from 11 commits
ba7b9f5
df7c3e9
900ca56
5d395c9
c7e58ee
2e9a038
7cb8898
6f0dd8b
940659e
93a58f2
9401181
3ff4c9c
9673713
0666a58
82896d5
6397882
79614ae
cf17cfc
8446f1b
ef80df5
f110208
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,38 @@ | ||
package blockchain | ||
|
||
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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be replace by using |
||
// 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 | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be replaced by |
||
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 | ||
} |
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() | ||
} | ||
} |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
return false | ||
} | ||
|
||
for mh.VerifyProofOfWork() == false { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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...) | ||
} |
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()) | ||
} |
There was a problem hiding this comment.
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