Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Jeiwan/blockchain_go
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: part_3
Choose a base ref
...
head repository: Jeiwan/blockchain_go
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: part_4
Choose a head ref
Able to merge. These branches can be automatically merged.

Commits on Aug 29, 2017

  1. Update README

    Jeiwan committed Aug 29, 2017
    Copy the full SHA
    d3b2c5c View commit details

Commits on Sep 3, 2017

  1. Implement transactions

    Jeiwan committed Sep 3, 2017
    Copy the full SHA
    2ba0f1b View commit details
  2. Copy the full SHA
    08a211b View commit details
  3. Copy the full SHA
    206f87e View commit details
  4. Copy the full SHA
    46a1654 View commit details
  5. Fix printChain

    Jeiwan committed Sep 3, 2017
    Copy the full SHA
    8e66369 View commit details
  6. Copy the full SHA
    6941c5f View commit details
  7. Copy the full SHA
    95d3f69 View commit details
  8. Rework Blockchain.FindUTXOs

    Jeiwan committed Sep 3, 2017
    Copy the full SHA
    f83ccd7 View commit details
  9. Copy the full SHA
    87eb17b View commit details
  10. Copy the full SHA
    751d791 View commit details
  11. Copy the full SHA
    6388b20 View commit details

Commits on Sep 4, 2017

  1. Minor improvements

    Jeiwan committed Sep 4, 2017
    Copy the full SHA
    78dbfc6 View commit details
  2. Copy the full SHA
    326ecb8 View commit details
  3. Copy the full SHA
    32dd771 View commit details
  4. Set PoW target to 24

    Jeiwan committed Sep 4, 2017
    Copy the full SHA
    7904009 View commit details

Commits on Sep 5, 2017

  1. Copy the full SHA
    c748768 View commit details
  2. Minor improvements

    Jeiwan committed Sep 5, 2017
    Copy the full SHA
    f4ae516 View commit details
  3. Rework UTXO related functions

    Jeiwan committed Sep 5, 2017
    Copy the full SHA
    e89846d View commit details
  4. Final fixes

    Jeiwan committed Sep 5, 2017
    Copy the full SHA
    d107d92 View commit details
  5. Add a link to the README

    Jeiwan committed Sep 5, 2017
    Copy the full SHA
    373a09b View commit details

Commits on Oct 31, 2017

  1. Copy the full SHA
    46e935c View commit details

Commits on Mar 1, 2018

  1. Merge pull request #6 from samguns/part_4

    SetID method should use pointer receiver.
    Jeiwan authored Mar 1, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    bf64f33 View commit details

Commits on Jun 26, 2021

  1. Fix URLs in README

    Jeiwan committed Jun 26, 2021
    Copy the full SHA
    e17722e View commit details
Showing with 366 additions and 56 deletions.
  1. +4 −2 README.md
  2. +19 −5 block.go
  3. +150 −24 blockchain.go
  4. +79 −19 cli.go
  5. +1 −4 main.go
  6. +2 −2 proofofwork.go
  7. +111 −0 transaction.go
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
@@ -2,6 +2,7 @@ package main

import (
"bytes"
"crypto/sha256"
"encoding/gob"
"log"
"time"
@@ -10,7 +11,7 @@ import (
// Block keeps block headers
type Block struct {
Timestamp int64
Data []byte
Transactions []*Transaction
PrevBlockHash []byte
Hash []byte
Nonce int
@@ -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()

@@ -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
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
@@ -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 {
@@ -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))
@@ -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}

@@ -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 {
@@ -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
})
98 changes: 79 additions & 19 deletions cli.go
Original file line number Diff line number Diff line change
@@ -9,14 +9,34 @@ import (
)

// CLI responsible for processing command line arguments
type CLI struct {
bc *Blockchain
type CLI struct{}

func (cli *CLI) createBlockchain(address string) {
bc := CreateBlockchain(address)
bc.db.Close()
fmt.Println("Done!")
}

func (cli *CLI) getBalance(address string) {
bc := NewBlockchain(address)
defer bc.db.Close()

balance := 0
UTXOs := bc.FindUTXO(address)

for _, out := range UTXOs {
balance += out.Value
}

fmt.Printf("Balance of '%s': %d\n", address, balance)
}

func (cli *CLI) printUsage() {
fmt.Println("Usage:")
fmt.Println(" addblock -data BLOCK_DATA - add a block to the blockchain")
fmt.Println(" printchain - print all the blocks of the blockchain")
fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS")
fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS")
fmt.Println(" printchain - Print all the blocks of the blockchain")
fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO")
}

func (cli *CLI) validateArgs() {
@@ -26,19 +46,17 @@ func (cli *CLI) validateArgs() {
}
}

func (cli *CLI) addBlock(data string) {
cli.bc.AddBlock(data)
fmt.Println("Success!")
}

func (cli *CLI) printChain() {
bci := cli.bc.Iterator()
// TODO: Fix this
bc := NewBlockchain("")
defer bc.db.Close()

bci := bc.Iterator()

for {
block := bci.Next()

fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
pow := NewProofOfWork(block)
fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
@@ -50,18 +68,38 @@ func (cli *CLI) printChain() {
}
}

func (cli *CLI) send(from, to string, amount int) {
bc := NewBlockchain(from)
defer bc.db.Close()

tx := NewUTXOTransaction(from, to, amount, bc)
bc.MineBlock([]*Transaction{tx})
fmt.Println("Success!")
}

// Run parses command line arguments and processes commands
func (cli *CLI) Run() {
cli.validateArgs()

addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)
getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError)
createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)
sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)

