diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c0363794 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..225f87d7 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Golang Blockchain + +### In this tutorial, we add a Wallet Module to our Blockchain app. + +## Run `go run main.go` to run the app, run `go build main.go` to build an executable file. + +### Check out the Youtube Tutorial for this [Go Program](https://youtu.be/O9rDas-0s2c). Here is our [Youtube Channel](https://www.youtube.com/channel/UCYqCZOwHbnPwyjawKfE21wg) Subscribe for more content. + +### Check out our blog at [tensor-programming.com](http://tensor-programming.com/). + +### Our [Twitter](https://twitter.com/TensorProgram), our [facebook](https://www.facebook.com/Tensor-Programming-1197847143611799/) and our [Steemit](https://steemit.com/@tensor). diff --git a/blockchain/block.go b/blockchain/block.go new file mode 100644 index 00000000..5bbad639 --- /dev/null +++ b/blockchain/block.go @@ -0,0 +1,72 @@ +package blockchain + +import ( + "bytes" + "encoding/gob" + "log" + "time" +) + +type Block struct { + Timestamp int64 + Hash []byte + Transactions []*Transaction + PrevHash []byte + Nonce int + Height int +} + +func (b *Block) HashTransactions() []byte { + var txHashes [][]byte + + for _, tx := range b.Transactions { + txHashes = append(txHashes, tx.Serialize()) + } + tree := NewMerkleTree(txHashes) + + return tree.RootNode.Data +} + +func CreateBlock(txs []*Transaction, prevHash []byte, height int) *Block { + block := &Block{time.Now().Unix(), []byte{}, txs, prevHash, 0, height} + pow := NewProof(block) + nonce, hash := pow.Run() + + block.Hash = hash[:] + block.Nonce = nonce + + return block +} + +func Genesis(coinbase *Transaction) *Block { + return CreateBlock([]*Transaction{coinbase}, []byte{}, 0) +} + +func (b *Block) Serialize() []byte { + var res bytes.Buffer + encoder := gob.NewEncoder(&res) + + err := encoder.Encode(b) + + Handle(err) + + return res.Bytes() +} + +func Deserialize(data []byte) *Block { + var block Block + + decoder := gob.NewDecoder(bytes.NewReader(data)) + + err := decoder.Decode(&block) + + Handle(err) + + return &block +} + +func Handle(err error) { + if err != nil { + log.Panic(err) + } +} diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go new file mode 100644 index 00000000..36b63fd0 --- /dev/null +++ b/blockchain/blockchain.go @@ -0,0 +1,344 @@ +package blockchain + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "errors" + "fmt" + "log" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/dgraph-io/badger" +) + +const ( + dbPath = "./tmp/blocks_%s" + genesisData = "First Transaction from Genesis" +) + +type BlockChain struct { + LastHash []byte + Database *badger.DB +} + +func DBexists(path string) bool { + if _, err := os.Stat(path + "/MANIFEST"); os.IsNotExist(err) { + return false + } + + return true +} + +func ContinueBlockChain(nodeId string) *BlockChain { + path := fmt.Sprintf(dbPath, nodeId) + if DBexists(path) == false { + fmt.Println("No existing blockchain found, create one!") + runtime.Goexit() + } + + var lastHash []byte + + opts := badger.DefaultOptions + opts.Dir = path + opts.ValueDir = path + + db, err := openDB(path, opts) + Handle(err) + + err = db.Update(func(txn *badger.Txn) error { + item, err := txn.Get([]byte("lh")) + Handle(err) + lastHash, err = item.Value() + + return err + }) + Handle(err) + + chain := BlockChain{lastHash, db} + + return &chain +} + +func InitBlockChain(address, nodeId string) *BlockChain { + path := fmt.Sprintf(dbPath, nodeId) + if DBexists(path) { + fmt.Println("Blockchain already exists") + runtime.Goexit() + } + var lastHash []byte + opts := badger.DefaultOptions + opts.Dir = path + opts.ValueDir = path + + db, err := openDB(path, opts) + Handle(err) + + err = db.Update(func(txn *badger.Txn) error { + cbtx := CoinbaseTx(address, genesisData) + genesis := Genesis(cbtx) + fmt.Println("Genesis created") + err = txn.Set(genesis.Hash, genesis.Serialize()) + Handle(err) + err = txn.Set([]byte("lh"), genesis.Hash) + + lastHash = genesis.Hash + + return err + + }) + + Handle(err) + + blockchain := BlockChain{lastHash, db} + return &blockchain +} + +func (chain *BlockChain) AddBlock(block *Block) { + err := chain.Database.Update(func(txn *badger.Txn) error { + if _, err := txn.Get(block.Hash); err == nil { + return nil + } + + blockData := block.Serialize() + err := txn.Set(block.Hash, blockData) + Handle(err) + + item, err := txn.Get([]byte("lh")) + Handle(err) + lastHash, _ := item.Value() + + item, err = txn.Get(lastHash) + Handle(err) + lastBlockData, _ := item.Value() + + lastBlock := Deserialize(lastBlockData) + + if block.Height > lastBlock.Height { + err = txn.Set([]byte("lh"), block.Hash) + Handle(err) + chain.LastHash = block.Hash + } + + return nil + }) + Handle(err) +} + +func (chain *BlockChain) GetBestHeight() int { + var lastBlock Block + + err := chain.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte("lh")) + Handle(err) + lastHash, _ := item.Value() + + item, err = txn.Get(lastHash) + Handle(err) + lastBlockData, _ := item.Value() + + lastBlock = *Deserialize(lastBlockData) + + return nil + }) + Handle(err) + + return lastBlock.Height +} + +func (chain *BlockChain) GetBlock(blockHash []byte) (Block, error) { + var block Block + + err := chain.Database.View(func(txn *badger.Txn) error { + if item, err := txn.Get(blockHash); err != nil { + return errors.New("Block is not found") + } else { + blockData, _ := item.Value() + + block = *Deserialize(blockData) + } + return nil + }) + if err != nil { + return block, err + } + + return block, nil +} + +func (chain *BlockChain) GetBlockHashes() [][]byte { + var blocks [][]byte + + iter := chain.Iterator() + + for { + block := iter.Next() + + blocks = append(blocks, block.Hash) + + if len(block.PrevHash) == 0 { + break + } + } + + return blocks +} + +func (chain *BlockChain) MineBlock(transactions []*Transaction) *Block { + var lastHash []byte + var lastHeight int + + for _, tx := range transactions { + if chain.VerifyTransaction(tx) != true { + log.Panic("Invalid Transaction") + } + } + + err := chain.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte("lh")) + Handle(err) + lastHash, err = item.Value() + + item, err = txn.Get(lastHash) + Handle(err) + lastBlockData, _ := item.Value() + + lastBlock := Deserialize(lastBlockData) + + lastHeight = lastBlock.Height + + return err + }) + Handle(err) + + newBlock := CreateBlock(transactions, lastHash, lastHeight+1) + + err = chain.Database.Update(func(txn *badger.Txn) error { + err := txn.Set(newBlock.Hash, newBlock.Serialize()) + Handle(err) + err = txn.Set([]byte("lh"), newBlock.Hash) + + chain.LastHash = newBlock.Hash + + return err + }) + Handle(err) + + return newBlock +} + +func (chain *BlockChain) FindUTXO() map[string]TxOutputs { + UTXO := make(map[string]TxOutputs) + spentTXOs := make(map[string][]int) + + iter := chain.Iterator() + + for { + block := iter.Next() + + for _, tx := range block.Transactions { + txID := hex.EncodeToString(tx.ID) + + Outputs: + for outIdx, out := range tx.Outputs { + if spentTXOs[txID] != nil { + for _, spentOut := range spentTXOs[txID] { + if spentOut == outIdx { + continue Outputs + } + } + } + outs := UTXO[txID] + outs.Outputs = append(outs.Outputs, out) + UTXO[txID] = outs + } + if tx.IsCoinbase() == false { + for _, in := range tx.Inputs { + inTxID := hex.EncodeToString(in.ID) + spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Out) + } + } + } + + if len(block.PrevHash) == 0 { + break + } + } + return UTXO +} + +func (bc *BlockChain) FindTransaction(ID []byte) (Transaction, error) { + iter := bc.Iterator() + + for { + block := iter.Next() + + for _, tx := range block.Transactions { + if bytes.Compare(tx.ID, ID) == 0 { + return *tx, nil + } + } + + if len(block.PrevHash) == 0 { + break + } + } + + return Transaction{}, errors.New("Transaction does not exist") +} + +func (bc *BlockChain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { + prevTXs := make(map[string]Transaction) + + for _, in := range tx.Inputs { + prevTX, err := bc.FindTransaction(in.ID) + Handle(err) + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + tx.Sign(privKey, prevTXs) +} + +func (bc *BlockChain) VerifyTransaction(tx *Transaction) bool { + if tx.IsCoinbase() { + return true + } + prevTXs := make(map[string]Transaction) + + for _, in := range tx.Inputs { + prevTX, err := bc.FindTransaction(in.ID) + Handle(err) + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + return tx.Verify(prevTXs) +} + +func retry(dir string, originalOpts badger.Options) (*badger.DB, error) { + lockPath := filepath.Join(dir, "LOCK") + if err := os.Remove(lockPath); err != nil { + return nil, fmt.Errorf(`removing "LOCK": %s`, err) + } + retryOpts := originalOpts + retryOpts.Truncate = true + db, err := badger.Open(retryOpts) + return db, err +} + +func openDB(dir string, opts badger.Options) (*badger.DB, error) { + if db, err := badger.Open(opts); err != nil { + if strings.Contains(err.Error(), "LOCK") { + if db, err := retry(dir, opts); err == nil { + log.Println("database unlocked, value log truncated") + return db, nil + } + log.Println("could not unlock database:", err) + } + return nil, err + } else { + return db, nil + } +} diff --git a/blockchain/chain_iter.go b/blockchain/chain_iter.go new file mode 100644 index 00000000..2137cde5 --- /dev/null +++ b/blockchain/chain_iter.go @@ -0,0 +1,32 @@ +package blockchain + +import "github.com/dgraph-io/badger" + +type BlockChainIterator struct { + CurrentHash []byte + Database *badger.DB +} + +func (chain *BlockChain) Iterator() *BlockChainIterator { + iter := &BlockChainIterator{chain.LastHash, chain.Database} + + return iter +} + +func (iter *BlockChainIterator) Next() *Block { + var block *Block + + err := iter.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get(iter.CurrentHash) + Handle(err) + encodedBlock, err := item.Value() + block = Deserialize(encodedBlock) + + return err + }) + Handle(err) + + iter.CurrentHash = block.PrevHash + + return block +} diff --git a/blockchain/merkle.go b/blockchain/merkle.go new file mode 100644 index 00000000..142be7fa --- /dev/null +++ b/blockchain/merkle.go @@ -0,0 +1,65 @@ +package blockchain + +import ( + "crypto/sha256" + "log" +) + +type MerkleTree struct { + RootNode *MerkleNode +} + +type MerkleNode struct { + Left *MerkleNode + Right *MerkleNode + Data []byte +} + +func NewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode { + node := MerkleNode{} + + if left == nil && right == nil { + hash := sha256.Sum256(data) + node.Data = hash[:] + } else { + prevHashes := append(left.Data, right.Data...) + hash := sha256.Sum256(prevHashes) + node.Data = hash[:] + } + + node.Left = left + node.Right = right + + return &node +} + +func NewMerkleTree(data [][]byte) *MerkleTree { + var nodes []MerkleNode + + for _, dat := range data { + node := NewMerkleNode(nil, nil, dat) + nodes = append(nodes, *node) + } + + if len(nodes) == 0 { + log.Panic("No merkel nodes") + } + + for len(nodes) > 1 { + if len(nodes)%2 != 0 { + nodes = append(nodes, nodes[len(nodes)-1]) + } + + var level []MerkleNode + for i := 0; i < len(nodes); i += 2 { + node := NewMerkleNode(&nodes[i], &nodes[i+1], nil) + level = append(level, *node) + } + + nodes = level + } + + tree := MerkleTree{&nodes[0]} + + return &tree +} diff --git a/blockchain/merkle_test.go b/blockchain/merkle_test.go new file mode 100644 index 00000000..ae9f566f --- /dev/null +++ b/blockchain/merkle_test.go @@ -0,0 +1,52 @@ +package blockchain + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewMerkleNode(t *testing.T) { + data := [][]byte{ + []byte("node1"), + []byte("node2"), + []byte("node3"), + []byte("node4"), + []byte("node5"), + []byte("node6"), + []byte("node7"), + } + + // level 1 + mn1 := NewMerkleNode(nil, nil, data[0]) + mn2 := NewMerkleNode(nil, nil, data[1]) + mn3 := NewMerkleNode(nil, nil, data[2]) + mn4 := NewMerkleNode(nil, nil, data[3]) + mn5 := NewMerkleNode(nil, nil, data[4]) + mn6 := NewMerkleNode(nil, nil, data[5]) + mn7 := NewMerkleNode(nil, nil, data[6]) + mn8 := NewMerkleNode(nil, nil, data[6]) + + // level 2 + mn9 := NewMerkleNode(mn1, mn2, nil) + mn10 := NewMerkleNode(mn3, mn4, nil) + mn11 := NewMerkleNode(mn5, mn6, nil) + mn12 := NewMerkleNode(mn7, mn8, nil) + + //level 3 + + mn13 := NewMerkleNode(mn9, mn10, nil) + mn14 := NewMerkleNode(mn11, mn12, nil) + + //level 4 + + mn15 := NewMerkleNode(mn13, mn14, nil) + + root := fmt.Sprintf("%x", mn15.Data) + tree := NewMerkleTree(data) + + assert.Equal(t, root, fmt.Sprintf("%x", tree.RootNode.Data), "Merkle node root has is equal") + +} + diff --git a/blockchain/proof.go b/blockchain/proof.go new file mode 100644 index 00000000..9a291e74 --- /dev/null +++ b/blockchain/proof.go @@ -0,0 +1,99 @@ +package blockchain + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "fmt" + "log" + "math" + "math/big" +) + +// Take the data from the block + +// create a counter (nonce) which starts at 0 + +// create a hash of the data plus the counter + +// check the hash to see if it meets a set of requirements + +// Requirements: +// The First few bytes must contain 0s + +const Difficulty = 12 + +type ProofOfWork struct { + Block *Block + Target *big.Int +} + +func NewProof(b *Block) *ProofOfWork { + target := big.NewInt(1) + target.Lsh(target, uint(256-Difficulty)) + + pow := &ProofOfWork{b, target} + + return pow +} + +func (pow *ProofOfWork) InitData(nonce int) []byte { + data := bytes.Join( + [][]byte{ + pow.Block.PrevHash, + pow.Block.HashTransactions(), + ToHex(int64(nonce)), + ToHex(int64(Difficulty)), + }, + []byte{}, + ) + + return data +} + +func (pow *ProofOfWork) Run() (int, []byte) { + var intHash big.Int + var hash [32]byte + + nonce := 0 + + for nonce < math.MaxInt64 { + data := pow.InitData(nonce) + hash = sha256.Sum256(data) + + fmt.Printf("\r%x", hash) + intHash.SetBytes(hash[:]) + + if intHash.Cmp(pow.Target) == -1 { + break + } else { + nonce++ + } + + } + fmt.Println() + + return nonce, hash[:] +} + +func (pow *ProofOfWork) Validate() bool { + var intHash big.Int + + data := pow.InitData(pow.Block.Nonce) + + hash := sha256.Sum256(data) + intHash.SetBytes(hash[:]) + + return intHash.Cmp(pow.Target) == -1 +} + +func ToHex(num int64) []byte { + buff := new(bytes.Buffer) + err := binary.Write(buff, binary.BigEndian, num) + if err != nil { + log.Panic(err) + + } + + return buff.Bytes() +} diff --git a/blockchain/transaction.go b/blockchain/transaction.go new file mode 100644 index 00000000..10cf598f --- /dev/null +++ b/blockchain/transaction.go @@ -0,0 +1,223 @@ +package blockchain + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/gob" + "encoding/hex" + "fmt" + "log" + "math/big" + "strings" + + "github.com/tensor-programming/golang-blockchain/wallet" +) + +type Transaction struct { + ID []byte + Inputs []TxInput + Outputs []TxOutput +} + +func (tx *Transaction) Hash() []byte { + var hash [32]byte + + txCopy := *tx + txCopy.ID = []byte{} + + hash = sha256.Sum256(txCopy.Serialize()) + + return hash[:] +} + +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() +} + +func DeserializeTransaction(data []byte) Transaction { + var transaction Transaction + + decoder := gob.NewDecoder(bytes.NewReader(data)) + err := decoder.Decode(&transaction) + Handle(err) + return transaction +} + +func CoinbaseTx(to, data string) *Transaction { + if data == "" { + randData := make([]byte, 24) + _, err := rand.Read(randData) + Handle(err) + data = fmt.Sprintf("%x", randData) + } + + txin := TxInput{[]byte{}, -1, nil, []byte(data)} + txout := NewTXOutput(20, to) + + tx := Transaction{nil, []TxInput{txin}, []TxOutput{*txout}} + tx.ID = tx.Hash() + + return &tx +} + +func NewTransaction(w *wallet.Wallet, to string, amount int, UTXO *UTXOSet) *Transaction { + var inputs []TxInput + var outputs []TxOutput + + pubKeyHash := wallet.PublicKeyHash(w.PublicKey) + acc, validOutputs := UTXO.FindSpendableOutputs(pubKeyHash, amount) + + if acc < amount { + log.Panic("Error: not enough funds") + } + + for txid, outs := range validOutputs { + txID, err := hex.DecodeString(txid) + Handle(err) + + for _, out := range outs { + input := TxInput{txID, out, nil, w.PublicKey} + inputs = append(inputs, input) + } + } + + from := fmt.Sprintf("%s", w.Address()) + + outputs = append(outputs, *NewTXOutput(amount, to)) + + if acc > amount { + outputs = append(outputs, *NewTXOutput(acc-amount, from)) + } + + tx := Transaction{nil, inputs, outputs} + tx.ID = tx.Hash() + UTXO.Blockchain.SignTransaction(&tx, w.PrivateKey) + + return &tx +} + +func (tx *Transaction) IsCoinbase() bool { + return len(tx.Inputs) == 1 && len(tx.Inputs[0].ID) == 0 && tx.Inputs[0].Out == -1 +} + +func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { + if tx.IsCoinbase() { + return + } + + for _, in := range tx.Inputs { + if prevTXs[hex.EncodeToString(in.ID)].ID == nil { + log.Panic("ERROR: Previous transaction is not correct") + } + } + + txCopy := tx.TrimmedCopy() + + for inId, in := range txCopy.Inputs { + prevTX := prevTXs[hex.EncodeToString(in.ID)] + txCopy.Inputs[inId].Signature = nil + txCopy.Inputs[inId].PubKey = prevTX.Outputs[in.Out].PubKeyHash + + dataToSign := fmt.Sprintf("%x\n", txCopy) + + r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign)) + Handle(err) + signature := append(r.Bytes(), s.Bytes()...) + + tx.Inputs[inId].Signature = signature + txCopy.Inputs[inId].PubKey = nil + } +} + +func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { + if tx.IsCoinbase() { + return true + } + + for _, in := range tx.Inputs { + if prevTXs[hex.EncodeToString(in.ID)].ID == nil { + log.Panic("Previous transaction not correct") + } + } + + txCopy := tx.TrimmedCopy() + curve := elliptic.P256() + + for inId, in := range tx.Inputs { + prevTx := prevTXs[hex.EncodeToString(in.ID)] + txCopy.Inputs[inId].Signature = nil + txCopy.Inputs[inId].PubKey = prevTx.Outputs[in.Out].PubKeyHash + + r := big.Int{} + s := big.Int{} + + sigLen := len(in.Signature) + r.SetBytes(in.Signature[:(sigLen / 2)]) + s.SetBytes(in.Signature[(sigLen / 2):]) + + x := big.Int{} + y := big.Int{} + keyLen := len(in.PubKey) + x.SetBytes(in.PubKey[:(keyLen / 2)]) + y.SetBytes(in.PubKey[(keyLen / 2):]) + + dataToVerify := fmt.Sprintf("%x\n", txCopy) + + rawPubKey := ecdsa.PublicKey{Curve: curve, X: &x, Y: &y} + if ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) == false { + return false + } + txCopy.Inputs[inId].PubKey = nil + } + + return true +} + +func (tx *Transaction) TrimmedCopy() Transaction { + var inputs []TxInput + var outputs []TxOutput + + for _, in := range tx.Inputs { + inputs = append(inputs, TxInput{in.ID, in.Out, nil, nil}) + } + + for _, out := range tx.Outputs { + outputs = append(outputs, TxOutput{out.Value, out.PubKeyHash}) + } + + txCopy := Transaction{tx.ID, inputs, outputs} + + return txCopy +} + +func (tx Transaction) String() string { + var lines []string + + lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID)) + for i, input := range tx.Inputs { + lines = append(lines, fmt.Sprintf(" Input %d:", i)) + lines = append(lines, fmt.Sprintf(" TXID: %x", input.ID)) + lines = append(lines, fmt.Sprintf(" Out: %d", input.Out)) + lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) + lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) + } + + for i, output := range tx.Outputs { + 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") +} diff --git a/blockchain/tx.go b/blockchain/tx.go new file mode 100644 index 00000000..4e733d5f --- /dev/null +++ b/blockchain/tx.go @@ -0,0 +1,63 @@ +package blockchain + +import ( + "bytes" + "encoding/gob" + + "github.com/tensor-programming/golang-blockchain/wallet" +) + +type TxOutput struct { + Value int + PubKeyHash []byte +} + +type TxOutputs struct { + Outputs []TxOutput +} + +type TxInput struct { + ID []byte + Out int + Signature []byte + PubKey []byte +} + +func (in *TxInput) UsesKey(pubKeyHash []byte) bool { + lockingHash := wallet.PublicKeyHash(in.PubKey) + + return bytes.Compare(lockingHash, pubKeyHash) == 0 +} + +func (out *TxOutput) Lock(address []byte) { + pubKeyHash := wallet.Base58Decode(address) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + out.PubKeyHash = pubKeyHash +} + +func (out *TxOutput) IsLockedWithKey(pubKeyHash []byte) bool { + return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0 +} + +func NewTXOutput(value int, address string) *TxOutput { + txo := &TxOutput{value, nil} + txo.Lock([]byte(address)) + + return txo +} + +func (outs TxOutputs) Serialize() []byte { + var buffer bytes.Buffer + encode := gob.NewEncoder(&buffer) + err := encode.Encode(outs) + Handle(err) + return buffer.Bytes() +} + +func DeserializeOutputs(data []byte) TxOutputs { + var outputs TxOutputs + decode := gob.NewDecoder(bytes.NewReader(data)) + err := decode.Decode(&outputs) + Handle(err) + return outputs +} diff --git a/blockchain/utxo.go b/blockchain/utxo.go new file mode 100644 index 00000000..a55df1ce --- /dev/null +++ b/blockchain/utxo.go @@ -0,0 +1,219 @@ +package blockchain + +import ( + "bytes" + "encoding/hex" + "log" + + "github.com/dgraph-io/badger" +) + +var ( + utxoPrefix = []byte("utxo-") + prefixLength = len(utxoPrefix) +) + +type UTXOSet struct { + Blockchain *BlockChain +} + +func (u UTXOSet) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) { + unspentOuts := make(map[string][]int) + accumulated := 0 + db := u.Blockchain.Database + + err := db.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + + it := txn.NewIterator(opts) + defer it.Close() + + for it.Seek(utxoPrefix); it.ValidForPrefix(utxoPrefix); it.Next() { + item := it.Item() + k := item.Key() + v, err := item.Value() + Handle(err) + k = bytes.TrimPrefix(k, utxoPrefix) + txID := hex.EncodeToString(k) + outs := DeserializeOutputs(v) + + for outIdx, out := range outs.Outputs { + if out.IsLockedWithKey(pubKeyHash) && accumulated < amount { + accumulated += out.Value + unspentOuts[txID] = append(unspentOuts[txID], outIdx) + } + } + } + return nil + }) + Handle(err) + + return accumulated, unspentOuts +} + +func (u UTXOSet) FindUnspentTransactions(pubKeyHash []byte) []TxOutput { + var UTXOs []TxOutput + + db := u.Blockchain.Database + + err := db.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + + it := txn.NewIterator(opts) + defer it.Close() + + for it.Seek(utxoPrefix); it.ValidForPrefix(utxoPrefix); it.Next() { + item := it.Item() + v, err := item.Value() + Handle(err) + outs := DeserializeOutputs(v) + for _, out := range outs.Outputs { + if out.IsLockedWithKey(pubKeyHash) { + UTXOs = append(UTXOs, out) + } + } + + } + return nil + }) + Handle(err) + + return UTXOs +} + +func (u UTXOSet) CountTransactions() int { + db := u.Blockchain.Database + counter := 0 + + err := db.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + + it := txn.NewIterator(opts) + defer it.Close() + for it.Seek(utxoPrefix); it.ValidForPrefix(utxoPrefix); it.Next() { + counter++ + } + + return nil + }) + + Handle(err) + + return counter +} + +func (u UTXOSet) Reindex() { + db := u.Blockchain.Database + + u.DeleteByPrefix(utxoPrefix) + + UTXO := u.Blockchain.FindUTXO() + + err := db.Update(func(txn *badger.Txn) error { + for txId, outs := range UTXO { + key, err := hex.DecodeString(txId) + Handle(err) + key = append(utxoPrefix, key...) + + err = txn.Set(key, outs.Serialize()) + Handle(err) + } + + return nil + }) + Handle(err) +} + +func (u *UTXOSet) Update(block *Block) { + db := u.Blockchain.Database + + err := db.Update(func(txn *badger.Txn) error { + for _, tx := range block.Transactions { + if tx.IsCoinbase() == false { + for _, in := range tx.Inputs { + updatedOuts := TxOutputs{} + inID := append(utxoPrefix, in.ID...) + item, err := txn.Get(inID) + Handle(err) + v, err := item.Value() + Handle(err) + + outs := DeserializeOutputs(v) + + for outIdx, out := range outs.Outputs { + if outIdx != in.Out { + updatedOuts.Outputs = append(updatedOuts.Outputs, out) + } + } + + if len(updatedOuts.Outputs) == 0 { + if err := txn.Delete(inID); err != nil { + log.Panic(err) + } + } else { + if err := txn.Set(inID, updatedOuts.Serialize()); err != nil { + log.Panic(err) + } + } + } + } + newOutputs := TxOutputs{} + for _, out := range tx.Outputs { + newOutputs.Outputs = append(newOutputs.Outputs, out) + } + + txID := append(utxoPrefix, tx.ID...) + if err := txn.Set(txID, newOutputs.Serialize()); err != nil { + log.Panic(err) + } + } + + return nil + }) + Handle(err) +} + +func (u *UTXOSet) DeleteByPrefix(prefix []byte) { + deleteKeys := func(keysForDelete [][]byte) error { + if err := u.Blockchain.Database.Update(func(txn *badger.Txn) error { + for _, key := range keysForDelete { + if err := txn.Delete(key); err != nil { + return err + } + } + return nil + }); err != nil { + return err + } + return nil + } + + collectSize := 100000 + u.Blockchain.Database.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + opts.PrefetchValues = false + it := txn.NewIterator(opts) + defer it.Close() + + keysForDelete := make([][]byte, 0, collectSize) + keysCollected := 0 + for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { + key := it.Item().KeyCopy(nil) + keysForDelete = append(keysForDelete, key) + keysCollected++ + if keysCollected == collectSize { + if err := deleteKeys(keysForDelete); err != nil { + log.Panic(err) + } + keysForDelete = make([][]byte, 0, collectSize) + keysCollected = 0 + } + } + if keysCollected > 0 { + if err := deleteKeys(keysForDelete); err != nil { + log.Panic(err) + } + } + return nil + }) +} diff --git a/cli/cli.go b/cli/cli.go new file mode 100644 index 00000000..8d405077 --- /dev/null +++ b/cli/cli.go @@ -0,0 +1,284 @@ +package cli + +import ( + "flag" + "fmt" + "log" + "os" + "runtime" + "strconv" + + "github.com/tensor-programming/golang-blockchain/blockchain" + "github.com/tensor-programming/golang-blockchain/network" + "github.com/tensor-programming/golang-blockchain/wallet" +) + +type CommandLine struct{} + +func (cli *CommandLine) printUsage() { + fmt.Println("Usage:") + fmt.Println(" getbalance -address ADDRESS - get the balance for an address") + fmt.Println(" createblockchain -address ADDRESS creates a blockchain and sends genesis reward to address") + fmt.Println(" printchain - Prints the blocks in the chain") + fmt.Println(" send -from FROM -to TO -amount AMOUNT -mine - Send amount of coins. Then -mine flag is set, mine off of this node") + fmt.Println(" createwallet - Creates a new Wallet") + fmt.Println(" listaddresses - Lists the addresses in our wallet file") + fmt.Println(" reindexutxo - Rebuilds the UTXO set") + fmt.Println(" startnode -miner ADDRESS - Start a node with ID specified in NODE_ID env. var. -miner enables mining") +} + +func (cli *CommandLine) validateArgs() { + if len(os.Args) < 2 { + cli.printUsage() + runtime.Goexit() + } +} + +func (cli *CommandLine) StartNode(nodeID, minerAddress string) { + fmt.Printf("Starting Node %s\n", nodeID) + + if len(minerAddress) > 0 { + if wallet.ValidateAddress(minerAddress) { + fmt.Println("Mining is on. Address to receive rewards: ", minerAddress) + } else { + log.Panic("Wrong miner address!") + } + } + network.StartServer(nodeID, minerAddress) +} + +func (cli *CommandLine) reindexUTXO(nodeID string) { + chain := blockchain.ContinueBlockChain(nodeID) + defer chain.Database.Close() + UTXOSet := blockchain.UTXOSet{chain} + UTXOSet.Reindex() + + count := UTXOSet.CountTransactions() + fmt.Printf("Done! There are %d transactions in the UTXO set.\n", count) +} + +func (cli *CommandLine) listAddresses(nodeID string) { + wallets, _ := wallet.CreateWallets(nodeID) + addresses := wallets.GetAllAddresses() + + for _, address := range addresses { + fmt.Println(address) + } + +} + +func (cli *CommandLine) createWallet(nodeID string) { + wallets, _ := wallet.CreateWallets(nodeID) + address := wallets.AddWallet() + wallets.SaveFile(nodeID) + + fmt.Printf("New address is: %s\n", address) +} + +func (cli *CommandLine) printChain(nodeID string) { + chain := blockchain.ContinueBlockChain(nodeID) + defer chain.Database.Close() + iter := chain.Iterator() + + for { + block := iter.Next() + + fmt.Printf("Hash: %x\n", block.Hash) + fmt.Printf("Prev. hash: %x\n", block.PrevHash) + pow := blockchain.NewProof(block) + fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) + for _, tx := range block.Transactions { + fmt.Println(tx) + } + fmt.Println() + + if len(block.PrevHash) == 0 { + break + } + } +} + +func (cli *CommandLine) createBlockChain(address, nodeID string) { + if !wallet.ValidateAddress(address) { + log.Panic("Address is not Valid") + } + chain := blockchain.InitBlockChain(address, nodeID) + defer chain.Database.Close() + + UTXOSet := blockchain.UTXOSet{chain} + UTXOSet.Reindex() + + fmt.Println("Finished!") +} + +func (cli *CommandLine) getBalance(address, nodeID string) { + if !wallet.ValidateAddress(address) { + log.Panic("Address is not Valid") + } + chain := blockchain.ContinueBlockChain(nodeID) + UTXOSet := blockchain.UTXOSet{chain} + defer chain.Database.Close() + + balance := 0 + pubKeyHash := wallet.Base58Decode([]byte(address)) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + UTXOs := UTXOSet.FindUnspentTransactions(pubKeyHash) + + for _, out := range UTXOs { + balance += out.Value + } + + fmt.Printf("Balance of %s: %d\n", address, balance) +} + +func (cli *CommandLine) send(from, to string, amount int, nodeID string, mineNow bool) { + if !wallet.ValidateAddress(to) { + log.Panic("Address is not Valid") + } + if !wallet.ValidateAddress(from) { + log.Panic("Address is not Valid") + } + chain := blockchain.ContinueBlockChain(nodeID) + UTXOSet := blockchain.UTXOSet{chain} + defer chain.Database.Close() + + wallets, err := wallet.CreateWallets(nodeID) + if err != nil { + log.Panic(err) + } + wallet := wallets.GetWallet(from) + + tx := blockchain.NewTransaction(&wallet, to, amount, &UTXOSet) + if mineNow { + cbTx := blockchain.CoinbaseTx(from, "") + txs := []*blockchain.Transaction{cbTx, tx} + block := chain.MineBlock(txs) + UTXOSet.Update(block) + } else { + network.SendTx(network.KnownNodes[0], tx) + fmt.Println("send tx") + } + + fmt.Println("Success!") +} + +func (cli *CommandLine) Run() { + cli.validateArgs() + + nodeID := os.Getenv("NODE_ID") + if nodeID == "" { + fmt.Printf("NODE_ID env is not set!") + runtime.Goexit() + } + + getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) + createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) + sendCmd := flag.NewFlagSet("send", flag.ExitOnError) + printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) + createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError) + listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError) + reindexUTXOCmd := flag.NewFlagSet("reindexutxo", 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") + 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 "reindexutxo": + err := reindexUTXOCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "getbalance": + err := getBalanceCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "createblockchain": + err := createBlockchainCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "startnode": + err := startNodeCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "listaddresses": + err := listAddressesCmd.Parse(os.Args[2:]) + 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 { + log.Panic(err) + } + case "send": + err := sendCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + default: + cli.printUsage() + runtime.Goexit() + } + + if getBalanceCmd.Parsed() { + if *getBalanceAddress == "" { + getBalanceCmd.Usage() + runtime.Goexit() + } + cli.getBalance(*getBalanceAddress, nodeID) + } + + if createBlockchainCmd.Parsed() { + if *createBlockchainAddress == "" { + createBlockchainCmd.Usage() + runtime.Goexit() + } + cli.createBlockChain(*createBlockchainAddress, nodeID) + } + + if printChainCmd.Parsed() { + cli.printChain(nodeID) + } + + if createWalletCmd.Parsed() { + cli.createWallet(nodeID) + } + if listAddressesCmd.Parsed() { + cli.listAddresses(nodeID) + } + if reindexUTXOCmd.Parsed() { + cli.reindexUTXO(nodeID) + } + + if sendCmd.Parsed() { + if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 { + sendCmd.Usage() + runtime.Goexit() + } + + cli.send(*sendFrom, *sendTo, *sendAmount, nodeID, *sendMine) + } + + if startNodeCmd.Parsed() { + nodeID := os.Getenv("NODE_ID") + if nodeID == "" { + startNodeCmd.Usage() + runtime.Goexit() + } + cli.StartNode(nodeID, *startNodeMiner) + } +} diff --git a/go.mod b/go.mod index a665fc59..f33cd6cc 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,16 @@ module github.com/tensor-programming/golang-blockchain + +require ( + github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 // indirect + github.com/dgraph-io/badger v1.5.4 + github.com/dgryski/go-farm v0.0.0-20190323171310-30f6f3c2b8f8 // indirect + github.com/golang/protobuf v1.3.1 // indirect + github.com/mr-tron/base58 v1.1.1 + github.com/pkg/errors v0.8.1 // indirect + github.com/stretchr/testify v1.3.0 + golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 + golang.org/x/net v0.0.0-20190322120337-addf6b3196f6 // indirect + gopkg.in/vrecan/death.v3 v3.0.1 +) + +go 1.13 diff --git a/main.go b/main.go index 8a275b19..d1798302 100644 --- a/main.go +++ b/main.go @@ -1,57 +1,14 @@ -package main - -import ( - "bytes" - "crypto/sha256" - "fmt" -) - -type BlockChain struct { - blocks []*Block -} - -type Block struct { - Hash []byte - Data []byte - PrevHash []byte -} - -func (b *Block) DeriveHash() { - info := bytes.Join([][]byte{b.Data, b.PrevHash}, []byte{}) - hash := sha256.Sum256(info) - b.Hash = hash[:] -} - -func CreateBlock(data string, prevHash []byte) *Block { - block := &Block{[]byte{}, []byte(data), prevHash} - block.DeriveHash() - return block -} - -func (chain *BlockChain) AddBlock(data string) { - prevBlock := chain.blocks[len(chain.blocks)-1] - new := CreateBlock(data, prevBlock.Hash) - chain.blocks = append(chain.blocks, new) -} - -func Genesis() *Block { - return CreateBlock("Genesis", []byte{}) -} - -func InitBlockChain() *BlockChain { - return &BlockChain{[]*Block{Genesis()}} -} - -func main() { - chain := InitBlockChain() - - chain.AddBlock("First Block after Genesis") - chain.AddBlock("Second Block after Genesis") - chain.AddBlock("Third Block after Genesis") - - for _, block := range chain.blocks { - fmt.Printf("Previous Hash: %x\n", block.PrevHash) - fmt.Printf("Data in Block: %s\n", block.Data) - fmt.Printf("Hash: %x\n", block.Hash) - } -} +package main + +import ( + "os" + + "github.com/tensor-programming/golang-blockchain/cli" +) + +func main() { + defer os.Exit(0) + + cmd := cli.CommandLine{} + cmd.Run() +} diff --git a/network/network.go b/network/network.go new file mode 100644 index 00000000..e0c97f1e --- /dev/null +++ b/network/network.go @@ -0,0 +1,495 @@ +package network + +import ( + "bytes" + "encoding/gob" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "syscall" + "runtime" + "os" + + "gopkg.in/vrecan/death.v3" + + "github.com/tensor-programming/golang-blockchain/blockchain" +) + +const ( + protocol = "tcp" + version = 1 + commandLength = 12 +) + +var ( + nodeAddress string + mineAddress string + KnownNodes = []string{"localhost:3000"} + blocksInTransit = [][]byte{} + memoryPool = make(map[string]blockchain.Transaction) +) + +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 { + AddrFrom string + Type string + Items [][]byte +} + +type Tx struct { + AddrFrom string + Transaction []byte +} + +type Version struct { + Version int + BestHeight int + AddrFrom string +} + +func CmdToBytes(cmd string) []byte { + var bytes [commandLength]byte + + for i, c := range cmd { + bytes[i] = byte(c) + } + + return bytes[:] +} + +func BytesToCmd(bytes []byte) string { + var cmd []byte + + for _, b := range bytes { + if b != 0x0 { + cmd = append(cmd, b) + } + } + + return fmt.Sprintf("%s", cmd) +} + +func ExtractCmd(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) + payload := GobEncode(nodes) + request := append(CmdToBytes("addr"), payload...) + + SendData(address, request) +} + +func SendBlock(addr string, b *blockchain.Block) { + data := Block{nodeAddress, b.Serialize()} + payload := GobEncode(data) + request := append(CmdToBytes("block"), payload...) + + SendData(addr, request) +} + +func SendData(addr string, data []byte) { + conn, err := net.Dial(protocol, addr) + + if err != nil { + 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() + + _, err = io.Copy(conn, bytes.NewReader(data)) + if err != nil { + log.Panic(err) + } +} + +func SendInv(address, kind string, items [][]byte) { + inventory := Inv{nodeAddress, kind, items} + payload := GobEncode(inventory) + request := append(CmdToBytes("inv"), payload...) + + SendData(address, request) +} + +func SendGetBlocks(address string) { + payload := GobEncode(GetBlocks{nodeAddress}) + request := append(CmdToBytes("getblocks"), payload...) + + SendData(address, request) +} + +func SendGetData(address, kind string, id []byte) { + payload := GobEncode(GetData{nodeAddress, kind, id}) + request := append(CmdToBytes("getdata"), payload...) + + SendData(address, request) +} + +func SendTx(addr string, tnx *blockchain.Transaction) { + data := Tx{nodeAddress, tnx.Serialize()} + payload := GobEncode(data) + request := append(CmdToBytes("tx"), payload...) + + SendData(addr, request) +} + +func SendVersion(addr string, chain *blockchain.BlockChain) { + bestHeight := chain.GetBestHeight() + payload := GobEncode(Version{version, bestHeight, nodeAddress}) + + request := append(CmdToBytes("version"), payload...) + + 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\n", len(KnownNodes)) + RequestBlocks() +} + +func HandleBlock(request []byte, chain *blockchain.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 := blockchain.Deserialize(blockData) + + fmt.Println("Recevied a new block!") + chain.AddBlock(block) + + fmt.Printf("Added block %x\n", block.Hash) + + if len(blocksInTransit) > 0 { + blockHash := blocksInTransit[0] + SendGetData(payload.AddrFrom, "block", blockHash) + + blocksInTransit = blocksInTransit[1:] + } else { + UTXOSet := blockchain.UTXOSet{chain} + UTXOSet.Reindex() + } +} + +func HandleInv(request []byte, chain *blockchain.BlockChain) { + 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 inventory with %d %s\n", len(payload.Items), payload.Type) + + if payload.Type == "block" { + 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 + } + + if payload.Type == "tx" { + txID := payload.Items[0] + + if memoryPool[hex.EncodeToString(txID)].ID == nil { + SendGetData(payload.AddrFrom, "tx", txID) + } + } +} + +func HandleGetBlocks(request []byte, chain *blockchain.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 := chain.GetBlockHashes() + SendInv(payload.AddrFrom, "block", blocks) +} + +func HandleGetData(request []byte, chain *blockchain.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 := chain.GetBlock([]byte(payload.ID)) + if err != nil { + return + } + + SendBlock(payload.AddrFrom, &block) + } + + if payload.Type == "tx" { + txID := hex.EncodeToString(payload.ID) + tx := memoryPool[txID] + + SendTx(payload.AddrFrom, &tx) + } +} + +func HandleTx(request []byte, chain *blockchain.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 := blockchain.DeserializeTransaction(txData) + memoryPool[hex.EncodeToString(tx.ID)] = tx + + fmt.Printf("%s, %d", nodeAddress, len(memoryPool)) + + if nodeAddress == KnownNodes[0] { + for _, node := range KnownNodes { + if node != nodeAddress && node != payload.AddrFrom { + SendInv(node, "tx", [][]byte{tx.ID}) + } + } + } else { + if len(memoryPool) >= 2 && len(mineAddress) > 0 { + MineTx(chain) + } + } +} + +func MineTx(chain *blockchain.BlockChain) { + var txs []*blockchain.Transaction + + for id := range memoryPool { + fmt.Printf("tx: %s\n", memoryPool[id].ID) + tx := memoryPool[id] + if chain.VerifyTransaction(&tx) { + txs = append(txs, &tx) + } + } + + if len(txs) == 0 { + fmt.Println("All Transactions are invalid") + return + } + + cbTx := blockchain.CoinbaseTx(mineAddress, "") + txs = append(txs, cbTx) + + newBlock := chain.MineBlock(txs) + UTXOSet := blockchain.UTXOSet{chain} + UTXOSet.Reindex() + + fmt.Println("New Block mined") + + for _, tx := range txs { + txID := hex.EncodeToString(tx.ID) + delete(memoryPool, txID) + } + + for _, node := range KnownNodes { + if node != nodeAddress { + SendInv(node, "block", [][]byte{newBlock.Hash}) + } + } + + if len(memoryPool) > 0 { + MineTx(chain) + } +} + +func HandleVersion(request []byte, chain *blockchain.BlockChain) { + var buff bytes.Buffer + var payload Version + + buff.Write(request[commandLength:]) + dec := gob.NewDecoder(&buff) + err := dec.Decode(&payload) + if err != nil { + log.Panic(err) + } + + bestHeight := chain.GetBestHeight() + otherHeight := payload.BestHeight + + if bestHeight < otherHeight { + SendGetBlocks(payload.AddrFrom) + } else if bestHeight > otherHeight { + SendVersion(payload.AddrFrom, chain) + } + + if !NodeIsKnown(payload.AddrFrom) { + KnownNodes = append(KnownNodes, payload.AddrFrom) + } +} + +func HandleConnection(conn net.Conn, chain *blockchain.BlockChain) { + req, err := ioutil.ReadAll(conn) + defer conn.Close() + + if err != nil { + log.Panic(err) + } + command := BytesToCmd(req[:commandLength]) + fmt.Printf("Received %s command\n", command) + + switch command { + case "addr": + HandleAddr(req) + case "block": + HandleBlock(req, chain) + case "inv": + HandleInv(req, chain) + case "getblocks": + HandleGetBlocks(req, chain) + case "getdata": + HandleGetData(req, chain) + case "tx": + HandleTx(req, chain) + case "version": + HandleVersion(req, chain) + default: + fmt.Println("Unknown command") + } + +} + +func StartServer(nodeID, minerAddress string) { + nodeAddress = fmt.Sprintf("localhost:%s", nodeID) + mineAddress = minerAddress + ln, err := net.Listen(protocol, nodeAddress) + if err != nil { + log.Panic(err) + } + defer ln.Close() + + chain := blockchain.ContinueBlockChain(nodeID) + defer chain.Database.Close() + go CloseDB(chain) + + if nodeAddress != KnownNodes[0] { + SendVersion(KnownNodes[0], chain) + } + for { + conn, err := ln.Accept() + if err != nil { + log.Panic(err) + } + go HandleConnection(conn, chain) + + } +} + +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() +} + +func NodeIsKnown(addr string) bool { + for _, node := range KnownNodes { + if node == addr { + return true + } + } + + return false +} + +func CloseDB(chain *blockchain.BlockChain) { + d := death.NewDeath(syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + + d.WaitForDeathWithFunc(func() { + defer os.Exit(1) + defer runtime.Goexit() + chain.Database.Close() + }) +} \ No newline at end of file diff --git a/wallet.mmd b/wallet.mmd new file mode 100644 index 00000000..c42e9cec --- /dev/null +++ b/wallet.mmd @@ -0,0 +1,22 @@ +graph TD + +subgraph Merkle Tree +a(Merkle Root) --> b +a --> c(Sha256 Branch A + B) +c --> d(Branch A: sha256 tx1) +c --> e(Branch B: sha256 tx2) +b(Sha256 Branch C + D) --> f(Branch C: sha256 tx3) +b --> g(Branch D: sha256 tx4) + +end +d --> tx1 +e --> tx2 +f --> tx3 +g --> tx4 + +subgraph Serialized Transactions +tx1(Transaction One) +tx2(Transaction Two) +tx3(Transaction Three) +tx4(Transaction Four) +end \ No newline at end of file diff --git a/wallet/utils.go b/wallet/utils.go new file mode 100644 index 00000000..9ef207f6 --- /dev/null +++ b/wallet/utils.go @@ -0,0 +1,24 @@ +package wallet + +import ( + "log" + + "github.com/mr-tron/base58" +) + +func Base58Encode(input []byte) []byte { + encode := base58.Encode(input) + + return []byte(encode) +} + +func Base58Decode(input []byte) []byte { + decode, err := base58.Decode(string(input[:])) + if err != nil { + log.Panic(err) + } + + return decode +} + +// 0 O l I + / diff --git a/wallet/wallet.go b/wallet/wallet.go new file mode 100644 index 00000000..c51888f7 --- /dev/null +++ b/wallet/wallet.go @@ -0,0 +1,84 @@ +package wallet + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "log" + + "golang.org/x/crypto/ripemd160" +) + +const ( + checksumLength = 4 + version = byte(0x00) +) + +type Wallet struct { + PrivateKey ecdsa.PrivateKey + PublicKey []byte +} + +func (w Wallet) Address() []byte { + pubHash := PublicKeyHash(w.PublicKey) + + versionedHash := append([]byte{version}, pubHash...) + checksum := Checksum(versionedHash) + + fullHash := append(versionedHash, checksum...) + address := Base58Encode(fullHash) + + return address +} + +func NewKeyPair() (ecdsa.PrivateKey, []byte) { + curve := elliptic.P256() + + private, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + log.Panic(err) + } + + pub := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) + return *private, pub +} + +func MakeWallet() *Wallet { + private, public := NewKeyPair() + wallet := Wallet{private, public} + + return &wallet +} + +func PublicKeyHash(pubKey []byte) []byte { + pubHash := sha256.Sum256(pubKey) + + hasher := ripemd160.New() + _, err := hasher.Write(pubHash[:]) + if err != nil { + log.Panic(err) + } + + publicRipMD := hasher.Sum(nil) + + return publicRipMD +} + +func Checksum(payload []byte) []byte { + firstHash := sha256.Sum256(payload) + secondHash := sha256.Sum256(firstHash[:]) + + return secondHash[:checksumLength] +} + +func ValidateAddress(address string) bool { + pubKeyHash := Base58Decode([]byte(address)) + actualChecksum := pubKeyHash[len(pubKeyHash)-checksumLength:] + version := pubKeyHash[0] + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-checksumLength] + targetChecksum := Checksum(append([]byte{version}, pubKeyHash...)) + + return bytes.Compare(actualChecksum, targetChecksum) == 0 +} diff --git a/wallet/wallets.go b/wallet/wallets.go new file mode 100644 index 00000000..f7b5288d --- /dev/null +++ b/wallet/wallets.go @@ -0,0 +1,92 @@ +package wallet + +import ( + "bytes" + "crypto/elliptic" + "encoding/gob" + "fmt" + "io/ioutil" + "log" + "os" +) + +const walletFile = "./tmp/wallets_%s.data" + +type Wallets struct { + Wallets map[string]*Wallet +} + +func CreateWallets(nodeId string) (*Wallets, error) { + wallets := Wallets{} + wallets.Wallets = make(map[string]*Wallet) + + err := wallets.LoadFile(nodeId) + + return &wallets, err +} + +func (ws *Wallets) AddWallet() string { + wallet := MakeWallet() + address := fmt.Sprintf("%s", wallet.Address()) + + ws.Wallets[address] = wallet + + return address +} + +func (ws *Wallets) GetAllAddresses() []string { + var addresses []string + + for address := range ws.Wallets { + addresses = append(addresses, address) + } + + return addresses +} + +func (ws Wallets) GetWallet(address string) Wallet { + return *ws.Wallets[address] +} + +func (ws *Wallets) LoadFile(nodeId string) error { + walletFile := fmt.Sprintf(walletFile, nodeId) + if _, err := os.Stat(walletFile); os.IsNotExist(err) { + return err + } + + var wallets Wallets + + fileContent, err := ioutil.ReadFile(walletFile) + if err != nil { + return err + } + + gob.Register(elliptic.P256()) + decoder := gob.NewDecoder(bytes.NewReader(fileContent)) + err = decoder.Decode(&wallets) + if err != nil { + return err + } + + ws.Wallets = wallets.Wallets + + return nil +} + +func (ws *Wallets) SaveFile(nodeId string) { + var content bytes.Buffer + walletFile := fmt.Sprintf(walletFile, nodeId) + + 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) + } +}