-
Notifications
You must be signed in to change notification settings - Fork 67
/
blockchain.go
203 lines (173 loc) · 5.65 KB
/
blockchain.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package gochain
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"log"
)
type BlockchainService interface {
// Add a new node to the list of nodes
RegisterNode(address string) bool
// Determine if a given blockchain is valid
ValidChain(chain Blockchain) bool
// This is our Consensus Algorithm, it resolves conflicts by replacing
// our chain with the longest one in the network.
ResolveConflicts() bool
// Create a new Block in the Blockchain
NewBlock(proof int64, previousHash string) Block
// Creates a new transaction to go into the next mined Block
NewTransaction(tx Transaction) int64
// Returns the last block on the chain
LastBlock() Block
// Simple Proof of Work Algorithm:
// - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
// - p is the previous proof, and p' is the new proof
ProofOfWork(lastProof int64)
// Validates the Proof: Does hash(lastProof, proof) contain 4 leading zeroes?
VerifyProof(lastProof, proof int64) bool
}
type Block struct {
Index int64 `json:"index"`
Timestamp int64 `json:"timestamp"`
Transactions []Transaction `json:"transactions"`
Proof int64 `json:"proof"`
PreviousHash string `json:"previous_hash"`
}
type Transaction struct {
Sender string `json:"sender"`
Recipient string `json:"recipient"`
Amount int64 `json:"amount"`
}
type Blockchain struct {
chain []Block
transactions []Transaction
nodes StringSet
}
func (bc *Blockchain) NewBlock(proof int64, previousHash string) Block {
prevHash := previousHash
if previousHash == "" {
prevBlock := bc.chain[len(bc.chain)-1]
prevHash = computeHashForBlock(prevBlock)
}
newBlock := Block{
Index: int64(len(bc.chain) + 1),
Timestamp: time.Now().UnixNano(),
Transactions: bc.transactions,
Proof: proof,
PreviousHash: prevHash,
}
bc.transactions = nil
bc.chain = append(bc.chain, newBlock)
return newBlock
}
func (bc *Blockchain) NewTransaction(tx Transaction) int64 {
bc.transactions = append(bc.transactions, tx)
return bc.LastBlock().Index + 1
}
func (bc *Blockchain) LastBlock() Block {
return bc.chain[len(bc.chain)-1]
}
func (bc *Blockchain) ProofOfWork(lastProof int64) int64 {
var proof int64 = 0
for !bc.ValidProof(lastProof, proof) {
proof += 1
}
return proof
}
func (bc *Blockchain) ValidProof(lastProof, proof int64) bool {
guess := fmt.Sprintf("%d%d", lastProof, proof)
guessHash := ComputeHashSha256([]byte(guess))
return guessHash[:4] == "0000"
}
func (bc *Blockchain) ValidChain(chain *[]Block) bool {
lastBlock := (*chain)[0]
currentIndex := 1
for currentIndex < len(*chain) {
block := (*chain)[currentIndex]
// Check that the hash of the block is correct
if block.PreviousHash != computeHashForBlock(lastBlock) {
return false
}
// Check that the Proof of Work is correct
if !bc.ValidProof(lastBlock.Proof, block.Proof) {
return false
}
lastBlock = block
currentIndex += 1
}
return true
}
func (bc *Blockchain) RegisterNode(address string) bool {
u, err := url.Parse(address)
if err != nil {
return false
}
return bc.nodes.Add(u.Host)
}
func (bc *Blockchain) ResolveConflicts() bool {
neighbours := bc.nodes
newChain := make([]Block, 0)
// We're only looking for chains longer than ours
maxLength := len(bc.chain)
// Grab and verify the chains from all the nodes in our network
for _, node := range neighbours.Keys() {
otherBlockchain, err := findExternalChain(node)
if err != nil {
continue
}
// Check if the length is longer and the chain is valid
if otherBlockchain.Length > maxLength && bc.ValidChain(&otherBlockchain.Chain) {
maxLength = otherBlockchain.Length
newChain = otherBlockchain.Chain
}
}
// Replace our chain if we discovered a new, valid chain longer than ours
if len(newChain) > 0 {
bc.chain = newChain
return true
}
return false
}
func NewBlockchain() *Blockchain {
newBlockchain := &Blockchain{
chain: make([]Block, 0),
transactions: make([]Transaction, 0),
nodes: NewStringSet(),
}
// Initial, sentinel block
newBlockchain.NewBlock(100, "1")
return newBlockchain
}
func computeHashForBlock(block Block) string {
var buf bytes.Buffer
// Data for binary.Write must be a fixed-size value or a slice of fixed-size values,
// or a pointer to such data.
jsonblock, marshalErr := json.Marshal(block)
if marshalErr != nil {
log.Fatalf("Could not marshal block: %s", marshalErr.Error())
}
hashingErr := binary.Write(&buf, binary.BigEndian, jsonblock)
if hashingErr != nil {
log.Fatalf("Could not hash block: %s", hashingErr.Error())
}
return ComputeHashSha256(buf.Bytes())
}
type blockchainInfo struct {
Length int `json:"length"`
Chain []Block `json:"chain"`
}
func findExternalChain(address string) (blockchainInfo, error) {
response, err := http.Get(fmt.Sprintf("http://%s/chain", address))
if err == nil && response.StatusCode == http.StatusOK {
var bi blockchainInfo
if err := json.NewDecoder(response.Body).Decode(&bi); err != nil {
return blockchainInfo{}, err
}
return bi, nil
}
return blockchainInfo{}, err
}