From d3b2c5c57648150c3a2df6bf08eb13a226ac1b9f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 29 Aug 2017 16:29:19 +0700 Subject: [PATCH 01/23] Update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 32bfdf1f..d1db820f 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,4 @@ 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/) From 2ba0f1bfddfa72a79a00283d548dd6911aefbe1c Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 09:20:47 +0700 Subject: [PATCH 02/23] Implement transactions --- transaction.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 transaction.go diff --git a/transaction.go b/transaction.go new file mode 100644 index 00000000..4c116f4f --- /dev/null +++ b/transaction.go @@ -0,0 +1,73 @@ +package main + +import "log" + +const subsidy = 10 + +// Transaction represents a Bitcoin transaction +type Transaction struct { + Vin []TXInput + Vout []TXOutput +} + +// IsCoinbase checks whether the transaction is coinbase +func (tx Transaction) IsCoinbase() bool { + return len(tx.Vin) == 1 && tx.Vin[0].Txid == -1 && tx.Vin[0].Vout == -1 +} + +// TXInput represents a transaction input +type TXInput struct { + Txid int + Vout int + ScriptSig string +} + +// TXOutput represents a transaction output +type TXOutput struct { + Value int + ScriptPubKey string +} + +// Unlock checks if the output can be unlocked with the provided data +func (out *TXOutput) Unlock(unlockingData string) bool { + return out.ScriptPubKey == unlockingData +} + +// NewCoinbaseTX creates a new coinbase transaction +func NewCoinbaseTX(to string) *Transaction { + txin := TXInput{-1, -1, "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"} + txout := TXOutput{subsidy, to} + tx := Transaction{[]TXInput{txin}, []TXOutput{txout}} + + return &tx +} + +// NewUTXOTransaction creates a new transaction +func NewUTXOTransaction(from, to string, value int) *Transaction { + var inputs []TXInput + var outputs []TXOutput + + acc, validOutputs := s.findUnspentOutputs(from, value) + + if acc < value { + log.Panic("ERROR: Not enough funds") + } + + // Build a list of inputs + for txid, outs := range validOutputs { + for _, out := range outs { + input := TXInput{txid, out, from} + inputs = append(inputs, input) + } + } + + // Build a list of outputs + outputs = append(outputs, TXOutput{value, to}) + if acc > value { + outputs = append(outputs, TXOutput{acc - value, from}) // a change + } + + tx := Transaction{inputs, outputs} + + return &tx +} From 08a211be413458d8a990fe909e27493f2328c25e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 09:45:49 +0700 Subject: [PATCH 03/23] Integrate transactions into the blockchain --- block.go | 10 +++++----- blockchain.go | 6 ++++-- proofofwork.go | 10 +++++++++- transaction.go | 30 +++++++++++++++++++++++++++--- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/block.go b/block.go index 46560801..1d04b4fb 100644 --- a/block.go +++ b/block.go @@ -10,7 +10,7 @@ import ( // Block keeps block headers type Block struct { Timestamp int64 - Data []byte + Transactions []*Transaction PrevBlockHash []byte Hash []byte Nonce int @@ -30,8 +30,8 @@ func (b *Block) Serialize() []byte { } // 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 +42,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 diff --git a/blockchain.go b/blockchain.go index edc29dfb..2290318f 100644 --- a/blockchain.go +++ b/blockchain.go @@ -9,6 +9,7 @@ import ( const dbFile = "blockchain.db" const blocksBucket = "blocks" +const genesisCoinbase = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" // Blockchain keeps a sequence of Blocks type Blockchain struct { @@ -86,7 +87,7 @@ func (i *BlockchainIterator) Next() *Block { } // NewBlockchain creates a new Blockchain with genesis Block -func NewBlockchain() *Blockchain { +func NewBlockchain(address string) *Blockchain { var tip []byte db, err := bolt.Open(dbFile, 0600, nil) if err != nil { @@ -98,7 +99,8 @@ func NewBlockchain() *Blockchain { if b == nil { fmt.Println("No existing blockchain found. Creating a new one...") - genesis := NewGenesisBlock() + cbtx := NewCoinbaseTX(address, genesisCoinbase) + genesis := NewGenesisBlock(cbtx) b, err := tx.CreateBucket([]byte(blocksBucket)) if err != nil { diff --git a/proofofwork.go b/proofofwork.go index 57127bb2..05de09be 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -31,10 +31,18 @@ func NewProofOfWork(b *Block) *ProofOfWork { } func (pow *ProofOfWork) prepareData(nonce int) []byte { + var txHashes [][]byte + var txHash [32]byte + + for _, tx := range pow.block.Transactions { + txHashes = append(txHashes, tx.GetHash()) + } + txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) + data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, - pow.block.Data, + txHash[:], IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), diff --git a/transaction.go b/transaction.go index 4c116f4f..61fcf457 100644 --- a/transaction.go +++ b/transaction.go @@ -1,6 +1,11 @@ package main -import "log" +import ( + "bytes" + "crypto/sha256" + "encoding/gob" + "log" +) const subsidy = 10 @@ -15,6 +20,21 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && tx.Vin[0].Txid == -1 && tx.Vin[0].Vout == -1 } +// GetHash hashes the transaction and returns the hash +func (tx Transaction) GetHash() []byte { + 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()) + + return hash[:] +} + // TXInput represents a transaction input type TXInput struct { Txid int @@ -34,8 +54,12 @@ func (out *TXOutput) Unlock(unlockingData string) bool { } // NewCoinbaseTX creates a new coinbase transaction -func NewCoinbaseTX(to string) *Transaction { - txin := TXInput{-1, -1, "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"} +func NewCoinbaseTX(to, data string) *Transaction { + if data == "" { + data = "Coinbase" + } + + txin := TXInput{-1, -1, data} txout := TXOutput{subsidy, to} tx := Transaction{[]TXInput{txin}, []TXOutput{txout}} From 206f87e2657f3987e0fac050a2a90d71854d7d5c Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 09:56:14 +0700 Subject: [PATCH 04/23] Improve block transactions hashing --- block.go | 14 ++++++++++++++ proofofwork.go | 12 ++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/block.go b/block.go index 1d04b4fb..4a62cad5 100644 --- a/block.go +++ b/block.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "crypto/sha256" "encoding/gob" "log" "time" @@ -29,6 +30,19 @@ 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.GetHash()) + } + txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) + + return txHash[:] +} + // NewBlock creates and returns Block func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} diff --git a/proofofwork.go b/proofofwork.go index 05de09be..fe555254 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -31,18 +31,10 @@ func NewProofOfWork(b *Block) *ProofOfWork { } func (pow *ProofOfWork) prepareData(nonce int) []byte { - var txHashes [][]byte - var txHash [32]byte - - for _, tx := range pow.block.Transactions { - txHashes = append(txHashes, tx.GetHash()) - } - txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) - data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, - txHash[:], + pow.block.HashTransactions(), IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), @@ -59,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) From 46a1654c5a019df9641ec7cddf135caaf32a085e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 09:56:43 +0700 Subject: [PATCH 05/23] Fix blocks adding to the blockchain --- blockchain.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blockchain.go b/blockchain.go index 2290318f..b7115b74 100644 --- a/blockchain.go +++ b/blockchain.go @@ -24,7 +24,7 @@ type BlockchainIterator struct { } // AddBlock saves provided data as a block in the blockchain -func (bc *Blockchain) AddBlock(data string) { +func (bc *Blockchain) AddBlock(transactions []*Transaction) { var lastHash []byte err := bc.db.View(func(tx *bolt.Tx) error { @@ -38,7 +38,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)) From 8e6636983a6ce96cda005e07160fde22eef7becd Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 09:57:32 +0700 Subject: [PATCH 06/23] Fix printChain --- cli.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cli.go b/cli.go index 127150ea..f84377d8 100644 --- a/cli.go +++ b/cli.go @@ -38,7 +38,6 @@ func (cli *CLI) printChain() { 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())) From 6941c5f32e98ee64db7a34e33c76c2a8959bf0a2 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 10:05:18 +0700 Subject: [PATCH 07/23] Replace 'addblock' command with 'spend' --- cli.go | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/cli.go b/cli.go index f84377d8..da2eddef 100644 --- a/cli.go +++ b/cli.go @@ -15,8 +15,8 @@ type CLI struct { 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(" send -from FROM -to TO -amount AMOUNT - send AMOUNT of coins from FROM address to TO") } func (cli *CLI) validateArgs() { @@ -26,11 +26,6 @@ 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() @@ -49,23 +44,31 @@ func (cli *CLI) printChain() { } } +func (cli *CLI) send(from, to string, amount int) { + tx := NewUTXOTransaction(from, to, amount) + cli.bc.AddBlock([]*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) + sendCmd := flag.NewFlagSet("send", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) - addBlockData := addBlockCmd.String("data", "", "Block data") + 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 "printchain": + err := printChainCmd.Parse(os.Args[2:]) if err != nil { log.Panic(err) } - case "printchain": - err := printChainCmd.Parse(os.Args[2:]) + case "send": + err := sendCmd.Parse(os.Args[2:]) if err != nil { log.Panic(err) } @@ -74,15 +77,16 @@ func (cli *CLI) Run() { os.Exit(1) } - if addBlockCmd.Parsed() { - if *addBlockData == "" { - addBlockCmd.Usage() + if printChainCmd.Parsed() { + cli.printChain() + } + + if sendCmd.Parsed() { + if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 { + sendCmd.Usage() os.Exit(1) } - cli.addBlock(*addBlockData) - } - if printChainCmd.Parsed() { - cli.printChain() + cli.send(*sendFrom, *sendTo, *sendAmount) } } From 95d3f693632d840f67d78acd4098f764684bca7b Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 10:41:45 +0700 Subject: [PATCH 08/23] Implement Blockchain.FindUTXOs --- blockchain.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ transaction.go | 23 +++++++++++++++++------ 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/blockchain.go b/blockchain.go index b7115b74..ca6e9c27 100644 --- a/blockchain.go +++ b/blockchain.go @@ -58,6 +58,57 @@ func (bc *Blockchain) AddBlock(transactions []*Transaction) { }) } +// FindUTXOs finds and returns unspend transaction outputs for the address +func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]int) { + var spentTXs map[string][]int + var unspentTXs map[string][]int + accumulated := 0 + bci := bc.Iterator() + + for { + block := bci.Next() + + Work: + for _, tx := range block.Transactions { + txid := string(tx.GetHash()) + + for outid, out := range tx.Vout { + // Was the output spent? + if spentTXs[txid] != nil { + for _, spentOut := range spentTXs[txid] { + if spentOut == outid { + continue + } + } + } + + if out.Unlock(address) && accumulated < amount { + accumulated += out.Value + unspentTXs[txid] = append(unspentTXs[txid], outid) + + if accumulated >= amount { + break Work + } + } + } + + if tx.IsCoinbase() == false { + for _, in := range tx.Vin { + if in.LockedBy(address) { + spentTXs[string(in.Txid)] = append(spentTXs[string(in.Txid)], in.Vout) + } + } + } + } + + if len(block.PrevBlockHash) == 0 { + break + } + } + + return accumulated, unspentTXs +} + // Iterator ... func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} diff --git a/transaction.go b/transaction.go index 61fcf457..7bb21d32 100644 --- a/transaction.go +++ b/transaction.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/sha256" "encoding/gob" + "encoding/hex" "log" ) @@ -17,7 +18,7 @@ type Transaction struct { // IsCoinbase checks whether the transaction is coinbase func (tx Transaction) IsCoinbase() bool { - return len(tx.Vin) == 1 && tx.Vin[0].Txid == -1 && tx.Vin[0].Vout == -1 + return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } // GetHash hashes the transaction and returns the hash @@ -37,7 +38,7 @@ func (tx Transaction) GetHash() []byte { // TXInput represents a transaction input type TXInput struct { - Txid int + Txid []byte Vout int ScriptSig string } @@ -48,6 +49,11 @@ type TXOutput struct { ScriptPubKey string } +// LockedBy checks whether the address initiated the transaction +func (in *TXInput) LockedBy(address string) bool { + return in.ScriptSig == address +} + // Unlock checks if the output can be unlocked with the provided data func (out *TXOutput) Unlock(unlockingData string) bool { return out.ScriptPubKey == unlockingData @@ -59,7 +65,7 @@ func NewCoinbaseTX(to, data string) *Transaction { data = "Coinbase" } - txin := TXInput{-1, -1, data} + txin := TXInput{[]byte{}, -1, data} txout := TXOutput{subsidy, to} tx := Transaction{[]TXInput{txin}, []TXOutput{txout}} @@ -67,11 +73,11 @@ func NewCoinbaseTX(to, data string) *Transaction { } // NewUTXOTransaction creates a new transaction -func NewUTXOTransaction(from, to string, value int) *Transaction { +func NewUTXOTransaction(from, to string, value int, bc *Blockchain) *Transaction { var inputs []TXInput var outputs []TXOutput - acc, validOutputs := s.findUnspentOutputs(from, value) + acc, validOutputs := bc.FindUTXOs(from, value) if acc < value { log.Panic("ERROR: Not enough funds") @@ -80,7 +86,12 @@ func NewUTXOTransaction(from, to string, value int) *Transaction { // Build a list of inputs for txid, outs := range validOutputs { for _, out := range outs { - input := TXInput{txid, out, from} + txidbytes, err := hex.DecodeString(txid) + if err != nil { + log.Panic(err) + } + + input := TXInput{txidbytes, out, from} inputs = append(inputs, input) } } From f83ccd7b4c2f1086a5c053fe5fa7944c1a0ec45c Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 11:01:02 +0700 Subject: [PATCH 09/23] Rework Blockchain.FindUTXOs --- blockchain.go | 49 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/blockchain.go b/blockchain.go index ca6e9c27..780e172d 100644 --- a/blockchain.go +++ b/blockchain.go @@ -58,17 +58,15 @@ func (bc *Blockchain) AddBlock(transactions []*Transaction) { }) } -// FindUTXOs finds and returns unspend transaction outputs for the address -func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]int) { +// FindUnspentTransactions returns a list of transactions containing unspent outputs for an address +func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { var spentTXs map[string][]int - var unspentTXs map[string][]int - accumulated := 0 + var unspentTXs []*Transaction bci := bc.Iterator() for { block := bci.Next() - Work: for _, tx := range block.Transactions { txid := string(tx.GetHash()) @@ -82,13 +80,8 @@ func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]i } } - if out.Unlock(address) && accumulated < amount { - accumulated += out.Value - unspentTXs[txid] = append(unspentTXs[txid], outid) - - if accumulated >= amount { - break Work - } + if out.Unlock(address) { + unspentTXs = append(unspentTXs, tx) } } @@ -106,7 +99,37 @@ func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]i } } - return accumulated, unspentTXs + return unspentTXs +} + +// FindUTXOs finds and returns unspend transaction outputs for the address +func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]int) { + var unspentOutputs map[string][]int + unspentTXs := bc.FindUnspentTransactions(address) + accumulated := 0 + + // TODO: Fix this + if amount == -1 { + accumulated = -2 + } + +Work: + for _, tx := range unspentTXs { + txid := string(tx.GetHash()) + + for outid, out := range tx.Vout { + if out.Unlock(address) && accumulated < amount { + accumulated += out.Value + unspentOutputs[txid] = append(unspentOutputs[txid], outid) + + if accumulated >= amount { + break Work + } + } + } + } + + return accumulated, unspentOutputs } // Iterator ... From 87eb17bbe5c40573c86b8ca9055c04a6785bf040 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 11:02:46 +0700 Subject: [PATCH 10/23] Implement 'getbalance' command --- cli.go | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/cli.go b/cli.go index da2eddef..b094f121 100644 --- a/cli.go +++ b/cli.go @@ -13,10 +13,27 @@ type CLI struct { bc *Blockchain } +func (cli *CLI) getBalance(address string) { + balance := 0 + + utxs := cli.bc.FindUnspentTransactions(address) + + for _, tx := range utxs { + for _, out := range tx.Vout { + if out.Unlock(address) { + balance += out.Value + } + } + } + + fmt.Printf("Balance of '%s': %d\n", address, balance) +} + func (cli *CLI) printUsage() { fmt.Println("Usage:") - 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") + fmt.Println(" getbalance -address ADDRESS - Get balance of 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() { @@ -54,14 +71,21 @@ func (cli *CLI) send(from, to string, amount int) { func (cli *CLI) Run() { cli.validateArgs() + getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) sendCmd := flag.NewFlagSet("send", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) + getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for") 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 "getbalance": + err := getBalanceCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } case "printchain": err := printChainCmd.Parse(os.Args[2:]) if err != nil { @@ -77,6 +101,14 @@ func (cli *CLI) Run() { os.Exit(1) } + if getBalanceCmd.Parsed() { + if *getBalanceAddress == "" { + getBalanceCmd.Usage() + os.Exit(1) + } + cli.getBalance(*getBalanceAddress) + } + if printChainCmd.Parsed() { cli.printChain() } From 751d7913991c801066097d91deaf0651cc24c72e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 11:17:10 +0700 Subject: [PATCH 11/23] Implement 'createblockchain' command --- blockchain.go | 74 ++++++++++++++++++++++++++++++++++++++------------- cli.go | 42 ++++++++++++++++++++++++----- main.go | 5 +--- 3 files changed, 92 insertions(+), 29 deletions(-) diff --git a/blockchain.go b/blockchain.go index 780e172d..53c0a729 100644 --- a/blockchain.go +++ b/blockchain.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "os" "github.com/boltdb/bolt" ) @@ -160,8 +161,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(address string) *Blockchain { + if dbExists() == false { + fmt.Println("No existing blockchain found. Creating one first.") + os.Exit(1) + } + var tip []byte db, err := bolt.Open(dbFile, 0600, nil) if err != nil { @@ -170,30 +184,52 @@ func NewBlockchain(address string) *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...") - cbtx := NewCoinbaseTX(address, genesisCoinbase) - genesis := NewGenesisBlock(cbtx) + 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, genesisCoinbase) + 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 }) diff --git a/cli.go b/cli.go index b094f121..1d7b1d91 100644 --- a/cli.go +++ b/cli.go @@ -9,14 +9,21 @@ 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 - utxs := cli.bc.FindUnspentTransactions(address) + utxs := bc.FindUnspentTransactions(address) for _, tx := range utxs { for _, out := range tx.Vout { @@ -32,6 +39,7 @@ func (cli *CLI) getBalance(address string) { func (cli *CLI) printUsage() { fmt.Println("Usage:") 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") } @@ -44,7 +52,11 @@ func (cli *CLI) validateArgs() { } func (cli *CLI) printChain() { - bci := cli.bc.Iterator() + // TODO: Fix this + bc := NewBlockchain("") + defer bc.db.Close() + + bci := bc.Iterator() for { block := bci.Next() @@ -62,8 +74,11 @@ func (cli *CLI) printChain() { } func (cli *CLI) send(from, to string, amount int) { - tx := NewUTXOTransaction(from, to, amount) - cli.bc.AddBlock([]*Transaction{tx}) + bc := NewBlockchain(from) + defer bc.db.Close() + + tx := NewUTXOTransaction(from, to, amount, bc) + bc.AddBlock([]*Transaction{tx}) fmt.Println("Success!") } @@ -72,10 +87,12 @@ func (cli *CLI) Run() { cli.validateArgs() getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) + createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) sendCmd := flag.NewFlagSet("send", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) 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") @@ -86,6 +103,11 @@ func (cli *CLI) Run() { if err != nil { log.Panic(err) } + case "createblockchain": + err := createBlockchainCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } case "printchain": err := printChainCmd.Parse(os.Args[2:]) if err != nil { @@ -109,6 +131,14 @@ func (cli *CLI) Run() { cli.getBalance(*getBalanceAddress) } + if createBlockchainCmd.Parsed() { + if *createBlockchainAddress == "" { + createBlockchainCmd.Usage() + os.Exit(1) + } + cli.createBlockchain(*createBlockchainAddress) + } + if printChainCmd.Parsed() { cli.printChain() } diff --git a/main.go b/main.go index 5ba110b7..a48ad8e5 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,6 @@ package main func main() { - bc := NewBlockchain() - defer bc.db.Close() - - cli := CLI{bc} + cli := CLI{} cli.Run() } From 6388b20f3274afd97b24456930e1996c7e3c56d7 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 3 Sep 2017 11:35:36 +0700 Subject: [PATCH 12/23] Fix unspent transactions finding --- blockchain.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/blockchain.go b/blockchain.go index 53c0a729..f9180c5d 100644 --- a/blockchain.go +++ b/blockchain.go @@ -1,6 +1,7 @@ package main import ( + "encoding/hex" "fmt" "log" "os" @@ -61,7 +62,7 @@ func (bc *Blockchain) AddBlock(transactions []*Transaction) { // FindUnspentTransactions returns a list of transactions containing unspent outputs for an address func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { - var spentTXs map[string][]int + spentTXs := make(map[string][]int) var unspentTXs []*Transaction bci := bc.Iterator() @@ -69,14 +70,15 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { block := bci.Next() for _, tx := range block.Transactions { - txid := string(tx.GetHash()) + txid := hex.EncodeToString(tx.GetHash()) + Outputs: for outid, out := range tx.Vout { // Was the output spent? if spentTXs[txid] != nil { for _, spentOut := range spentTXs[txid] { if spentOut == outid { - continue + continue Outputs } } } @@ -89,7 +91,8 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { if tx.IsCoinbase() == false { for _, in := range tx.Vin { if in.LockedBy(address) { - spentTXs[string(in.Txid)] = append(spentTXs[string(in.Txid)], in.Vout) + inTxid := hex.EncodeToString(in.Txid) + spentTXs[inTxid] = append(spentTXs[inTxid], in.Vout) } } } @@ -105,7 +108,7 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { // FindUTXOs finds and returns unspend transaction outputs for the address func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]int) { - var unspentOutputs map[string][]int + unspentOutputs := make(map[string][]int) unspentTXs := bc.FindUnspentTransactions(address) accumulated := 0 @@ -116,7 +119,7 @@ func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]i Work: for _, tx := range unspentTXs { - txid := string(tx.GetHash()) + txid := hex.EncodeToString(tx.GetHash()) for outid, out := range tx.Vout { if out.Unlock(address) && accumulated < amount { From 78dbfc69b6dabb4bd06ba19690d27051deff7a14 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 4 Sep 2017 11:02:24 +0700 Subject: [PATCH 13/23] Minor improvements --- blockchain.go | 35 ++++++++++++++++++----------------- cli.go | 2 +- proofofwork.go | 2 +- transaction.go | 7 ++++--- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/blockchain.go b/blockchain.go index f9180c5d..bcb9728a 100644 --- a/blockchain.go +++ b/blockchain.go @@ -13,7 +13,7 @@ const dbFile = "blockchain.db" const blocksBucket = "blocks" const genesisCoinbase = "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 @@ -25,8 +25,8 @@ type BlockchainIterator struct { db *bolt.DB } -// AddBlock saves provided data as a block in the blockchain -func (bc *Blockchain) AddBlock(transactions []*Transaction) { +// 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 { @@ -60,24 +60,24 @@ func (bc *Blockchain) AddBlock(transactions []*Transaction) { }) } -// FindUnspentTransactions returns a list of transactions containing unspent outputs for an address +// FindUnspentTransactions returns a list of transactions containing unspent outputs func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { - spentTXs := make(map[string][]int) var unspentTXs []*Transaction + spentTXOs := make(map[string][]int) bci := bc.Iterator() for { block := bci.Next() for _, tx := range block.Transactions { - txid := hex.EncodeToString(tx.GetHash()) + txID := hex.EncodeToString(tx.GetHash()) Outputs: - for outid, out := range tx.Vout { + for outIdx, out := range tx.Vout { // Was the output spent? - if spentTXs[txid] != nil { - for _, spentOut := range spentTXs[txid] { - if spentOut == outid { + if spentTXOs[txID] != nil { + for _, spentOut := range spentTXOs[txID] { + if spentOut == outIdx { continue Outputs } } @@ -85,14 +85,15 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { if out.Unlock(address) { unspentTXs = append(unspentTXs, tx) + continue Outputs } } if tx.IsCoinbase() == false { for _, in := range tx.Vin { if in.LockedBy(address) { - inTxid := hex.EncodeToString(in.Txid) - spentTXs[inTxid] = append(spentTXs[inTxid], in.Vout) + inTxID := hex.EncodeToString(in.Txid) + spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } } } @@ -119,12 +120,12 @@ func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]i Work: for _, tx := range unspentTXs { - txid := hex.EncodeToString(tx.GetHash()) + txID := hex.EncodeToString(tx.GetHash()) - for outid, out := range tx.Vout { + for outIdx, out := range tx.Vout { if out.Unlock(address) && accumulated < amount { accumulated += out.Value - unspentOutputs[txid] = append(unspentOutputs[txid], outid) + unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) if accumulated >= amount { break Work @@ -136,7 +137,7 @@ Work: return accumulated, unspentOutputs } -// Iterator ... +// Iterator returns a BlockchainIterat func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} @@ -175,7 +176,7 @@ func dbExists() bool { // NewBlockchain creates a new Blockchain with genesis Block func NewBlockchain(address string) *Blockchain { if dbExists() == false { - fmt.Println("No existing blockchain found. Creating one first.") + fmt.Println("No existing blockchain found. Create one first.") os.Exit(1) } diff --git a/cli.go b/cli.go index 1d7b1d91..3fda1543 100644 --- a/cli.go +++ b/cli.go @@ -78,7 +78,7 @@ func (cli *CLI) send(from, to string, amount int) { defer bc.db.Close() tx := NewUTXOTransaction(from, to, amount, bc) - bc.AddBlock([]*Transaction{tx}) + bc.MineBlock([]*Transaction{tx}) fmt.Println("Success!") } diff --git a/proofofwork.go b/proofofwork.go index fe555254..967a4bc1 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 { diff --git a/transaction.go b/transaction.go index 7bb21d32..dee83d15 100644 --- a/transaction.go +++ b/transaction.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/gob" "encoding/hex" + "fmt" "log" ) @@ -62,7 +63,7 @@ func (out *TXOutput) Unlock(unlockingData string) bool { // NewCoinbaseTX creates a new coinbase transaction func NewCoinbaseTX(to, data string) *Transaction { if data == "" { - data = "Coinbase" + data = fmt.Sprintf("Reward to '%s'", to) } txin := TXInput{[]byte{}, -1, data} @@ -86,12 +87,12 @@ func NewUTXOTransaction(from, to string, value int, bc *Blockchain) *Transaction // Build a list of inputs for txid, outs := range validOutputs { for _, out := range outs { - txidbytes, err := hex.DecodeString(txid) + txID, err := hex.DecodeString(txid) if err != nil { log.Panic(err) } - input := TXInput{txidbytes, out, from} + input := TXInput{txID, out, from} inputs = append(inputs, input) } } From 326ecb828cfddfd4f6cdabfe9639a1ea1b4aafe5 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 4 Sep 2017 11:26:30 +0700 Subject: [PATCH 14/23] Rename TXInput.LockedBy and TXOutput.Unlock methods --- blockchain.go | 11 +++-------- cli.go | 2 +- transaction.go | 10 +++++----- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/blockchain.go b/blockchain.go index bcb9728a..32cb5db2 100644 --- a/blockchain.go +++ b/blockchain.go @@ -83,7 +83,7 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { } } - if out.Unlock(address) { + if out.CanBeUnlockedWith(address) { unspentTXs = append(unspentTXs, tx) continue Outputs } @@ -91,7 +91,7 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { if tx.IsCoinbase() == false { for _, in := range tx.Vin { - if in.LockedBy(address) { + if in.CanUnlockOutputWith(address) { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } @@ -113,17 +113,12 @@ func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]i unspentTXs := bc.FindUnspentTransactions(address) accumulated := 0 - // TODO: Fix this - if amount == -1 { - accumulated = -2 - } - Work: for _, tx := range unspentTXs { txID := hex.EncodeToString(tx.GetHash()) for outIdx, out := range tx.Vout { - if out.Unlock(address) && accumulated < amount { + if out.CanBeUnlockedWith(address) && accumulated < amount { accumulated += out.Value unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) diff --git a/cli.go b/cli.go index 3fda1543..d125686f 100644 --- a/cli.go +++ b/cli.go @@ -27,7 +27,7 @@ func (cli *CLI) getBalance(address string) { for _, tx := range utxs { for _, out := range tx.Vout { - if out.Unlock(address) { + if out.CanBeUnlockedWith(address) { balance += out.Value } } diff --git a/transaction.go b/transaction.go index dee83d15..9a6ec1e2 100644 --- a/transaction.go +++ b/transaction.go @@ -50,13 +50,13 @@ type TXOutput struct { ScriptPubKey string } -// LockedBy checks whether the address initiated the transaction -func (in *TXInput) LockedBy(address string) bool { - return in.ScriptSig == address +// CanUnlockOutputWith checks whether the address initiated the transaction +func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { + return in.ScriptSig == unlockingData } -// Unlock checks if the output can be unlocked with the provided data -func (out *TXOutput) Unlock(unlockingData string) bool { +// CanBeUnlockedWith checks if the output can be unlocked with the provided data +func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { return out.ScriptPubKey == unlockingData } From 32dd771eefa336b69c87435ff0900641e65d4c9b Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 4 Sep 2017 11:32:24 +0700 Subject: [PATCH 15/23] Rename Transaction.GetHash to SetID; add Transaction.ID field --- block.go | 2 +- blockchain.go | 4 ++-- transaction.go | 14 ++++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/block.go b/block.go index 4a62cad5..04d96cc2 100644 --- a/block.go +++ b/block.go @@ -36,7 +36,7 @@ func (b *Block) HashTransactions() []byte { var txHash [32]byte for _, tx := range b.Transactions { - txHashes = append(txHashes, tx.GetHash()) + txHashes = append(txHashes, tx.ID) } txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) diff --git a/blockchain.go b/blockchain.go index 32cb5db2..15e30a4d 100644 --- a/blockchain.go +++ b/blockchain.go @@ -70,7 +70,7 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { block := bci.Next() for _, tx := range block.Transactions { - txID := hex.EncodeToString(tx.GetHash()) + txID := hex.EncodeToString(tx.ID) Outputs: for outIdx, out := range tx.Vout { @@ -115,7 +115,7 @@ func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]i Work: for _, tx := range unspentTXs { - txID := hex.EncodeToString(tx.GetHash()) + txID := hex.EncodeToString(tx.ID) for outIdx, out := range tx.Vout { if out.CanBeUnlockedWith(address) && accumulated < amount { diff --git a/transaction.go b/transaction.go index 9a6ec1e2..b7afd5a7 100644 --- a/transaction.go +++ b/transaction.go @@ -13,6 +13,7 @@ const subsidy = 10 // Transaction represents a Bitcoin transaction type Transaction struct { + ID []byte Vin []TXInput Vout []TXOutput } @@ -22,8 +23,8 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } -// GetHash hashes the transaction and returns the hash -func (tx Transaction) GetHash() []byte { +// SetID sets ID of a transaction +func (tx Transaction) SetID() { var encoded bytes.Buffer var hash [32]byte @@ -33,8 +34,7 @@ func (tx Transaction) GetHash() []byte { log.Panic(err) } hash = sha256.Sum256(encoded.Bytes()) - - return hash[:] + tx.ID = hash[:] } // TXInput represents a transaction input @@ -68,7 +68,8 @@ func NewCoinbaseTX(to, data string) *Transaction { txin := TXInput{[]byte{}, -1, data} txout := TXOutput{subsidy, to} - tx := Transaction{[]TXInput{txin}, []TXOutput{txout}} + tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} + tx.SetID() return &tx } @@ -103,7 +104,8 @@ func NewUTXOTransaction(from, to string, value int, bc *Blockchain) *Transaction outputs = append(outputs, TXOutput{acc - value, from}) // a change } - tx := Transaction{inputs, outputs} + tx := Transaction{nil, inputs, outputs} + tx.SetID() return &tx } From 7904009c2fbc29e20a87c49fc74a9a3ce819962d Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 4 Sep 2017 11:32:59 +0700 Subject: [PATCH 16/23] Set PoW target to 24 --- proofofwork.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proofofwork.go b/proofofwork.go index 967a4bc1..fe555254 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -12,7 +12,7 @@ var ( maxNonce = math.MaxInt64 ) -const targetBits = 16 +const targetBits = 24 // ProofOfWork represents a proof-of-work type ProofOfWork struct { From c748768da2c51a12c5c7f4a3c5e482408ff35987 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 5 Sep 2017 11:34:47 +0700 Subject: [PATCH 17/23] =?UTF-8?q?genesisCoinbase=20=E2=86=92=20genesisCoin?= =?UTF-8?q?baseData?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blockchain.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blockchain.go b/blockchain.go index 15e30a4d..040f6ae7 100644 --- a/blockchain.go +++ b/blockchain.go @@ -11,7 +11,7 @@ import ( const dbFile = "blockchain.db" const blocksBucket = "blocks" -const genesisCoinbase = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" +const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" // Blockchain implements interactions with a DB type Blockchain struct { @@ -211,7 +211,7 @@ func CreateBlockchain(address string) *Blockchain { } err = db.Update(func(tx *bolt.Tx) error { - cbtx := NewCoinbaseTX(address, genesisCoinbase) + cbtx := NewCoinbaseTX(address, genesisCoinbaseData) genesis := NewGenesisBlock(cbtx) b, err := tx.CreateBucket([]byte(blocksBucket)) From f4ae5168b0ecd43e6ce5e6a5c9ca20520b6e9947 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 5 Sep 2017 12:26:29 +0700 Subject: [PATCH 18/23] Minor improvements --- blockchain.go | 1 - cli.go | 1 - 2 files changed, 2 deletions(-) diff --git a/blockchain.go b/blockchain.go index 040f6ae7..ccb59795 100644 --- a/blockchain.go +++ b/blockchain.go @@ -85,7 +85,6 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { if out.CanBeUnlockedWith(address) { unspentTXs = append(unspentTXs, tx) - continue Outputs } } diff --git a/cli.go b/cli.go index d125686f..fb407a08 100644 --- a/cli.go +++ b/cli.go @@ -22,7 +22,6 @@ func (cli *CLI) getBalance(address string) { defer bc.db.Close() balance := 0 - utxs := bc.FindUnspentTransactions(address) for _, tx := range utxs { From e89846d490f5663730d746eb5e04e86f45c3a8ee Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 5 Sep 2017 14:33:33 +0700 Subject: [PATCH 19/23] Rework UTXO related functions --- blockchain.go | 20 ++++++++++++++++++-- cli.go | 10 +++------- transaction.go | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/blockchain.go b/blockchain.go index ccb59795..d8259556 100644 --- a/blockchain.go +++ b/blockchain.go @@ -106,8 +106,24 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { return unspentTXs } -// FindUTXOs finds and returns unspend transaction outputs for the address -func (bc *Blockchain) FindUTXOs(address string, amount int) (int, map[string][]int) { +// 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 diff --git a/cli.go b/cli.go index fb407a08..1270c6e2 100644 --- a/cli.go +++ b/cli.go @@ -22,14 +22,10 @@ func (cli *CLI) getBalance(address string) { defer bc.db.Close() balance := 0 - utxs := bc.FindUnspentTransactions(address) + UTXOs := bc.FindUTXO(address) - for _, tx := range utxs { - for _, out := range tx.Vout { - if out.CanBeUnlockedWith(address) { - balance += out.Value - } - } + for _, out := range UTXOs { + balance += out.Value } fmt.Printf("Balance of '%s': %d\n", address, balance) diff --git a/transaction.go b/transaction.go index b7afd5a7..d67a505d 100644 --- a/transaction.go +++ b/transaction.go @@ -79,7 +79,7 @@ func NewUTXOTransaction(from, to string, value int, bc *Blockchain) *Transaction var inputs []TXInput var outputs []TXOutput - acc, validOutputs := bc.FindUTXOs(from, value) + acc, validOutputs := bc.FindSpendableOutputs(from, value) if acc < value { log.Panic("ERROR: Not enough funds") From d107d924a8a70c095d6de3527dbc3394579a9c69 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 5 Sep 2017 21:35:45 +0700 Subject: [PATCH 20/23] Final fixes --- blockchain.go | 6 +++--- transaction.go | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/blockchain.go b/blockchain.go index d8259556..b9cfb428 100644 --- a/blockchain.go +++ b/blockchain.go @@ -61,8 +61,8 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { } // FindUnspentTransactions returns a list of transactions containing unspent outputs -func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { - var unspentTXs []*Transaction +func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { + var unspentTXs []Transaction spentTXOs := make(map[string][]int) bci := bc.Iterator() @@ -84,7 +84,7 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []*Transaction { } if out.CanBeUnlockedWith(address) { - unspentTXs = append(unspentTXs, tx) + unspentTXs = append(unspentTXs, *tx) } } diff --git a/transaction.go b/transaction.go index d67a505d..fe82a7be 100644 --- a/transaction.go +++ b/transaction.go @@ -75,33 +75,33 @@ func NewCoinbaseTX(to, data string) *Transaction { } // NewUTXOTransaction creates a new transaction -func NewUTXOTransaction(from, to string, value int, bc *Blockchain) *Transaction { +func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { var inputs []TXInput var outputs []TXOutput - acc, validOutputs := bc.FindSpendableOutputs(from, value) + acc, validOutputs := bc.FindSpendableOutputs(from, amount) - if acc < value { + if acc < amount { log.Panic("ERROR: Not enough funds") } // Build a list of inputs for txid, outs := range validOutputs { - for _, out := range outs { - txID, err := hex.DecodeString(txid) - if err != nil { - log.Panic(err) - } + 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{value, to}) - if acc > value { - outputs = append(outputs, TXOutput{acc - value, from}) // a change + outputs = append(outputs, TXOutput{amount, to}) + if acc > amount { + outputs = append(outputs, TXOutput{acc - amount, from}) // a change } tx := Transaction{nil, inputs, outputs} From 373a09b2bcdf8e9f55c237f60018e0762c2a9b30 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 5 Sep 2017 21:38:03 +0700 Subject: [PATCH 21/23] Add a link to the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d1db820f..7c9b5850 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,4 @@ 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/) From 46e935c8514369064901828921943cd858e30a2e Mon Sep 17 00:00:00 2001 From: samguns Date: Tue, 31 Oct 2017 17:21:50 +0800 Subject: [PATCH 22/23] SetID method should use pointer receiver. --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index fe82a7be..e5c51db0 100644 --- a/transaction.go +++ b/transaction.go @@ -24,7 +24,7 @@ func (tx Transaction) IsCoinbase() bool { } // SetID sets ID of a transaction -func (tx Transaction) SetID() { +func (tx *Transaction) SetID() { var encoded bytes.Buffer var hash [32]byte From e17722eb18caa7618dee298da677a9f5667a0513 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sat, 26 Jun 2021 18:21:49 +0700 Subject: [PATCH 23/23] Fix URLs in README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7c9b5850..38c89b17 100644 --- a/README.md +++ b/README.md @@ -2,7 +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/) -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/) +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/)