From 0f23d64aa03c86207cfaf82a17480efa4bd64f43 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 21 Aug 2017 17:50:41 +0700 Subject: [PATCH 001/122] Implement Proof-of-Work --- block.go | 17 ++++------ main.go | 4 +-- proofofwork.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 proofofwork.go diff --git a/block.go b/block.go index dd0afd1f..8e87b8be 100644 --- a/block.go +++ b/block.go @@ -1,9 +1,6 @@ package main import ( - "bytes" - "crypto/sha256" - "strconv" "time" ) @@ -13,21 +10,21 @@ type Block struct { Data []byte PrevBlockHash []byte Hash []byte + Nonce int } -// SetHash calculates and sets block hash -func (b *Block) SetHash() { - timestamp := []byte(strconv.FormatInt(b.Timestamp, 10)) - headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{}) - hash := sha256.Sum256(headers) +// Prove obtains proof of work +func (b *Block) Prove() { + nonce, hash := Prove(b) b.Hash = hash[:] + b.Nonce = nonce } // NewBlock creates and returns Block func NewBlock(data string, prevBlockHash []byte) *Block { - block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}} - block.SetHash() + block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} + block.Prove() return block } diff --git a/main.go b/main.go index 68e4642d..02948e9e 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,6 @@ package main -import ( - "fmt" -) +import "fmt" func main() { bc := NewBlockchain() diff --git a/proofofwork.go b/proofofwork.go new file mode 100644 index 00000000..6d17ced1 --- /dev/null +++ b/proofofwork.go @@ -0,0 +1,86 @@ +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "log" + "math" + "math/big" +) + +var ( + target big.Int + maxNonce = math.MaxInt64 +) + +const targetBits = 12 + +func setTarget() { + targetBytes := make([]byte, 32) + numOfZeros := targetBits / 4 + targetBytes[numOfZeros-1] = 1 + target.SetBytes(targetBytes) +} + +func prepareData(block *Block, nonce int) []byte { + data := bytes.Join( + [][]byte{ + block.PrevBlockHash, + block.Data, + intToHex(block.Timestamp), + intToHex(int64(targetBits)), + intToHex(int64(nonce)), + }, + []byte{}, + ) + + return data +} + +func intToHex(num int64) []byte { + buff := new(bytes.Buffer) + err := binary.Write(buff, binary.BigEndian, num) + if err != nil { + log.Panic(err) + } + + return buff.Bytes() +} + +// Prove ... +func Prove(block *Block) (int, []byte) { + setTarget() + var hashInt big.Int + var hash [32]byte + nonce := 0 + + for nonce < maxNonce { + data := prepareData(block, nonce) + + hash = sha256.Sum256(data) + hashInt.SetBytes(hash[:]) + + if hashInt.Cmp(&target) == -1 { + break + } else { + nonce++ + } + } + + return nonce, hash[:] +} + +// ConfirmProof .. +func ConfirmProof(block *Block, nonce int) bool { + setTarget() + var hashInt big.Int + + data := prepareData(block, nonce) + hash := sha256.Sum256(data) + hashInt.SetBytes(hash[:]) + + confirmation := hashInt.Cmp(&target) == -1 + + return confirmation +} From 94f3654c8bb44346952328f07500871581435718 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 21 Aug 2017 20:51:17 +0700 Subject: [PATCH 002/122] Output mining progress --- proofofwork.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proofofwork.go b/proofofwork.go index 6d17ced1..c9958f95 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/sha256" "encoding/binary" + "fmt" "log" "math" "math/big" @@ -55,10 +56,12 @@ func Prove(block *Block) (int, []byte) { var hash [32]byte nonce := 0 + fmt.Printf("Mining the block containing \"%s\"\n", block.Data) for nonce < maxNonce { data := prepareData(block, nonce) hash = sha256.Sum256(data) + fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) if hashInt.Cmp(&target) == -1 { @@ -67,6 +70,7 @@ func Prove(block *Block) (int, []byte) { nonce++ } } + fmt.Print("\n\n") return nonce, hash[:] } From 35449eb8a6d913f2bec5fe1b1b7f87f21669edab Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 21 Aug 2017 21:06:52 +0700 Subject: [PATCH 003/122] Clean up and refactor proofofwork.go --- block.go | 15 +++++-------- proofofwork.go | 60 ++++++++++++++++++++++++-------------------------- utils.go | 18 +++++++++++++++ 3 files changed, 53 insertions(+), 40 deletions(-) create mode 100644 utils.go diff --git a/block.go b/block.go index 8e87b8be..fd3d07c9 100644 --- a/block.go +++ b/block.go @@ -13,18 +13,15 @@ type Block struct { Nonce int } -// Prove obtains proof of work -func (b *Block) Prove() { - nonce, hash := Prove(b) - - b.Hash = hash[:] - b.Nonce = nonce -} - // NewBlock creates and returns Block func NewBlock(data string, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} - block.Prove() + pow := NewProofOfWork(block) + nonce, hash := pow.Run() + + block.Hash = hash[:] + block.Nonce = nonce + return block } diff --git a/proofofwork.go b/proofofwork.go index c9958f95..21fa91ab 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -3,35 +3,45 @@ package main import ( "bytes" "crypto/sha256" - "encoding/binary" "fmt" - "log" "math" "math/big" ) var ( - target big.Int maxNonce = math.MaxInt64 ) const targetBits = 12 -func setTarget() { +// ProofOfWork represents a proof-of-work +type ProofOfWork struct { + block *Block + target big.Int +} + +// NewProofOfWork builds and returns a ProofOfWork +func NewProofOfWork(b *Block) *ProofOfWork { targetBytes := make([]byte, 32) + target := big.Int{} + numOfZeros := targetBits / 4 targetBytes[numOfZeros-1] = 1 target.SetBytes(targetBytes) + + pow := &ProofOfWork{b, target} + + return pow } -func prepareData(block *Block, nonce int) []byte { +func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ - block.PrevBlockHash, - block.Data, - intToHex(block.Timestamp), - intToHex(int64(targetBits)), - intToHex(int64(nonce)), + pow.block.PrevBlockHash, + pow.block.Data, + IntToHex(pow.block.Timestamp), + IntToHex(int64(targetBits)), + IntToHex(int64(nonce)), }, []byte{}, ) @@ -39,32 +49,21 @@ func prepareData(block *Block, nonce int) []byte { return data } -func intToHex(num int64) []byte { - buff := new(bytes.Buffer) - err := binary.Write(buff, binary.BigEndian, num) - if err != nil { - log.Panic(err) - } - - return buff.Bytes() -} - -// Prove ... -func Prove(block *Block) (int, []byte) { - setTarget() +// Run performs a proof-of-work +func (pow *ProofOfWork) Run() (int, []byte) { var hashInt big.Int var hash [32]byte nonce := 0 - fmt.Printf("Mining the block containing \"%s\"\n", block.Data) + fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data) for nonce < maxNonce { - data := prepareData(block, nonce) + data := pow.prepareData(nonce) hash = sha256.Sum256(data) fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) - if hashInt.Cmp(&target) == -1 { + if hashInt.Cmp(&pow.target) == -1 { break } else { nonce++ @@ -75,16 +74,15 @@ func Prove(block *Block) (int, []byte) { return nonce, hash[:] } -// ConfirmProof .. -func ConfirmProof(block *Block, nonce int) bool { - setTarget() +// ConfirmProof confirms that the proof is correct +func (pow *ProofOfWork) ConfirmProof(nonce int) bool { var hashInt big.Int - data := prepareData(block, nonce) + data := pow.prepareData(nonce) hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) - confirmation := hashInt.Cmp(&target) == -1 + confirmation := hashInt.Cmp(&pow.target) == -1 return confirmation } diff --git a/utils.go b/utils.go new file mode 100644 index 00000000..7f5e727b --- /dev/null +++ b/utils.go @@ -0,0 +1,18 @@ +package main + +import ( + "bytes" + "encoding/binary" + "log" +) + +// IntToHex converts an int64 to a byte array +func IntToHex(num int64) []byte { + buff := new(bytes.Buffer) + err := binary.Write(buff, binary.BigEndian, num) + if err != nil { + log.Panic(err) + } + + return buff.Bytes() +} From f94bbb44519ef61c92c8db6b09d7c58aad6703d8 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 22 Aug 2017 11:41:15 +0700 Subject: [PATCH 004/122] Improve ConfirmProof and print it out in main() --- main.go | 7 ++++++- proofofwork.go | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 02948e9e..c8414fe9 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,9 @@ package main -import "fmt" +import ( + "fmt" + "strconv" +) func main() { bc := NewBlockchain() @@ -12,6 +15,8 @@ func main() { 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("PoF: %s\n", strconv.FormatBool(pow.ConfirmProof())) fmt.Println() } } diff --git a/proofofwork.go b/proofofwork.go index 21fa91ab..84a60c1c 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -75,10 +75,10 @@ func (pow *ProofOfWork) Run() (int, []byte) { } // ConfirmProof confirms that the proof is correct -func (pow *ProofOfWork) ConfirmProof(nonce int) bool { +func (pow *ProofOfWork) ConfirmProof() bool { var hashInt big.Int - data := pow.prepareData(nonce) + data := pow.prepareData(pow.block.Nonce) hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) From d379fdeae298bc754274e5376ca8e362cb4e681c Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 22 Aug 2017 17:26:12 +0700 Subject: [PATCH 005/122] Simplify 'target' --- proofofwork.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/proofofwork.go b/proofofwork.go index 84a60c1c..ff685b3b 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -12,22 +12,18 @@ var ( maxNonce = math.MaxInt64 ) -const targetBits = 12 +const targetBits = 24 // 3 bytes // ProofOfWork represents a proof-of-work type ProofOfWork struct { block *Block - target big.Int + target *big.Int } // NewProofOfWork builds and returns a ProofOfWork func NewProofOfWork(b *Block) *ProofOfWork { - targetBytes := make([]byte, 32) - target := big.Int{} - - numOfZeros := targetBits / 4 - targetBytes[numOfZeros-1] = 1 - target.SetBytes(targetBytes) + target := big.NewInt(1) + target.Lsh(target, uint(256-targetBits)) pow := &ProofOfWork{b, target} @@ -63,7 +59,7 @@ func (pow *ProofOfWork) Run() (int, []byte) { fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) - if hashInt.Cmp(&pow.target) == -1 { + if hashInt.Cmp(pow.target) == -1 { break } else { nonce++ @@ -82,7 +78,7 @@ func (pow *ProofOfWork) ConfirmProof() bool { hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) - confirmation := hashInt.Cmp(&pow.target) == -1 + confirmation := hashInt.Cmp(pow.target) == -1 return confirmation } From 2a9385dd5b396ab436889f49ce559c8dec0fa497 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 24 Aug 2017 10:35:14 +0700 Subject: [PATCH 006/122] Rename ConfirmProof function to Validate --- main.go | 2 +- proofofwork.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index c8414fe9..4025010b 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ func main() { fmt.Printf("Data: %s\n", block.Data) fmt.Printf("Hash: %x\n", block.Hash) pow := NewProofOfWork(block) - fmt.Printf("PoF: %s\n", strconv.FormatBool(pow.ConfirmProof())) + fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) fmt.Println() } } diff --git a/proofofwork.go b/proofofwork.go index ff685b3b..f97ec108 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -70,15 +70,15 @@ func (pow *ProofOfWork) Run() (int, []byte) { return nonce, hash[:] } -// ConfirmProof confirms that the proof is correct -func (pow *ProofOfWork) ConfirmProof() bool { +// Validate validates block's PoW +func (pow *ProofOfWork) Validate() bool { var hashInt big.Int data := pow.prepareData(pow.block.Nonce) hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) - confirmation := hashInt.Cmp(pow.target) == -1 + isValid := hashInt.Cmp(pow.target) == -1 - return confirmation + return isValid } From fec88e3443b4ebf8f65870f567a55d290179b58e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 24 Aug 2017 12:20:43 +0700 Subject: [PATCH 007/122] Remove a comment --- proofofwork.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proofofwork.go b/proofofwork.go index f97ec108..57127bb2 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -12,7 +12,7 @@ var ( maxNonce = math.MaxInt64 ) -const targetBits = 24 // 3 bytes +const targetBits = 24 // ProofOfWork represents a proof-of-work type ProofOfWork struct { From 56cb2de10660b7ff0d9bc05bc4185ae4dd68e5a2 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 27 Aug 2017 08:17:57 +0700 Subject: [PATCH 008/122] Add README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..32bfdf1f --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Blockchain in Go + +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/) From 85022254ec669649bf61085006df1b8cc6a007ae Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 28 Aug 2017 12:11:51 +0700 Subject: [PATCH 009/122] Implement serialization and deserialization of a block --- block.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/block.go b/block.go index fd3d07c9..46560801 100644 --- a/block.go +++ b/block.go @@ -1,6 +1,9 @@ package main import ( + "bytes" + "encoding/gob" + "log" "time" ) @@ -13,6 +16,19 @@ type Block struct { Nonce int } +// Serialize serializes the block +func (b *Block) Serialize() []byte { + var result bytes.Buffer + encoder := gob.NewEncoder(&result) + + err := encoder.Encode(b) + if err != nil { + log.Panic(err) + } + + return result.Bytes() +} + // NewBlock creates and returns Block func NewBlock(data string, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} @@ -29,3 +45,16 @@ func NewBlock(data string, prevBlockHash []byte) *Block { func NewGenesisBlock() *Block { return NewBlock("Genesis Block", []byte{}) } + +// DeserializeBlock deserializes a block +func DeserializeBlock(d []byte) *Block { + var block Block + + decoder := gob.NewDecoder(bytes.NewReader(d)) + err := decoder.Decode(&block) + if err != nil { + log.Panic(err) + } + + return &block +} From fdccadfb63def5008768c99a409cedd3b8e30870 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 28 Aug 2017 13:57:27 +0700 Subject: [PATCH 010/122] Implement DB persistence --- blockchain.db | Bin 0 -> 32768 bytes blockchain.go | 133 ++++++++++++++++++++++++++++++++++++++++++++++++-- main.go | 14 ++++-- 3 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 blockchain.db diff --git a/blockchain.db b/blockchain.db new file mode 100644 index 0000000000000000000000000000000000000000..f3f24abde6fae89fb4a4ef491047ec1bd402c7d4 GIT binary patch literal 32768 zcmeI&&1(}u7{K9~G>ND!77sNS%N$h@wCKTuMOvk5#EJ&=&`TN@TibltjS(Rt2BD&; zpcqfyJhYxfv3~T}Kfpr~ZG{%Wi-jI6G=fOt>}E$|QTJdDHU1VhGqai9yz`LD?E4B) zJ%V1lINQ7!aQdx3U-!NiA+HnmT65o6?(#zSRAjfAM~eUg2q1s}0tg_000IagfB*uk zM?iX^n0@@p{lEJzpeEcVuW$DMX1t-VSlaewPiM_kx(FbE00IagfB*srAbp} zp3KOgkiq@&Y+SArvgO!_m2>AEjN1dwm7wW_j;2!yOV1Xfvr>@Xp7!MDWJ`~gvg|=y zx%UKgFn^9jmczS*STmcQpSpAWuH8P@|49``@6Wu*Pn>IO|9W*fS}Ja>ceqwH+)EvD z;|VKuTJ2O_v2K-3t3$bXO8UpF`~sHc(2^MTo2B765I_I{1Q0*~0R#|0009IVM8NFd zTSfIvfT;s8b^Z;qOqPKF0tg_000IagfB*srAb`MsEnxQhzsmoA(60pi^8e<3A@*rU zrB9RppZCU-x^<36I{B}3Yw7_q`swJBM&zB-k8kfFGOxOFCUqH z{(f)l)#K~o@v-gGLo?&`4)^SjdVo!CJwS&_rbnz5MFBoF3Q`H5sAaj(*RZ7yK5N(i zLk Date: Mon, 28 Aug 2017 16:16:45 +0700 Subject: [PATCH 011/122] Refactor NewBlockchain --- blockchain.db | Bin 32768 -> 32768 bytes blockchain.go | 47 +++++++++++++++++------------------------------ main.go | 4 ++-- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/blockchain.db b/blockchain.db index f3f24abde6fae89fb4a4ef491047ec1bd402c7d4..090c32ce8f4764e27c0298725bbf74d4e9e09ced 100644 GIT binary patch delta 661 zcmZo@U}|V!+EA__&wTi3TKV_ie~hG8FPfdz`D5Ewz1gqKKJoqWRh>V_+Ig~pQ$W1k z|0c#io85Q4W8`&D%}XuLELL#J$xqH^QeYsUo$;Th7Xt(LQXcnnmaYDoZZ8T&RN^CK z7GFDQqIY_-c7!wSdN+BVqIfzX^Ardf#`sSuk0FPUs*Qn9csCgY2?*|H zfBt oEKnw|bCgLV;6+9S1_EX>{)_iuAQYTKH8u$NePiHHo<#u$0ISO)!vFvP delta 655 zcmZo@U}|V!+EA__&+xHht61_*2}bV9C!5k4GrTqWO0TxR@{U+?J>1pn#^grF@Q9jw zjJ)otd8x&j#R^V2`N`Q#3Je7FG5%vSVPIgtRdWCEq@c;ga`%#6Doksiesf37wn8zv zUoBs~zfIEw8XhpY!HJ!z)@brPMe%e(W-1UekntbuCx#qCsx}5b;oW2qBp}$&00H-b z6wpTuyC)|*sL(bB`8EVv;H?R n{@Lsf43z&4GHC?DkWqnwfSHW{nqmelDhRl3W8hDoMF9o?@z5bY diff --git a/blockchain.go b/blockchain.go index affba083..0aecb89f 100644 --- a/blockchain.go +++ b/blockchain.go @@ -1,9 +1,7 @@ package main import ( - "fmt" "log" - "os" "github.com/boltdb/bolt" ) @@ -13,8 +11,7 @@ const blocksBucket = "blocks" // Blockchain keeps a sequence of Blocks type Blockchain struct { - blocks []*Block - tip []byte + tip []byte } // BlockchainIterator is used to iterate over blockchain blocks @@ -91,18 +88,18 @@ func (i *BlockchainIterator) Next() *Block { // NewBlockchain creates a new Blockchain with genesis Block func NewBlockchain() *Blockchain { bc := Blockchain{} + db, err := bolt.Open(dbFile, 0600, nil) + if err != nil { + log.Panic(err) + } + defer db.Close() - if _, err := os.Stat(dbFile); os.IsNotExist(err) { - fmt.Println("Creating a new blockchain...") - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - defer db.Close() + err = db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) - genesis := NewGenesisBlock() + if b == nil { + genesis := NewGenesisBlock() - err = db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte(blocksBucket)) if err != nil { log.Panic(err) @@ -117,26 +114,16 @@ func NewBlockchain() *Blockchain { if err != nil { log.Panic(err) } - - return nil - }) - - bc.tip = genesis.Hash - } else { - // TODO: remove the duplication, check for the "l" key - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - defer db.Close() - - err = db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) + bc.tip = genesis.Hash + } else { tip := b.Get([]byte("l")) bc.tip = tip + } - return nil - }) + return nil + }) + if err != nil { + log.Panic(err) } return &bc diff --git a/main.go b/main.go index dec9ab41..58fcacd4 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,8 @@ import ( func main() { bc := NewBlockchain() - // bc.AddBlock("Send 1 BTC to Ivan") - // bc.AddBlock("Send 2 more BTC to Ivan") + bc.AddBlock("Send 1 BTC to Ivan") + bc.AddBlock("Send 2 more BTC to Ivan") bci := bc.Iterator() From a93e1e96c9e2419e298ece656578db140e1eb4ff Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 28 Aug 2017 16:28:23 +0700 Subject: [PATCH 012/122] Store a DB connection in Blockchain --- blockchain.db | Bin 32768 -> 32768 bytes blockchain.go | 40 ++++++++++++++++++++-------------------- main.go | 5 +++-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/blockchain.db b/blockchain.db index 090c32ce8f4764e27c0298725bbf74d4e9e09ced..a4e79c3c7e308999f553d34e061ae120c9849391 100644 GIT binary patch delta 542 zcmZo@U}|V!nqV;5KtO<>jR695md}d1m=Um9(O@J0Bmsqq3IdZ9I3!@QtWZ_!Zhux1 zyZOl#sB8m2Pywd^0~1(^d9$Fwd;UoV0+R&-I5tZdum~_do-FHN8nO+llZ}yqK>^5~ z0Oc=(^7labML=_aLSQuv3<(+tOm1`x znB3sR&U8d-@;pWHG=|;`$qQUxRi|8CUu2)tJCSGlWufe1qj_Ae;^+PaykJxyWFX@| zlY2nB@f!oOHNpOW6XT!F?nfjTdEHa0YA`U`1<4MytN!x@tiUmPM za1dQ8D2ga{6f@`|mJT8)I5;?nE+UmwbZCW!#COTPQcLBQA~k*wF8AK`-Q{AY8vYHu=AqH=MdTvwgO`W5H5}2q1s}0tg_000IagfB*sr zAh1FNOn&bZ)i(jor&IcO^7|P*IX#$;4`luntWTKyUx*!gk<$A$ckvr{o_FUr^xd4h zD`)PXm^^!KcCHxSG=6ui|MJiLleV#`JwMhKkF?&twJ*|LzTlTK*yVh=ERPBql&gCZ zNjsCZl0z~eWMIFQwd5Kh>yM4txyrhjmFaWN1k50KG@XjudbJQ8!Wtc z&LlFba*a=UMIuYv4hd0rKNNl4ez7}o;LGs3t-U8(?!9}ttL@eGjUS(f*AIVK?r`n- zaGQ?XsWWP)>gtK8Y+4=6St;oqv+@d9mbRqCpw}wrAH{|M0tg_000IagfB*srAW$0u zCVy`f)i(jA55V;K*Tz%w6bK-I00IagfB*srAbjmyCf{GG{{Kn867Z`3o9l(> z)R0Q=&-CtWb-LKE^wIPKJaXrzA7Dt|U7xC~5%NXz+Xv&H?7`BK42O$mg>y-TdfWr zKQR3}WJ3S}1Q0*~0R#|0009JQTfo%$P5r;x1)#Im+J0=F4gmxZKmY**5I_I{1Q0*~ w0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~fqzQi7iwCc(f|Me diff --git a/blockchain.go b/blockchain.go index 0aecb89f..5c6b119b 100644 --- a/blockchain.go +++ b/blockchain.go @@ -12,33 +12,33 @@ const blocksBucket = "blocks" // Blockchain keeps a sequence of Blocks type Blockchain struct { tip []byte + db *bolt.DB } // BlockchainIterator is used to iterate over blockchain blocks type BlockchainIterator struct { currentHash []byte + db *bolt.DB } // AddBlock saves provided data as a block in the blockchain func (bc *Blockchain) AddBlock(data string) { var lastHash []byte - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - defer db.Close() - - err = db.View(func(tx *bolt.Tx) error { + err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) return nil }) + if err != nil { + log.Panic(err) + } + newBlock := NewBlock(data, lastHash) - err = db.Update(func(tx *bolt.Tx) error { + err = bc.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { @@ -58,7 +58,7 @@ func (bc *Blockchain) AddBlock(data string) { // Iterator ... func (bc *Blockchain) Iterator() *BlockchainIterator { - bci := &BlockchainIterator{bc.tip} + bci := &BlockchainIterator{bc.tip, bc.db} return bci } @@ -66,13 +66,8 @@ func (bc *Blockchain) Iterator() *BlockchainIterator { // Next returns next block starting from the tip func (i *BlockchainIterator) Next() *Block { var block *Block - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - defer db.Close() - err = db.View(func(tx *bolt.Tx) error { + err := i.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) encodedBlock := b.Get(i.currentHash) block = DeserializeBlock(encodedBlock) @@ -80,6 +75,10 @@ func (i *BlockchainIterator) Next() *Block { return nil }) + if err != nil { + log.Panic(err) + } + i.currentHash = block.PrevBlockHash return block @@ -87,12 +86,11 @@ func (i *BlockchainIterator) Next() *Block { // NewBlockchain creates a new Blockchain with genesis Block func NewBlockchain() *Blockchain { - bc := Blockchain{} + var tip []byte db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Panic(err) } - defer db.Close() err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) @@ -114,17 +112,19 @@ func NewBlockchain() *Blockchain { if err != nil { log.Panic(err) } - bc.tip = genesis.Hash + tip = genesis.Hash } else { - tip := b.Get([]byte("l")) - bc.tip = tip + tip = b.Get([]byte("l")) } return nil }) + if err != nil { log.Panic(err) } + bc := Blockchain{tip, db} + return &bc } diff --git a/main.go b/main.go index 58fcacd4..57cc9bc5 100644 --- a/main.go +++ b/main.go @@ -7,9 +7,10 @@ import ( func main() { bc := NewBlockchain() + defer bc.db.Close() - bc.AddBlock("Send 1 BTC to Ivan") - bc.AddBlock("Send 2 more BTC to Ivan") + // bc.AddBlock("Send 1 BTC to Ivan") + // bc.AddBlock("Send 2 more BTC to Ivan") bci := bc.Iterator() From d99bbc1b632e223c5d0b50bb43e5e71a7a7e3c27 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 28 Aug 2017 16:28:59 +0700 Subject: [PATCH 013/122] Add .gitignore --- .gitignore | 1 + blockchain.db | Bin 32768 -> 0 bytes 2 files changed, 1 insertion(+) create mode 100644 .gitignore delete mode 100644 blockchain.db diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..98e6ef67 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.db diff --git a/blockchain.db b/blockchain.db deleted file mode 100644 index a4e79c3c7e308999f553d34e061ae120c9849391..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI(KWGzC9KiA4yW|25DU=RY#oKO6cik)Al9X$iw>fQgB3;4!6hOVoowQ}yYwoycBU9;?qq zl(L6|$*6i+l&!kPr}C~}O6R9lR8;g>x|CLNQQfJcJJX0ep7t}ro~RkvrwZPrtD{BS zdRhH`Jo;>>>OJjx7o9`S@c2=uRB%qrq&?+`R5CkW#eeQyc>Az8l&f4z+<4QQ^#^aq zlYK91sjorA`V&^Q!yl#HWi~pip)jlMs(aM+Tz|@U8WL9fgw`TmC-r=*PeyXJ=an;i z7L(@|?tZZb9vz(j<&8b~v~+&p#r#IkH~V)L`WmY+=;RAUw{ Date: Mon, 28 Aug 2017 16:48:53 +0700 Subject: [PATCH 014/122] Implement basic CLI --- main.go | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/main.go b/main.go index 57cc9bc5..5c94a853 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "strconv" ) @@ -9,23 +10,32 @@ func main() { bc := NewBlockchain() defer bc.db.Close() - // bc.AddBlock("Send 1 BTC to Ivan") - // bc.AddBlock("Send 2 more BTC to Ivan") + if len(os.Args) < 2 { + fmt.Println("Wrong!") + os.Exit(1) + } + + if os.Args[1] == "addBlock" { + bc.AddBlock(os.Args[2]) + fmt.Println("Success!") + } - bci := bc.Iterator() + if os.Args[1] == "printChain" { + bci := bc.Iterator() - for { - block := bci.Next() + 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())) - fmt.Println() + 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())) + fmt.Println() - if len(block.PrevBlockHash) == 0 { - break + if len(block.PrevBlockHash) == 0 { + break + } } } } From b0791af5c6fa57f433234154e268af1adf92266a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 28 Aug 2017 21:03:43 +0700 Subject: [PATCH 015/122] Improve command line arguments processing --- cli.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 36 ++---------------------------- 2 files changed, 70 insertions(+), 34 deletions(-) create mode 100644 cli.go diff --git a/cli.go b/cli.go new file mode 100644 index 00000000..7893a534 --- /dev/null +++ b/cli.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "os" + "strconv" +) + +// CLI responsible for processing command line arguments +type CLI struct { + bc *Blockchain +} + +func (cli *CLI) printUsage() { + fmt.Println("Usage:") + fmt.Println(" addBlock BLOCK_DATA - add a block to the blockchain") + fmt.Println(" printChain - print all the blocks of the blockchain") +} + +func (cli *CLI) validateArgs() { + if len(os.Args) < 2 { + cli.printUsage() + os.Exit(1) + } +} + +func (cli *CLI) addBlock() { + if len(os.Args) < 3 { + fmt.Println("Error: BLOCK_DATA is not specified.") + os.Exit(1) + } + cli.bc.AddBlock(os.Args[2]) + fmt.Println("Success!") +} + +func (cli *CLI) printChain() { + bci := cli.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())) + fmt.Println() + + if len(block.PrevBlockHash) == 0 { + break + } + } +} + +// ProcessArgs parses command line arguments and processes commands +func (cli *CLI) ProcessArgs() { + cli.validateArgs() + + switch os.Args[1] { + case "addBlock": + cli.addBlock() + case "printChain": + cli.printChain() + default: + cli.printUsage() + os.Exit(1) + } +} diff --git a/main.go b/main.go index 5c94a853..4d455358 100644 --- a/main.go +++ b/main.go @@ -1,41 +1,9 @@ package main -import ( - "fmt" - "os" - "strconv" -) - func main() { bc := NewBlockchain() defer bc.db.Close() - if len(os.Args) < 2 { - fmt.Println("Wrong!") - os.Exit(1) - } - - if os.Args[1] == "addBlock" { - bc.AddBlock(os.Args[2]) - fmt.Println("Success!") - } - - if os.Args[1] == "printChain" { - 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())) - fmt.Println() - - if len(block.PrevBlockHash) == 0 { - break - } - } - } + cli := CLI{bc} + cli.ProcessArgs() } From 5b46248ff20447cff39c974c5bf5450bb85d19ca Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 29 Aug 2017 12:09:47 +0700 Subject: [PATCH 016/122] Rework the CLI using 'flag' --- cli.go | 49 +++++++++++++++++++++++++++++++++++-------------- main.go | 2 +- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/cli.go b/cli.go index 7893a534..127150ea 100644 --- a/cli.go +++ b/cli.go @@ -1,7 +1,9 @@ package main import ( + "flag" "fmt" + "log" "os" "strconv" ) @@ -13,8 +15,8 @@ type CLI struct { func (cli *CLI) printUsage() { fmt.Println("Usage:") - fmt.Println(" addBlock BLOCK_DATA - add a block to the blockchain") - fmt.Println(" printChain - print all the blocks of the blockchain") + fmt.Println(" addblock -data BLOCK_DATA - add a block to the blockchain") + fmt.Println(" printchain - print all the blocks of the blockchain") } func (cli *CLI) validateArgs() { @@ -24,12 +26,8 @@ func (cli *CLI) validateArgs() { } } -func (cli *CLI) addBlock() { - if len(os.Args) < 3 { - fmt.Println("Error: BLOCK_DATA is not specified.") - os.Exit(1) - } - cli.bc.AddBlock(os.Args[2]) +func (cli *CLI) addBlock(data string) { + cli.bc.AddBlock(data) fmt.Println("Success!") } @@ -52,17 +50,40 @@ func (cli *CLI) printChain() { } } -// ProcessArgs parses command line arguments and processes commands -func (cli *CLI) ProcessArgs() { +// Run parses command line arguments and processes commands +func (cli *CLI) Run() { cli.validateArgs() + addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) + printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) + + addBlockData := addBlockCmd.String("data", "", "Block data") + switch os.Args[1] { - case "addBlock": - cli.addBlock() - case "printChain": - cli.printChain() + case "addblock": + err := addBlockCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "printchain": + err := printChainCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } default: cli.printUsage() os.Exit(1) } + + if addBlockCmd.Parsed() { + if *addBlockData == "" { + addBlockCmd.Usage() + os.Exit(1) + } + cli.addBlock(*addBlockData) + } + + if printChainCmd.Parsed() { + cli.printChain() + } } diff --git a/main.go b/main.go index 4d455358..5ba110b7 100644 --- a/main.go +++ b/main.go @@ -5,5 +5,5 @@ func main() { defer bc.db.Close() cli := CLI{bc} - cli.ProcessArgs() + cli.Run() } From 54b6c07b6c45e5b89a08a3b7919d55591d24a7cf Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 29 Aug 2017 16:27:33 +0700 Subject: [PATCH 017/122] Add an information print when there's no blockchain --- blockchain.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blockchain.go b/blockchain.go index 5c6b119b..edc29dfb 100644 --- a/blockchain.go +++ b/blockchain.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "github.com/boltdb/bolt" @@ -96,6 +97,7 @@ func NewBlockchain() *Blockchain { b := tx.Bucket([]byte(blocksBucket)) if b == nil { + fmt.Println("No existing blockchain found. Creating a new one...") genesis := NewGenesisBlock() b, err := tx.CreateBucket([]byte(blocksBucket)) From d3b2c5c57648150c3a2df6bf08eb13a226ac1b9f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 29 Aug 2017 16:29:19 +0700 Subject: [PATCH 018/122] 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 019/122] 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 020/122] 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 021/122] 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 022/122] 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 023/122] 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 024/122] 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 025/122] 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 026/122] 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 027/122] 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 028/122] 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 029/122] 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 030/122] 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 031/122] 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 032/122] 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 033/122] 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 034/122] =?UTF-8?q?genesisCoinbase=20=E2=86=92=20genesisCo?= =?UTF-8?q?inbaseData?= 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 035/122] 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 036/122] 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 037/122] 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 038/122] 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 70c04fa8ce91d9dcd9df446c9bd247e20f138bbe Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 14:18:12 +0700 Subject: [PATCH 039/122] Implement address generation and wallets --- address.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ base58.go | 35 +++++++++++++++++++++++++++++ cli.go | 15 +++++++++++++ utils.go | 7 ++++++ 4 files changed, 123 insertions(+) create mode 100644 address.go create mode 100644 base58.go diff --git a/address.go b/address.go new file mode 100644 index 00000000..8c26e3a0 --- /dev/null +++ b/address.go @@ -0,0 +1,66 @@ +package main + +import ( + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "log" + + "golang.org/x/crypto/ripemd160" +) + +const version = byte(0x00) + +// Wallet ... +type Wallet struct { + PrivateKey []byte + PublicKey []byte +} + +// GetAddress returns wallet address +func (w Wallet) GetAddress() []byte { + publicSHA256 := sha256.Sum256(w.PublicKey) + + RIPEMD160Hasher := ripemd160.New() + _, err := RIPEMD160Hasher.Write(publicSHA256[:]) + if err != nil { + log.Panic(err) + } + publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) + + versionedPayload := append([]byte{version}, publicRIPEMD160...) + checksum := checksum(versionedPayload) + + fullPayload := append(versionedPayload, checksum...) + address := Base58Encode(fullPayload) + + return address +} + +// NewWallet ... +func NewWallet() *Wallet { + private, public := newKeyPair() + wallet := Wallet{private, public} + + return &wallet +} + +func newKeyPair() ([]byte, []byte) { + curve := elliptic.P256() + private, x, y, err := elliptic.GenerateKey(curve, rand.Reader) + if err != nil { + log.Panic(err) + } + + public := append(x.Bytes(), y.Bytes()...) + + return private, public +} + +// Checksum ... +func checksum(payload []byte) []byte { + firstSHA := sha256.Sum256(payload) + secondSHA := sha256.Sum256(firstSHA[:]) + + return secondSHA[:4] +} diff --git a/base58.go b/base58.go new file mode 100644 index 00000000..b9ccdcee --- /dev/null +++ b/base58.go @@ -0,0 +1,35 @@ +package main + +import ( + "math/big" +) + +var alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + +// Base58Encode encodes a byte array to Base58 +func Base58Encode(input []byte) []byte { + var result []byte + + x := big.NewInt(0) + x.SetBytes(input) + + base := big.NewInt(int64(len(alphabet))) + zero := big.NewInt(0) + mod := &big.Int{} + + for x.Cmp(zero) != 0 { + x.DivMod(x, base, mod) + result = append(result, alphabet[mod.Int64()]) + } + + ReverseBytes(result) + for c := range input { + if c == 0x00 { + result = append([]byte{alphabet[0]}, result...) + } else { + break + } + } + + return result +} diff --git a/cli.go b/cli.go index 1270c6e2..6476822e 100644 --- a/cli.go +++ b/cli.go @@ -17,6 +17,11 @@ func (cli *CLI) createBlockchain(address string) { fmt.Println("Done!") } +func (cli *CLI) createWallet() { + wallet := NewWallet() + fmt.Printf("Your address: %s\n", wallet.GetAddress()) +} + func (cli *CLI) getBalance(address string) { bc := NewBlockchain(address) defer bc.db.Close() @@ -83,6 +88,7 @@ func (cli *CLI) Run() { getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) + createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError) sendCmd := flag.NewFlagSet("send", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) @@ -103,6 +109,11 @@ func (cli *CLI) Run() { if err != nil { log.Panic(err) } + case "createwallet": + err := createWalletCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } case "printchain": err := printChainCmd.Parse(os.Args[2:]) if err != nil { @@ -134,6 +145,10 @@ func (cli *CLI) Run() { cli.createBlockchain(*createBlockchainAddress) } + if createWalletCmd.Parsed() { + cli.createWallet() + } + if printChainCmd.Parsed() { cli.printChain() } diff --git a/utils.go b/utils.go index 7f5e727b..aecf9192 100644 --- a/utils.go +++ b/utils.go @@ -16,3 +16,10 @@ func IntToHex(num int64) []byte { return buff.Bytes() } + +// ReverseBytes reverses a byte array +func ReverseBytes(data []byte) { + for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { + data[i], data[j] = data[j], data[i] + } +} From 24b19381d2ff9323917cf45373f7ef7ec1cf6fcd Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 16:33:17 +0700 Subject: [PATCH 040/122] Rename address.go to wallet.go --- address.go => wallet.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) rename address.go => wallet.go (91%) diff --git a/address.go b/wallet.go similarity index 91% rename from address.go rename to wallet.go index 8c26e3a0..e9c3a1bd 100644 --- a/address.go +++ b/wallet.go @@ -37,7 +37,12 @@ func (w Wallet) GetAddress() []byte { return address } -// NewWallet ... +// SaveToFile saves the wallet to a file +func (w Wallet) SaveToFile() { + +} + +// NewWallet creates and returns a Wallet func NewWallet() *Wallet { private, public := newKeyPair() wallet := Wallet{private, public} From 8d7f9452515556060be764ce4d99a66c54e0f85e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 16:42:38 +0700 Subject: [PATCH 041/122] Save wallet to a file --- .gitignore | 1 + cli.go | 1 + wallet.go | 15 +++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/.gitignore b/.gitignore index 98e6ef67..570133a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.db +wallet.dat diff --git a/cli.go b/cli.go index 6476822e..f32e3439 100644 --- a/cli.go +++ b/cli.go @@ -20,6 +20,7 @@ func (cli *CLI) createBlockchain(address string) { func (cli *CLI) createWallet() { wallet := NewWallet() fmt.Printf("Your address: %s\n", wallet.GetAddress()) + wallet.SaveToFile() } func (cli *CLI) getBalance(address string) { diff --git a/wallet.go b/wallet.go index e9c3a1bd..6752f620 100644 --- a/wallet.go +++ b/wallet.go @@ -1,15 +1,19 @@ package main import ( + "bytes" "crypto/elliptic" "crypto/rand" "crypto/sha256" + "encoding/gob" + "io/ioutil" "log" "golang.org/x/crypto/ripemd160" ) const version = byte(0x00) +const walletFile = "wallet.dat" // Wallet ... type Wallet struct { @@ -39,7 +43,18 @@ func (w Wallet) GetAddress() []byte { // SaveToFile saves the wallet to a file func (w Wallet) SaveToFile() { + var content bytes.Buffer + encoder := gob.NewEncoder(&content) + err := encoder.Encode(w) + if err != nil { + log.Panic(err) + } + + err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) + if err != nil { + log.Panic(err) + } } // NewWallet creates and returns a Wallet From 5a1e6f7e47eec498fefa66e9dcd8b3eab255c705 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 16:46:55 +0700 Subject: [PATCH 042/122] Don't create a wallet when wallet.dat already exists --- cli.go | 11 ++++++++--- wallet.go | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cli.go b/cli.go index f32e3439..86880564 100644 --- a/cli.go +++ b/cli.go @@ -18,9 +18,14 @@ func (cli *CLI) createBlockchain(address string) { } func (cli *CLI) createWallet() { - wallet := NewWallet() - fmt.Printf("Your address: %s\n", wallet.GetAddress()) - wallet.SaveToFile() + wallet, err := NewWallet() + if err == nil { + fmt.Printf("Your address: %s\n", wallet.GetAddress()) + wallet.SaveToFile() + } else { + fmt.Println(err.Error()) + os.Exit(1) + } } func (cli *CLI) getBalance(address string) { diff --git a/wallet.go b/wallet.go index 6752f620..0bb5ee57 100644 --- a/wallet.go +++ b/wallet.go @@ -6,8 +6,10 @@ import ( "crypto/rand" "crypto/sha256" "encoding/gob" + "errors" "io/ioutil" "log" + "os" "golang.org/x/crypto/ripemd160" ) @@ -58,11 +60,14 @@ func (w Wallet) SaveToFile() { } // NewWallet creates and returns a Wallet -func NewWallet() *Wallet { +func NewWallet() (*Wallet, error) { + if _, err := os.Stat(walletFile); !os.IsNotExist(err) { + return nil, errors.New("Wallet already exists") + } private, public := newKeyPair() wallet := Wallet{private, public} - return &wallet + return &wallet, nil } func newKeyPair() ([]byte, []byte) { From caf71744f585b31210737fa27b2cb67f159c9138 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 17:05:57 +0700 Subject: [PATCH 043/122] Use crypto/ecdsa to generate ECDSA key pair --- wallet.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/wallet.go b/wallet.go index 0bb5ee57..338edbf1 100644 --- a/wallet.go +++ b/wallet.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" @@ -19,13 +20,14 @@ const walletFile = "wallet.dat" // Wallet ... type Wallet struct { - PrivateKey []byte - PublicKey []byte + PrivateKey ecdsa.PrivateKey + PublicKey ecdsa.PublicKey } // GetAddress returns wallet address func (w Wallet) GetAddress() []byte { - publicSHA256 := sha256.Sum256(w.PublicKey) + public := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) + publicSHA256 := sha256.Sum256(public) RIPEMD160Hasher := ripemd160.New() _, err := RIPEMD160Hasher.Write(publicSHA256[:]) @@ -47,6 +49,7 @@ func (w Wallet) GetAddress() []byte { func (w Wallet) SaveToFile() { var content bytes.Buffer + gob.Register(w.PrivateKey.Curve) encoder := gob.NewEncoder(&content) err := encoder.Encode(w) if err != nil { @@ -70,16 +73,14 @@ func NewWallet() (*Wallet, error) { return &wallet, nil } -func newKeyPair() ([]byte, []byte) { +func newKeyPair() (ecdsa.PrivateKey, ecdsa.PublicKey) { curve := elliptic.P256() - private, x, y, err := elliptic.GenerateKey(curve, rand.Reader) + private, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { log.Panic(err) } - public := append(x.Bytes(), y.Bytes()...) - - return private, public + return *private, private.PublicKey } // Checksum ... From 4805ce1bdb27914159903ead330f812446b235b5 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 20:47:16 +0700 Subject: [PATCH 044/122] Implement Base58Decode --- base58.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/base58.go b/base58.go index b9ccdcee..bdeccc2f 100644 --- a/base58.go +++ b/base58.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "math/big" ) @@ -33,3 +34,27 @@ func Base58Encode(input []byte) []byte { return result } + +// Base58Decode decodes Base58 data +func Base58Decode(input []byte) []byte { + result := big.NewInt(0) + zeroBytes := 0 + + for c := range input { + if c == 0x00 { + zeroBytes++ + } + } + + address := input[zeroBytes:] + for _, b := range address { + charIndex := bytes.IndexByte(alphabet, b) + result.Mul(result, big.NewInt(58)) + result.Add(result, big.NewInt(int64(charIndex))) + } + + raw := result.Bytes() + raw = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), raw...) + + return raw +} From 5b0e4ecc193ddd05ec98d1dd59c80c2195599e99 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 09:46:06 +0700 Subject: [PATCH 045/122] Allow to create multiple wallets --- cli.go | 13 ++++---- wallet.go | 88 +++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/cli.go b/cli.go index 86880564..e3bb29e1 100644 --- a/cli.go +++ b/cli.go @@ -18,14 +18,11 @@ func (cli *CLI) createBlockchain(address string) { } func (cli *CLI) createWallet() { - wallet, err := NewWallet() - if err == nil { - fmt.Printf("Your address: %s\n", wallet.GetAddress()) - wallet.SaveToFile() - } else { - fmt.Println(err.Error()) - os.Exit(1) - } + wallets := NewWallets() + address := wallets.CreateWallet() + wallets.SaveToFile() + + fmt.Printf("Your new address: %s\n", address) } func (cli *CLI) getBalance(address string) { diff --git a/wallet.go b/wallet.go index 338edbf1..ace99fc4 100644 --- a/wallet.go +++ b/wallet.go @@ -7,7 +7,7 @@ import ( "crypto/rand" "crypto/sha256" "encoding/gob" - "errors" + "fmt" "io/ioutil" "log" "os" @@ -18,12 +18,17 @@ import ( const version = byte(0x00) const walletFile = "wallet.dat" -// Wallet ... +// Wallet stores private and public keys type Wallet struct { PrivateKey ecdsa.PrivateKey PublicKey ecdsa.PublicKey } +// Wallets stores a collection of wallets +type Wallets struct { + Wallets map[string]*Wallet +} + // GetAddress returns wallet address func (w Wallet) GetAddress() []byte { public := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) @@ -45,13 +50,42 @@ func (w Wallet) GetAddress() []byte { return address } -// SaveToFile saves the wallet to a file -func (w Wallet) SaveToFile() { +// NewWallet creates and returns a Wallet +func NewWallet() *Wallet { + private, public := newKeyPair() + wallet := Wallet{private, public} + + return &wallet +} + +func newKeyPair() (ecdsa.PrivateKey, ecdsa.PublicKey) { + curve := elliptic.P256() + private, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + log.Panic(err) + } + + return *private, private.PublicKey +} + +// CreateWallet adds a Wallet to Wallets +func (ws *Wallets) CreateWallet() string { + wallet := NewWallet() + address := fmt.Sprintf("%s", wallet.GetAddress()) + + ws.Wallets[address] = wallet + + return address +} + +// SaveToFile saves wallets to a file +func (ws Wallets) SaveToFile() { var content bytes.Buffer - gob.Register(w.PrivateKey.Curve) + gob.Register(elliptic.P256()) + encoder := gob.NewEncoder(&content) - err := encoder.Encode(w) + err := encoder.Encode(ws) if err != nil { log.Panic(err) } @@ -62,25 +96,43 @@ func (w Wallet) SaveToFile() { } } -// NewWallet creates and returns a Wallet -func NewWallet() (*Wallet, error) { - if _, err := os.Stat(walletFile); !os.IsNotExist(err) { - return nil, errors.New("Wallet already exists") +// LoadFromFile loads wallets from the file +func (ws *Wallets) LoadFromFile() error { + if _, err := os.Stat(walletFile); os.IsNotExist(err) { + return err } - private, public := newKeyPair() - wallet := Wallet{private, public} - return &wallet, nil -} + fileContent, err := ioutil.ReadFile(walletFile) + if err != nil { + log.Panic(err) + } -func newKeyPair() (ecdsa.PrivateKey, ecdsa.PublicKey) { - curve := elliptic.P256() - private, err := ecdsa.GenerateKey(curve, rand.Reader) + var wallets Wallets + gob.Register(elliptic.P256()) + decoder := gob.NewDecoder(bytes.NewReader(fileContent)) + err = decoder.Decode(&wallets) if err != nil { log.Panic(err) } - return *private, private.PublicKey + ws.Wallets = wallets.Wallets + + return nil +} + +// NewWallets ... +func NewWallets() *Wallets { + wallets := Wallets{} + wallets.Wallets = make(map[string]*Wallet) + + err := wallets.LoadFromFile() + if err != nil { + fmt.Println("Wallets file doesn't exist") + // wallets.CreateWallet() + // wallets.SaveToFile() + } + + return &wallets } // Checksum ... From deb7e2ce030fed640c7a504d338c36d0c235a6fc Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 09:51:44 +0700 Subject: [PATCH 046/122] Implement 'listaddresses' CLI command --- cli.go | 19 +++++++++++++++++++ wallet.go | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/cli.go b/cli.go index e3bb29e1..5b6e24fd 100644 --- a/cli.go +++ b/cli.go @@ -39,6 +39,15 @@ func (cli *CLI) getBalance(address string) { fmt.Printf("Balance of '%s': %d\n", address, balance) } +func (cli *CLI) listAddresses() { + wallets := NewWallets() + addresses := wallets.GetAddresses() + + for _, address := range addresses { + fmt.Println(address) + } +} + func (cli *CLI) printUsage() { fmt.Println("Usage:") fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS") @@ -92,6 +101,7 @@ func (cli *CLI) Run() { getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) 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) @@ -117,6 +127,11 @@ func (cli *CLI) Run() { if err != nil { log.Panic(err) } + case "listaddresses": + err := listAddressesCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } case "printchain": err := printChainCmd.Parse(os.Args[2:]) if err != nil { @@ -152,6 +167,10 @@ func (cli *CLI) Run() { cli.createWallet() } + if listAddressesCmd.Parsed() { + cli.listAddresses() + } + if printChainCmd.Parsed() { cli.printChain() } diff --git a/wallet.go b/wallet.go index ace99fc4..edadfbe1 100644 --- a/wallet.go +++ b/wallet.go @@ -120,6 +120,17 @@ func (ws *Wallets) LoadFromFile() error { return nil } +// GetAddresses returns an array of addresses stored in the wallet file +func (ws *Wallets) GetAddresses() []string { + var addresses []string + + for address := range ws.Wallets { + addresses = append(addresses, address) + } + + return addresses +} + // NewWallets ... func NewWallets() *Wallets { wallets := Wallets{} From 75105982ae6d983755e700a2ea148b2fb7132b4a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 09:53:26 +0700 Subject: [PATCH 047/122] Update usage --- cli.go | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/cli.go b/cli.go index 5b6e24fd..44de517b 100644 --- a/cli.go +++ b/cli.go @@ -48,23 +48,7 @@ func (cli *CLI) listAddresses() { } } -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") -} - -func (cli *CLI) validateArgs() { - if len(os.Args) < 2 { - cli.printUsage() - os.Exit(1) - } -} - func (cli *CLI) printChain() { - // TODO: Fix this bc := NewBlockchain("") defer bc.db.Close() @@ -94,6 +78,23 @@ func (cli *CLI) send(from, to string, amount int) { fmt.Println("Success!") } +func (cli *CLI) printUsage() { + fmt.Println("Usage:") + fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS") + fmt.Println(" createwallet - Generates a new key-pair and saves it into the wallet file") + 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(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO") +} + +func (cli *CLI) validateArgs() { + if len(os.Args) < 2 { + cli.printUsage() + os.Exit(1) + } +} + // Run parses command line arguments and processes commands func (cli *CLI) Run() { cli.validateArgs() From 2b0619e103469d8294a4e8d391cfb7cf0bfadc74 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 09:56:04 +0700 Subject: [PATCH 048/122] Improve NewWallets and fix comments --- cli.go | 7 +++++-- wallet.go | 13 ++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/cli.go b/cli.go index 44de517b..ca150655 100644 --- a/cli.go +++ b/cli.go @@ -18,7 +18,7 @@ func (cli *CLI) createBlockchain(address string) { } func (cli *CLI) createWallet() { - wallets := NewWallets() + wallets, _ := NewWallets() address := wallets.CreateWallet() wallets.SaveToFile() @@ -40,7 +40,10 @@ func (cli *CLI) getBalance(address string) { } func (cli *CLI) listAddresses() { - wallets := NewWallets() + wallets, err := NewWallets() + if err != nil { + log.Panic(err) + } addresses := wallets.GetAddresses() for _, address := range addresses { diff --git a/wallet.go b/wallet.go index edadfbe1..1c2bd3e3 100644 --- a/wallet.go +++ b/wallet.go @@ -131,22 +131,17 @@ func (ws *Wallets) GetAddresses() []string { return addresses } -// NewWallets ... -func NewWallets() *Wallets { +// NewWallets creates Wallets and fills it from a file if it exists +func NewWallets() (*Wallets, error) { wallets := Wallets{} wallets.Wallets = make(map[string]*Wallet) err := wallets.LoadFromFile() - if err != nil { - fmt.Println("Wallets file doesn't exist") - // wallets.CreateWallet() - // wallets.SaveToFile() - } - return &wallets + return &wallets, err } -// Checksum ... +// Checksum generates a checksum for a public key func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) From 6b400109e90662ca342f1b92bb206c332f656772 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 10:06:19 +0700 Subject: [PATCH 049/122] In the 'printchain' command, print transactions as well --- cli.go | 3 +++ transaction.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/cli.go b/cli.go index ca150655..1a0010d7 100644 --- a/cli.go +++ b/cli.go @@ -64,6 +64,9 @@ func (cli *CLI) printChain() { fmt.Printf("Hash: %x\n", block.Hash) pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) + for _, tx := range block.Transactions { + fmt.Println(tx) + } fmt.Println() if len(block.PrevBlockHash) == 0 { diff --git a/transaction.go b/transaction.go index fe82a7be..336a9e4b 100644 --- a/transaction.go +++ b/transaction.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "fmt" "log" + "strings" ) const subsidy = 10 @@ -23,6 +24,28 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } +// String returns a human-readable representation of a transaction +func (tx Transaction) String() string { + var lines []string + + lines = append(lines, fmt.Sprintf("Transaction %x:", tx.ID)) + + for i, input := range tx.Vin { + lines = append(lines, fmt.Sprintf(" Input %d:", i)) + lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) + lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) + lines = append(lines, fmt.Sprintf(" Script: %s", input.ScriptSig)) + } + + for i, output := range tx.Vout { + lines = append(lines, fmt.Sprintf(" Output %d:", i)) + lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) + lines = append(lines, fmt.Sprintf(" Script: %s", output.ScriptPubKey)) + } + + return strings.Join(lines, "\n") +} + // SetID sets ID of a transaction func (tx Transaction) SetID() { var encoded bytes.Buffer From e6eed1105f778fde024ea00d8f508ac49b1f8a8b Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 10:09:04 +0700 Subject: [PATCH 050/122] Fix Transaction.SetID --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index 336a9e4b..bab3562f 100644 --- a/transaction.go +++ b/transaction.go @@ -47,7 +47,7 @@ func (tx Transaction) String() string { } // SetID sets ID of a transaction -func (tx Transaction) SetID() { +func (tx *Transaction) SetID() { var encoded bytes.Buffer var hash [32]byte From 484d0bbae2a79fc977ad68d9c580294d976243d4 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 10:27:28 +0700 Subject: [PATCH 051/122] Extract public key hashing into a separate function --- wallet.go | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/wallet.go b/wallet.go index 1c2bd3e3..27b5d711 100644 --- a/wallet.go +++ b/wallet.go @@ -31,17 +31,10 @@ type Wallets struct { // GetAddress returns wallet address func (w Wallet) GetAddress() []byte { - public := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) - publicSHA256 := sha256.Sum256(public) + pubKey := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) + pubKeyHash := HashPubKey(pubKey) - RIPEMD160Hasher := ripemd160.New() - _, err := RIPEMD160Hasher.Write(publicSHA256[:]) - if err != nil { - log.Panic(err) - } - publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) - - versionedPayload := append([]byte{version}, publicRIPEMD160...) + versionedPayload := append([]byte{version}, pubKeyHash...) checksum := checksum(versionedPayload) fullPayload := append(versionedPayload, checksum...) @@ -141,6 +134,20 @@ func NewWallets() (*Wallets, error) { return &wallets, err } +// HashPubKey hashes public key +func HashPubKey(pubKey []byte) []byte { + publicSHA256 := sha256.Sum256(pubKey) + + RIPEMD160Hasher := ripemd160.New() + _, err := RIPEMD160Hasher.Write(publicSHA256[:]) + if err != nil { + log.Panic(err) + } + publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) + + return publicRIPEMD160 +} + // Checksum generates a checksum for a public key func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) From cb1776224ef97ebcd330c084bb8f530e0e139c2e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 10:41:03 +0700 Subject: [PATCH 052/122] Store public key as a byte array --- wallet.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/wallet.go b/wallet.go index 27b5d711..ca28c4fd 100644 --- a/wallet.go +++ b/wallet.go @@ -21,7 +21,7 @@ const walletFile = "wallet.dat" // Wallet stores private and public keys type Wallet struct { PrivateKey ecdsa.PrivateKey - PublicKey ecdsa.PublicKey + PublicKey []byte } // Wallets stores a collection of wallets @@ -31,8 +31,7 @@ type Wallets struct { // GetAddress returns wallet address func (w Wallet) GetAddress() []byte { - pubKey := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) - pubKeyHash := HashPubKey(pubKey) + pubKeyHash := HashPubKey(w.PublicKey) versionedPayload := append([]byte{version}, pubKeyHash...) checksum := checksum(versionedPayload) @@ -51,14 +50,15 @@ func NewWallet() *Wallet { return &wallet } -func newKeyPair() (ecdsa.PrivateKey, ecdsa.PublicKey) { +func newKeyPair() (ecdsa.PrivateKey, []byte) { curve := elliptic.P256() private, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { log.Panic(err) } + pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) - return *private, private.PublicKey + return *private, pubKey } // CreateWallet adds a Wallet to Wallets @@ -124,6 +124,11 @@ func (ws *Wallets) GetAddresses() []string { return addresses } +// GetWallet returns a Wallet by its address +func (ws Wallets) GetWallet(address string) Wallet { + return *ws.Wallets[address] +} + // NewWallets creates Wallets and fills it from a file if it exists func NewWallets() (*Wallets, error) { wallets := Wallets{} From 92be537fcdfe4a2464209fdc2aa073658189ee22 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 11:31:34 +0700 Subject: [PATCH 053/122] Use public key in transactions --- blockchain.go | 18 ++++++++-------- cli.go | 4 +++- transaction.go | 58 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/blockchain.go b/blockchain.go index b9cfb428..d644fe3b 100644 --- a/blockchain.go +++ b/blockchain.go @@ -61,7 +61,7 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { } // FindUnspentTransactions returns a list of transactions containing unspent outputs -func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { +func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { var unspentTXs []Transaction spentTXOs := make(map[string][]int) bci := bc.Iterator() @@ -83,14 +83,14 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { } } - if out.CanBeUnlockedWith(address) { + if out.Unlock(pubKeyHash) { unspentTXs = append(unspentTXs, *tx) } } if tx.IsCoinbase() == false { for _, in := range tx.Vin { - if in.CanUnlockOutputWith(address) { + if in.UnlocksOutputWith(pubKeyHash) { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } @@ -107,13 +107,13 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { } // FindUTXO finds and returns all unspent transaction outputs -func (bc *Blockchain) FindUTXO(address string) []TXOutput { +func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { var UTXOs []TXOutput - unspentTransactions := bc.FindUnspentTransactions(address) + unspentTransactions := bc.FindUnspentTransactions(pubKeyHash) for _, tx := range unspentTransactions { for _, out := range tx.Vout { - if out.CanBeUnlockedWith(address) { + if out.Unlock(pubKeyHash) { UTXOs = append(UTXOs, out) } } @@ -123,9 +123,9 @@ func (bc *Blockchain) FindUTXO(address string) []TXOutput { } // FindSpendableOutputs finds and returns unspent outputs to reference in inputs -func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { +func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) { unspentOutputs := make(map[string][]int) - unspentTXs := bc.FindUnspentTransactions(address) + unspentTXs := bc.FindUnspentTransactions(pubKeyHash) accumulated := 0 Work: @@ -133,7 +133,7 @@ Work: txID := hex.EncodeToString(tx.ID) for outIdx, out := range tx.Vout { - if out.CanBeUnlockedWith(address) && accumulated < amount { + if out.Unlock(pubKeyHash) && accumulated < amount { accumulated += out.Value unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) diff --git a/cli.go b/cli.go index 1a0010d7..2b011633 100644 --- a/cli.go +++ b/cli.go @@ -30,7 +30,9 @@ func (cli *CLI) getBalance(address string) { defer bc.db.Close() balance := 0 - UTXOs := bc.FindUTXO(address) + pubKeyHash := Base58Decode([]byte(address)) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + UTXOs := bc.FindUTXO(pubKeyHash) for _, out := range UTXOs { balance += out.Value diff --git a/transaction.go b/transaction.go index bab3562f..99393583 100644 --- a/transaction.go +++ b/transaction.go @@ -3,6 +3,7 @@ package main import ( "bytes" "crypto/sha256" + "encoding/gob" "encoding/hex" "fmt" @@ -34,13 +35,13 @@ func (tx Transaction) String() string { lines = append(lines, fmt.Sprintf(" Input %d:", i)) lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) - lines = append(lines, fmt.Sprintf(" Script: %s", input.ScriptSig)) + lines = append(lines, fmt.Sprintf(" Script: %x", input.ScriptSig)) } for i, output := range tx.Vout { lines = append(lines, fmt.Sprintf(" Output %d:", i)) lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) - lines = append(lines, fmt.Sprintf(" Script: %s", output.ScriptPubKey)) + lines = append(lines, fmt.Sprintf(" Script: %x", output.ScriptPubKey)) } return strings.Join(lines, "\n") @@ -64,23 +65,40 @@ func (tx *Transaction) SetID() { type TXInput struct { Txid []byte Vout int - ScriptSig string + ScriptSig []byte } // TXOutput represents a transaction output type TXOutput struct { Value int - ScriptPubKey string + ScriptPubKey []byte +} + +// UnlocksOutputWith checks whether the address initiated the transaction +func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { + lockingHash := HashPubKey(in.ScriptSig) + + return bytes.Compare(lockingHash, pubKeyHash) == 0 +} + +// Lock signs the output +func (out *TXOutput) Lock(address []byte) { + pubKeyHash := Base58Decode(address) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + out.ScriptPubKey = pubKeyHash } -// 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 used by the owner of the pubkey +func (out *TXOutput) Unlock(pubKeyHash []byte) bool { + return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 } -// CanBeUnlockedWith checks if the output can be unlocked with the provided data -func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { - return out.ScriptPubKey == unlockingData +// NewTXOutput create a new TXOutput +func NewTXOutput(value int, address string) *TXOutput { + txo := &TXOutput{value, nil} + txo.Lock([]byte(address)) + + return txo } // NewCoinbaseTX creates a new coinbase transaction @@ -89,9 +107,9 @@ func NewCoinbaseTX(to, data string) *Transaction { data = fmt.Sprintf("Reward to '%s'", to) } - txin := TXInput{[]byte{}, -1, data} - txout := TXOutput{subsidy, to} - tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} + txin := TXInput{[]byte{}, -1, []byte(data)} + txout := NewTXOutput(subsidy, to) + tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}} tx.SetID() return &tx @@ -102,7 +120,13 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio var inputs []TXInput var outputs []TXOutput - acc, validOutputs := bc.FindSpendableOutputs(from, amount) + wallets, err := NewWallets() + if err != nil { + log.Panic(err) + } + wallet := wallets.GetWallet(from) + pubKeyHash := HashPubKey(wallet.PublicKey) + acc, validOutputs := bc.FindSpendableOutputs(pubKeyHash, amount) if acc < amount { log.Panic("ERROR: Not enough funds") @@ -116,15 +140,15 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio } for _, out := range outs { - input := TXInput{txID, out, from} + input := TXInput{txID, out, wallet.PublicKey} inputs = append(inputs, input) } } // Build a list of outputs - outputs = append(outputs, TXOutput{amount, to}) + outputs = append(outputs, *NewTXOutput(amount, to)) if acc > amount { - outputs = append(outputs, TXOutput{acc - amount, from}) // a change + outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change } tx := Transaction{nil, inputs, outputs} From 7e8c88867da8e2fb67c4ca21c77a7834d11f579f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 21:29:34 +0700 Subject: [PATCH 054/122] Implement Transaction.Sign and Transaction.Verify --- transaction.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/transaction.go b/transaction.go index 99393583..81bbf460 100644 --- a/transaction.go +++ b/transaction.go @@ -2,7 +2,11 @@ package main import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "crypto/sha256" + "math/big" "encoding/gob" "encoding/hex" @@ -61,6 +65,98 @@ func (tx *Transaction) SetID() { tx.ID = hash[:] } +// TrimmedCopy creates a trimmed copy of Transaction to be used in signing +func (tx *Transaction) TrimmedCopy() Transaction { + var inputs []TXInput + var outputs []TXOutput + + for _, vin := range tx.Vin { + inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) + } + + for _, vout := range tx.Vout { + outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) + } + + txCopy := Transaction{tx.ID, inputs, outputs} + + return txCopy +} + +// Sign signs each input of a Transaction +func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTx *Transaction) { + if tx.IsCoinbase() { + return + } + + for _, vin := range tx.Vin { + if bytes.Compare(vin.Txid, prevTx.ID) != 0 { + log.Panic("ERROR: Previous transaction is not correct") + } + } + + txCopy := tx.TrimmedCopy() + + for inID, vin := range txCopy.Vin { + txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey + txCopy.SetID() + txCopy.Vin[inID].ScriptSig = []byte{} + + r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) + if err != nil { + log.Panic(err) + } + signature := append(r.Bytes(), s.Bytes()...) + + tx.Vin[inID].ScriptSig = append(signature, tx.Vin[inID].ScriptSig...) + } +} + +// Verify verifies signatures of Transaction inputs +func (tx *Transaction) Verify(prevTx *Transaction) bool { + sigLen := 64 + + if tx.IsCoinbase() { + return true + } + + for _, vin := range tx.Vin { + if bytes.Compare(vin.Txid, prevTx.ID) != 0 { + log.Panic("ERROR: Previous transaction is not correct") + } + } + + txCopy := tx.TrimmedCopy() + curve := elliptic.P256() + + for inID, vin := range tx.Vin { + txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey + txCopy.SetID() + txCopy.Vin[inID].ScriptSig = []byte{} + + signature := vin.ScriptSig[:sigLen] + pubKey := vin.ScriptSig[sigLen:] + + r := big.Int{} + s := big.Int{} + r.SetBytes(signature[:(sigLen / 2)]) + s.SetBytes(signature[(sigLen / 2):]) + + x := big.Int{} + y := big.Int{} + keyLen := len(pubKey) + x.SetBytes(pubKey[:(keyLen / 2)]) + y.SetBytes(pubKey[(keyLen / 2):]) + + rawPubKey := ecdsa.PublicKey{curve, &x, &y} + if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { + return false + } + } + + return true +} + // TXInput represents a transaction input type TXInput struct { Txid []byte From 2ce04f8f595ad4442a54cbeb7fcd026f657d2934 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 10:34:39 +0700 Subject: [PATCH 055/122] Implement transactions signing and verification --- blockchain.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ transaction.go | 12 ++++++--- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/blockchain.go b/blockchain.go index d644fe3b..48709aeb 100644 --- a/blockchain.go +++ b/blockchain.go @@ -1,7 +1,10 @@ package main import ( + "bytes" + "crypto/ecdsa" "encoding/hex" + "errors" "fmt" "log" "os" @@ -29,6 +32,12 @@ type BlockchainIterator struct { func (bc *Blockchain) MineBlock(transactions []*Transaction) { var lastHash []byte + for _, tx := range transactions { + if bc.VerifyTransaction(tx) != true { + log.Panic("ERROR: Invalid transaction") + } + } + err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) @@ -60,6 +69,63 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { }) } +// FindTransaction finds a transaction by its ID +func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { + bci := bc.Iterator() + + for { + block := bci.Next() + + for _, tx := range block.Transactions { + if bytes.Compare(tx.ID, ID) == 0 { + return *tx, nil + } + } + + if len(block.PrevBlockHash) == 0 { + break + } + } + + return Transaction{}, errors.New("Transaction is not found") +} + +// SignTransaction signs a Transaction +func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) + if err != nil { + log.Panic(err) + } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + tx.Sign(privKey, prevTXs) +} + +// VerifyTransaction verifies transaction +func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) + if err != nil { + log.Panic(err) + } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + for _, tx := range prevTXs { + fmt.Println(tx) + } + // fmt.Println() + // fmt.Println(tx) + + return tx.Verify(prevTXs) +} + // FindUnspentTransactions returns a list of transactions containing unspent outputs func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { var unspentTXs []Transaction diff --git a/transaction.go b/transaction.go index 81bbf460..b7463f16 100644 --- a/transaction.go +++ b/transaction.go @@ -36,6 +36,7 @@ func (tx Transaction) String() string { lines = append(lines, fmt.Sprintf("Transaction %x:", tx.ID)) for i, input := range tx.Vin { + lines = append(lines, fmt.Sprintf(" Input %d:", i)) lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) @@ -84,13 +85,13 @@ func (tx *Transaction) TrimmedCopy() Transaction { } // Sign signs each input of a Transaction -func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTx *Transaction) { +func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { return } for _, vin := range tx.Vin { - if bytes.Compare(vin.Txid, prevTx.ID) != 0 { + if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil { log.Panic("ERROR: Previous transaction is not correct") } } @@ -98,6 +99,7 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTx *Transaction) { txCopy := tx.TrimmedCopy() for inID, vin := range txCopy.Vin { + prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey txCopy.SetID() txCopy.Vin[inID].ScriptSig = []byte{} @@ -113,7 +115,7 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTx *Transaction) { } // Verify verifies signatures of Transaction inputs -func (tx *Transaction) Verify(prevTx *Transaction) bool { +func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { sigLen := 64 if tx.IsCoinbase() { @@ -121,7 +123,7 @@ func (tx *Transaction) Verify(prevTx *Transaction) bool { } for _, vin := range tx.Vin { - if bytes.Compare(vin.Txid, prevTx.ID) != 0 { + if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil { log.Panic("ERROR: Previous transaction is not correct") } } @@ -130,6 +132,7 @@ func (tx *Transaction) Verify(prevTx *Transaction) bool { curve := elliptic.P256() for inID, vin := range tx.Vin { + prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey txCopy.SetID() txCopy.Vin[inID].ScriptSig = []byte{} @@ -249,6 +252,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio tx := Transaction{nil, inputs, outputs} tx.SetID() + bc.SignTransaction(&tx, wallet.PrivateKey) return &tx } From fc0c819c437cd369b3cae3564c90c2e253676407 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 10:54:58 +0700 Subject: [PATCH 056/122] Extract some structs into separate files --- block.go | 52 ++++---- blockchain.go | 297 +++++++++++++++++++---------------------- blockchain_iterator.go | 34 +++++ transaction.go | 122 ++++++----------- transaction_input.go | 17 +++ transaction_output.go | 29 ++++ wallet.go | 100 +------------- wallets.go | 94 +++++++++++++ 8 files changed, 382 insertions(+), 363 deletions(-) create mode 100644 blockchain_iterator.go create mode 100644 transaction_input.go create mode 100644 transaction_output.go create mode 100644 wallets.go diff --git a/block.go b/block.go index 04d96cc2..3baaa469 100644 --- a/block.go +++ b/block.go @@ -17,32 +17,6 @@ type Block struct { Nonce int } -// Serialize serializes the block -func (b *Block) Serialize() []byte { - var result bytes.Buffer - encoder := gob.NewEncoder(&result) - - err := encoder.Encode(b) - if err != nil { - log.Panic(err) - } - - 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(transactions []*Transaction, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} @@ -72,3 +46,29 @@ func DeserializeBlock(d []byte) *Block { return &block } + +// 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[:] +} + +// Serialize serializes the block +func (b *Block) Serialize() []byte { + var result bytes.Buffer + encoder := gob.NewEncoder(&result) + + err := encoder.Encode(b) + if err != nil { + log.Panic(err) + } + + return result.Bytes() +} diff --git a/blockchain.go b/blockchain.go index 48709aeb..ef4d221b 100644 --- a/blockchain.go +++ b/blockchain.go @@ -22,51 +22,103 @@ type Blockchain struct { db *bolt.DB } -// BlockchainIterator is used to iterate over blockchain blocks -type BlockchainIterator struct { - currentHash []byte - db *bolt.DB -} - -// MineBlock mines a new block with the provided transactions -func (bc *Blockchain) MineBlock(transactions []*Transaction) { - var lastHash []byte - - for _, tx := range transactions { - if bc.VerifyTransaction(tx) != true { - log.Panic("ERROR: Invalid transaction") - } +// CreateBlockchain creates a new blockchain DB +func CreateBlockchain(address string) *Blockchain { + if dbExists() { + fmt.Println("Blockchain already exists.") + os.Exit(1) } - err := bc.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - lastHash = b.Get([]byte("l")) - - return nil - }) - + var tip []byte + db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Panic(err) } - newBlock := NewBlock(transactions, lastHash) + err = db.Update(func(tx *bolt.Tx) error { + cbtx := NewCoinbaseTX(address, genesisCoinbaseData) + genesis := NewGenesisBlock(cbtx) - err = bc.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - err := b.Put(newBlock.Hash, newBlock.Serialize()) + b, err := tx.CreateBucket([]byte(blocksBucket)) if err != nil { log.Panic(err) } - err = b.Put([]byte("l"), newBlock.Hash) + err = b.Put(genesis.Hash, genesis.Serialize()) if err != nil { log.Panic(err) } - bc.tip = newBlock.Hash + err = b.Put([]byte("l"), genesis.Hash) + if err != nil { + log.Panic(err) + } + tip = genesis.Hash return nil }) + + if err != nil { + log.Panic(err) + } + + bc := Blockchain{tip, db} + + return &bc +} + +// NewBlockchain creates a new Blockchain with genesis Block +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 { + log.Panic(err) + } + + err = db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + tip = b.Get([]byte("l")) + + return nil + }) + + if err != nil { + log.Panic(err) + } + + bc := Blockchain{tip, db} + + 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.Unlock(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 @@ -90,42 +142,6 @@ func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { return Transaction{}, errors.New("Transaction is not found") } -// SignTransaction signs a Transaction -func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { - prevTXs := make(map[string]Transaction) - - for _, vin := range tx.Vin { - prevTX, err := bc.FindTransaction(vin.Txid) - if err != nil { - log.Panic(err) - } - prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX - } - - tx.Sign(privKey, prevTXs) -} - -// VerifyTransaction verifies transaction -func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { - prevTXs := make(map[string]Transaction) - - for _, vin := range tx.Vin { - prevTX, err := bc.FindTransaction(vin.Txid) - if err != nil { - log.Panic(err) - } - prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX - } - - for _, tx := range prevTXs { - fmt.Println(tx) - } - // fmt.Println() - // fmt.Println(tx) - - return tx.Verify(prevTXs) -} - // FindUnspentTransactions returns a list of transactions containing unspent outputs func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { var unspentTXs []Transaction @@ -188,46 +204,19 @@ func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { return UTXOs } -// 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.Unlock(pubKeyHash) && accumulated < amount { - accumulated += out.Value - unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) +// MineBlock mines a new block with the provided transactions +func (bc *Blockchain) MineBlock(transactions []*Transaction) { + var lastHash []byte - if accumulated >= amount { - break Work - } - } + for _, tx := range transactions { + if bc.VerifyTransaction(tx) != true { + log.Panic("ERROR: Invalid transaction") } } - return accumulated, unspentOutputs -} - -// Iterator returns a BlockchainIterat -func (bc *Blockchain) Iterator() *BlockchainIterator { - bci := &BlockchainIterator{bc.tip, bc.db} - - return bci -} - -// Next returns next block starting from the tip -func (i *BlockchainIterator) Next() *Block { - var block *Block - - err := i.db.View(func(tx *bolt.Tx) error { + err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) - encodedBlock := b.Get(i.currentHash) - block = DeserializeBlock(encodedBlock) + lastHash = b.Get([]byte("l")) return nil }) @@ -236,89 +225,73 @@ func (i *BlockchainIterator) Next() *Block { log.Panic(err) } - i.currentHash = block.PrevBlockHash - - return block -} - -func dbExists() bool { - if _, err := os.Stat(dbFile); os.IsNotExist(err) { - return false - } - - return true -} + newBlock := NewBlock(transactions, lastHash) -// NewBlockchain creates a new Blockchain with genesis Block -func NewBlockchain(address string) *Blockchain { - if dbExists() == false { - fmt.Println("No existing blockchain found. Create one first.") - os.Exit(1) - } + err = bc.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + err := b.Put(newBlock.Hash, newBlock.Serialize()) + if err != nil { + log.Panic(err) + } - var tip []byte - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } + err = b.Put([]byte("l"), newBlock.Hash) + if err != nil { + log.Panic(err) + } - err = db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - tip = b.Get([]byte("l")) + bc.tip = newBlock.Hash return nil }) - - if err != nil { - log.Panic(err) - } - - bc := Blockchain{tip, db} - - 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) +// SignTransaction signs a Transaction +func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { + prevTXs := make(map[string]Transaction) - b, err := tx.CreateBucket([]byte(blocksBucket)) + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) if err != nil { log.Panic(err) } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } - err = b.Put(genesis.Hash, genesis.Serialize()) - if err != nil { - log.Panic(err) - } + tx.Sign(privKey, prevTXs) +} - err = b.Put([]byte("l"), genesis.Hash) +// VerifyTransaction verifies transaction +func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) if err != nil { log.Panic(err) } - tip = genesis.Hash - - return nil - }) + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } - if err != nil { - log.Panic(err) + for _, tx := range prevTXs { + fmt.Println(tx) } + // fmt.Println() + // fmt.Println(tx) - bc := Blockchain{tip, db} + return tx.Verify(prevTXs) +} - return &bc +// Iterator returns a BlockchainIterat +func (bc *Blockchain) Iterator() *BlockchainIterator { + bci := &BlockchainIterator{bc.tip, bc.db} + + return bci +} + +func dbExists() bool { + if _, err := os.Stat(dbFile); os.IsNotExist(err) { + return false + } + + return true } diff --git a/blockchain_iterator.go b/blockchain_iterator.go new file mode 100644 index 00000000..305311d9 --- /dev/null +++ b/blockchain_iterator.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + + "github.com/boltdb/bolt" +) + +// BlockchainIterator is used to iterate over blockchain blocks +type BlockchainIterator struct { + currentHash []byte + db *bolt.DB +} + +// Next returns next block starting from the tip +func (i *BlockchainIterator) Next() *Block { + var block *Block + + err := i.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + encodedBlock := b.Get(i.currentHash) + block = DeserializeBlock(encodedBlock) + + return nil + }) + + if err != nil { + log.Panic(err) + } + + i.currentHash = block.PrevBlockHash + + return block +} diff --git a/transaction.go b/transaction.go index b7463f16..e5176d61 100644 --- a/transaction.go +++ b/transaction.go @@ -29,29 +29,6 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } -// String returns a human-readable representation of a transaction -func (tx Transaction) String() string { - var lines []string - - lines = append(lines, fmt.Sprintf("Transaction %x:", tx.ID)) - - for i, input := range tx.Vin { - - lines = append(lines, fmt.Sprintf(" Input %d:", i)) - lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) - lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) - lines = append(lines, fmt.Sprintf(" Script: %x", input.ScriptSig)) - } - - for i, output := range tx.Vout { - lines = append(lines, fmt.Sprintf(" Output %d:", i)) - lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) - lines = append(lines, fmt.Sprintf(" Script: %x", output.ScriptPubKey)) - } - - return strings.Join(lines, "\n") -} - // SetID sets ID of a transaction func (tx *Transaction) SetID() { var encoded bytes.Buffer @@ -66,24 +43,6 @@ func (tx *Transaction) SetID() { tx.ID = hash[:] } -// TrimmedCopy creates a trimmed copy of Transaction to be used in signing -func (tx *Transaction) TrimmedCopy() Transaction { - var inputs []TXInput - var outputs []TXOutput - - for _, vin := range tx.Vin { - inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) - } - - for _, vout := range tx.Vout { - outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) - } - - txCopy := Transaction{tx.ID, inputs, outputs} - - return txCopy -} - // Sign signs each input of a Transaction func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { @@ -114,6 +73,47 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac } } +// String returns a human-readable representation of a transaction +func (tx Transaction) String() string { + var lines []string + + lines = append(lines, fmt.Sprintf("Transaction %x:", tx.ID)) + + for i, input := range tx.Vin { + + lines = append(lines, fmt.Sprintf(" Input %d:", i)) + lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) + lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) + lines = append(lines, fmt.Sprintf(" Script: %x", input.ScriptSig)) + } + + for i, output := range tx.Vout { + lines = append(lines, fmt.Sprintf(" Output %d:", i)) + lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) + lines = append(lines, fmt.Sprintf(" Script: %x", output.ScriptPubKey)) + } + + return strings.Join(lines, "\n") +} + +// TrimmedCopy creates a trimmed copy of Transaction to be used in signing +func (tx *Transaction) TrimmedCopy() Transaction { + var inputs []TXInput + var outputs []TXOutput + + for _, vin := range tx.Vin { + inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) + } + + for _, vout := range tx.Vout { + outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) + } + + txCopy := Transaction{tx.ID, inputs, outputs} + + return txCopy +} + // Verify verifies signatures of Transaction inputs func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { sigLen := 64 @@ -160,46 +160,6 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { return true } -// TXInput represents a transaction input -type TXInput struct { - Txid []byte - Vout int - ScriptSig []byte -} - -// TXOutput represents a transaction output -type TXOutput struct { - Value int - ScriptPubKey []byte -} - -// UnlocksOutputWith checks whether the address initiated the transaction -func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { - lockingHash := HashPubKey(in.ScriptSig) - - return bytes.Compare(lockingHash, pubKeyHash) == 0 -} - -// Lock signs the output -func (out *TXOutput) Lock(address []byte) { - pubKeyHash := Base58Decode(address) - pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] - out.ScriptPubKey = pubKeyHash -} - -// Unlock checks if the output can be used by the owner of the pubkey -func (out *TXOutput) Unlock(pubKeyHash []byte) bool { - return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 -} - -// NewTXOutput create a new TXOutput -func NewTXOutput(value int, address string) *TXOutput { - txo := &TXOutput{value, nil} - txo.Lock([]byte(address)) - - return txo -} - // NewCoinbaseTX creates a new coinbase transaction func NewCoinbaseTX(to, data string) *Transaction { if data == "" { diff --git a/transaction_input.go b/transaction_input.go new file mode 100644 index 00000000..1c57c2b5 --- /dev/null +++ b/transaction_input.go @@ -0,0 +1,17 @@ +package main + +import "bytes" + +// TXInput represents a transaction input +type TXInput struct { + Txid []byte + Vout int + ScriptSig []byte +} + +// UnlocksOutputWith checks whether the address initiated the transaction +func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { + lockingHash := HashPubKey(in.ScriptSig) + + return bytes.Compare(lockingHash, pubKeyHash) == 0 +} diff --git a/transaction_output.go b/transaction_output.go new file mode 100644 index 00000000..f509dd88 --- /dev/null +++ b/transaction_output.go @@ -0,0 +1,29 @@ +package main + +import "bytes" + +// TXOutput represents a transaction output +type TXOutput struct { + Value int + ScriptPubKey []byte +} + +// Lock signs the output +func (out *TXOutput) Lock(address []byte) { + pubKeyHash := Base58Decode(address) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + out.ScriptPubKey = pubKeyHash +} + +// Unlock checks if the output can be used by the owner of the pubkey +func (out *TXOutput) Unlock(pubKeyHash []byte) bool { + return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 +} + +// NewTXOutput create a new TXOutput +func NewTXOutput(value int, address string) *TXOutput { + txo := &TXOutput{value, nil} + txo.Lock([]byte(address)) + + return txo +} diff --git a/wallet.go b/wallet.go index ca28c4fd..d527988d 100644 --- a/wallet.go +++ b/wallet.go @@ -1,16 +1,11 @@ package main import ( - "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" - "encoding/gob" - "fmt" - "io/ioutil" "log" - "os" "golang.org/x/crypto/ripemd160" ) @@ -24,9 +19,12 @@ type Wallet struct { PublicKey []byte } -// Wallets stores a collection of wallets -type Wallets struct { - Wallets map[string]*Wallet +// NewWallet creates and returns a Wallet +func NewWallet() *Wallet { + private, public := newKeyPair() + wallet := Wallet{private, public} + + return &wallet } // GetAddress returns wallet address @@ -42,14 +40,6 @@ func (w Wallet) GetAddress() []byte { return address } -// NewWallet creates and returns a Wallet -func NewWallet() *Wallet { - private, public := newKeyPair() - wallet := Wallet{private, public} - - return &wallet -} - func newKeyPair() (ecdsa.PrivateKey, []byte) { curve := elliptic.P256() private, err := ecdsa.GenerateKey(curve, rand.Reader) @@ -61,84 +51,6 @@ func newKeyPair() (ecdsa.PrivateKey, []byte) { return *private, pubKey } -// CreateWallet adds a Wallet to Wallets -func (ws *Wallets) CreateWallet() string { - wallet := NewWallet() - address := fmt.Sprintf("%s", wallet.GetAddress()) - - ws.Wallets[address] = wallet - - return address -} - -// SaveToFile saves wallets to a file -func (ws Wallets) SaveToFile() { - var content bytes.Buffer - - gob.Register(elliptic.P256()) - - encoder := gob.NewEncoder(&content) - err := encoder.Encode(ws) - if err != nil { - log.Panic(err) - } - - err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) - if err != nil { - log.Panic(err) - } -} - -// LoadFromFile loads wallets from the file -func (ws *Wallets) LoadFromFile() error { - if _, err := os.Stat(walletFile); os.IsNotExist(err) { - return err - } - - fileContent, err := ioutil.ReadFile(walletFile) - if err != nil { - log.Panic(err) - } - - var wallets Wallets - gob.Register(elliptic.P256()) - decoder := gob.NewDecoder(bytes.NewReader(fileContent)) - err = decoder.Decode(&wallets) - if err != nil { - log.Panic(err) - } - - ws.Wallets = wallets.Wallets - - return nil -} - -// GetAddresses returns an array of addresses stored in the wallet file -func (ws *Wallets) GetAddresses() []string { - var addresses []string - - for address := range ws.Wallets { - addresses = append(addresses, address) - } - - return addresses -} - -// GetWallet returns a Wallet by its address -func (ws Wallets) GetWallet(address string) Wallet { - return *ws.Wallets[address] -} - -// NewWallets creates Wallets and fills it from a file if it exists -func NewWallets() (*Wallets, error) { - wallets := Wallets{} - wallets.Wallets = make(map[string]*Wallet) - - err := wallets.LoadFromFile() - - return &wallets, err -} - // HashPubKey hashes public key func HashPubKey(pubKey []byte) []byte { publicSHA256 := sha256.Sum256(pubKey) diff --git a/wallets.go b/wallets.go new file mode 100644 index 00000000..e5b2134c --- /dev/null +++ b/wallets.go @@ -0,0 +1,94 @@ +package main + +import ( + "bytes" + "crypto/elliptic" + "encoding/gob" + "fmt" + "io/ioutil" + "log" + "os" +) + +// Wallets stores a collection of wallets +type Wallets struct { + Wallets map[string]*Wallet +} + +// NewWallets creates Wallets and fills it from a file if it exists +func NewWallets() (*Wallets, error) { + wallets := Wallets{} + wallets.Wallets = make(map[string]*Wallet) + + err := wallets.LoadFromFile() + + return &wallets, err +} + +// CreateWallet adds a Wallet to Wallets +func (ws *Wallets) CreateWallet() string { + wallet := NewWallet() + address := fmt.Sprintf("%s", wallet.GetAddress()) + + ws.Wallets[address] = wallet + + return address +} + +// GetAddresses returns an array of addresses stored in the wallet file +func (ws *Wallets) GetAddresses() []string { + var addresses []string + + for address := range ws.Wallets { + addresses = append(addresses, address) + } + + return addresses +} + +// GetWallet returns a Wallet by its address +func (ws Wallets) GetWallet(address string) Wallet { + return *ws.Wallets[address] +} + +// LoadFromFile loads wallets from the file +func (ws *Wallets) LoadFromFile() error { + if _, err := os.Stat(walletFile); os.IsNotExist(err) { + return err + } + + fileContent, err := ioutil.ReadFile(walletFile) + if err != nil { + log.Panic(err) + } + + var wallets Wallets + gob.Register(elliptic.P256()) + decoder := gob.NewDecoder(bytes.NewReader(fileContent)) + err = decoder.Decode(&wallets) + if err != nil { + log.Panic(err) + } + + ws.Wallets = wallets.Wallets + + return nil +} + +// SaveToFile saves wallets to a file +func (ws Wallets) SaveToFile() { + var content bytes.Buffer + + gob.Register(elliptic.P256()) + + encoder := gob.NewEncoder(&content) + err := encoder.Encode(ws) + if err != nil { + log.Panic(err) + } + + err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) + if err != nil { + log.Panic(err) + } +} From 843858dc3739e2067555b34f5839ee9788cfde5a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 11:06:12 +0700 Subject: [PATCH 057/122] Fix TXInput.UnlocksOutputWith --- transaction_input.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transaction_input.go b/transaction_input.go index 1c57c2b5..e64e9e67 100644 --- a/transaction_input.go +++ b/transaction_input.go @@ -11,7 +11,9 @@ type TXInput struct { // UnlocksOutputWith checks whether the address initiated the transaction func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { - lockingHash := HashPubKey(in.ScriptSig) + sigLen := 64 + pubKey := in.ScriptSig[sigLen:] + lockingHash := HashPubKey(pubKey) return bytes.Compare(lockingHash, pubKeyHash) == 0 } From 80e320a16f0af2e9d7184996d712423a841483d1 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 11:42:11 +0700 Subject: [PATCH 058/122] Clean up base58.go --- base58.go | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/base58.go b/base58.go index bdeccc2f..589e00c0 100644 --- a/base58.go +++ b/base58.go @@ -5,28 +5,27 @@ import ( "math/big" ) -var alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") +var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") // Base58Encode encodes a byte array to Base58 func Base58Encode(input []byte) []byte { var result []byte - x := big.NewInt(0) - x.SetBytes(input) + x := big.NewInt(0).SetBytes(input) - base := big.NewInt(int64(len(alphabet))) + base := big.NewInt(int64(len(b58Alphabet))) zero := big.NewInt(0) mod := &big.Int{} for x.Cmp(zero) != 0 { x.DivMod(x, base, mod) - result = append(result, alphabet[mod.Int64()]) + result = append(result, b58Alphabet[mod.Int64()]) } ReverseBytes(result) - for c := range input { - if c == 0x00 { - result = append([]byte{alphabet[0]}, result...) + for b := range input { + if b == 0x00 { + result = append([]byte{b58Alphabet[0]}, result...) } else { break } @@ -35,26 +34,26 @@ func Base58Encode(input []byte) []byte { return result } -// Base58Decode decodes Base58 data +// Base58Decode decodes Base58-encoded data func Base58Decode(input []byte) []byte { result := big.NewInt(0) zeroBytes := 0 - for c := range input { - if c == 0x00 { + for b := range input { + if b == 0x00 { zeroBytes++ } } - address := input[zeroBytes:] - for _, b := range address { - charIndex := bytes.IndexByte(alphabet, b) + payload := input[zeroBytes:] + for _, b := range payload { + charIndex := bytes.IndexByte(b58Alphabet, b) result.Mul(result, big.NewInt(58)) result.Add(result, big.NewInt(int64(charIndex))) } - raw := result.Bytes() - raw = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), raw...) + decoded := result.Bytes() + decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...) - return raw + return decoded } From 942120679bd8fd845df826314c592cb508396edf Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 12:02:46 +0700 Subject: [PATCH 059/122] Clean up block.go; rework transaction hashing --- block.go | 30 +++++++++++++++--------------- transaction.go | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/block.go b/block.go index 3baaa469..b98c79cc 100644 --- a/block.go +++ b/block.go @@ -8,7 +8,7 @@ import ( "time" ) -// Block keeps block headers +// Block represents a block in the blockchain type Block struct { Timestamp int64 Transactions []*Transaction @@ -34,26 +34,13 @@ func NewGenesisBlock(coinbase *Transaction) *Block { return NewBlock([]*Transaction{coinbase}, []byte{}) } -// DeserializeBlock deserializes a block -func DeserializeBlock(d []byte) *Block { - var block Block - - decoder := gob.NewDecoder(bytes.NewReader(d)) - err := decoder.Decode(&block) - if err != nil { - log.Panic(err) - } - - return &block -} - // 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) + txHashes = append(txHashes, tx.Hash()) } txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) @@ -72,3 +59,16 @@ func (b *Block) Serialize() []byte { return result.Bytes() } + +// DeserializeBlock deserializes a block +func DeserializeBlock(d []byte) *Block { + var block Block + + decoder := gob.NewDecoder(bytes.NewReader(d)) + err := decoder.Decode(&block) + if err != nil { + log.Panic(err) + } + + return &block +} diff --git a/transaction.go b/transaction.go index e5176d61..99e42825 100644 --- a/transaction.go +++ b/transaction.go @@ -29,7 +29,33 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } +// Serialize returns a serialized Transaction +func (tx Transaction) Serialize() []byte { + var encoded bytes.Buffer + + enc := gob.NewEncoder(&encoded) + err := enc.Encode(tx) + if err != nil { + log.Panic(err) + } + + return encoded.Bytes() +} + +// Hash returns the hash of the Transaction +func (tx *Transaction) Hash() []byte { + var hash [32]byte + + txCopy := *tx + txCopy.ID = []byte{} + + hash = sha256.Sum256(txCopy.Serialize()) + + return hash[:] +} + // SetID sets ID of a transaction +// TODO: Remove this func (tx *Transaction) SetID() { var encoded bytes.Buffer var hash [32]byte From bb70b4924ba97dda7d31b5c85c2ab80b38600ddd Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 12:45:15 +0700 Subject: [PATCH 060/122] Clean up blockchain.go; improve TXInput and TXOutput --- blockchain.go | 49 +++++++++++++++++++------------------------ transaction_input.go | 4 ++-- transaction_output.go | 4 ++-- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/blockchain.go b/blockchain.go index ef4d221b..e2579feb 100644 --- a/blockchain.go +++ b/blockchain.go @@ -30,15 +30,16 @@ func CreateBlockchain(address string) *Blockchain { } var tip []byte + + cbtx := NewCoinbaseTX(address, genesisCoinbaseData) + genesis := NewGenesisBlock(cbtx) + 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) @@ -57,7 +58,6 @@ func CreateBlockchain(address string) *Blockchain { return nil }) - if err != nil { log.Panic(err) } @@ -86,7 +86,6 @@ func NewBlockchain(address string) *Blockchain { return nil }) - if err != nil { log.Panic(err) } @@ -107,7 +106,7 @@ Work: txID := hex.EncodeToString(tx.ID) for outIdx, out := range tx.Vout { - if out.Unlock(pubKeyHash) && accumulated < amount { + if out.IsLockedWithKey(pubKeyHash) && accumulated < amount { accumulated += out.Value unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) @@ -158,21 +157,21 @@ func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { for outIdx, out := range tx.Vout { // Was the output spent? if spentTXOs[txID] != nil { - for _, spentOut := range spentTXOs[txID] { - if spentOut == outIdx { + for _, spentOutIdx := range spentTXOs[txID] { + if spentOutIdx == outIdx { continue Outputs } } } - if out.Unlock(pubKeyHash) { + if out.IsLockedWithKey(pubKeyHash) { unspentTXs = append(unspentTXs, *tx) } } if tx.IsCoinbase() == false { for _, in := range tx.Vin { - if in.UnlocksOutputWith(pubKeyHash) { + if in.UsesKey(pubKeyHash) { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } @@ -195,7 +194,7 @@ func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { for _, tx := range unspentTransactions { for _, out := range tx.Vout { - if out.Unlock(pubKeyHash) { + if out.IsLockedWithKey(pubKeyHash) { UTXOs = append(UTXOs, out) } } @@ -204,6 +203,13 @@ func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { return UTXOs } +// Iterator returns a BlockchainIterat +func (bc *Blockchain) Iterator() *BlockchainIterator { + bci := &BlockchainIterator{bc.tip, bc.db} + + return bci +} + // MineBlock mines a new block with the provided transactions func (bc *Blockchain) MineBlock(transactions []*Transaction) { var lastHash []byte @@ -220,7 +226,6 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { return nil }) - if err != nil { log.Panic(err) } @@ -243,9 +248,12 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { return nil }) + if err != nil { + log.Panic(err) + } } -// SignTransaction signs a Transaction +// SignTransaction signs inputs of a Transaction func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { prevTXs := make(map[string]Transaction) @@ -260,7 +268,7 @@ func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) tx.Sign(privKey, prevTXs) } -// VerifyTransaction verifies transaction +// VerifyTransaction verifies transaction input signatures func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { prevTXs := make(map[string]Transaction) @@ -272,22 +280,9 @@ func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } - for _, tx := range prevTXs { - fmt.Println(tx) - } - // fmt.Println() - // fmt.Println(tx) - return tx.Verify(prevTXs) } -// Iterator returns a BlockchainIterat -func (bc *Blockchain) Iterator() *BlockchainIterator { - bci := &BlockchainIterator{bc.tip, bc.db} - - return bci -} - func dbExists() bool { if _, err := os.Stat(dbFile); os.IsNotExist(err) { return false diff --git a/transaction_input.go b/transaction_input.go index e64e9e67..baeccd9d 100644 --- a/transaction_input.go +++ b/transaction_input.go @@ -9,8 +9,8 @@ type TXInput struct { ScriptSig []byte } -// UnlocksOutputWith checks whether the address initiated the transaction -func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { +// UsesKey checks whether the address initiated the transaction +func (in *TXInput) UsesKey(pubKeyHash []byte) bool { sigLen := 64 pubKey := in.ScriptSig[sigLen:] lockingHash := HashPubKey(pubKey) diff --git a/transaction_output.go b/transaction_output.go index f509dd88..27d89b50 100644 --- a/transaction_output.go +++ b/transaction_output.go @@ -15,8 +15,8 @@ func (out *TXOutput) Lock(address []byte) { out.ScriptPubKey = pubKeyHash } -// Unlock checks if the output can be used by the owner of the pubkey -func (out *TXOutput) Unlock(pubKeyHash []byte) bool { +// IsLockedWithKey checks if the output can be used by the owner of the pubkey +func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 } From 5f9e6c0c91962143106c2b3db8092e85d1c49803 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 12:53:06 +0700 Subject: [PATCH 061/122] Extract CLI commands into separate files --- cli.go | 76 ----------------------------------------- cli_createblockchain.go | 9 +++++ cli_createwallet.go | 11 ++++++ cli_getbalance.go | 19 +++++++++++ cli_listaddress.go | 18 ++++++++++ cli_printchain.go | 30 ++++++++++++++++ cli_send.go | 12 +++++++ 7 files changed, 99 insertions(+), 76 deletions(-) create mode 100644 cli_createblockchain.go create mode 100644 cli_createwallet.go create mode 100644 cli_getbalance.go create mode 100644 cli_listaddress.go create mode 100644 cli_printchain.go create mode 100644 cli_send.go diff --git a/cli.go b/cli.go index 2b011633..863ffd11 100644 --- a/cli.go +++ b/cli.go @@ -5,87 +5,11 @@ import ( "fmt" "log" "os" - "strconv" ) // CLI responsible for processing command line arguments type CLI struct{} -func (cli *CLI) createBlockchain(address string) { - bc := CreateBlockchain(address) - bc.db.Close() - fmt.Println("Done!") -} - -func (cli *CLI) createWallet() { - wallets, _ := NewWallets() - address := wallets.CreateWallet() - wallets.SaveToFile() - - fmt.Printf("Your new address: %s\n", address) -} - -func (cli *CLI) getBalance(address string) { - bc := NewBlockchain(address) - defer bc.db.Close() - - balance := 0 - pubKeyHash := Base58Decode([]byte(address)) - pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] - UTXOs := bc.FindUTXO(pubKeyHash) - - for _, out := range UTXOs { - balance += out.Value - } - - fmt.Printf("Balance of '%s': %d\n", address, balance) -} - -func (cli *CLI) listAddresses() { - wallets, err := NewWallets() - if err != nil { - log.Panic(err) - } - addresses := wallets.GetAddresses() - - for _, address := range addresses { - fmt.Println(address) - } -} - -func (cli *CLI) printChain() { - bc := NewBlockchain("") - defer bc.db.Close() - - bci := bc.Iterator() - - for { - block := bci.Next() - - fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) - fmt.Printf("Hash: %x\n", block.Hash) - pow := NewProofOfWork(block) - fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) - for _, tx := range block.Transactions { - fmt.Println(tx) - } - fmt.Println() - - if len(block.PrevBlockHash) == 0 { - break - } - } -} - -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!") -} - func (cli *CLI) printUsage() { fmt.Println("Usage:") fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS") diff --git a/cli_createblockchain.go b/cli_createblockchain.go new file mode 100644 index 00000000..c2acb48c --- /dev/null +++ b/cli_createblockchain.go @@ -0,0 +1,9 @@ +package main + +import "fmt" + +func (cli *CLI) createBlockchain(address string) { + bc := CreateBlockchain(address) + bc.db.Close() + fmt.Println("Done!") +} diff --git a/cli_createwallet.go b/cli_createwallet.go new file mode 100644 index 00000000..b42a1425 --- /dev/null +++ b/cli_createwallet.go @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func (cli *CLI) createWallet() { + wallets, _ := NewWallets() + address := wallets.CreateWallet() + wallets.SaveToFile() + + fmt.Printf("Your new address: %s\n", address) +} diff --git a/cli_getbalance.go b/cli_getbalance.go new file mode 100644 index 00000000..76ab0d0d --- /dev/null +++ b/cli_getbalance.go @@ -0,0 +1,19 @@ +package main + +import "fmt" + +func (cli *CLI) getBalance(address string) { + bc := NewBlockchain(address) + defer bc.db.Close() + + balance := 0 + pubKeyHash := Base58Decode([]byte(address)) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + UTXOs := bc.FindUTXO(pubKeyHash) + + for _, out := range UTXOs { + balance += out.Value + } + + fmt.Printf("Balance of '%s': %d\n", address, balance) +} diff --git a/cli_listaddress.go b/cli_listaddress.go new file mode 100644 index 00000000..96fd282f --- /dev/null +++ b/cli_listaddress.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "log" +) + +func (cli *CLI) listAddresses() { + wallets, err := NewWallets() + if err != nil { + log.Panic(err) + } + addresses := wallets.GetAddresses() + + for _, address := range addresses { + fmt.Println(address) + } +} diff --git a/cli_printchain.go b/cli_printchain.go new file mode 100644 index 00000000..4c2e865f --- /dev/null +++ b/cli_printchain.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "strconv" +) + +func (cli *CLI) printChain() { + bc := NewBlockchain("") + defer bc.db.Close() + + bci := bc.Iterator() + + for { + block := bci.Next() + + fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) + fmt.Printf("Hash: %x\n", block.Hash) + pow := NewProofOfWork(block) + fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) + for _, tx := range block.Transactions { + fmt.Println(tx) + } + fmt.Println() + + if len(block.PrevBlockHash) == 0 { + break + } + } +} diff --git a/cli_send.go b/cli_send.go new file mode 100644 index 00000000..ac3e0016 --- /dev/null +++ b/cli_send.go @@ -0,0 +1,12 @@ +package main + +import "fmt" + +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!") +} From 7290aaac649c275e11f9ed3d466d2d473eb54192 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 13:34:47 +0700 Subject: [PATCH 062/122] Use Hash funcion to set transaction ID --- transaction.go | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/transaction.go b/transaction.go index 99e42825..132ce836 100644 --- a/transaction.go +++ b/transaction.go @@ -54,21 +54,6 @@ func (tx *Transaction) Hash() []byte { return hash[:] } -// SetID sets ID of a transaction -// TODO: Remove this -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[:] -} - // Sign signs each input of a Transaction func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { @@ -86,7 +71,7 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey - txCopy.SetID() + txCopy.ID = txCopy.Hash() txCopy.Vin[inID].ScriptSig = []byte{} r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) @@ -160,7 +145,7 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey - txCopy.SetID() + txCopy.ID = txCopy.Hash() txCopy.Vin[inID].ScriptSig = []byte{} signature := vin.ScriptSig[:sigLen] @@ -195,7 +180,7 @@ func NewCoinbaseTX(to, data string) *Transaction { txin := TXInput{[]byte{}, -1, []byte(data)} txout := NewTXOutput(subsidy, to) tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}} - tx.SetID() + tx.ID = tx.Hash() return &tx } @@ -237,7 +222,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio } tx := Transaction{nil, inputs, outputs} - tx.SetID() + tx.ID = tx.Hash() bc.SignTransaction(&tx, wallet.PrivateKey) return &tx From a436da6c194174fa0f3b14a241ae5e7e7cddb80e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 13:53:14 +0700 Subject: [PATCH 063/122] Implement ValidateAddress --- cli_createblockchain.go | 8 +++++++- cli_getbalance.go | 8 +++++++- cli_send.go | 12 +++++++++++- wallet.go | 37 +++++++++++++++++++++++++------------ 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/cli_createblockchain.go b/cli_createblockchain.go index c2acb48c..06ff1b1b 100644 --- a/cli_createblockchain.go +++ b/cli_createblockchain.go @@ -1,8 +1,14 @@ package main -import "fmt" +import ( + "fmt" + "log" +) func (cli *CLI) createBlockchain(address string) { + if !ValidateAddress(address) { + log.Panic("ERROR: Address is not valid") + } bc := CreateBlockchain(address) bc.db.Close() fmt.Println("Done!") diff --git a/cli_getbalance.go b/cli_getbalance.go index 76ab0d0d..27cb55e3 100644 --- a/cli_getbalance.go +++ b/cli_getbalance.go @@ -1,8 +1,14 @@ package main -import "fmt" +import ( + "fmt" + "log" +) func (cli *CLI) getBalance(address string) { + if !ValidateAddress(address) { + log.Panic("ERROR: Address is not valid") + } bc := NewBlockchain(address) defer bc.db.Close() diff --git a/cli_send.go b/cli_send.go index ac3e0016..fb8117cb 100644 --- a/cli_send.go +++ b/cli_send.go @@ -1,8 +1,18 @@ package main -import "fmt" +import ( + "fmt" + "log" +) func (cli *CLI) send(from, to string, amount int) { + if !ValidateAddress(from) { + log.Panic("ERROR: Sender address is not valid") + } + if !ValidateAddress(to) { + log.Panic("ERROR: Recipient address is not valid") + } + bc := NewBlockchain(from) defer bc.db.Close() diff --git a/wallet.go b/wallet.go index d527988d..31a22583 100644 --- a/wallet.go +++ b/wallet.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -12,6 +13,7 @@ import ( const version = byte(0x00) const walletFile = "wallet.dat" +const addressChecksumLen = 4 // Wallet stores private and public keys type Wallet struct { @@ -40,17 +42,6 @@ func (w Wallet) GetAddress() []byte { return address } -func newKeyPair() (ecdsa.PrivateKey, []byte) { - curve := elliptic.P256() - private, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - log.Panic(err) - } - pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) - - return *private, pubKey -} - // HashPubKey hashes public key func HashPubKey(pubKey []byte) []byte { publicSHA256 := sha256.Sum256(pubKey) @@ -65,10 +56,32 @@ func HashPubKey(pubKey []byte) []byte { return publicRIPEMD160 } +// ValidateAddress check if address if valid +func ValidateAddress(address string) bool { + pubKeyHash := Base58Decode([]byte(address)) + actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:] + version := pubKeyHash[0] + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-addressChecksumLen] + targetChecksum := checksum(append([]byte{version}, pubKeyHash...)) + + return bytes.Compare(actualChecksum, targetChecksum) == 0 +} + // Checksum generates a checksum for a public key func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) - return secondSHA[:4] + return secondSHA[:addressChecksumLen] +} + +func newKeyPair() (ecdsa.PrivateKey, []byte) { + curve := elliptic.P256() + private, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + log.Panic(err) + } + pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) + + return *private, pubKey } From bf8c5d22e19c7f460fe75fbe3a885271f70df17a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 14:05:23 +0700 Subject: [PATCH 064/122] Store input sig and key in different fields; rename TXInput.ScriptPubKey to PubKeyHash --- transaction.go | 39 +++++++++++++++++++-------------------- transaction_input.go | 7 +++---- transaction_output.go | 8 ++++---- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/transaction.go b/transaction.go index 132ce836..4102a228 100644 --- a/transaction.go +++ b/transaction.go @@ -70,9 +70,10 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] - txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey + txCopy.Vin[inID].Signature = []byte{} + txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].ScriptSig = []byte{} + txCopy.Vin[inID].PubKey = []byte{} r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) if err != nil { @@ -80,7 +81,7 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac } signature := append(r.Bytes(), s.Bytes()...) - tx.Vin[inID].ScriptSig = append(signature, tx.Vin[inID].ScriptSig...) + tx.Vin[inID].Signature = signature } } @@ -93,15 +94,16 @@ func (tx Transaction) String() string { for i, input := range tx.Vin { lines = append(lines, fmt.Sprintf(" Input %d:", i)) - lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) - lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) - lines = append(lines, fmt.Sprintf(" Script: %x", input.ScriptSig)) + lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) + lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) + lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) + lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) } for i, output := range tx.Vout { lines = append(lines, fmt.Sprintf(" Output %d:", i)) lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) - lines = append(lines, fmt.Sprintf(" Script: %x", output.ScriptPubKey)) + lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash)) } return strings.Join(lines, "\n") @@ -117,7 +119,7 @@ func (tx *Transaction) TrimmedCopy() Transaction { } for _, vout := range tx.Vout { - outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) + outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash}) } txCopy := Transaction{tx.ID, inputs, outputs} @@ -127,8 +129,6 @@ func (tx *Transaction) TrimmedCopy() Transaction { // Verify verifies signatures of Transaction inputs func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { - sigLen := 64 - if tx.IsCoinbase() { return true } @@ -144,23 +144,22 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] - txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey + txCopy.Vin[inID].Signature = []byte{} + txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].ScriptSig = []byte{} - - signature := vin.ScriptSig[:sigLen] - pubKey := vin.ScriptSig[sigLen:] + txCopy.Vin[inID].PubKey = []byte{} r := big.Int{} s := big.Int{} - r.SetBytes(signature[:(sigLen / 2)]) - s.SetBytes(signature[(sigLen / 2):]) + sigLen := len(vin.Signature) + r.SetBytes(vin.Signature[:(sigLen / 2)]) + s.SetBytes(vin.Signature[(sigLen / 2):]) x := big.Int{} y := big.Int{} - keyLen := len(pubKey) - x.SetBytes(pubKey[:(keyLen / 2)]) - y.SetBytes(pubKey[(keyLen / 2):]) + keyLen := len(vin.PubKey) + x.SetBytes(vin.PubKey[:(keyLen / 2)]) + y.SetBytes(vin.PubKey[(keyLen / 2):]) rawPubKey := ecdsa.PublicKey{curve, &x, &y} if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { diff --git a/transaction_input.go b/transaction_input.go index baeccd9d..23beeba5 100644 --- a/transaction_input.go +++ b/transaction_input.go @@ -6,14 +6,13 @@ import "bytes" type TXInput struct { Txid []byte Vout int - ScriptSig []byte + Signature []byte + PubKey []byte } // UsesKey checks whether the address initiated the transaction func (in *TXInput) UsesKey(pubKeyHash []byte) bool { - sigLen := 64 - pubKey := in.ScriptSig[sigLen:] - lockingHash := HashPubKey(pubKey) + lockingHash := HashPubKey(in.PubKey) return bytes.Compare(lockingHash, pubKeyHash) == 0 } diff --git a/transaction_output.go b/transaction_output.go index 27d89b50..4d70d74a 100644 --- a/transaction_output.go +++ b/transaction_output.go @@ -4,20 +4,20 @@ import "bytes" // TXOutput represents a transaction output type TXOutput struct { - Value int - ScriptPubKey []byte + Value int + PubKeyHash []byte } // Lock signs the output func (out *TXOutput) Lock(address []byte) { pubKeyHash := Base58Decode(address) pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] - out.ScriptPubKey = pubKeyHash + out.PubKeyHash = pubKeyHash } // IsLockedWithKey checks if the output can be used by the owner of the pubkey func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { - return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 + return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0 } // NewTXOutput create a new TXOutput From 7b6d5695d38f9c676378b3c6d0ef7b7c50b2ead4 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 14:11:21 +0700 Subject: [PATCH 065/122] Fix some initializations --- transaction.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/transaction.go b/transaction.go index 4102a228..598c65fa 100644 --- a/transaction.go +++ b/transaction.go @@ -70,10 +70,10 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] - txCopy.Vin[inID].Signature = []byte{} + txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].PubKey = []byte{} + txCopy.Vin[inID].PubKey = nil r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) if err != nil { @@ -115,7 +115,7 @@ func (tx *Transaction) TrimmedCopy() Transaction { var outputs []TXOutput for _, vin := range tx.Vin { - inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) + inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil}) } for _, vout := range tx.Vout { @@ -176,7 +176,7 @@ func NewCoinbaseTX(to, data string) *Transaction { data = fmt.Sprintf("Reward to '%s'", to) } - txin := TXInput{[]byte{}, -1, []byte(data)} + txin := TXInput{[]byte{}, -1, nil, []byte(data)} txout := NewTXOutput(subsidy, to) tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}} tx.ID = tx.Hash() @@ -209,7 +209,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio } for _, out := range outs { - input := TXInput{txID, out, wallet.PublicKey} + input := TXInput{txID, out, nil, wallet.PublicKey} inputs = append(inputs, input) } } From c0b4d6d107140fb62770f9ccdcc661fdfbc1c974 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 14:31:01 +0700 Subject: [PATCH 066/122] Improve the printchain command --- cli_printchain.go | 8 ++++---- transaction.go | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cli_printchain.go b/cli_printchain.go index 4c2e865f..cd5c7a03 100644 --- a/cli_printchain.go +++ b/cli_printchain.go @@ -14,14 +14,14 @@ func (cli *CLI) printChain() { for { block := bci.Next() - fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) - fmt.Printf("Hash: %x\n", block.Hash) + fmt.Printf("============ Block %x ============\n", block.Hash) + fmt.Printf("Prev. block: %x\n", block.PrevBlockHash) pow := NewProofOfWork(block) - fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) + fmt.Printf("PoW: %s\n\n", strconv.FormatBool(pow.Validate())) for _, tx := range block.Transactions { fmt.Println(tx) } - fmt.Println() + fmt.Printf("\n\n") if len(block.PrevBlockHash) == 0 { break diff --git a/transaction.go b/transaction.go index 598c65fa..7f035a87 100644 --- a/transaction.go +++ b/transaction.go @@ -89,21 +89,21 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac func (tx Transaction) String() string { var lines []string - lines = append(lines, fmt.Sprintf("Transaction %x:", tx.ID)) + lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID)) for i, input := range tx.Vin { - lines = append(lines, fmt.Sprintf(" Input %d:", i)) - lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) - lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) - lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) - lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) + lines = append(lines, fmt.Sprintf(" Input %d:", i)) + lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) + lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) + lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) + lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) } for i, output := range tx.Vout { - lines = append(lines, fmt.Sprintf(" Output %d:", i)) - lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) - lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash)) + lines = append(lines, fmt.Sprintf(" Output %d:", i)) + lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) + lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash)) } return strings.Join(lines, "\n") From 465b85d5f28b6264d23ae225adc2ab53ab34d045 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 12 Sep 2017 20:57:19 +0700 Subject: [PATCH 067/122] Fix the 'checksum' function --- wallet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet.go b/wallet.go index 31a22583..262a3665 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[:addressChecksumLen] + return secondSHA[len(secondSHA)-addressChecksumLen:] } func newKeyPair() (ecdsa.PrivateKey, []byte) { From b6f7626a13f6b85dd192d66b9929c2f72683ccf7 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 12 Sep 2017 21:09:13 +0700 Subject: [PATCH 068/122] Fix Signature resetting --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index 7f035a87..794c66cb 100644 --- a/transaction.go +++ b/transaction.go @@ -144,7 +144,7 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] - txCopy.Vin[inID].Signature = []byte{} + txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = []byte{} From a6394c7afae7a09a2b8c5fff40075f03c7963fe5 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 12 Sep 2017 21:10:18 +0700 Subject: [PATCH 069/122] Fix PubKey resetting --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index 794c66cb..90950cc2 100644 --- a/transaction.go +++ b/transaction.go @@ -147,7 +147,7 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].PubKey = []byte{} + txCopy.Vin[inID].PubKey = nil r := big.Int{} s := big.Int{} From 402b298d4f908d14df5d7e51e7ae917c0347da47 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 12 Sep 2017 21:18:50 +0700 Subject: [PATCH 070/122] Update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c9b5850..87fc9f42 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,4 @@ A blockchain implementation in Go, as described in these articles: 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/) From 56ccd7c8caaea1782a52dd7affeec5a02b77b4f1 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 17 Sep 2017 09:16:50 +0700 Subject: [PATCH 071/122] 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 e2579feb..543ddc28 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 fb8117cb..fea84d8a 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 072/122] Implement Blockchain.FindAllUTXO --- blockchain.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/blockchain.go b/blockchain.go index 543ddc28..bd2e721e 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 073/122] Implement TXOutputs --- transaction_output.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/transaction_output.go b/transaction_output.go index 4d70d74a..34dabad8 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 074/122] 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 00000000..e43c32ed --- /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 075/122] 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 863ffd11..72693ea2 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 00000000..e2d37178 --- /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 e43c32ed..49c4c9fb 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 076/122] 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 bd2e721e..89aa3738 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 27cb55e3..c67c536e 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 cd5c7a03..8541d2f6 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 e2d37178..e6f841f6 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 fea84d8a..f9c45d6a 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 077/122] Implement DeserializeOutputs --- transaction_output.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/transaction_output.go b/transaction_output.go index 34dabad8..2ae68dec 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 078/122] Implment UTXOSet.FindUTXO --- utxo_set.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/utxo_set.go b/utxo_set.go index 49c4c9fb..0ee851d7 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 079/122] 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 c67c536e..814569df 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 080/122] Remove Blockchain.FindUTXO --- blockchain.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/blockchain.go b/blockchain.go index 89aa3738..2faa7c61 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 081/122] 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 2faa7c61..fa68abcb 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 f9c45d6a..35286113 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 90950cc2..e2fac831 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 0ee851d7..74e7fec3 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 082/122] Implement UTXOSet.Update --- utxo_set.go | 160 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 53 deletions(-) diff --git a/utxo_set.go b/utxo_set.go index 74e7fec3..c85cf922 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -14,24 +14,26 @@ type UTXOSet struct { Blockchain *Blockchain } -// Reindex rebuilds the UTXO set -func (u UTXOSet) Reindex() { +// 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.Update(func(tx *bolt.Tx) error { - bucketName := []byte(utxoBucket) - b := tx.Bucket(bucketName) + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(utxoBucket)) + c := b.Cursor() - if b != nil { - err := tx.DeleteBucket(bucketName) - if err != nil { - log.Panic(err) - } - } + for k, v := c.First(); k != nil; k, v = c.Next() { + txID := hex.EncodeToString(k) + outs := DeserializeOutputs(v) - _, err := tx.CreateBucket(bucketName) - if err != nil { - log.Panic(err) + for outIdx, out := range outs.Outputs { + if out.IsLockedWithKey(pubkeyHash) && accumulated < amount { + accumulated += out.Value + unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) + } + } } return nil @@ -40,25 +42,35 @@ func (u UTXOSet) Reindex() { log.Panic(err) } - UTXO := u.Blockchain.FindAllUTXO() + return accumulated, unspentOutputs +} - err = db.Update(func(tx *bolt.Tx) error { +// 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 txID, outs := range UTXO { - key, err := hex.DecodeString(txID) - if err != nil { - log.Panic(err) - } + for k, v := c.First(); k != nil; k, v = c.Next() { + outs := DeserializeOutputs(v) - err = b.Put(key, outs.Serialize()) - if err != nil { - log.Panic(err) + 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 @@ -83,54 +95,98 @@ 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 +// Reindex rebuilds the UTXO set +func (u UTXOSet) Reindex() { 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) + err := db.Update(func(tx *bolt.Tx) error { + bucketName := []byte(utxoBucket) + b := tx.Bucket(bucketName) - for _, out := range outs.Outputs { - if out.IsLockedWithKey(pubKeyHash) { - UTXOs = append(UTXOs, out) - } + 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) } - return UTXOs + UTXO := u.Blockchain.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 + }) } -// 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 +// 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() { - txID := hex.EncodeToString(k) - 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 outIdx, out := range outs.Outputs { - if out.IsLockedWithKey(pubkeyHash) && accumulated < amount { - accumulated += out.Value - unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) } } + + 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 @@ -138,6 +194,4 @@ func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[s 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 083/122] 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 fa68abcb..bbcb976e 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 35286113..456c2643 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 084/122] 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 06ff1b1b..358e88b2 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 085/122] =?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 bbcb976e..e00fb405 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 c85cf922..96cea336 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 086/122] 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 e6f841f6..622d0df4 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 087/122] 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 622d0df4..74d3b03d 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 96cea336..44ff27f4 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 088/122] 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 44ff27f4..180ce04b 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 089/122] 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 00000000..7a4156bd --- /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 00000000..acff5ffa --- /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 090/122] 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 b98c79cc..65a0a6e5 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 091/122] 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 262a3665..31a22583 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 092/122] Lower the difficulty of PoW --- proofofwork.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From 9b9b571028aee3debd6a826855eaf61f9e664071 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 19 Sep 2017 15:47:03 +0700 Subject: [PATCH 093/122] 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 e2fac831..67ba940c 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 094/122] Update the README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 87fc9f42..a7afaceb 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/) From 60a1386f3dc945f56970d28b720c69beedaf1c07 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 08:44:41 +0700 Subject: [PATCH 095/122] Implement 'startnode' CLI command --- cli.go | 20 ++++++++++++++++++-- cli_startnode.go | 8 ++++++++ server.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 cli_startnode.go create mode 100644 server.go diff --git a/cli.go b/cli.go index 72693ea2..366a7a6d 100644 --- a/cli.go +++ b/cli.go @@ -19,6 +19,7 @@ func (cli *CLI) printUsage() { 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") + fmt.Println(" startnode -node-id NODE_ID - Start a node with specified ID") } func (cli *CLI) validateArgs() { @@ -39,12 +40,14 @@ func (cli *CLI) Run() { printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) reindexUTXOCmd := flag.NewFlagSet("reindexutxo", flag.ExitOnError) sendCmd := flag.NewFlagSet("send", flag.ExitOnError) + startNodeCmd := flag.NewFlagSet("startnode", 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") + startNodeID := startNodeCmd.Int("node-id", 0, "Node ID") switch os.Args[1] { case "getbalance": @@ -72,13 +75,18 @@ func (cli *CLI) Run() { if err != nil { log.Panic(err) } + case "reindexutxo": + err := reindexUTXOCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } case "send": err := sendCmd.Parse(os.Args[2:]) if err != nil { log.Panic(err) } - case "reindexutxo": - err := reindexUTXOCmd.Parse(os.Args[2:]) + case "startnode": + err := startNodeCmd.Parse(os.Args[2:]) if err != nil { log.Panic(err) } @@ -127,4 +135,12 @@ func (cli *CLI) Run() { cli.send(*sendFrom, *sendTo, *sendAmount) } + + if startNodeCmd.Parsed() { + if *startNodeID == 0 { + startNodeCmd.Usage() + os.Exit(1) + } + cli.startNode(*startNodeID) + } } diff --git a/cli_startnode.go b/cli_startnode.go new file mode 100644 index 00000000..5b64f3cd --- /dev/null +++ b/cli_startnode.go @@ -0,0 +1,8 @@ +package main + +import "fmt" + +func (cli *CLI) startNode(nodeID int) { + fmt.Printf("Starting node %d\n", nodeID) + StartServer(nodeID) +} diff --git a/server.go b/server.go new file mode 100644 index 00000000..ff88b79e --- /dev/null +++ b/server.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "io" + "log" + "net" +) + +func handleConnection(conn net.Conn) { + io.Copy(conn, conn) + conn.Close() +} + +// StartServer starts a node +func StartServer(nodeID int) { + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", nodeID)) + if err != nil { + log.Panic(err) + } + defer ln.Close() + + for { + conn, err := ln.Accept() + if err != nil { + log.Panic(err) + } + go handleConnection(conn) + } +} From 1c5bc460f445f09ee7c8837b68b31b5937c146eb Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 09:33:33 +0700 Subject: [PATCH 096/122] Implement 'version' command --- server.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index ff88b79e..d13ce506 100644 --- a/server.go +++ b/server.go @@ -1,25 +1,109 @@ package main import ( + "bytes" + "encoding/gob" "fmt" "io" + "io/ioutil" "log" "net" ) +const protocol = "tcp" +const dnsNodeID = 3000 +const nodeVersion = 1 +const commandLength = 12 + +var nodeAddress string + +type verzion struct { + Version int + AddrFrom string +} + +func commandToBytes(command string) []byte { + var bytes [commandLength]byte + + for i, c := range command { + bytes[i] = byte(c) + } + + return bytes[:] +} + +func bytesToCommand(bytes []byte) string { + var command []byte + + for _, b := range bytes { + if b != 0x0 { + command = append(command, b) + } + } + + return fmt.Sprintf("%s", command) +} + +func extractCommand(request []byte) []byte { + return request[:commandLength] +} + +func sendVersion(addr string) { + var payload bytes.Buffer + + enc := gob.NewEncoder(&payload) + err := enc.Encode(verzion{nodeVersion, nodeAddress}) + if err != nil { + log.Panic(err) + } + + request := append(commandToBytes("version"), payload.Bytes()...) + + conn, err := net.Dial(protocol, addr) + if err != nil { + log.Panic(err) + } + defer conn.Close() + + fmt.Printf("%x\n", request) + _, err = io.Copy(conn, bytes.NewReader(request)) + if err != nil { + log.Panic(err) + } +} + func handleConnection(conn net.Conn) { - io.Copy(conn, conn) + request, err := ioutil.ReadAll(conn) + if err != nil { + log.Panic(err) + } + command := bytesToCommand(request[:commandLength]) + + switch command { + case "version": + fmt.Printf("Received %s command", command) + // send verack + // send addr + default: + fmt.Println("Unknown command received!") + } + conn.Close() } // StartServer starts a node func StartServer(nodeID int) { - ln, err := net.Listen("tcp", fmt.Sprintf(":%d", nodeID)) + nodeAddress = fmt.Sprintf("localhost:%d", nodeID) + ln, err := net.Listen(protocol, nodeAddress) if err != nil { log.Panic(err) } defer ln.Close() + if nodeID != dnsNodeID { + sendVersion(fmt.Sprintf("localhost:%d", dnsNodeID)) + } + for { conn, err := ln.Accept() if err != nil { From 7935589f8a8ec4d21c4c1bc7e6f78be631a194f3 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 10:08:51 +0700 Subject: [PATCH 097/122] Send 'vrack' in response to 'version' --- server.go | 64 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/server.go b/server.go index d13ce506..8558697d 100644 --- a/server.go +++ b/server.go @@ -22,6 +22,9 @@ type verzion struct { AddrFrom string } +type verack struct { +} + func commandToBytes(command string) []byte { var bytes [commandLength]byte @@ -48,28 +51,48 @@ func extractCommand(request []byte) []byte { return request[:commandLength] } -func sendVersion(addr string) { - var payload bytes.Buffer - - enc := gob.NewEncoder(&payload) - err := enc.Encode(verzion{nodeVersion, nodeAddress}) +func sendData(addr string, data []byte) { + conn, err := net.Dial(protocol, addr) if err != nil { log.Panic(err) } + defer conn.Close() - request := append(commandToBytes("version"), payload.Bytes()...) - - conn, err := net.Dial(protocol, addr) + fmt.Printf("%x\n", data) + _, err = io.Copy(conn, bytes.NewReader(data)) if err != nil { log.Panic(err) } - defer conn.Close() +} + +func sendVersion(addr string) { + payload := gobEncode(verzion{nodeVersion, nodeAddress}) + + request := append(commandToBytes("version"), payload...) + + sendData(addr, request) +} + +func sendVrack(addr string) { + payload := gobEncode(verack{}) - fmt.Printf("%x\n", request) - _, err = io.Copy(conn, bytes.NewReader(request)) + request := append(commandToBytes("verack"), payload...) + + sendData(addr, request) +} + +func handleVersion(request []byte) { + var buff bytes.Buffer + var payload verzion + + buff.Write(request[commandLength:]) + dec := gob.NewDecoder(&buff) + err := dec.Decode(&payload) if err != nil { log.Panic(err) } + + sendVrack(payload.AddrFrom) } func handleConnection(conn net.Conn) { @@ -81,9 +104,10 @@ func handleConnection(conn net.Conn) { switch command { case "version": - fmt.Printf("Received %s command", command) - // send verack - // send addr + fmt.Printf("Received %s command\n", command) + handleVersion(request) + case "verack": + fmt.Printf("Received %s command\n", command) default: fmt.Println("Unknown command received!") } @@ -112,3 +136,15 @@ func StartServer(nodeID int) { go handleConnection(conn) } } + +func gobEncode(data interface{}) []byte { + var buff bytes.Buffer + + enc := gob.NewEncoder(&buff) + err := enc.Encode(data) + if err != nil { + log.Panic(err) + } + + return buff.Bytes() +} From 4a9f7be98c15ab9e7afa79bf42f17b2ca09b7225 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 10:25:11 +0700 Subject: [PATCH 098/122] Implement 'addr' command --- server.go | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index 8558697d..a071ba01 100644 --- a/server.go +++ b/server.go @@ -16,15 +16,21 @@ const nodeVersion = 1 const commandLength = 12 var nodeAddress string +var knownNodes []string -type verzion struct { - Version int - AddrFrom string +type addr struct { + AddrList []string } type verack struct { } +type verzion struct { + Version int + + AddrFrom string +} + func commandToBytes(command string) []byte { var bytes [commandLength]byte @@ -51,6 +57,15 @@ func extractCommand(request []byte) []byte { return request[:commandLength] } +func sendAddr(address string) { + nodes := addr{knownNodes} + nodes.AddrList = append(nodes.AddrList, nodeAddress) + payload := gobEncode(nodes) + request := append(commandToBytes("addr"), payload...) + + sendData(address, request) +} + func sendData(addr string, data []byte) { conn, err := net.Dial(protocol, addr) if err != nil { @@ -58,7 +73,6 @@ func sendData(addr string, data []byte) { } defer conn.Close() - fmt.Printf("%x\n", data) _, err = io.Copy(conn, bytes.NewReader(data)) if err != nil { log.Panic(err) @@ -81,6 +95,21 @@ func sendVrack(addr string) { sendData(addr, request) } +func handleAddr(request []byte) { + var buff bytes.Buffer + var payload addr + + buff.Write(request[commandLength:]) + dec := gob.NewDecoder(&buff) + err := dec.Decode(&payload) + if err != nil { + log.Panic(err) + } + + knownNodes = append(knownNodes, payload.AddrList...) + fmt.Printf("There are %d known nodes now!\n", len(knownNodes)) +} + func handleVersion(request []byte) { var buff bytes.Buffer var payload verzion @@ -93,6 +122,8 @@ func handleVersion(request []byte) { } sendVrack(payload.AddrFrom) + sendAddr(payload.AddrFrom) + knownNodes = append(knownNodes, payload.AddrFrom) } func handleConnection(conn net.Conn) { @@ -101,15 +132,17 @@ func handleConnection(conn net.Conn) { log.Panic(err) } command := bytesToCommand(request[:commandLength]) + fmt.Printf("Received %s command\n", command) switch command { + case "addr": + handleAddr(request) case "version": - fmt.Printf("Received %s command\n", command) handleVersion(request) case "verack": - fmt.Printf("Received %s command\n", command) + // default: - fmt.Println("Unknown command received!") + fmt.Println("Unknown command!") } conn.Close() From 504b6c85bf948df93e57e2240f03ce1e9d2c8b32 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 10:29:04 +0700 Subject: [PATCH 099/122] Set node ID via an env. var --- cli.go | 8 ++++---- cli_startnode.go | 4 ++-- server.go | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cli.go b/cli.go index 366a7a6d..df4af02b 100644 --- a/cli.go +++ b/cli.go @@ -19,7 +19,7 @@ func (cli *CLI) printUsage() { 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") - fmt.Println(" startnode -node-id NODE_ID - Start a node with specified ID") + fmt.Println(" startnode - Start a node with ID specified in NODE_ID env. var") } func (cli *CLI) validateArgs() { @@ -47,7 +47,6 @@ func (cli *CLI) Run() { sendFrom := sendCmd.String("from", "", "Source wallet address") sendTo := sendCmd.String("to", "", "Destination wallet address") sendAmount := sendCmd.Int("amount", 0, "Amount to send") - startNodeID := startNodeCmd.Int("node-id", 0, "Node ID") switch os.Args[1] { case "getbalance": @@ -137,10 +136,11 @@ func (cli *CLI) Run() { } if startNodeCmd.Parsed() { - if *startNodeID == 0 { + nodeID := os.Getenv("NODE_ID") + if nodeID == "" { startNodeCmd.Usage() os.Exit(1) } - cli.startNode(*startNodeID) + cli.startNode(nodeID) } } diff --git a/cli_startnode.go b/cli_startnode.go index 5b64f3cd..289ccd49 100644 --- a/cli_startnode.go +++ b/cli_startnode.go @@ -2,7 +2,7 @@ package main import "fmt" -func (cli *CLI) startNode(nodeID int) { - fmt.Printf("Starting node %d\n", nodeID) +func (cli *CLI) startNode(nodeID string) { + fmt.Printf("Starting node %s\n", nodeID) StartServer(nodeID) } diff --git a/server.go b/server.go index a071ba01..ba0906d4 100644 --- a/server.go +++ b/server.go @@ -11,7 +11,7 @@ import ( ) const protocol = "tcp" -const dnsNodeID = 3000 +const dnsNodeID = "3000" const nodeVersion = 1 const commandLength = 12 @@ -149,8 +149,8 @@ func handleConnection(conn net.Conn) { } // StartServer starts a node -func StartServer(nodeID int) { - nodeAddress = fmt.Sprintf("localhost:%d", nodeID) +func StartServer(nodeID string) { + nodeAddress = fmt.Sprintf("localhost:%s", nodeID) ln, err := net.Listen(protocol, nodeAddress) if err != nil { log.Panic(err) @@ -158,7 +158,7 @@ func StartServer(nodeID int) { defer ln.Close() if nodeID != dnsNodeID { - sendVersion(fmt.Sprintf("localhost:%d", dnsNodeID)) + sendVersion(fmt.Sprintf("localhost:%s", dnsNodeID)) } for { From 57f3680551c12c7064358b501a4b23722ad9c14f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 10:42:34 +0700 Subject: [PATCH 100/122] Blockchain file name must depend on node ID --- blockchain.go | 14 ++++++++------ cli.go | 17 ++++++++++++----- cli_createblockchain.go | 4 ++-- cli_getbalance.go | 4 ++-- cli_printchain.go | 4 ++-- cli_reindexutxo.go | 4 ++-- cli_send.go | 4 ++-- server.go | 6 ++++-- 8 files changed, 34 insertions(+), 23 deletions(-) diff --git a/blockchain.go b/blockchain.go index e00fb405..842905ac 100644 --- a/blockchain.go +++ b/blockchain.go @@ -12,7 +12,7 @@ import ( "github.com/boltdb/bolt" ) -const dbFile = "blockchain.db" +const dbFile = "blockchain_%s.db" const blocksBucket = "blocks" const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" @@ -23,8 +23,9 @@ type Blockchain struct { } // CreateBlockchain creates a new blockchain DB -func CreateBlockchain(address string) *Blockchain { - if dbExists() { +func CreateBlockchain(address, nodeID string) *Blockchain { + dbFile := fmt.Sprintf(dbFile, nodeID) + if dbExists(dbFile) { fmt.Println("Blockchain already exists.") os.Exit(1) } @@ -68,8 +69,9 @@ func CreateBlockchain(address string) *Blockchain { } // NewBlockchain creates a new Blockchain with genesis Block -func NewBlockchain() *Blockchain { - if dbExists() == false { +func NewBlockchain(nodeID string) *Blockchain { + dbFile := fmt.Sprintf(dbFile, nodeID) + if dbExists(dbFile) == false { fmt.Println("No existing blockchain found. Create one first.") os.Exit(1) } @@ -246,7 +248,7 @@ func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { return tx.Verify(prevTXs) } -func dbExists() bool { +func dbExists(dbFile string) bool { if _, err := os.Stat(dbFile); os.IsNotExist(err) { return false } diff --git a/cli.go b/cli.go index df4af02b..7441e3ef 100644 --- a/cli.go +++ b/cli.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "log" + "os" ) @@ -33,6 +34,12 @@ func (cli *CLI) validateArgs() { func (cli *CLI) Run() { cli.validateArgs() + nodeID := os.Getenv("NODE_ID") + if nodeID == "" { + fmt.Printf("NODE_ID env. var is not set!") + os.Exit(1) + } + getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError) @@ -99,7 +106,7 @@ func (cli *CLI) Run() { getBalanceCmd.Usage() os.Exit(1) } - cli.getBalance(*getBalanceAddress) + cli.getBalance(*getBalanceAddress, nodeID) } if createBlockchainCmd.Parsed() { @@ -107,7 +114,7 @@ func (cli *CLI) Run() { createBlockchainCmd.Usage() os.Exit(1) } - cli.createBlockchain(*createBlockchainAddress) + cli.createBlockchain(*createBlockchainAddress, nodeID) } if createWalletCmd.Parsed() { @@ -119,11 +126,11 @@ func (cli *CLI) Run() { } if printChainCmd.Parsed() { - cli.printChain() + cli.printChain(nodeID) } if reindexUTXOCmd.Parsed() { - cli.reindexUTXO() + cli.reindexUTXO(nodeID) } if sendCmd.Parsed() { @@ -132,7 +139,7 @@ func (cli *CLI) Run() { os.Exit(1) } - cli.send(*sendFrom, *sendTo, *sendAmount) + cli.send(*sendFrom, *sendTo, *sendAmount, nodeID) } if startNodeCmd.Parsed() { diff --git a/cli_createblockchain.go b/cli_createblockchain.go index 358e88b2..7e69405c 100644 --- a/cli_createblockchain.go +++ b/cli_createblockchain.go @@ -5,11 +5,11 @@ import ( "log" ) -func (cli *CLI) createBlockchain(address string) { +func (cli *CLI) createBlockchain(address, nodeID string) { if !ValidateAddress(address) { log.Panic("ERROR: Address is not valid") } - bc := CreateBlockchain(address) + bc := CreateBlockchain(address, nodeID) defer bc.db.Close() UTXOSet := UTXOSet{bc} diff --git a/cli_getbalance.go b/cli_getbalance.go index 814569df..a86e3dc1 100644 --- a/cli_getbalance.go +++ b/cli_getbalance.go @@ -5,11 +5,11 @@ import ( "log" ) -func (cli *CLI) getBalance(address string) { +func (cli *CLI) getBalance(address, nodeID string) { if !ValidateAddress(address) { log.Panic("ERROR: Address is not valid") } - bc := NewBlockchain() + bc := NewBlockchain(nodeID) UTXOSet := UTXOSet{bc} defer bc.db.Close() diff --git a/cli_printchain.go b/cli_printchain.go index 8541d2f6..ba22cf56 100644 --- a/cli_printchain.go +++ b/cli_printchain.go @@ -5,8 +5,8 @@ import ( "strconv" ) -func (cli *CLI) printChain() { - bc := NewBlockchain() +func (cli *CLI) printChain(nodeID string) { + bc := NewBlockchain(nodeID) defer bc.db.Close() bci := bc.Iterator() diff --git a/cli_reindexutxo.go b/cli_reindexutxo.go index 74d3b03d..87db3245 100644 --- a/cli_reindexutxo.go +++ b/cli_reindexutxo.go @@ -2,8 +2,8 @@ package main import "fmt" -func (cli *CLI) reindexUTXO() { - bc := NewBlockchain() +func (cli *CLI) reindexUTXO(nodeID string) { + bc := NewBlockchain(nodeID) UTXOSet := UTXOSet{bc} UTXOSet.Reindex() diff --git a/cli_send.go b/cli_send.go index 456c2643..a7c6f729 100644 --- a/cli_send.go +++ b/cli_send.go @@ -5,7 +5,7 @@ import ( "log" ) -func (cli *CLI) send(from, to string, amount int) { +func (cli *CLI) send(from, to string, amount int, nodeID string) { if !ValidateAddress(from) { log.Panic("ERROR: Sender address is not valid") } @@ -13,7 +13,7 @@ func (cli *CLI) send(from, to string, amount int) { log.Panic("ERROR: Recipient address is not valid") } - bc := NewBlockchain() + bc := NewBlockchain(nodeID) UTXOSet := UTXOSet{bc} defer bc.db.Close() diff --git a/server.go b/server.go index ba0906d4..37a87ace 100644 --- a/server.go +++ b/server.go @@ -126,7 +126,7 @@ func handleVersion(request []byte) { knownNodes = append(knownNodes, payload.AddrFrom) } -func handleConnection(conn net.Conn) { +func handleConnection(conn net.Conn, bc *Blockchain) { request, err := ioutil.ReadAll(conn) if err != nil { log.Panic(err) @@ -161,12 +161,14 @@ func StartServer(nodeID string) { sendVersion(fmt.Sprintf("localhost:%s", dnsNodeID)) } + bc := NewBlockchain(nodeID) + for { conn, err := ln.Accept() if err != nil { log.Panic(err) } - go handleConnection(conn) + go handleConnection(conn, bc) } } From 0c91da0e520ee9f4357ea9dcfada52c90efc451c Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 10:48:51 +0700 Subject: [PATCH 101/122] Wallet file name must depend on node ID --- cli.go | 4 ++-- cli_createwallet.go | 4 ++-- cli_listaddress.go | 4 ++-- cli_send.go | 8 +++++++- transaction.go | 8 ++------ wallet.go | 1 - wallets.go | 4 +++- 7 files changed, 18 insertions(+), 15 deletions(-) diff --git a/cli.go b/cli.go index 7441e3ef..00e72d20 100644 --- a/cli.go +++ b/cli.go @@ -118,11 +118,11 @@ func (cli *CLI) Run() { } if createWalletCmd.Parsed() { - cli.createWallet() + cli.createWallet(nodeID) } if listAddressesCmd.Parsed() { - cli.listAddresses() + cli.listAddresses(nodeID) } if printChainCmd.Parsed() { diff --git a/cli_createwallet.go b/cli_createwallet.go index b42a1425..57f74e33 100644 --- a/cli_createwallet.go +++ b/cli_createwallet.go @@ -2,8 +2,8 @@ package main import "fmt" -func (cli *CLI) createWallet() { - wallets, _ := NewWallets() +func (cli *CLI) createWallet(nodeID string) { + wallets, _ := NewWallets(nodeID) address := wallets.CreateWallet() wallets.SaveToFile() diff --git a/cli_listaddress.go b/cli_listaddress.go index 96fd282f..0d30563d 100644 --- a/cli_listaddress.go +++ b/cli_listaddress.go @@ -5,8 +5,8 @@ import ( "log" ) -func (cli *CLI) listAddresses() { - wallets, err := NewWallets() +func (cli *CLI) listAddresses(nodeID string) { + wallets, err := NewWallets(nodeID) if err != nil { log.Panic(err) } diff --git a/cli_send.go b/cli_send.go index a7c6f729..4e5a59a9 100644 --- a/cli_send.go +++ b/cli_send.go @@ -17,7 +17,13 @@ func (cli *CLI) send(from, to string, amount int, nodeID string) { UTXOSet := UTXOSet{bc} defer bc.db.Close() - tx := NewUTXOTransaction(from, to, amount, &UTXOSet) + wallets, err := NewWallets(nodeID) + if err != nil { + log.Panic(err) + } + wallet := wallets.GetWallet(from) + + tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet) cbTx := NewCoinbaseTX(from, "") txs := []*Transaction{cbTx, tx} diff --git a/transaction.go b/transaction.go index 67ba940c..0da21171 100644 --- a/transaction.go +++ b/transaction.go @@ -191,15 +191,10 @@ func NewCoinbaseTX(to, data string) *Transaction { } // NewUTXOTransaction creates a new transaction -func NewUTXOTransaction(from, to string, amount int, UTXOSet *UTXOSet) *Transaction { +func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) *Transaction { var inputs []TXInput var outputs []TXOutput - wallets, err := NewWallets() - if err != nil { - log.Panic(err) - } - wallet := wallets.GetWallet(from) pubKeyHash := HashPubKey(wallet.PublicKey) acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount) @@ -221,6 +216,7 @@ func NewUTXOTransaction(from, to string, amount int, UTXOSet *UTXOSet) *Transact } // Build a list of outputs + from := fmt.Sprintf("%s", wallet.GetAddress()) outputs = append(outputs, *NewTXOutput(amount, to)) if acc > amount { outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change diff --git a/wallet.go b/wallet.go index 31a22583..506b5444 100644 --- a/wallet.go +++ b/wallet.go @@ -12,7 +12,6 @@ import ( ) const version = byte(0x00) -const walletFile = "wallet.dat" const addressChecksumLen = 4 // Wallet stores private and public keys diff --git a/wallets.go b/wallets.go index e5b2134c..34d47994 100644 --- a/wallets.go +++ b/wallets.go @@ -10,13 +10,15 @@ import ( "os" ) +const walletFile = "wallet_%s.dat" + // Wallets stores a collection of wallets type Wallets struct { Wallets map[string]*Wallet } // NewWallets creates Wallets and fills it from a file if it exists -func NewWallets() (*Wallets, error) { +func NewWallets(nodeID string) (*Wallets, error) { wallets := Wallets{} wallets.Wallets = make(map[string]*Wallet) From 4acc3ae27165b0c63c14dca6c885523575887d6c Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 10:53:19 +0700 Subject: [PATCH 102/122] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 570133a7..1b07dece 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ *.db -wallet.dat +*.dat From 130cf66a90bfb933fd81dcf29a19fdb0fd5e1bf9 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 10:53:26 +0700 Subject: [PATCH 103/122] Fix wallet file name --- cli_createwallet.go | 2 +- wallets.go | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cli_createwallet.go b/cli_createwallet.go index 57f74e33..0e05f202 100644 --- a/cli_createwallet.go +++ b/cli_createwallet.go @@ -5,7 +5,7 @@ import "fmt" func (cli *CLI) createWallet(nodeID string) { wallets, _ := NewWallets(nodeID) address := wallets.CreateWallet() - wallets.SaveToFile() + wallets.SaveToFile(nodeID) fmt.Printf("Your new address: %s\n", address) } diff --git a/wallets.go b/wallets.go index 34d47994..9f376f53 100644 --- a/wallets.go +++ b/wallets.go @@ -22,7 +22,7 @@ func NewWallets(nodeID string) (*Wallets, error) { wallets := Wallets{} wallets.Wallets = make(map[string]*Wallet) - err := wallets.LoadFromFile() + err := wallets.LoadFromFile(nodeID) return &wallets, err } @@ -54,7 +54,8 @@ func (ws Wallets) GetWallet(address string) Wallet { } // LoadFromFile loads wallets from the file -func (ws *Wallets) LoadFromFile() error { +func (ws *Wallets) LoadFromFile(nodeID string) error { + walletFile := fmt.Sprintf(walletFile, nodeID) if _, err := os.Stat(walletFile); os.IsNotExist(err) { return err } @@ -78,8 +79,9 @@ func (ws *Wallets) LoadFromFile() error { } // SaveToFile saves wallets to a file -func (ws Wallets) SaveToFile() { +func (ws Wallets) SaveToFile(nodeID string) { var content bytes.Buffer + walletFile := fmt.Sprintf(walletFile, nodeID) gob.Register(elliptic.P256()) From 273428545024c2450717156879d1f85e9683971b Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 11:02:38 +0700 Subject: [PATCH 104/122] Implement block height --- block.go | 7 ++++--- blockchain.go | 8 +++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/block.go b/block.go index 65a0a6e5..e403e549 100644 --- a/block.go +++ b/block.go @@ -14,11 +14,12 @@ type Block struct { PrevBlockHash []byte Hash []byte Nonce int + Height int } // NewBlock creates and returns Block -func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { - block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} +func NewBlock(transactions []*Transaction, prevBlockHash []byte, height int) *Block { + block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0, height} pow := NewProofOfWork(block) nonce, hash := pow.Run() @@ -30,7 +31,7 @@ func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { // NewGenesisBlock creates and returns genesis Block func NewGenesisBlock(coinbase *Transaction) *Block { - return NewBlock([]*Transaction{coinbase}, []byte{}) + return NewBlock([]*Transaction{coinbase}, []byte{}, 0) } // HashTransactions returns a hash of the transactions in the block diff --git a/blockchain.go b/blockchain.go index 842905ac..8d1cab7d 100644 --- a/blockchain.go +++ b/blockchain.go @@ -172,6 +172,7 @@ func (bc *Blockchain) Iterator() *BlockchainIterator { // MineBlock mines a new block with the provided transactions func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block { var lastHash []byte + var lastHeight int for _, tx := range transactions { if bc.VerifyTransaction(tx) != true { @@ -183,13 +184,18 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) + blockData := b.Get(lastHash) + block := DeserializeBlock(blockData) + + lastHeight = block.Height + return nil }) if err != nil { log.Panic(err) } - newBlock := NewBlock(transactions, lastHash) + newBlock := NewBlock(transactions, lastHash, lastHeight+1) err = bc.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) From 0c59d8cd5221f9ed0ce35daa97f68b9189eae0de Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 11:04:31 +0700 Subject: [PATCH 105/122] Show block height in 'printchain' command --- cli_printchain.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli_printchain.go b/cli_printchain.go index ba22cf56..81b7edf7 100644 --- a/cli_printchain.go +++ b/cli_printchain.go @@ -15,6 +15,7 @@ func (cli *CLI) printChain(nodeID string) { block := bci.Next() fmt.Printf("============ Block %x ============\n", block.Hash) + fmt.Printf("Height: %d\n", block.Height) fmt.Printf("Prev. block: %x\n", block.PrevBlockHash) pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n\n", strconv.FormatBool(pow.Validate())) From 9adb5fc1393c2d2ab0f4662cbff0fcffa296d87f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 11:19:43 +0700 Subject: [PATCH 106/122] Implement 'getblocks' command --- blockchain.go | 18 +++++++++++++++ server.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/blockchain.go b/blockchain.go index 8d1cab7d..0f310afe 100644 --- a/blockchain.go +++ b/blockchain.go @@ -169,6 +169,24 @@ func (bc *Blockchain) Iterator() *BlockchainIterator { return bci } +// GetBlockHashes returns a list of hashes of all the blocks in the chain +func (bc *Blockchain) GetBlockHashes() [][]byte { + var blocks [][]byte + bci := bc.Iterator() + + for { + block := bci.Next() + + blocks = append(blocks, block.Hash) + + if len(block.PrevBlockHash) == 0 { + break + } + } + + return blocks +} + // MineBlock mines a new block with the provided transactions func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block { var lastHash []byte diff --git a/server.go b/server.go index 37a87ace..0cbf5998 100644 --- a/server.go +++ b/server.go @@ -22,6 +22,15 @@ type addr struct { AddrList []string } +type getblocks struct { + AddrFrom string +} + +type inv struct { + Type string + Items [][]byte +} + type verack struct { } @@ -57,6 +66,12 @@ func extractCommand(request []byte) []byte { return request[:commandLength] } +func requestBlocks() { + for _, node := range knownNodes { + sendGetBlocks(node) + } +} + func sendAddr(address string) { nodes := addr{knownNodes} nodes.AddrList = append(nodes.AddrList, nodeAddress) @@ -79,6 +94,21 @@ func sendData(addr string, data []byte) { } } +func sendInv(address, kind string, items [][]byte) { + inventory := inv{kind, items} + payload := gobEncode(inventory) + request := append(commandToBytes("inv"), payload...) + + sendData(address, request) +} + +func sendGetBlocks(address string) { + payload := gobEncode(getblocks{nodeAddress}) + request := append(commandToBytes("getblocks"), payload...) + + sendData(address, request) +} + func sendVersion(addr string) { payload := gobEncode(verzion{nodeVersion, nodeAddress}) @@ -108,6 +138,36 @@ func handleAddr(request []byte) { knownNodes = append(knownNodes, payload.AddrList...) fmt.Printf("There are %d known nodes now!\n", len(knownNodes)) + requestBlocks() +} + +func handleInv(request []byte) { + var buff bytes.Buffer + var payload inv + + buff.Write(request[commandLength:]) + dec := gob.NewDecoder(&buff) + err := dec.Decode(&payload) + if err != nil { + log.Panic(err) + } + + fmt.Printf("Recevied %d %s\n", len(payload.Items), payload.Type) +} + +func handleGetBlocks(request []byte, bc *Blockchain) { + var buff bytes.Buffer + var payload getblocks + + buff.Write(request[commandLength:]) + dec := gob.NewDecoder(&buff) + err := dec.Decode(&payload) + if err != nil { + log.Panic(err) + } + + blocks := bc.GetBlockHashes() + sendInv(payload.AddrFrom, "blocks", blocks) } func handleVersion(request []byte) { @@ -137,6 +197,10 @@ func handleConnection(conn net.Conn, bc *Blockchain) { switch command { case "addr": handleAddr(request) + case "inv": + handleInv(request) + case "getblocks": + handleGetBlocks(request, bc) case "version": handleVersion(request) case "verack": From 470adef2c34879ac7f7c6c16291f40ef345a7414 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 11:50:27 +0700 Subject: [PATCH 107/122] Implement 'block' and 'getdata' commands --- blockchain.go | 29 +++++++++++++++++ server.go | 89 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 112 insertions(+), 6 deletions(-) diff --git a/blockchain.go b/blockchain.go index 0f310afe..328cf069 100644 --- a/blockchain.go +++ b/blockchain.go @@ -97,6 +97,11 @@ func NewBlockchain(nodeID string) *Blockchain { return &bc } +// Reset removes all blockchain data +func (bc *Blockchain) Reset() { + +} + // FindTransaction finds a transaction by its ID func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { bci := bc.Iterator() @@ -169,6 +174,30 @@ func (bc *Blockchain) Iterator() *BlockchainIterator { return bci } +// GetBlock finds a block by its hash and returns it +func (bc *Blockchain) GetBlock(blockHash []byte) (Block, error) { + var block Block + + err := bc.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + + blockData := b.Get(blockHash) + + if blockData == nil { + return errors.New("Block is not found.") + } + + block = *DeserializeBlock(blockData) + + return nil + }) + if err != nil { + return block, err + } + + return block, nil +} + // GetBlockHashes returns a list of hashes of all the blocks in the chain func (bc *Blockchain) GetBlockHashes() [][]byte { var blocks [][]byte diff --git a/server.go b/server.go index 0cbf5998..83b649c5 100644 --- a/server.go +++ b/server.go @@ -22,13 +22,25 @@ type addr struct { AddrList []string } +type block struct { + AddrFrom string + Block []byte +} + type getblocks struct { AddrFrom string } +type getdata struct { + AddrFrom string + Type string + ID []byte +} + type inv struct { - Type string - Items [][]byte + AddrFrom string + Type string + Items [][]byte } type verack struct { @@ -81,6 +93,14 @@ func sendAddr(address string) { sendData(address, request) } +func sendBlock(addr string, b *Block) { + data := block{nodeAddress, b.Serialize()} + payload := gobEncode(data) + request := append(commandToBytes("block"), payload...) + + sendData(addr, request) +} + func sendData(addr string, data []byte) { conn, err := net.Dial(protocol, addr) if err != nil { @@ -95,7 +115,7 @@ func sendData(addr string, data []byte) { } func sendInv(address, kind string, items [][]byte) { - inventory := inv{kind, items} + inventory := inv{nodeAddress, kind, items} payload := gobEncode(inventory) request := append(commandToBytes("inv"), payload...) @@ -109,6 +129,13 @@ func sendGetBlocks(address string) { sendData(address, request) } +func sendGetData(address, kind string, id []byte) { + payload := gobEncode(getdata{nodeAddress, kind, id}) + request := append(commandToBytes("getdata"), payload...) + + sendData(address, request) +} + func sendVersion(addr string) { payload := gobEncode(verzion{nodeVersion, nodeAddress}) @@ -141,7 +168,25 @@ func handleAddr(request []byte) { requestBlocks() } -func handleInv(request []byte) { +func handleBlock(request []byte, bc *Blockchain) { + var buff bytes.Buffer + var payload block + + buff.Write(request[commandLength:]) + dec := gob.NewDecoder(&buff) + err := dec.Decode(&payload) + if err != nil { + log.Panic(err) + } + + blockData := payload.Block + block := DeserializeBlock(blockData) + + fmt.Println("Recevied a new block!") + fmt.Println(block) +} + +func handleInv(request []byte, bc *Blockchain) { var buff bytes.Buffer var payload inv @@ -152,7 +197,14 @@ func handleInv(request []byte) { log.Panic(err) } - fmt.Printf("Recevied %d %s\n", len(payload.Items), payload.Type) + fmt.Printf("Recevied inventory with %d %s\n", len(payload.Items), payload.Type) + blocks := bc.GetBlockHashes() + + if len(blocks) < len(payload.Items) { + for _, blockHash := range invResponse.Items { + sendGetData(sourceNode, "block", blockHash) + } + } } func handleGetBlocks(request []byte, bc *Blockchain) { @@ -170,6 +222,27 @@ func handleGetBlocks(request []byte, bc *Blockchain) { sendInv(payload.AddrFrom, "blocks", blocks) } +func handleGetData(request []byte, bc *Blockchain) { + var buff bytes.Buffer + var payload getdata + + buff.Write(request[commandLength:]) + dec := gob.NewDecoder(&buff) + err := dec.Decode(&payload) + if err != nil { + log.Panic(err) + } + + if payload.Type == "block" { + block, err := bc.GetBlock([]byte(payload.ID)) + if err != nil { + return + } + + sendBlock(payload.AddrFrom, &block) + } +} + func handleVersion(request []byte) { var buff bytes.Buffer var payload verzion @@ -197,10 +270,14 @@ func handleConnection(conn net.Conn, bc *Blockchain) { switch command { case "addr": handleAddr(request) + case "block": + handleBlock(request, bc) case "inv": - handleInv(request) + handleInv(request, bc) case "getblocks": handleGetBlocks(request, bc) + case "getdata": + handleGetData(request, bc) case "version": handleVersion(request) case "verack": From a79d78ad8cf4179252d983319196a48393e1d37e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 20:30:21 +0700 Subject: [PATCH 108/122] Implement Blockchain.AddBlock --- blockchain.go | 18 ++++++++++++++++++ server.go | 6 +++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/blockchain.go b/blockchain.go index 328cf069..1874f454 100644 --- a/blockchain.go +++ b/blockchain.go @@ -102,6 +102,24 @@ func (bc *Blockchain) Reset() { } +// AddBlock saves the block into the blockchain +func (bc *Blockchain) AddBlock(block *Block) { + err := bc.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + blockInDb := b.Get(block.Hash) + + if blockInDb != nil { + blockData := block.Serialize() + b.Put(block.Hash, blockData) + } + + return nil + }) + if err != nil { + log.Panic(err) + } +} + // FindTransaction finds a transaction by its ID func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { bci := bc.Iterator() diff --git a/server.go b/server.go index 83b649c5..bfc9d2b6 100644 --- a/server.go +++ b/server.go @@ -183,7 +183,7 @@ func handleBlock(request []byte, bc *Blockchain) { block := DeserializeBlock(blockData) fmt.Println("Recevied a new block!") - fmt.Println(block) + bc.AddBlock(block) } func handleInv(request []byte, bc *Blockchain) { @@ -201,8 +201,8 @@ func handleInv(request []byte, bc *Blockchain) { blocks := bc.GetBlockHashes() if len(blocks) < len(payload.Items) { - for _, blockHash := range invResponse.Items { - sendGetData(sourceNode, "block", blockHash) + for _, blockHash := range payload.Items { + sendGetData(payload.AddrFrom, "block", blockHash) } } } From c753df287acfd07ec0327bec5987377fbfe2a5fb Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 1 Oct 2017 21:33:03 +0700 Subject: [PATCH 109/122] Implement the correct way of synchronizing a blockchain --- blockchain.go | 39 ++++++++++++++++++++-- server.go | 92 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 96 insertions(+), 35 deletions(-) diff --git a/blockchain.go b/blockchain.go index 1874f454..b6efe0fa 100644 --- a/blockchain.go +++ b/blockchain.go @@ -109,8 +109,24 @@ func (bc *Blockchain) AddBlock(block *Block) { blockInDb := b.Get(block.Hash) if blockInDb != nil { - blockData := block.Serialize() - b.Put(block.Hash, blockData) + return nil + } + + blockData := block.Serialize() + err := b.Put(block.Hash, blockData) + if err != nil { + log.Panic(err) + } + + lastHash := b.Get([]byte("l")) + lastBlockData := b.Get(lastHash) + lastBlock := DeserializeBlock(lastBlockData) + + if block.Height > lastBlock.Height { + err = b.Put([]byte("l"), block.Hash) + if err != nil { + log.Panic(err) + } } return nil @@ -192,6 +208,25 @@ func (bc *Blockchain) Iterator() *BlockchainIterator { return bci } +// GetBestHeight returns the height of the latest block +func (bc *Blockchain) GetBestHeight() int { + var lastBlock Block + + err := bc.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + lastHash := b.Get([]byte("l")) + blockData := b.Get(lastHash) + lastBlock = *DeserializeBlock(blockData) + + return nil + }) + if err != nil { + log.Panic(err) + } + + return lastBlock.Height +} + // GetBlock finds a block by its hash and returns it func (bc *Blockchain) GetBlock(blockHash []byte) (Block, error) { var block Block diff --git a/server.go b/server.go index bfc9d2b6..22fb2ddb 100644 --- a/server.go +++ b/server.go @@ -11,12 +11,12 @@ import ( ) const protocol = "tcp" -const dnsNodeID = "3000" const nodeVersion = 1 const commandLength = 12 var nodeAddress string -var knownNodes []string +var knownNodes = []string{"localhost:3000"} +var blocksInTransit = [][]byte{} type addr struct { AddrList []string @@ -43,13 +43,10 @@ type inv struct { Items [][]byte } -type verack struct { -} - type verzion struct { - Version int - - AddrFrom string + Version int + BestHeight int + AddrFrom string } func commandToBytes(command string) []byte { @@ -136,22 +133,15 @@ func sendGetData(address, kind string, id []byte) { sendData(address, request) } -func sendVersion(addr string) { - payload := gobEncode(verzion{nodeVersion, nodeAddress}) +func sendVersion(addr string, bc *Blockchain) { + bestHeight := bc.GetBestHeight() + payload := gobEncode(verzion{nodeVersion, bestHeight, nodeAddress}) request := append(commandToBytes("version"), payload...) sendData(addr, request) } -func sendVrack(addr string) { - payload := gobEncode(verack{}) - - request := append(commandToBytes("verack"), payload...) - - sendData(addr, request) -} - func handleAddr(request []byte) { var buff bytes.Buffer var payload addr @@ -184,6 +174,16 @@ func handleBlock(request []byte, bc *Blockchain) { fmt.Println("Recevied a new block!") bc.AddBlock(block) + fmt.Printf("Added block %x\n", block.Hash) + fmt.Printf("Added block %d\n", block.Height) + + fmt.Println(blocksInTransit) + if len(blocksInTransit) > 0 { + blockHash := blocksInTransit[0] + sendGetData(payload.AddrFrom, "block", blockHash) + + blocksInTransit = blocksInTransit[1:] + } } func handleInv(request []byte, bc *Blockchain) { @@ -198,12 +198,20 @@ func handleInv(request []byte, bc *Blockchain) { } fmt.Printf("Recevied inventory with %d %s\n", len(payload.Items), payload.Type) - blocks := bc.GetBlockHashes() - if len(blocks) < len(payload.Items) { - for _, blockHash := range payload.Items { - sendGetData(payload.AddrFrom, "block", blockHash) + if payload.Type == "blocks" { + blocksInTransit = payload.Items + + blockHash := payload.Items[0] + sendGetData(payload.AddrFrom, "block", blockHash) + + newInTransit := [][]byte{} + for _, b := range blocksInTransit { + if bytes.Compare(b, blockHash) != 0 { + newInTransit = append(newInTransit, b) + } } + blocksInTransit = newInTransit } } @@ -243,7 +251,7 @@ func handleGetData(request []byte, bc *Blockchain) { } } -func handleVersion(request []byte) { +func handleVersion(request []byte, bc *Blockchain) { var buff bytes.Buffer var payload verzion @@ -254,9 +262,19 @@ func handleVersion(request []byte) { log.Panic(err) } - sendVrack(payload.AddrFrom) - sendAddr(payload.AddrFrom) - knownNodes = append(knownNodes, payload.AddrFrom) + myBestHeight := bc.GetBestHeight() + foreignerBestHeight := payload.BestHeight + + if myBestHeight < foreignerBestHeight { + sendGetBlocks(payload.AddrFrom) + } else { + sendVersion(payload.AddrFrom, bc) + } + + // sendAddr(payload.AddrFrom) + if !nodeIsKnown(payload.AddrFrom) { + knownNodes = append(knownNodes, payload.AddrFrom) + } } func handleConnection(conn net.Conn, bc *Blockchain) { @@ -279,9 +297,7 @@ func handleConnection(conn net.Conn, bc *Blockchain) { case "getdata": handleGetData(request, bc) case "version": - handleVersion(request) - case "verack": - // + handleVersion(request, bc) default: fmt.Println("Unknown command!") } @@ -298,12 +314,12 @@ func StartServer(nodeID string) { } defer ln.Close() - if nodeID != dnsNodeID { - sendVersion(fmt.Sprintf("localhost:%s", dnsNodeID)) - } - bc := NewBlockchain(nodeID) + if nodeAddress != knownNodes[0] { + sendVersion(knownNodes[0], bc) + } + for { conn, err := ln.Accept() if err != nil { @@ -324,3 +340,13 @@ func gobEncode(data interface{}) []byte { return buff.Bytes() } + +func nodeIsKnown(addr string) bool { + for _, node := range knownNodes { + if node == addr { + return true + } + } + + return false +} From 3cb93b52785e54df7f09bad8bc8ec32c91fd5045 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 3 Oct 2017 15:47:27 +0700 Subject: [PATCH 110/122] Implement 'tx' command (WIP) --- cli_send.go | 13 +++++++--- server.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ transaction.go | 13 ++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/cli_send.go b/cli_send.go index 4e5a59a9..84b500ab 100644 --- a/cli_send.go +++ b/cli_send.go @@ -25,9 +25,16 @@ func (cli *CLI) send(from, to string, amount int, nodeID string) { tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet) cbTx := NewCoinbaseTX(from, "") - txs := []*Transaction{cbTx, tx} + // txs := []*Transaction{cbTx, tx} - newBlock := bc.MineBlock(txs) - UTXOSet.Update(newBlock) + // var txHashes [][]byte + // txHashes = append(txHashes, tx.Hash()) + // txHashes = append(txHashes, cbTx.Hash()) + + sendTx(knownNodes[0], tx) + sendTx(knownNodes[0], cbTx) + + // newBlock := bc.MineBlock(txs) + // UTXOSet.Update(newBlock) fmt.Println("Success!") } diff --git a/server.go b/server.go index 22fb2ddb..2d6577e4 100644 --- a/server.go +++ b/server.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/gob" + "encoding/hex" "fmt" "io" "io/ioutil" @@ -17,6 +18,7 @@ const commandLength = 12 var nodeAddress string var knownNodes = []string{"localhost:3000"} var blocksInTransit = [][]byte{} +var mempool = make(map[string]Transaction) type addr struct { AddrList []string @@ -43,6 +45,11 @@ type inv struct { Items [][]byte } +type tx struct { + AddFrom string + Transaction []byte +} + type verzion struct { Version int BestHeight int @@ -133,6 +140,14 @@ func sendGetData(address, kind string, id []byte) { sendData(address, request) } +func sendTx(addr string, tnx *Transaction) { + data := tx{nodeAddress, tnx.Serialize()} + payload := gobEncode(data) + request := append(commandToBytes("tx"), payload...) + + sendData(addr, request) +} + func sendVersion(addr string, bc *Blockchain) { bestHeight := bc.GetBestHeight() payload := gobEncode(verzion{nodeVersion, bestHeight, nodeAddress}) @@ -213,6 +228,14 @@ func handleInv(request []byte, bc *Blockchain) { } blocksInTransit = newInTransit } + + if payload.Type == "tx" { + txID := payload.Items[0] + + if mempool[hex.EncodeToString(txID)].ID == nil { + sendGetData(payload.AddrFrom, "tx", txID) + } + } } func handleGetBlocks(request []byte, bc *Blockchain) { @@ -249,6 +272,51 @@ func handleGetData(request []byte, bc *Blockchain) { sendBlock(payload.AddrFrom, &block) } + + if payload.Type == "tx" { + txID := hex.EncodeToString(payload.ID) + tx := mempool[txID] + + sendTx(payload.AddrFrom, &tx) + // delete(mempool, txID) + } +} + +func handleTx(request []byte, bc *Blockchain) { + var buff bytes.Buffer + var payload tx + + buff.Write(request[commandLength:]) + dec := gob.NewDecoder(&buff) + err := dec.Decode(&payload) + if err != nil { + log.Panic(err) + } + + txData := payload.Transaction + tx := DeserializeTransaction(txData) + mempool[hex.EncodeToString(tx.ID)] = tx + + if nodeAddress == knownNodes[0] { + for _, node := range knownNodes { + if node != nodeAddress { + sendInv(node, "tx", [][]byte{tx.ID}) + } + } + } else { + if len(mempool) >= 2 { + var txs []*Transaction + + for _, tx := range mempool { + txs = append(txs, &tx) + } + newBlock := bc.MineBlock(txs) + UTXOSet := UTXOSet{bc} + UTXOSet.Update(newBlock) + + mempool = make(map[string]Transaction) + } + } } func handleVersion(request []byte, bc *Blockchain) { @@ -296,6 +364,8 @@ func handleConnection(conn net.Conn, bc *Blockchain) { handleGetBlocks(request, bc) case "getdata": handleGetData(request, bc) + case "tx": + handleTx(request, bc) case "version": handleVersion(request, bc) default: diff --git a/transaction.go b/transaction.go index 0da21171..a982384a 100644 --- a/transaction.go +++ b/transaction.go @@ -228,3 +228,16 @@ func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) return &tx } + +// DeserializeTransaction deserializes a transaction +func DeserializeTransaction(data []byte) Transaction { + var transaction Transaction + + decoder := gob.NewDecoder(bytes.NewReader(data)) + err := decoder.Decode(&transaction) + if err != nil { + log.Panic(err) + } + + return transaction +} From 9de40a9385ce9f2aeedcd2316dc0d5fabdde06d3 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 3 Oct 2017 15:47:56 +0700 Subject: [PATCH 111/122] Fix 'version' message exhanging --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 2d6577e4..c74d1274 100644 --- a/server.go +++ b/server.go @@ -335,7 +335,7 @@ func handleVersion(request []byte, bc *Blockchain) { if myBestHeight < foreignerBestHeight { sendGetBlocks(payload.AddrFrom) - } else { + } else if myBestHeight > foreignerBestHeight { sendVersion(payload.AddrFrom, bc) } From 150778f920057e79407115116e85d1dd4c144a34 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 3 Oct 2017 15:54:31 +0700 Subject: [PATCH 112/122] Add -mine option to the 'send' command --- cli.go | 5 +++-- cli_send.go | 18 +++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cli.go b/cli.go index 00e72d20..82a1127d 100644 --- a/cli.go +++ b/cli.go @@ -19,7 +19,7 @@ func (cli *CLI) printUsage() { 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") + fmt.Println(" send -from FROM -to TO -amount AMOUNT -mine - Send AMOUNT of coins from FROM address to TO. Mine on the same node, when -mine is set.") fmt.Println(" startnode - Start a node with ID specified in NODE_ID env. var") } @@ -54,6 +54,7 @@ func (cli *CLI) Run() { sendFrom := sendCmd.String("from", "", "Source wallet address") sendTo := sendCmd.String("to", "", "Destination wallet address") sendAmount := sendCmd.Int("amount", 0, "Amount to send") + sendMine := sendCmd.Bool("mine", false, "Mine immediately on the same node") switch os.Args[1] { case "getbalance": @@ -139,7 +140,7 @@ func (cli *CLI) Run() { os.Exit(1) } - cli.send(*sendFrom, *sendTo, *sendAmount, nodeID) + cli.send(*sendFrom, *sendTo, *sendAmount, nodeID, *sendMine) } if startNodeCmd.Parsed() { diff --git a/cli_send.go b/cli_send.go index 84b500ab..cb607a86 100644 --- a/cli_send.go +++ b/cli_send.go @@ -5,7 +5,7 @@ import ( "log" ) -func (cli *CLI) send(from, to string, amount int, nodeID string) { +func (cli *CLI) send(from, to string, amount int, nodeID string, mineNow bool) { if !ValidateAddress(from) { log.Panic("ERROR: Sender address is not valid") } @@ -25,16 +25,16 @@ func (cli *CLI) send(from, to string, amount int, nodeID string) { tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet) cbTx := NewCoinbaseTX(from, "") - // txs := []*Transaction{cbTx, tx} - // var txHashes [][]byte - // txHashes = append(txHashes, tx.Hash()) - // txHashes = append(txHashes, cbTx.Hash()) + if mineNow { + txs := []*Transaction{cbTx, tx} - sendTx(knownNodes[0], tx) - sendTx(knownNodes[0], cbTx) + newBlock := bc.MineBlock(txs) + UTXOSet.Update(newBlock) + } else { + sendTx(knownNodes[0], tx) + sendTx(knownNodes[0], cbTx) + } - // newBlock := bc.MineBlock(txs) - // UTXOSet.Update(newBlock) fmt.Println("Success!") } From 13f22d2e403a23de347c5b723ee6e2077b5b3ea8 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 3 Oct 2017 15:54:50 +0700 Subject: [PATCH 113/122] Update UTXOSet after adding a new block --- server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server.go b/server.go index c74d1274..7457cb0e 100644 --- a/server.go +++ b/server.go @@ -199,6 +199,9 @@ func handleBlock(request []byte, bc *Blockchain) { blocksInTransit = blocksInTransit[1:] } + + UTXOSet := UTXOSet{bc} + UTXOSet.Update(block) } func handleInv(request []byte, bc *Blockchain) { From 5c4340f47d7ee44a4ae85ed467e2d1554fcf5f2f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 3 Oct 2017 16:21:15 +0700 Subject: [PATCH 114/122] Add -miner flag to 'startnode' command --- blockchain.go | 1 + cli.go | 5 +++-- cli_send.go | 3 +-- cli_startnode.go | 16 +++++++++++++--- server.go | 29 +++++++++++++++++++++-------- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/blockchain.go b/blockchain.go index b6efe0fa..255d9e22 100644 --- a/blockchain.go +++ b/blockchain.go @@ -275,6 +275,7 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block { var lastHeight int for _, tx := range transactions { + // TODO: ignore transaction if it's not valid if bc.VerifyTransaction(tx) != true { log.Panic("ERROR: Invalid transaction") } diff --git a/cli.go b/cli.go index 82a1127d..b597b4c7 100644 --- a/cli.go +++ b/cli.go @@ -20,7 +20,7 @@ func (cli *CLI) printUsage() { 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 -mine - Send AMOUNT of coins from FROM address to TO. Mine on the same node, when -mine is set.") - fmt.Println(" startnode - Start a node with ID specified in NODE_ID env. var") + fmt.Println(" startnode -miner ADDRESS - Start a node with ID specified in NODE_ID env. var. -miner enables mining") } func (cli *CLI) validateArgs() { @@ -55,6 +55,7 @@ func (cli *CLI) Run() { sendTo := sendCmd.String("to", "", "Destination wallet address") sendAmount := sendCmd.Int("amount", 0, "Amount to send") sendMine := sendCmd.Bool("mine", false, "Mine immediately on the same node") + startNodeMiner := startNodeCmd.String("miner", "", "Enable mining mode and send reward to ADDRESS") switch os.Args[1] { case "getbalance": @@ -149,6 +150,6 @@ func (cli *CLI) Run() { startNodeCmd.Usage() os.Exit(1) } - cli.startNode(nodeID) + cli.startNode(nodeID, *startNodeMiner) } } diff --git a/cli_send.go b/cli_send.go index cb607a86..75a59301 100644 --- a/cli_send.go +++ b/cli_send.go @@ -24,16 +24,15 @@ func (cli *CLI) send(from, to string, amount int, nodeID string, mineNow bool) { wallet := wallets.GetWallet(from) tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet) - cbTx := NewCoinbaseTX(from, "") if mineNow { + cbTx := NewCoinbaseTX(from, "") txs := []*Transaction{cbTx, tx} newBlock := bc.MineBlock(txs) UTXOSet.Update(newBlock) } else { sendTx(knownNodes[0], tx) - sendTx(knownNodes[0], cbTx) } fmt.Println("Success!") diff --git a/cli_startnode.go b/cli_startnode.go index 289ccd49..d390512e 100644 --- a/cli_startnode.go +++ b/cli_startnode.go @@ -1,8 +1,18 @@ package main -import "fmt" +import ( + "fmt" + "log" +) -func (cli *CLI) startNode(nodeID string) { +func (cli *CLI) startNode(nodeID, minerAddress string) { fmt.Printf("Starting node %s\n", nodeID) - StartServer(nodeID) + if len(minerAddress) > 0 { + if ValidateAddress(minerAddress) { + fmt.Println("Mining is on. Address to receive rewards: ", minerAddress) + } else { + log.Panic("Wrong miner address!") + } + } + StartServer(nodeID, minerAddress) } diff --git a/server.go b/server.go index 7457cb0e..3ccb0f3e 100644 --- a/server.go +++ b/server.go @@ -16,6 +16,7 @@ const nodeVersion = 1 const commandLength = 12 var nodeAddress string +var miningAddress string var knownNodes = []string{"localhost:3000"} var blocksInTransit = [][]byte{} var mempool = make(map[string]Transaction) @@ -189,6 +190,10 @@ func handleBlock(request []byte, bc *Blockchain) { fmt.Println("Recevied a new block!") bc.AddBlock(block) + + // TODO: how to update UTXOSet when the order of new blocks is not correct? + // UTXOSet := UTXOSet{bc} + // UTXOSet.Update(block) fmt.Printf("Added block %x\n", block.Hash) fmt.Printf("Added block %d\n", block.Height) @@ -199,9 +204,6 @@ func handleBlock(request []byte, bc *Blockchain) { blocksInTransit = blocksInTransit[1:] } - - UTXOSet := UTXOSet{bc} - UTXOSet.Update(block) } func handleInv(request []byte, bc *Blockchain) { @@ -302,22 +304,32 @@ func handleTx(request []byte, bc *Blockchain) { if nodeAddress == knownNodes[0] { for _, node := range knownNodes { - if node != nodeAddress { + if node != nodeAddress && node != payload.AddFrom { sendInv(node, "tx", [][]byte{tx.ID}) } } } else { - if len(mempool) >= 2 { + if len(mempool) >= 2 && len(miningAddress) > 0 { var txs []*Transaction for _, tx := range mempool { - txs = append(txs, &tx) + // TODO: remove this check after improving MineBlock + if bc.VerifyTransaction(&tx) { + txs = append(txs, &tx) + } } + + cbTx := NewCoinbaseTX(miningAddress, "") + txs = append(txs, cbTx) + newBlock := bc.MineBlock(txs) UTXOSet := UTXOSet{bc} UTXOSet.Update(newBlock) - mempool = make(map[string]Transaction) + for _, tx := range txs { + txID := hex.EncodeToString(tx.ID) + delete(mempool, txID) + } } } } @@ -379,8 +391,9 @@ func handleConnection(conn net.Conn, bc *Blockchain) { } // StartServer starts a node -func StartServer(nodeID string) { +func StartServer(nodeID, minerAddress string) { nodeAddress = fmt.Sprintf("localhost:%s", nodeID) + miningAddress = minerAddress ln, err := net.Listen(protocol, nodeAddress) if err != nil { log.Panic(err) From 64d1cc5569541878be9ea231e8e5debeee56d10c Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 6 Oct 2017 11:30:51 +0700 Subject: [PATCH 115/122] When a new block is mined, let everyone know --- server.go | 30 +++++++++++++++++++++++++++--- transaction.go | 16 +++++++++------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/server.go b/server.go index 3ccb0f3e..9653af98 100644 --- a/server.go +++ b/server.go @@ -109,7 +109,18 @@ func sendBlock(addr string, b *Block) { func sendData(addr string, data []byte) { conn, err := net.Dial(protocol, addr) if err != nil { - log.Panic(err) + fmt.Printf("%s is not available\n", addr) + var updatedNodes []string + + for _, node := range knownNodes { + if node != addr { + updatedNodes = append(updatedNodes, node) + } + } + + knownNodes = updatedNodes + + return } defer conn.Close() @@ -219,7 +230,7 @@ func handleInv(request []byte, bc *Blockchain) { fmt.Printf("Recevied inventory with %d %s\n", len(payload.Items), payload.Type) - if payload.Type == "blocks" { + if payload.Type == "block" { blocksInTransit = payload.Items blockHash := payload.Items[0] @@ -255,7 +266,7 @@ func handleGetBlocks(request []byte, bc *Blockchain) { } blocks := bc.GetBlockHashes() - sendInv(payload.AddrFrom, "blocks", blocks) + sendInv(payload.AddrFrom, "block", blocks) } func handleGetData(request []byte, bc *Blockchain) { @@ -319,6 +330,11 @@ func handleTx(request []byte, bc *Blockchain) { } } + if len(txs) == 0 { + fmt.Println("All transactions are invalid! Waiting for new ones...") + return + } + cbTx := NewCoinbaseTX(miningAddress, "") txs = append(txs, cbTx) @@ -326,10 +342,18 @@ func handleTx(request []byte, bc *Blockchain) { UTXOSet := UTXOSet{bc} UTXOSet.Update(newBlock) + fmt.Println("New block is mined!") + for _, tx := range txs { txID := hex.EncodeToString(tx.ID) delete(mempool, txID) } + + for _, node := range knownNodes { + if node != nodeAddress { + sendInv(node, "block", [][]byte{newBlock.Hash}) + } + } } } } diff --git a/transaction.go b/transaction.go index a982384a..f2031d3d 100644 --- a/transaction.go +++ b/transaction.go @@ -7,12 +7,12 @@ import ( "crypto/rand" "crypto/sha256" "math/big" + "strings" "encoding/gob" "encoding/hex" "fmt" "log" - "strings" ) const subsidy = 10 @@ -72,16 +72,17 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash - txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].PubKey = nil - r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) + dataToSign := fmt.Sprintf("%x\n", txCopy) + + r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign)) if err != nil { log.Panic(err) } signature := append(r.Bytes(), s.Bytes()...) tx.Vin[inID].Signature = signature + txCopy.Vin[inID].PubKey = nil } } @@ -146,8 +147,6 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash - txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].PubKey = nil r := big.Int{} s := big.Int{} @@ -161,10 +160,13 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { x.SetBytes(vin.PubKey[:(keyLen / 2)]) y.SetBytes(vin.PubKey[(keyLen / 2):]) + dataToVerify := fmt.Sprintf("%x\n", txCopy) + rawPubKey := ecdsa.PublicKey{curve, &x, &y} - if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { + if ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) == false { return false } + txCopy.Vin[inID].PubKey = nil } return true From a96a5bc1e63b6a7fe04066da2e1786a227eb019f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 6 Oct 2017 12:29:55 +0700 Subject: [PATCH 116/122] Fix the main scenario --- blockchain.go | 6 +----- server.go | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/blockchain.go b/blockchain.go index 255d9e22..8436035b 100644 --- a/blockchain.go +++ b/blockchain.go @@ -97,11 +97,6 @@ func NewBlockchain(nodeID string) *Blockchain { return &bc } -// Reset removes all blockchain data -func (bc *Blockchain) Reset() { - -} - // AddBlock saves the block into the blockchain func (bc *Blockchain) AddBlock(block *Block) { err := bc.db.Update(func(tx *bolt.Tx) error { @@ -127,6 +122,7 @@ func (bc *Blockchain) AddBlock(block *Block) { if err != nil { log.Panic(err) } + bc.tip = block.Hash } return nil diff --git a/server.go b/server.go index 9653af98..b13f520b 100644 --- a/server.go +++ b/server.go @@ -202,18 +202,16 @@ func handleBlock(request []byte, bc *Blockchain) { fmt.Println("Recevied a new block!") bc.AddBlock(block) - // TODO: how to update UTXOSet when the order of new blocks is not correct? - // UTXOSet := UTXOSet{bc} - // UTXOSet.Update(block) fmt.Printf("Added block %x\n", block.Hash) - fmt.Printf("Added block %d\n", block.Height) - fmt.Println(blocksInTransit) if len(blocksInTransit) > 0 { blockHash := blocksInTransit[0] sendGetData(payload.AddrFrom, "block", blockHash) blocksInTransit = blocksInTransit[1:] + } else { + UTXOSet := UTXOSet{bc} + UTXOSet.Reindex() } } @@ -321,10 +319,11 @@ func handleTx(request []byte, bc *Blockchain) { } } else { if len(mempool) >= 2 && len(miningAddress) > 0 { + MineTransactions: var txs []*Transaction - for _, tx := range mempool { - // TODO: remove this check after improving MineBlock + for id := range mempool { + tx := mempool[id] if bc.VerifyTransaction(&tx) { txs = append(txs, &tx) } @@ -340,7 +339,7 @@ func handleTx(request []byte, bc *Blockchain) { newBlock := bc.MineBlock(txs) UTXOSet := UTXOSet{bc} - UTXOSet.Update(newBlock) + UTXOSet.Reindex() fmt.Println("New block is mined!") @@ -354,6 +353,10 @@ func handleTx(request []byte, bc *Blockchain) { sendInv(node, "block", [][]byte{newBlock.Hash}) } } + + if len(mempool) > 0 { + goto MineTransactions + } } } } From 58f9eb7dc5445c66f8f36084eb345747b131815b Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 6 Oct 2017 17:32:52 +0700 Subject: [PATCH 117/122] Update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a7afaceb..76ae180b 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,4 @@ A blockchain implementation in Go, as described in these articles: 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/) +7. [Network](https://jeiwan.cc/posts/building-blockchain-in-go-part-7/) From cb32e7ca13c3b1c61f78f6d2e1025d85acdee447 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 6 Oct 2017 17:42:49 +0700 Subject: [PATCH 118/122] Fix ranging in Base58Encode and Base58Decode --- base58.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base58.go b/base58.go index 589e00c0..5db078e4 100644 --- a/base58.go +++ b/base58.go @@ -23,7 +23,7 @@ func Base58Encode(input []byte) []byte { } ReverseBytes(result) - for b := range input { + for _, b := range input { if b == 0x00 { result = append([]byte{b58Alphabet[0]}, result...) } else { @@ -39,7 +39,7 @@ func Base58Decode(input []byte) []byte { result := big.NewInt(0) zeroBytes := 0 - for b := range input { + for _, b := range input { if b == 0x00 { zeroBytes++ } From 989569ce612ff7dc3aa8ea2f68e09efa9f1a8ed1 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 18 Oct 2017 12:21:38 +0300 Subject: [PATCH 119/122] Don't output every hash onto console, its too slow Mining is about 3 times faster in average on my computer with this patch. --- proofofwork.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proofofwork.go b/proofofwork.go index 967a4bc1..cc10c1dd 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -56,7 +56,9 @@ func (pow *ProofOfWork) Run() (int, []byte) { data := pow.prepareData(nonce) hash = sha256.Sum256(data) - fmt.Printf("\r%x", hash) + if math.Remainder(float64(nonce), 100000) == 0 { + fmt.Printf("\r%x", hash) + } hashInt.SetBytes(hash[:]) if hashInt.Cmp(pow.target) == -1 { From 28de8475a5e0eee5230a72beb773b67db26bf2c0 Mon Sep 17 00:00:00 2001 From: Leon Johnson Date: Thu, 19 Oct 2017 14:33:26 -0400 Subject: [PATCH 120/122] :shirt: Fixes crypto/ecdsa.PublicKey composite literal uses unkeyed fields My linter never runs out of things to complain about :) --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index f2031d3d..107f0154 100644 --- a/transaction.go +++ b/transaction.go @@ -162,7 +162,7 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { dataToVerify := fmt.Sprintf("%x\n", txCopy) - rawPubKey := ecdsa.PublicKey{curve, &x, &y} + rawPubKey := ecdsa.PublicKey{Curve: curve, X: &x, Y: &y} if ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) == false { return false } From fee9bfd3afeef02e237e93335fa2076c30b9d6d6 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Mon, 29 Jan 2018 15:53:44 +0700 Subject: [PATCH 121/122] Fix address version processing in Base58 encoding/decoding --- base58.go | 25 +++++++++---------------- base58_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 base58_test.go diff --git a/base58.go b/base58.go index 5db078e4..38c1c358 100644 --- a/base58.go +++ b/base58.go @@ -22,38 +22,31 @@ func Base58Encode(input []byte) []byte { result = append(result, b58Alphabet[mod.Int64()]) } - ReverseBytes(result) - for _, b := range input { - if b == 0x00 { - result = append([]byte{b58Alphabet[0]}, result...) - } else { - break - } + // https://en.bitcoin.it/wiki/Base58Check_encoding#Version_bytes + if input[0] == 0x00 { + result = append(result, b58Alphabet[0]) } + ReverseBytes(result) + return result } // Base58Decode decodes Base58-encoded data func Base58Decode(input []byte) []byte { result := big.NewInt(0) - zeroBytes := 0 for _, b := range input { - if b == 0x00 { - zeroBytes++ - } - } - - payload := input[zeroBytes:] - for _, b := range payload { charIndex := bytes.IndexByte(b58Alphabet, b) result.Mul(result, big.NewInt(58)) result.Add(result, big.NewInt(int64(charIndex))) } decoded := result.Bytes() - decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...) + + if input[0] == b58Alphabet[0] { + decoded = append([]byte{0x00}, decoded...) + } return decoded } diff --git a/base58_test.go b/base58_test.go new file mode 100644 index 00000000..c65875b4 --- /dev/null +++ b/base58_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "encoding/hex" + "log" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBase58(t *testing.T) { + rawHash := "00010966776006953D5567439E5E39F86A0D273BEED61967F6" + hash, err := hex.DecodeString(rawHash) + if err != nil { + log.Fatal(err) + } + + encoded := Base58Encode(hash) + assert.Equal(t, "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", string(encoded)) + + decoded := Base58Decode([]byte("16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM")) + assert.Equal(t, strings.ToLower("00010966776006953D5567439E5E39F86A0D273BEED61967F6"), hex.EncodeToString(decoded)) +} From 5dbf3577244f2476e8ca5dd16883417b822a9167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Pace?= Date: Thu, 17 Jun 2021 22:05:38 -0300 Subject: [PATCH 122/122] readme: Fix urls --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 76ae180b..6eedbb5d 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ 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/) -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/) -7. [Network](https://jeiwan.cc/posts/building-blockchain-in-go-part-7/) +1. [Basic Prototype](https://jeiwan.net/posts/building-blockchain-in-go-part-1/) +2. [Proof-of-Work](https://jeiwan.net/posts/building-blockchain-in-go-part-2/) +3. [Persistence and CLI](https://jeiwan.net/posts/building-blockchain-in-go-part-3/) +4. [Transactions 1](https://jeiwan.net/posts/building-blockchain-in-go-part-4/) +5. [Addresses](https://jeiwan.net/posts/building-blockchain-in-go-part-5/) +6. [Transactions 2](https://jeiwan.net/posts/building-blockchain-in-go-part-6/) +7. [Network](https://jeiwan.net/posts/building-blockchain-in-go-part-7/)