You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

179 lines
4.4 KiB

package core
import (
"bytes"
"encoding/json"
"log"
"net/http"
"strings"
"time"
)
// Blockchain struct
type Blockchain struct {
Hostname string
Blocks []Block
PendingTransactions []Transaction
Peers []string
}
// MineBlock adds a block to the chain
func (bc *Blockchain) MineBlock() Block {
block := Block{}
block.Data = bc.PendingTransactions
block.Timestamp = time.Now()
block.PreviousHash = bc.GetLastHash()
// Mine Block
log.Print("Mining Block...")
for {
if strings.HasPrefix(block.Hash(), "0000") {
bc.Blocks = append(bc.Blocks, block)
bc.PendingTransactions = []Transaction{}
bc.NotifyPeers()
log.Print("Block Added: ", block.Hash())
return block
}
block.Proof++
}
}
// NewBlockchain creates a new Blockchain
func NewBlockchain(hostname string) Blockchain {
log.Print("Creating Blockchain...")
blockchain := Blockchain{Hostname: hostname, Blocks: make([]Block, 0), PendingTransactions: make([]Transaction, 0), Peers: make([]string, 0)}
// Mine Genesis Block
blockchain.MineBlock()
return blockchain
}
// AddTransaction takes in a transaction and adds it to the block
func (bc *Blockchain) AddTransaction(transaction Transaction) error {
bc.PendingTransactions = append(bc.PendingTransactions, transaction)
return nil
}
// GetLastHash returns the hash of the latest block on the chain
func (bc *Blockchain) GetLastHash() string {
bcLength := len(bc.Blocks)
if bcLength == 0 {
return "0"
}
return bc.Blocks[len(bc.Blocks)-1].Hash()
}
// AddPeer appends the IP address to the list of peers known to the chain
func (bc *Blockchain) AddPeer(peer string) (resp *http.Response, err error) {
resp, err = http.Get("http://" + peer)
// Return Error, if an error occured
if err != nil {
return nil, err
}
// Add Peer, if it is not already in the list
if !contains(bc.Peers, peer) {
bc.Peers = append(bc.Peers, peer)
var body []string
data, _ := json.Marshal(append(body, bc.Hostname))
log.Println("request http://" + peer + "/add_peers")
http.Post("http://"+peer+"/add_peers", "application/json", bytes.NewBuffer(data))
}
return resp, nil
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// IsValid checks, if the chain has any faulty blocks
func (bc Blockchain) IsValid() bool {
for i := 1; i < len(bc.Blocks); i++ {
if bc.Blocks[i-1].Hash() != bc.Blocks[i].PreviousHash {
return false
}
}
return true
}
// NotifyPeers requests an update to all peers
func (bc *Blockchain) NotifyPeers() {
for _, peer := range bc.Peers {
http.Get("http://" + peer + "/update")
}
}
// Update compares the length of the own chain to the blocklength of every known peer, and updates the chain accordingly
func (bc *Blockchain) Update() bool {
var hasBeenReplaced = false
for _, peer := range bc.Peers {
resp, err := http.Get("http://" + peer)
decoder := json.NewDecoder(resp.Body)
var receivedBlockchain JSONBlockchain
err = decoder.Decode(&receivedBlockchain)
if err == nil {
if receivedBlockchain.Blockcount > bc.AsJSON().Blockcount && receivedBlockchain.FromJSON().IsValid() {
bc.replaceChain(receivedBlockchain.FromJSON())
log.Println("Blockchain has been updated by " + peer)
hasBeenReplaced = true
}
}
}
return hasBeenReplaced
}
func (bc *Blockchain) replaceChain(newChain Blockchain) {
newBlocks := make([]Block, 0)
for _, block := range newChain.Blocks {
newBlocks = append(newBlocks, block)
}
bc.Blocks = newBlocks
bc.NotifyPeers()
}
// JSONBlockchain is needed, because the hash of each block is calculated dynamically, and therefore is not stored in the `Block` struct
type JSONBlockchain struct {
Blocks []JSONBlock
Blockcount int
PendingTransactions []Transaction
Peers []string
}
// AsJSON returns the Blockchain as a JSON Blockchain
func (bc *Blockchain) AsJSON() JSONBlockchain {
jsonChain := JSONBlockchain{PendingTransactions: bc.PendingTransactions, Peers: bc.Peers}
for _, block := range bc.Blocks {
jsonChain.Blocks = append(jsonChain.Blocks, block.AsJSON())
}
jsonChain.Blockcount = len(jsonChain.Blocks)
return jsonChain
}
// FromJSON casts a `JSONBlockchain` to a regular `Blockchain` struct
func (bc *JSONBlockchain) FromJSON() Blockchain {
chain := Blockchain{PendingTransactions: bc.PendingTransactions, Peers: bc.Peers}
for _, block := range bc.Blocks {
chain.Blocks = append(chain.Blocks, block.FromJSON())
}
return chain
}