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 4 #62

Open
wants to merge 24 commits into
base: part_3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@

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/)
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/)
2. [Persistence and CLI](https://jeiwan.net/posts/building-blockchain-in-go-part-3/)
3. [Transactions 1](https://jeiwan.net/posts/building-blockchain-in-go-part-4/)
24 changes: 19 additions & 5 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"crypto/sha256"
"encoding/gob"
"log"
"time"
Expand All @@ -10,7 +11,7 @@ import (
// Block keeps block headers
type Block struct {
Timestamp int64
Data []byte
Transactions []*Transaction
PrevBlockHash []byte
Hash []byte
Nonce int
Expand All @@ -29,9 +30,22 @@ func (b *Block) Serialize() []byte {
return result.Bytes()
}

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

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

return txHash[:]
}

// NewBlock creates and returns Block
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0}
func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()

Expand All @@ -42,8 +56,8 @@ func NewBlock(data string, prevBlockHash []byte) *Block {
}

// NewGenesisBlock creates and returns genesis Block
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
func NewGenesisBlock(coinbase *Transaction) *Block {
return NewBlock([]*Transaction{coinbase}, []byte{})
}

// DeserializeBlock deserializes a block
Expand Down
174 changes: 150 additions & 24 deletions blockchain.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package main

import (
"encoding/hex"
"fmt"
"log"
"os"

"github.com/boltdb/bolt"
)

const dbFile = "blockchain.db"
const blocksBucket = "blocks"
const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"

// Blockchain keeps a sequence of Blocks
// Blockchain implements interactions with a DB
type Blockchain struct {
tip []byte
db *bolt.DB
Expand All @@ -22,8 +25,8 @@ type BlockchainIterator struct {
db *bolt.DB
}

// AddBlock saves provided data as a block in the blockchain
func (bc *Blockchain) AddBlock(data string) {
// MineBlock mines a new block with the provided transactions
func (bc *Blockchain) MineBlock(transactions []*Transaction) {
var lastHash []byte

err := bc.db.View(func(tx *bolt.Tx) error {
Expand All @@ -37,7 +40,7 @@ func (bc *Blockchain) AddBlock(data string) {
log.Panic(err)
}

newBlock := NewBlock(data, lastHash)
newBlock := NewBlock(transactions, lastHash)

err = bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
Expand All @@ -57,7 +60,94 @@ func (bc *Blockchain) AddBlock(data string) {
})
}

// Iterator ...
// FindUnspentTransactions returns a list of transactions containing unspent outputs
func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction {
var unspentTXs []Transaction
spentTXOs := make(map[string][]int)
bci := bc.Iterator()

for {
block := bci.Next()

for _, tx := range block.Transactions {
txID := hex.EncodeToString(tx.ID)

Outputs:
for outIdx, out := range tx.Vout {
// Was the output spent?
if spentTXOs[txID] != nil {
for _, spentOut := range spentTXOs[txID] {
if spentOut == outIdx {
continue Outputs
}
}
}

if out.CanBeUnlockedWith(address) {
unspentTXs = append(unspentTXs, *tx)
}
}

if tx.IsCoinbase() == false {
for _, in := range tx.Vin {
if in.CanUnlockOutputWith(address) {
inTxID := hex.EncodeToString(in.Txid)
spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
}
}
}
}

if len(block.PrevBlockHash) == 0 {
break
}
}

return unspentTXs
}

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

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

return UTXOs
}

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

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

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

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

return accumulated, unspentOutputs
}

// Iterator returns a BlockchainIterat
func (bc *Blockchain) Iterator() *BlockchainIterator {
bci := &BlockchainIterator{bc.tip, bc.db}

Expand Down Expand Up @@ -85,8 +175,21 @@ func (i *BlockchainIterator) Next() *Block {
return block
}

func dbExists() bool {
if _, err := os.Stat(dbFile); os.IsNotExist(err) {
return false
}

return true
}

// NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain() *Blockchain {
func NewBlockchain(address string) *Blockchain {
if dbExists() == false {
fmt.Println("No existing blockchain found. Create one first.")
os.Exit(1)
}

var tip []byte
db, err := bolt.Open(dbFile, 0600, nil)
if err != nil {
Expand All @@ -95,29 +198,52 @@ func NewBlockchain() *Blockchain {

err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
tip = b.Get([]byte("l"))

if b == nil {
fmt.Println("No existing blockchain found. Creating a new one...")
genesis := NewGenesisBlock()
return nil
})

b, err := tx.CreateBucket([]byte(blocksBucket))
if err != nil {
log.Panic(err)
}
if err != nil {
log.Panic(err)
}

err = b.Put(genesis.Hash, genesis.Serialize())
if err != nil {
log.Panic(err)
}
bc := Blockchain{tip, db}

err = b.Put([]byte("l"), genesis.Hash)
if err != nil {
log.Panic(err)
}
tip = genesis.Hash
} else {
tip = b.Get([]byte("l"))
return &bc
}

// CreateBlockchain creates a new blockchain DB
func CreateBlockchain(address string) *Blockchain {
if dbExists() {
fmt.Println("Blockchain already exists.")
os.Exit(1)
}

var tip []byte
db, err := bolt.Open(dbFile, 0600, nil)
if err != nil {
log.Panic(err)
}

err = db.Update(func(tx *bolt.Tx) error {
cbtx := NewCoinbaseTX(address, genesisCoinbaseData)
genesis := NewGenesisBlock(cbtx)

b, err := tx.CreateBucket([]byte(blocksBucket))
if err != nil {
log.Panic(err)
}

err = b.Put(genesis.Hash, genesis.Serialize())
if err != nil {
log.Panic(err)
}

err = b.Put([]byte("l"), genesis.Hash)
if err != nil {
log.Panic(err)
}
tip = genesis.Hash

return nil
})
Expand Down
Loading