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

Part 6 #63

Open
wants to merge 25 commits into
base: part_5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

A blockchain implementation in Go, as described in these articles:

1. [Basic Prototype](https://jeiwan.cc/posts/building-blockchain-in-go-part-1/)
2. [Proof-of-Work](https://jeiwan.cc/posts/building-blockchain-in-go-part-2/)
2. [Persistence and CLI](https://jeiwan.cc/posts/building-blockchain-in-go-part-3/)
3. [Transactions 1](https://jeiwan.cc/posts/building-blockchain-in-go-part-4/)
3. [Addresses](https://jeiwan.cc/posts/building-blockchain-in-go-part-5/)
1. [Basic Prototype](https://jeiwan.net/posts/building-blockchain-in-go-part-1/)
2. [Proof-of-Work](https://jeiwan.net/posts/building-blockchain-in-go-part-2/)
3. [Persistence and CLI](https://jeiwan.net/posts/building-blockchain-in-go-part-3/)
4. [Transactions 1](https://jeiwan.net/posts/building-blockchain-in-go-part-4/)
5. [Addresses](https://jeiwan.net/posts/building-blockchain-in-go-part-5/)
6. [Transactions 2](https://jeiwan.net/posts/building-blockchain-in-go-part-6/)
10 changes: 4 additions & 6 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"bytes"
"crypto/sha256"
"encoding/gob"
"log"
"time"
Expand Down Expand Up @@ -36,15 +35,14 @@ func NewGenesisBlock(coinbase *Transaction) *Block {

// HashTransactions returns a hash of the transactions in the block
func (b *Block) HashTransactions() []byte {
var txHashes [][]byte
var txHash [32]byte
var transactions [][]byte

for _, tx := range b.Transactions {
txHashes = append(txHashes, tx.Hash())
transactions = append(transactions, tx.Serialize())
}
txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))
mTree := NewMerkleTree(transactions)

return txHash[:]
return mTree.RootNode.Data
}

// Serialize serializes the block
Expand Down
71 changes: 17 additions & 54 deletions blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func CreateBlockchain(address string) *Blockchain {
}

// NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain(address string) *Blockchain {
func NewBlockchain() *Blockchain {
if dbExists() == false {
fmt.Println("No existing blockchain found. Create one first.")
os.Exit(1)
Expand All @@ -95,31 +95,6 @@ func NewBlockchain(address string) *Blockchain {
return &bc
}

// FindSpendableOutputs finds and returns unspent outputs to reference in inputs
func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) {
unspentOutputs := make(map[string][]int)
unspentTXs := bc.FindUnspentTransactions(pubKeyHash)
accumulated := 0

Work:
for _, tx := range unspentTXs {
txID := hex.EncodeToString(tx.ID)

for outIdx, out := range tx.Vout {
if out.IsLockedWithKey(pubKeyHash) && accumulated < amount {
accumulated += out.Value
unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)

if accumulated >= amount {
break Work
}
}
}
}

return accumulated, unspentOutputs
}

// FindTransaction finds a transaction by its ID
func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
bci := bc.Iterator()
Expand All @@ -141,9 +116,9 @@ func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
return Transaction{}, errors.New("Transaction is not found")
}

// FindUnspentTransactions returns a list of transactions containing unspent outputs
func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction {
var unspentTXs []Transaction
// FindUTXO finds all unspent transaction outputs and returns transactions with spent outputs removed
func (bc *Blockchain) FindUTXO() map[string]TXOutputs {
UTXO := make(map[string]TXOutputs)
spentTXOs := make(map[string][]int)
bci := bc.Iterator()

Expand All @@ -164,17 +139,15 @@ func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction {
}
}

if out.IsLockedWithKey(pubKeyHash) {
unspentTXs = append(unspentTXs, *tx)
}
outs := UTXO[txID]
outs.Outputs = append(outs.Outputs, out)
UTXO[txID] = outs
}

if tx.IsCoinbase() == false {
for _, in := range tx.Vin {
if in.UsesKey(pubKeyHash) {
inTxID := hex.EncodeToString(in.Txid)
spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
}
inTxID := hex.EncodeToString(in.Txid)
spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
}
}
}
Expand All @@ -184,23 +157,7 @@ func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction {
}
}

return unspentTXs
}

// FindUTXO finds and returns all unspent transaction outputs
func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput {
var UTXOs []TXOutput
unspentTransactions := bc.FindUnspentTransactions(pubKeyHash)

for _, tx := range unspentTransactions {
for _, out := range tx.Vout {
if out.IsLockedWithKey(pubKeyHash) {
UTXOs = append(UTXOs, out)
}
}
}

return UTXOs
return UTXO
}

// Iterator returns a BlockchainIterat
Expand All @@ -211,7 +168,7 @@ func (bc *Blockchain) Iterator() *BlockchainIterator {
}

// MineBlock mines a new block with the provided transactions
func (bc *Blockchain) MineBlock(transactions []*Transaction) {
func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block {
var lastHash []byte

for _, tx := range transactions {
Expand Down Expand Up @@ -251,6 +208,8 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) {
if err != nil {
log.Panic(err)
}

return newBlock
}

// SignTransaction signs inputs of a Transaction
Expand All @@ -270,6 +229,10 @@ func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey)

// VerifyTransaction verifies transaction input signatures
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
if tx.IsCoinbase() {
return true
}

prevTXs := make(map[string]Transaction)

for _, vin := range tx.Vin {
Expand Down
13 changes: 12 additions & 1 deletion cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func (cli *CLI) printUsage() {
fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS")
fmt.Println(" listaddresses - Lists all addresses from the wallet file")
fmt.Println(" printchain - Print all the blocks of the blockchain")
fmt.Println(" reindexutxo - Rebuilds the UTXO set")
fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO")
}

Expand All @@ -35,8 +36,9 @@ func (cli *CLI) Run() {
createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)
createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError)
listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError)
sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
reindexUTXOCmd := flag.NewFlagSet("reindexutxo", flag.ExitOnError)
sendCmd := flag.NewFlagSet("send", flag.ExitOnError)

getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for")
createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to")
Expand Down Expand Up @@ -75,6 +77,11 @@ func (cli *CLI) Run() {
if err != nil {
log.Panic(err)
}
case "reindexutxo":
err := reindexUTXOCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
default:
cli.printUsage()
os.Exit(1)
Expand Down Expand Up @@ -108,6 +115,10 @@ func (cli *CLI) Run() {
cli.printChain()
}

if reindexUTXOCmd.Parsed() {
cli.reindexUTXO()
}

if sendCmd.Parsed() {
if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
sendCmd.Usage()
Expand Down
6 changes: 5 additions & 1 deletion cli_createblockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ func (cli *CLI) createBlockchain(address string) {
log.Panic("ERROR: Address is not valid")
}
bc := CreateBlockchain(address)
bc.db.Close()
defer bc.db.Close()

UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()

fmt.Println("Done!")
}
5 changes: 3 additions & 2 deletions cli_getbalance.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ func (cli *CLI) getBalance(address string) {
if !ValidateAddress(address) {
log.Panic("ERROR: Address is not valid")
}
bc := NewBlockchain(address)
bc := NewBlockchain()
UTXOSet := UTXOSet{bc}
defer bc.db.Close()

balance := 0
pubKeyHash := Base58Decode([]byte(address))
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
UTXOs := bc.FindUTXO(pubKeyHash)
UTXOs := UTXOSet.FindUTXO(pubKeyHash)

for _, out := range UTXOs {
balance += out.Value
Expand Down
2 changes: 1 addition & 1 deletion cli_printchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

func (cli *CLI) printChain() {
bc := NewBlockchain("")
bc := NewBlockchain()
defer bc.db.Close()

bci := bc.Iterator()
Expand Down
12 changes: 12 additions & 0 deletions cli_reindexutxo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import "fmt"

func (cli *CLI) reindexUTXO() {
bc := NewBlockchain()
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()

count := UTXOSet.CountTransactions()
fmt.Printf("Done! There are %d transactions in the UTXO set.\n", count)
}
11 changes: 8 additions & 3 deletions cli_send.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ func (cli *CLI) send(from, to string, amount int) {
log.Panic("ERROR: Recipient address is not valid")
}

bc := NewBlockchain(from)
bc := NewBlockchain()
UTXOSet := UTXOSet{bc}
defer bc.db.Close()

tx := NewUTXOTransaction(from, to, amount, bc)
bc.MineBlock([]*Transaction{tx})
tx := NewUTXOTransaction(from, to, amount, &UTXOSet)
cbTx := NewCoinbaseTX(from, "")
txs := []*Transaction{cbTx, tx}

newBlock := bc.MineBlock(txs)
UTXOSet.Update(newBlock)
fmt.Println("Success!")
}
65 changes: 65 additions & 0 deletions merkle_tree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"crypto/sha256"
)

// MerkleTree represent a Merkle tree
type MerkleTree struct {
RootNode *MerkleNode
}

// MerkleNode represent a Merkle tree node
type MerkleNode struct {
Left *MerkleNode
Right *MerkleNode
Data []byte
}

// NewMerkleTree creates a new Merkle tree from a sequence of data
func NewMerkleTree(data [][]byte) *MerkleTree {
var nodes []MerkleNode

if len(data)%2 != 0 {
data = append(data, data[len(data)-1])
}

for _, datum := range data {
node := NewMerkleNode(nil, nil, datum)
nodes = append(nodes, *node)
}

for i := 0; i < len(data)/2; i++ {
var newLevel []MerkleNode

for j := 0; j < len(nodes); j += 2 {
node := NewMerkleNode(&nodes[j], &nodes[j+1], nil)
newLevel = append(newLevel, *node)
}

nodes = newLevel
}

mTree := MerkleTree{&nodes[0]}

return &mTree
}

// NewMerkleNode creates a new Merkle tree node
func NewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode {
mNode := MerkleNode{}

if left == nil && right == nil {
hash := sha256.Sum256(data)
mNode.Data = hash[:]
} else {
prevHashes := append(left.Data, right.Data...)
hash := sha256.Sum256(prevHashes)
mNode.Data = hash[:]
}

mNode.Left = left
mNode.Right = right

return &mNode
}
Loading