addBlockData := addBlockCmd.String("data", "", "Block data")
getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for")
createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to")
sendFrom := sendCmd.String("from", "", "Source wallet address")
sendTo := sendCmd.String("to", "", "Destination wallet address")
sendAmount := sendCmd.Int("amount", 0, "Amount to send")

switch os.Args[1] {
case "addblock":
err := addBlockCmd.Parse(os.Args[2:])
case "getbalance":
err := getBalanceCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "createblockchain":
err := createBlockchainCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
@@ -70,20 +108,42 @@ func (cli *CLI) Run() {
if err != nil {
log.Panic(err)
}
case "send":
err := sendCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
default:
cli.printUsage()
os.Exit(1)
}

if addBlockCmd.Parsed() {
if *addBlockData == "" {
addBlockCmd.Usage()
if getBalanceCmd.Parsed() {
if *getBalanceAddress == "" {
getBalanceCmd.Usage()
os.Exit(1)
}
cli.addBlock(*addBlockData)
cli.getBalance(*getBalanceAddress)
}

if createBlockchainCmd.Parsed() {
if *createBlockchainAddress == "" {
createBlockchainCmd.Usage()
os.Exit(1)
}
cli.createBlockchain(*createBlockchainAddress)
}

if printChainCmd.Parsed() {
cli.printChain()
}

if sendCmd.Parsed() {
if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
sendCmd.Usage()
os.Exit(1)
}

cli.send(*sendFrom, *sendTo, *sendAmount)
}
}
5 changes: 1 addition & 4 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package main

func main() {
bc := NewBlockchain()
defer bc.db.Close()

cli := CLI{bc}
cli := CLI{}
cli.Run()
}
4 changes: 2 additions & 2 deletions proofofwork.go
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ func (pow *ProofOfWork) prepareData(nonce int) []byte {
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.Data,
pow.block.HashTransactions(),
IntToHex(pow.block.Timestamp),
IntToHex(int64(targetBits)),
IntToHex(int64(nonce)),
@@ -51,7 +51,7 @@ func (pow *ProofOfWork) Run() (int, []byte) {
var hash [32]byte
nonce := 0

fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
fmt.Printf("Mining a new block")
for nonce < maxNonce {
data := pow.prepareData(nonce)

111 changes: 111 additions & 0 deletions transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package main

import (
"bytes"
"crypto/sha256"
"encoding/gob"
"encoding/hex"
"fmt"
"log"
)

const subsidy = 10

// Transaction represents a Bitcoin transaction
type Transaction struct {
ID []byte
Vin []TXInput
Vout []TXOutput
}

// IsCoinbase checks whether the transaction is coinbase
func (tx Transaction) IsCoinbase() bool {
return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1
}

// SetID sets ID of a transaction
func (tx *Transaction) SetID() {
var encoded bytes.Buffer
var hash [32]byte

enc := gob.NewEncoder(&encoded)
err := enc.Encode(tx)
if err != nil {
log.Panic(err)
}
hash = sha256.Sum256(encoded.Bytes())
tx.ID = hash[:]
}

// TXInput represents a transaction input
type TXInput struct {
Txid []byte
Vout int
ScriptSig string
}

// TXOutput represents a transaction output
type TXOutput struct {
Value int
ScriptPubKey string
}

// CanUnlockOutputWith checks whether the address initiated the transaction
func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {
return in.ScriptSig == unlockingData
}

// CanBeUnlockedWith checks if the output can be unlocked with the provided data
func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {
return out.ScriptPubKey == unlockingData
}

// NewCoinbaseTX creates a new coinbase transaction
func NewCoinbaseTX(to, data string) *Transaction {
if data == "" {
data = fmt.Sprintf("Reward to '%s'", to)
}

txin := TXInput{[]byte{}, -1, data}
txout := TXOutput{subsidy, to}
tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
tx.SetID()

return &tx
}

// NewUTXOTransaction creates a new transaction
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
var inputs []TXInput
var outputs []TXOutput

acc, validOutputs := bc.FindSpendableOutputs(from, amount)

if acc < amount {
log.Panic("ERROR: Not enough funds")
}

// Build a list of inputs
for txid, outs := range validOutputs {
txID, err := hex.DecodeString(txid)
if err != nil {
log.Panic(err)
}

for _, out := range outs {
input := TXInput{txID, out, from}
inputs = append(inputs, input)
}
}

// Build a list of outputs
outputs = append(outputs, TXOutput{amount, to})
if acc > amount {
outputs = append(outputs, TXOutput{acc - amount, from}) // a change
}

tx := Transaction{nil, inputs, outputs}
tx.SetID()

return &tx
}