From 56ccd7c8caaea1782a52dd7affeec5a02b77b4f1 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 09:16:50 +0700 Subject: [PATCH 01/24] Implement rewards --- blockchain.go | 4 ++++ cli_send.go | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/blockchain.go b/blockchain.go index e2579fe..543ddc2 100644 --- a/blockchain.go +++ b/blockchain.go @@ -270,6 +270,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 { diff --git a/cli_send.go b/cli_send.go index fb8117c..fea84d8 100644 --- a/cli_send.go +++ b/cli_send.go @@ -17,6 +17,9 @@ func (cli *CLI) send(from, to string, amount int) { defer bc.db.Close() tx := NewUTXOTransaction(from, to, amount, bc) - bc.MineBlock([]*Transaction{tx}) + cbTx := NewCoinbaseTX(from, "") + txs := []*Transaction{cbTx, tx} + + bc.MineBlock(txs) fmt.Println("Success!") } From 01b9dd2eabd2ce5a9bb371b0f35a4d606353c756 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 10:04:28 +0700 Subject: [PATCH 02/24] Implement Blockchain.FindAllUTXO --- blockchain.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/blockchain.go b/blockchain.go index 543ddc2..bd2e721 100644 --- a/blockchain.go +++ b/blockchain.go @@ -203,6 +203,50 @@ func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { return UTXOs } +// FindAllUTXO finds all unspent transaction outputs and returns transactions with spent outputs removed +func (bc *Blockchain) FindAllUTXO() map[string]TXOutputs { + UTXO := make(map[string]TXOutputs) + 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 _, spentOutIdx := range spentTXOs[txID] { + if spentOutIdx == outIdx { + continue Outputs + } + } + } + + outs := UTXO[txID] + outs.Outputs = append(outs.Outputs, out) + UTXO[txID] = outs + } + + if tx.IsCoinbase() == false { + for _, in := range tx.Vin { + inTxID := hex.EncodeToString(in.Txid) + spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) + } + } + } + + if len(block.PrevBlockHash) == 0 { + break + } + } + + return UTXO +} + // Iterator returns a BlockchainIterat func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} From 2f54328190cdf8c1683fd7b340714a5e3a6cab72 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 10:15:58 +0700 Subject: [PATCH 03/24] Implement TXOutputs --- transaction_output.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/transaction_output.go b/transaction_output.go index 4d70d74..34dabad 100644 --- a/transaction_output.go +++ b/transaction_output.go @@ -1,6 +1,10 @@ package main -import "bytes" +import ( + "bytes" + "encoding/gob" + "log" +) // TXOutput represents a transaction output type TXOutput struct { @@ -27,3 +31,21 @@ func NewTXOutput(value int, address string) *TXOutput { return txo } + +// TXOutputs collects TXOutput +type TXOutputs struct { + Outputs []TXOutput +} + +// Serialize serializes TXOutputs +func (outs TXOutputs) Serialize() []byte { + var buff bytes.Buffer + + enc := gob.NewEncoder(&buff) + err := enc.Encode(outs) + if err != nil { + log.Panic(err) + } + + return buff.Bytes() +} From 249b7f4effe13989b6663fb5d80ec1e5d3c5489e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 10:16:14 +0700 Subject: [PATCH 04/24] Implement UTXOSet --- utxo_set.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 utxo_set.go diff --git a/utxo_set.go b/utxo_set.go new file mode 100644 index 0000000..e43c32e --- /dev/null +++ b/utxo_set.go @@ -0,0 +1,86 @@ +package main + +import ( + "encoding/hex" + "log" + + "github.com/boltdb/bolt" +) + +const utxoBucket = "chainstate" + +// UTXOSet represents UTXO set +type UTXOSet struct{} + +// Reindex rebuilds the UTXO set +func (u UTXOSet) Reindex(bc *Blockchain) { + db, err := bolt.Open(dbFile, 0600, nil) + if err != nil { + log.Panic(err) + } + + err = db.Update(func(tx *bolt.Tx) error { + bucketName := []byte(utxoBucket) + b := tx.Bucket(bucketName) + + if b != nil { + err := tx.DeleteBucket(bucketName) + if err != nil { + log.Panic(err) + } + } + + _, err := tx.CreateBucket(bucketName) + if err != nil { + log.Panic(err) + } + + return nil + }) + if err != nil { + log.Panic(err) + } + + UTXO := bc.FindAllUTXO() + + err = db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(utxoBucket)) + + for txID, outs := range UTXO { + key, err := hex.DecodeString(txID) + if err != nil { + log.Panic(err) + } + + err = b.Put(key, outs.Serialize()) + if err != nil { + log.Panic(err) + } + } + + return nil + }) +} + +// GetCount returns the number of transactions in the UTXO set +func (u UTXOSet) GetCount() int { + counter := 0 + + db, err := bolt.Open(dbFile, 0600, nil) + if err != nil { + log.Panic(err) + } + + err = db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(utxoBucket)) + c := b.Cursor() + + for k, _ := c.First(); k != nil; k, _ = c.Next() { + counter++ + } + + return nil + }) + + return counter +} From 7eda539141fdc633f9ca1478d27922710509ef6a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 10:30:30 +0700 Subject: [PATCH 05/24] Improve UTXOSet --- cli.go | 13 ++++++++++++- cli_reindexutxo.go | 12 ++++++++++++ utxo_set.go | 26 ++++++++++++-------------- 3 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 cli_reindexutxo.go diff --git a/cli.go b/cli.go index 863ffd1..72693ea 100644 --- a/cli.go +++ b/cli.go @@ -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") } @@ -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") @@ -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) @@ -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() diff --git a/cli_reindexutxo.go b/cli_reindexutxo.go new file mode 100644 index 0000000..e2d3717 --- /dev/null +++ b/cli_reindexutxo.go @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func (cli *CLI) reindexUTXO() { + bc := NewBlockchain("") + UTXOSet := UTXOSet{bc} + UTXOSet.Reindex() + + count := UTXOSet.GetCount() + fmt.Printf("Done! There are %d transactions in the UTXO set.", count) +} diff --git a/utxo_set.go b/utxo_set.go index e43c32e..49c4c9f 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -10,16 +10,15 @@ import ( const utxoBucket = "chainstate" // UTXOSet represents UTXO set -type UTXOSet struct{} +type UTXOSet struct { + Blockchain *Blockchain +} // Reindex rebuilds the UTXO set -func (u UTXOSet) Reindex(bc *Blockchain) { - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } +func (u UTXOSet) Reindex() { + db := u.Blockchain.db - err = db.Update(func(tx *bolt.Tx) error { + err := db.Update(func(tx *bolt.Tx) error { bucketName := []byte(utxoBucket) b := tx.Bucket(bucketName) @@ -41,7 +40,7 @@ func (u UTXOSet) Reindex(bc *Blockchain) { log.Panic(err) } - UTXO := bc.FindAllUTXO() + UTXO := u.Blockchain.FindAllUTXO() err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(utxoBucket)) @@ -64,14 +63,10 @@ func (u UTXOSet) Reindex(bc *Blockchain) { // GetCount returns the number of transactions in the UTXO set func (u UTXOSet) GetCount() int { + db := u.Blockchain.db counter := 0 - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - - err = db.View(func(tx *bolt.Tx) error { + err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(utxoBucket)) c := b.Cursor() @@ -81,6 +76,9 @@ func (u UTXOSet) GetCount() int { return nil }) + if err != nil { + log.Panic(err) + } return counter } From cb78220abbec6cef17e51a4f3832fda32be08377 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 10:43:23 +0700 Subject: [PATCH 06/24] Remove the 'address' argument from NewBlockchain, since it's not used anymore --- blockchain.go | 2 +- cli_getbalance.go | 2 +- cli_printchain.go | 2 +- cli_reindexutxo.go | 2 +- cli_send.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/blockchain.go b/blockchain.go index bd2e721..89aa373 100644 --- a/blockchain.go +++ b/blockchain.go @@ -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) diff --git a/cli_getbalance.go b/cli_getbalance.go index 27cb55e..c67c536 100644 --- a/cli_getbalance.go +++ b/cli_getbalance.go @@ -9,7 +9,7 @@ func (cli *CLI) getBalance(address string) { if !ValidateAddress(address) { log.Panic("ERROR: Address is not valid") } - bc := NewBlockchain(address) + bc := NewBlockchain() defer bc.db.Close() balance := 0 diff --git a/cli_printchain.go b/cli_printchain.go index cd5c7a0..8541d2f 100644 --- a/cli_printchain.go +++ b/cli_printchain.go @@ -6,7 +6,7 @@ import ( ) func (cli *CLI) printChain() { - bc := NewBlockchain("") + bc := NewBlockchain() defer bc.db.Close() bci := bc.Iterator() diff --git a/cli_reindexutxo.go b/cli_reindexutxo.go index e2d3717..e6f841f 100644 --- a/cli_reindexutxo.go +++ b/cli_reindexutxo.go @@ -3,7 +3,7 @@ package main import "fmt" func (cli *CLI) reindexUTXO() { - bc := NewBlockchain("") + bc := NewBlockchain() UTXOSet := UTXOSet{bc} UTXOSet.Reindex() diff --git a/cli_send.go b/cli_send.go index fea84d8..f9c45d6 100644 --- a/cli_send.go +++ b/cli_send.go @@ -13,7 +13,7 @@ func (cli *CLI) send(from, to string, amount int) { log.Panic("ERROR: Recipient address is not valid") } - bc := NewBlockchain(from) + bc := NewBlockchain() defer bc.db.Close() tx := NewUTXOTransaction(from, to, amount, bc) From b15e1117f97b83e54d27f02c51284f29f8bec6c2 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 11:01:06 +0700 Subject: [PATCH 07/24] Implement DeserializeOutputs --- transaction_output.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/transaction_output.go b/transaction_output.go index 34dabad..2ae68de 100644 --- a/transaction_output.go +++ b/transaction_output.go @@ -49,3 +49,16 @@ func (outs TXOutputs) Serialize() []byte { return buff.Bytes() } + +// DeserializeOutputs deserializes TXOutputs +func DeserializeOutputs(data []byte) TXOutputs { + var outputs TXOutputs + + dec := gob.NewDecoder(bytes.NewReader(data)) + err := dec.Decode(&outputs) + if err != nil { + log.Panic(err) + } + + return outputs +} From c3aa6782911812ff0ff602dc4eb29c738f9cad4a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 11:01:18 +0700 Subject: [PATCH 08/24] Implment UTXOSet.FindUTXO --- utxo_set.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/utxo_set.go b/utxo_set.go index 49c4c9f..0ee851d 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -82,3 +82,31 @@ func (u UTXOSet) GetCount() int { return counter } + +// FindUTXO finds UTXO for a public key hash +func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { + var UTXOs []TXOutput + db := u.Blockchain.db + + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(utxoBucket)) + c := b.Cursor() + + for k, v := c.First(); k != nil; k, v = c.Next() { + outs := DeserializeOutputs(v) + + for _, out := range outs.Outputs { + if out.IsLockedWithKey(pubKeyHash) { + UTXOs = append(UTXOs, out) + } + } + } + + return nil + }) + if err != nil { + log.Panic(err) + } + + return UTXOs +} From e3739acac9f4133a120aafdf134e9ab75bb7915d Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 11:01:29 +0700 Subject: [PATCH 09/24] Use the UTXO set to get balance --- cli_getbalance.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli_getbalance.go b/cli_getbalance.go index c67c536..814569d 100644 --- a/cli_getbalance.go +++ b/cli_getbalance.go @@ -10,12 +10,13 @@ func (cli *CLI) getBalance(address string) { log.Panic("ERROR: Address is not valid") } 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 From 0b7d2ac63f1e92bf4032cbee9b5764e78526ff3f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 11:33:58 +0700 Subject: [PATCH 10/24] Remove Blockchain.FindUTXO --- blockchain.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/blockchain.go b/blockchain.go index 89aa373..2faa7c6 100644 --- a/blockchain.go +++ b/blockchain.go @@ -187,22 +187,6 @@ 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 -} - // FindAllUTXO finds all unspent transaction outputs and returns transactions with spent outputs removed func (bc *Blockchain) FindAllUTXO() map[string]TXOutputs { UTXO := make(map[string]TXOutputs) From 3e491be4d7bfd102e8c7f60c7e21767c194592d3 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 11:49:59 +0700 Subject: [PATCH 11/24] Use the UTXO set to send coins --- blockchain.go | 71 -------------------------------------------------- cli_send.go | 3 ++- transaction.go | 6 ++--- utxo_set.go | 31 ++++++++++++++++++++++ 4 files changed, 36 insertions(+), 75 deletions(-) diff --git a/blockchain.go b/blockchain.go index 2faa7c6..fa68abc 100644 --- a/blockchain.go +++ b/blockchain.go @@ -95,31 +95,6 @@ func NewBlockchain() *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() @@ -141,52 +116,6 @@ 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 - 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 _, spentOutIdx := range spentTXOs[txID] { - if spentOutIdx == outIdx { - continue Outputs - } - } - } - - if out.IsLockedWithKey(pubKeyHash) { - unspentTXs = append(unspentTXs, *tx) - } - } - - 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) - } - } - } - } - - if len(block.PrevBlockHash) == 0 { - break - } - } - - return unspentTXs -} - // FindAllUTXO finds all unspent transaction outputs and returns transactions with spent outputs removed func (bc *Blockchain) FindAllUTXO() map[string]TXOutputs { UTXO := make(map[string]TXOutputs) diff --git a/cli_send.go b/cli_send.go index f9c45d6..3528611 100644 --- a/cli_send.go +++ b/cli_send.go @@ -14,9 +14,10 @@ func (cli *CLI) send(from, to string, amount int) { } bc := NewBlockchain() + UTXOSet := UTXOSet{bc} defer bc.db.Close() - tx := NewUTXOTransaction(from, to, amount, bc) + tx := NewUTXOTransaction(from, to, amount, &UTXOSet) cbTx := NewCoinbaseTX(from, "") txs := []*Transaction{cbTx, tx} diff --git a/transaction.go b/transaction.go index 90950cc..e2fac83 100644 --- a/transaction.go +++ b/transaction.go @@ -185,7 +185,7 @@ func NewCoinbaseTX(to, data string) *Transaction { } // NewUTXOTransaction creates a new transaction -func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { +func NewUTXOTransaction(from, to string, amount int, UTXOSet *UTXOSet) *Transaction { var inputs []TXInput var outputs []TXOutput @@ -195,7 +195,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio } wallet := wallets.GetWallet(from) pubKeyHash := HashPubKey(wallet.PublicKey) - acc, validOutputs := bc.FindSpendableOutputs(pubKeyHash, amount) + acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount) if acc < amount { log.Panic("ERROR: Not enough funds") @@ -222,7 +222,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio tx := Transaction{nil, inputs, outputs} tx.ID = tx.Hash() - bc.SignTransaction(&tx, wallet.PrivateKey) + UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey) return &tx } diff --git a/utxo_set.go b/utxo_set.go index 0ee851d..74e7fec 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -110,3 +110,34 @@ func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { return UTXOs } + +// FindSpendableOutputs finds and returns unspent outputs to reference in inputs +func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) { + unspentOutputs := make(map[string][]int) + accumulated := 0 + db := u.Blockchain.db + + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(utxoBucket)) + c := b.Cursor() + + for k, v := c.First(); k != nil; k, v = c.Next() { + txID := hex.EncodeToString(k) + outs := DeserializeOutputs(v) + + for outIdx, out := range outs.Outputs { + if out.IsLockedWithKey(pubkeyHash) && accumulated < amount { + accumulated += out.Value + unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) + } + } + } + + return nil + }) + if err != nil { + log.Panic(err) + } + + return accumulated, unspentOutputs +} From fe34c88dfcaf32c2ce91df9b7cb34935aa9f3b8d Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 12:19:01 +0700 Subject: [PATCH 12/24] Implement UTXOSet.Update --- utxo_set.go | 184 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 119 insertions(+), 65 deletions(-) diff --git a/utxo_set.go b/utxo_set.go index 74e7fec..c85cf92 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -14,6 +14,87 @@ type UTXOSet struct { Blockchain *Blockchain } +// FindSpendableOutputs finds and returns unspent outputs to reference in inputs +func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) { + unspentOutputs := make(map[string][]int) + accumulated := 0 + db := u.Blockchain.db + + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(utxoBucket)) + c := b.Cursor() + + for k, v := c.First(); k != nil; k, v = c.Next() { + txID := hex.EncodeToString(k) + outs := DeserializeOutputs(v) + + for outIdx, out := range outs.Outputs { + if out.IsLockedWithKey(pubkeyHash) && accumulated < amount { + accumulated += out.Value + unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) + } + } + } + + return nil + }) + if err != nil { + log.Panic(err) + } + + return accumulated, unspentOutputs +} + +// FindUTXO finds UTXO for a public key hash +func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { + var UTXOs []TXOutput + db := u.Blockchain.db + + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(utxoBucket)) + c := b.Cursor() + + for k, v := c.First(); k != nil; k, v = c.Next() { + outs := DeserializeOutputs(v) + + for _, out := range outs.Outputs { + if out.IsLockedWithKey(pubKeyHash) { + UTXOs = append(UTXOs, out) + } + } + } + + return nil + }) + if err != nil { + log.Panic(err) + } + + return UTXOs +} + +// GetCount returns the number of transactions in the UTXO set +func (u UTXOSet) GetCount() int { + db := u.Blockchain.db + counter := 0 + + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(utxoBucket)) + c := b.Cursor() + + for k, _ := c.First(); k != nil; k, _ = c.Next() { + counter++ + } + + return nil + }) + if err != nil { + log.Panic(err) + } + + return counter +} + // Reindex rebuilds the UTXO set func (u UTXOSet) Reindex() { db := u.Blockchain.db @@ -61,45 +142,51 @@ func (u UTXOSet) Reindex() { }) } -// GetCount returns the number of transactions in the UTXO set -func (u UTXOSet) GetCount() int { - db := u.Blockchain.db - counter := 0 - - err := db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(utxoBucket)) - c := b.Cursor() - - for k, _ := c.First(); k != nil; k, _ = c.Next() { - counter++ - } - - return nil - }) - if err != nil { - log.Panic(err) - } - - return counter -} - -// FindUTXO finds UTXO for a public key hash -func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { - var UTXOs []TXOutput +// Update updates the UTXO set with transactions from the Block +// The Block is considered to be the tip of a blockchain +func (u UTXOSet) Update(block *Block) { db := u.Blockchain.db - err := db.View(func(tx *bolt.Tx) error { + err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(utxoBucket)) - c := b.Cursor() - for k, v := c.First(); k != nil; k, v = c.Next() { - outs := DeserializeOutputs(v) + for _, tx := range block.Transactions { + if tx.IsCoinbase() == false { + for _, vin := range tx.Vin { + updatedOuts := TXOutputs{} + data := b.Get(vin.Txid) + outs := DeserializeOutputs(data) + + for outIdx, out := range outs.Outputs { + if outIdx != vin.Vout { + updatedOuts.Outputs = append(updatedOuts.Outputs, out) + } + } + + if len(updatedOuts.Outputs) == 0 { + err := b.Delete(vin.Txid) + if err != nil { + log.Panic(err) + } + } else { + err := b.Put(vin.Txid, updatedOuts.Serialize()) + if err != nil { + log.Panic(err) + } + } - for _, out := range outs.Outputs { - if out.IsLockedWithKey(pubKeyHash) { - UTXOs = append(UTXOs, out) } } + + newOutputs := TXOutputs{} + for _, out := range tx.Vout { + newOutputs.Outputs = append(newOutputs.Outputs, out) + } + + err := b.Put(tx.ID, newOutputs.Serialize()) + if err != nil { + log.Panic(err) + } } return nil @@ -107,37 +194,4 @@ func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { if err != nil { log.Panic(err) } - - return UTXOs -} - -// FindSpendableOutputs finds and returns unspent outputs to reference in inputs -func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) { - unspentOutputs := make(map[string][]int) - accumulated := 0 - db := u.Blockchain.db - - err := db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(utxoBucket)) - c := b.Cursor() - - for k, v := c.First(); k != nil; k, v = c.Next() { - txID := hex.EncodeToString(k) - outs := DeserializeOutputs(v) - - for outIdx, out := range outs.Outputs { - if out.IsLockedWithKey(pubkeyHash) && accumulated < amount { - accumulated += out.Value - unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) - } - } - } - - return nil - }) - if err != nil { - log.Panic(err) - } - - return accumulated, unspentOutputs } From 99d1134beb8497ba487e3c6062c654989ac0742a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 12:21:24 +0700 Subject: [PATCH 13/24] Update the UTXO set after mining a new block --- blockchain.go | 4 +++- cli_send.go | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/blockchain.go b/blockchain.go index fa68abc..bbcb976 100644 --- a/blockchain.go +++ b/blockchain.go @@ -168,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 { @@ -208,6 +208,8 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { if err != nil { log.Panic(err) } + + return newBlock } // SignTransaction signs inputs of a Transaction diff --git a/cli_send.go b/cli_send.go index 3528611..456c264 100644 --- a/cli_send.go +++ b/cli_send.go @@ -21,6 +21,7 @@ func (cli *CLI) send(from, to string, amount int) { cbTx := NewCoinbaseTX(from, "") txs := []*Transaction{cbTx, tx} - bc.MineBlock(txs) + newBlock := bc.MineBlock(txs) + UTXOSet.Update(newBlock) fmt.Println("Success!") } From 4f0e04fde73ab75d3164fa71e107ab1e7583ed08 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 12:37:45 +0700 Subject: [PATCH 14/24] Reindex the UTXO set after creating a new blockchain --- cli_createblockchain.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cli_createblockchain.go b/cli_createblockchain.go index 06ff1b1..358e88b 100644 --- a/cli_createblockchain.go +++ b/cli_createblockchain.go @@ -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!") } From 47737a28af90765dd03b4eb2f2a7af07979edebf Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 12:44:43 +0700 Subject: [PATCH 15/24] =?UTF-8?q?FindAllUTXO=20=E2=86=92=20FindUTXO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blockchain.go | 4 ++-- utxo_set.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blockchain.go b/blockchain.go index bbcb976..e00fb40 100644 --- a/blockchain.go +++ b/blockchain.go @@ -116,8 +116,8 @@ func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { return Transaction{}, errors.New("Transaction is not found") } -// FindAllUTXO finds all unspent transaction outputs and returns transactions with spent outputs removed -func (bc *Blockchain) FindAllUTXO() map[string]TXOutputs { +// 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() diff --git a/utxo_set.go b/utxo_set.go index c85cf92..96cea33 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -121,7 +121,7 @@ func (u UTXOSet) Reindex() { log.Panic(err) } - UTXO := u.Blockchain.FindAllUTXO() + UTXO := u.Blockchain.FindUTXO() err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(utxoBucket)) From 8ef0f2c86bbb1296238a5e5eecb39be32732a52f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 12:45:53 +0700 Subject: [PATCH 16/24] Add a newline --- cli_reindexutxo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli_reindexutxo.go b/cli_reindexutxo.go index e6f841f..622d0df 100644 --- a/cli_reindexutxo.go +++ b/cli_reindexutxo.go @@ -8,5 +8,5 @@ func (cli *CLI) reindexUTXO() { UTXOSet.Reindex() count := UTXOSet.GetCount() - fmt.Printf("Done! There are %d transactions in the UTXO set.", count) + fmt.Printf("Done! There are %d transactions in the UTXO set.\n", count) } From 337a63782586daa2a2f80ccc1abfcb406e1186b1 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 18 Sep 2017 10:41:36 +0700 Subject: [PATCH 17/24] Rename UTXOSet.GetCount to UTXOSet.CountTransactions --- cli_reindexutxo.go | 2 +- utxo_set.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli_reindexutxo.go b/cli_reindexutxo.go index 622d0df..74d3b03 100644 --- a/cli_reindexutxo.go +++ b/cli_reindexutxo.go @@ -7,6 +7,6 @@ func (cli *CLI) reindexUTXO() { UTXOSet := UTXOSet{bc} UTXOSet.Reindex() - count := UTXOSet.GetCount() + count := UTXOSet.CountTransactions() fmt.Printf("Done! There are %d transactions in the UTXO set.\n", count) } diff --git a/utxo_set.go b/utxo_set.go index 96cea33..44ff27f 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -73,8 +73,8 @@ func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { return UTXOs } -// GetCount returns the number of transactions in the UTXO set -func (u UTXOSet) GetCount() int { +// CountTransactions returns the number of transactions in the UTXO set +func (u UTXOSet) CountTransactions() int { db := u.Blockchain.db counter := 0 From 827f124c61f48819d809e82967fad4449d22587d Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 18 Sep 2017 11:01:24 +0700 Subject: [PATCH 18/24] Refactor something in UTXOSet --- utxo_set.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/utxo_set.go b/utxo_set.go index 44ff27f..180ce04 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -98,19 +98,15 @@ func (u UTXOSet) CountTransactions() int { // Reindex rebuilds the UTXO set func (u UTXOSet) Reindex() { db := u.Blockchain.db + bucketName := []byte(utxoBucket) err := db.Update(func(tx *bolt.Tx) error { - bucketName := []byte(utxoBucket) - b := tx.Bucket(bucketName) - - if b != nil { - err := tx.DeleteBucket(bucketName) - if err != nil { - log.Panic(err) - } + err := tx.DeleteBucket(bucketName) + if err != nil && err != bolt.ErrBucketNotFound { + log.Panic(err) } - _, err := tx.CreateBucket(bucketName) + _, err = tx.CreateBucket(bucketName) if err != nil { log.Panic(err) } @@ -124,7 +120,7 @@ func (u UTXOSet) Reindex() { UTXO := u.Blockchain.FindUTXO() err = db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(utxoBucket)) + b := tx.Bucket(bucketName) for txID, outs := range UTXO { key, err := hex.DecodeString(txID) @@ -154,8 +150,8 @@ func (u UTXOSet) Update(block *Block) { if tx.IsCoinbase() == false { for _, vin := range tx.Vin { updatedOuts := TXOutputs{} - data := b.Get(vin.Txid) - outs := DeserializeOutputs(data) + outsBytes := b.Get(vin.Txid) + outs := DeserializeOutputs(outsBytes) for outIdx, out := range outs.Outputs { if outIdx != vin.Vout { From 668d209f5ee549c835bd081fa1978805bce82aff Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 18 Sep 2017 12:45:58 +0700 Subject: [PATCH 19/24] Implement Merkle tree --- merkle_tree.go | 65 +++++++++++++++++++++++++++++++++++++++ merkle_tree_test.go | 75 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 merkle_tree.go create mode 100644 merkle_tree_test.go diff --git a/merkle_tree.go b/merkle_tree.go new file mode 100644 index 0000000..7a4156b --- /dev/null +++ b/merkle_tree.go @@ -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 +} diff --git a/merkle_tree_test.go b/merkle_tree_test.go new file mode 100644 index 0000000..acff5ff --- /dev/null +++ b/merkle_tree_test.go @@ -0,0 +1,75 @@ +package main + +import ( + "encoding/hex" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewMerkleNode(t *testing.T) { + data := [][]byte{ + []byte("node1"), + []byte("node2"), + []byte("node3"), + } + + // Level 1 + + n1 := NewMerkleNode(nil, nil, data[0]) + n2 := NewMerkleNode(nil, nil, data[1]) + n3 := NewMerkleNode(nil, nil, data[2]) + n4 := NewMerkleNode(nil, nil, data[2]) + + // Level 2 + n5 := NewMerkleNode(n1, n2, nil) + n6 := NewMerkleNode(n3, n4, nil) + + // Level 3 + n7 := NewMerkleNode(n5, n6, nil) + + assert.Equal( + t, + "64b04b718d8b7c5b6fd17f7ec221945c034cfce3be4118da33244966150c4bd4", + hex.EncodeToString(n5.Data), + "Level 1 hash 1 is correct", + ) + assert.Equal( + t, + "08bd0d1426f87a78bfc2f0b13eccdf6f5b58dac6b37a7b9441c1a2fab415d76c", + hex.EncodeToString(n6.Data), + "Level 1 hash 2 is correct", + ) + assert.Equal( + t, + "4e3e44e55926330ab6c31892f980f8bfd1a6e910ff1ebc3f778211377f35227e", + hex.EncodeToString(n7.Data), + "Root hash is correct", + ) +} + +func TestNewMerkleTree(t *testing.T) { + data := [][]byte{ + []byte("node1"), + []byte("node2"), + []byte("node3"), + } + // Level 1 + n1 := NewMerkleNode(nil, nil, data[0]) + n2 := NewMerkleNode(nil, nil, data[1]) + n3 := NewMerkleNode(nil, nil, data[2]) + n4 := NewMerkleNode(nil, nil, data[2]) + + // Level 2 + n5 := NewMerkleNode(n1, n2, nil) + n6 := NewMerkleNode(n3, n4, nil) + + // Level 3 + n7 := NewMerkleNode(n5, n6, nil) + + rootHash := fmt.Sprintf("%x", n7.Data) + mTree := NewMerkleTree(data) + + assert.Equal(t, rootHash, fmt.Sprintf("%x", mTree.RootNode.Data), "Merkle tree root hash is correct") +} From 8cafc0ef1ef5b14a835ae2278b58c99d5ba5b7c6 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 18 Sep 2017 13:01:43 +0700 Subject: [PATCH 20/24] Use Merkle root hash in proof-of-work --- block.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/block.go b/block.go index b98c79c..65a0a6e 100644 --- a/block.go +++ b/block.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "crypto/sha256" "encoding/gob" "log" "time" @@ -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 From 74cbac4e8f9ff7fe092e2128a3f26ee733b2daaa Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 19 Sep 2017 14:53:53 +0700 Subject: [PATCH 21/24] Revert "Fix the 'checksum' function" This reverts commit 465b85d5f28b6264d23ae225adc2ab53ab34d045. --- wallet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet.go b/wallet.go index 262a366..31a2258 100644 --- a/wallet.go +++ b/wallet.go @@ -72,7 +72,7 @@ func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) - return secondSHA[len(secondSHA)-addressChecksumLen:] + return secondSHA[:addressChecksumLen] } func newKeyPair() (ecdsa.PrivateKey, []byte) { From ffac3de519dcc3f87c9d4ba2d3b95c788d31c730 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 19 Sep 2017 15:03:30 +0700 Subject: [PATCH 22/24] Lower the difficulty of PoW --- proofofwork.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proofofwork.go b/proofofwork.go index fe55525..967a4bc 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -12,7 +12,7 @@ var ( maxNonce = math.MaxInt64 ) -const targetBits = 24 +const targetBits = 16 // ProofOfWork represents a proof-of-work type ProofOfWork struct { From 9b9b571028aee3debd6a826855eaf61f9e664071 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 19 Sep 2017 15:47:03 +0700 Subject: [PATCH 23/24] Fill coinbase transaction data with random bytes --- transaction.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index e2fac83..67ba940 100644 --- a/transaction.go +++ b/transaction.go @@ -173,7 +173,13 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { // NewCoinbaseTX creates a new coinbase transaction func NewCoinbaseTX(to, data string) *Transaction { if data == "" { - data = fmt.Sprintf("Reward to '%s'", to) + randData := make([]byte, 20) + _, err := rand.Read(randData) + if err != nil { + log.Panic(err) + } + + data = fmt.Sprintf("%x", randData) } txin := TXInput{[]byte{}, -1, nil, []byte(data)} From 2e06c0a6370851f520d6c2056d561b7a77cf7e3f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 19 Sep 2017 16:44:23 +0700 Subject: [PATCH 24/24] Update the README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 87fc9f4..a7aface 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,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/) -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/) +3. [Persistence and CLI](https://jeiwan.cc/posts/building-blockchain-in-go-part-3/) +4. [Transactions 1](https://jeiwan.cc/posts/building-blockchain-in-go-part-4/) +5. [Addresses](https://jeiwan.cc/posts/building-blockchain-in-go-part-5/) +6. [Transactions 2](https://jeiwan.cc/posts/building-blockchain-in-go-part-6/)