From 9e6d5d4def96000163e079138f347ba7d73bfb71 Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Thu, 1 Dec 2022 16:52:37 -0800 Subject: [PATCH] transactions --- blockchain/block.go | 29 +++++-- blockchain/blockchain.go | 153 ++++++++++++++++++++++++++++++++----- blockchain/proof.go | 2 +- blockchain/transaction.go | 97 ++++++++++++++++++++++++ main.go | 154 +++++++++++++++++++++++++------------- 5 files changed, 356 insertions(+), 79 deletions(-) create mode 100644 blockchain/transaction.go diff --git a/blockchain/block.go b/blockchain/block.go index 809e981..f2d245a 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -2,19 +2,32 @@ package blockchain import ( "bytes" + "crypto/sha256" "encoding/gob" "log" ) type Block struct { - Hash []byte - Data []byte - PrevHash []byte - Nonce int + Hash []byte + Transactions []*Transaction + PrevHash []byte + Nonce int } -func CreateBlock(data string, prevHash []byte) *Block { - block := &Block{[]byte{}, []byte(data), prevHash, 0} +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[:] +} + +func CreateBlock(txs []*Transaction, prevHash []byte) *Block { + block := &Block{[]byte{}, txs, prevHash, 0} pow := NewProof(block) nonce, hash := pow.Run() @@ -24,8 +37,8 @@ func CreateBlock(data string, prevHash []byte) *Block { return block } -func Genesis() *Block { - return CreateBlock("Genesis", []byte{}) +func Genesis(coinbase *Transaction) *Block { + return CreateBlock([]*Transaction{coinbase}, []byte{}) } func (b *Block) Serialize() []byte { diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 846c287..54e8e13 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -1,13 +1,18 @@ package blockchain import ( + "encoding/hex" "fmt" + "os" + "runtime" "github.com/dgraph-io/badger" ) const ( - dbPath = "./tmp/blocks" + dbPath = "./tmp/blocks" + dbFile = "./tmp/blocks/MANIFEST" + genesisData = "First Transaction from Genesis" ) type BlockChain struct { @@ -20,7 +25,20 @@ type BlockChainIterator struct { Database *badger.DB } -func InitBlockChain() *BlockChain { +func DBexists() bool { + if _, err := os.Stat(dbFile); os.IsNotExist(err) { + return false + } + + return true +} + +func ContinueBlockChain(address string) *BlockChain { + if !DBexists() { + fmt.Println("No existing blockchain found, create one!") + runtime.Goexit() + } + var lastHash []byte opts := badger.DefaultOptions(dbPath) @@ -29,23 +47,43 @@ func InitBlockChain() *BlockChain { Handle(err) err = db.Update(func(txn *badger.Txn) error { - if _, err := txn.Get([]byte("lh")); err == badger.ErrKeyNotFound { - fmt.Println("No existing blockchain found") - genesis := Genesis() - fmt.Println("Genesis proved") - err = txn.Set(genesis.Hash, genesis.Serialize()) - Handle(err) - err = txn.Set([]byte("lh"), genesis.Hash) + item, err := txn.Get([]byte("lh")) + Handle(err) + lastHash, err = item.ValueCopy(nil) - lastHash = genesis.Hash + return err + }) + Handle(err) - return err - } else { - item, err := txn.Get([]byte("lh")) - Handle(err) - lastHash, err = item.ValueCopy(nil) - return err - } + chain := BlockChain{lastHash, db} + + return &chain +} + +func InitBlockChain(address string) *BlockChain { + var lastHash []byte + + if DBexists() { + fmt.Println("Blockchain already exists") + runtime.Goexit() + } + + opts := badger.DefaultOptions(dbPath) + opts.Logger = nil + db, err := badger.Open(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) @@ -54,7 +92,7 @@ func InitBlockChain() *BlockChain { return &blockchain } -func (chain *BlockChain) AddBlock(data string) { +func (chain *BlockChain) AddBlock(transactions []*Transaction) { var lastHash []byte err := chain.Database.View(func(txn *badger.Txn) error { @@ -66,7 +104,7 @@ func (chain *BlockChain) AddBlock(data string) { }) Handle(err) - newBlock := CreateBlock(data, lastHash) + newBlock := CreateBlock(transactions, lastHash) err = chain.Database.Update(func(txn *badger.Txn) error { err := txn.Set(newBlock.Hash, newBlock.Serialize()) @@ -103,3 +141,80 @@ func (iter *BlockChainIterator) Next() *Block { return block } + +func (chain *BlockChain) FindUnspentTransactions(address string) []Transaction { + var unspentTxs []Transaction + + 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 + } + } + } + if out.CanBeUnlocked(address) { + unspentTxs = append(unspentTxs, *tx) + } + } + if !tx.IsCoinbase() { + for _, in := range tx.Inputs { + if in.CanUnlock(address) { + inTxID := hex.EncodeToString(in.ID) + spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Out) + } + } + } + } + + if len(block.PrevHash) == 0 { + break + } + } + return unspentTxs +} + +func (chain *BlockChain) FindUTXO(address string) []TxOutput { + var UTXOs []TxOutput + unspentTransactions := chain.FindUnspentTransactions(address) + + for _, tx := range unspentTransactions { + UTXOs = append(UTXOs, tx.Outputs...) + } + return UTXOs +} + +func (chain *BlockChain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { + unspentOuts := make(map[string][]int) + unspentTxs := chain.FindUnspentTransactions(address) + accumulated := 0 + +Work: + for _, tx := range unspentTxs { + txID := hex.EncodeToString(tx.ID) + + for outIdx, out := range tx.Outputs { + if out.CanBeUnlocked(address) && accumulated < amount { + accumulated += out.Value + unspentOuts[txID] = append(unspentOuts[txID], outIdx) + + if accumulated >= amount { + break Work + } + } + } + } + + return accumulated, unspentOuts +} diff --git a/blockchain/proof.go b/blockchain/proof.go index d1d1e9a..f372b60 100644 --- a/blockchain/proof.go +++ b/blockchain/proof.go @@ -41,7 +41,7 @@ func (pow *ProofOfWork) InitData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.Block.PrevHash, - pow.Block.Data, + pow.Block.HashTransactions(), ToHex(int64(nonce)), ToHex(int64(Difficulty)), }, diff --git a/blockchain/transaction.go b/blockchain/transaction.go new file mode 100644 index 0000000..a2afbaf --- /dev/null +++ b/blockchain/transaction.go @@ -0,0 +1,97 @@ +package blockchain + +import ( + "bytes" + "crypto/sha256" + "encoding/gob" + "encoding/hex" + "fmt" + "log" +) + +type Transaction struct { + ID []byte + Inputs []TxInput + Outputs []TxOutput +} + +type TxOutput struct { + Value int + PubKey string +} + +type TxInput struct { + ID []byte + Out int + Sig string +} + +func (tx *Transaction) SetID() { + var encoded bytes.Buffer + var hash [32]byte + + encode := gob.NewEncoder(&encoded) + err := encode.Encode(tx) + Handle(err) + + hash = sha256.Sum256(encoded.Bytes()) + tx.ID = hash[:] +} + +func CoinbaseTx(to, data string) *Transaction { + if data == "" { + data = fmt.Sprintf("COints to %s", to) + } + + txin := TxInput{[]byte{}, -1, data} + txout := TxOutput{100, to} + + tx := Transaction{nil, []TxInput{txin}, []TxOutput{txout}} + tx.SetID() + + return &tx +} + +func NewTransaction(from, to string, amount int, chain *BlockChain) *Transaction { + var inputs []TxInput + var outputs []TxOutput + + acc, validOutputs := chain.FindSpendableOutputs(from, 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, from} + inputs = append(inputs, input) + } + } + + outputs = append(outputs, TxOutput{amount, to}) + + if acc > amount { + outputs = append(outputs, TxOutput{acc - amount, from}) + } + + tx := Transaction{nil, inputs, outputs} + tx.SetID() + + return &tx +} + +func (tx *Transaction) IsCoinbase() bool { + return len(tx.Inputs) == 1 && len(tx.Inputs[0].ID) == 0 && tx.Inputs[0].Out == -1 +} + +func (in *TxInput) CanUnlock(data string) bool { + return in.Sig == data +} + +func (out *TxOutput) CanBeUnlocked(data string) bool { + return out.PubKey == data +} diff --git a/main.go b/main.go index 0a98c1b..a6cdd0b 100644 --- a/main.go +++ b/main.go @@ -10,14 +10,14 @@ import ( "github.com/SowinskiBraeden/golang-blockchain/blockchain" ) -type CommandLine struct { - blockchain *blockchain.BlockChain -} +type CommandLine struct{} func (cli *CommandLine) printUsage() { fmt.Println("Usage:") - fmt.Println(" add -block BLOCK_DATA - add a block to the chain") - fmt.Println(" print - Prints the blocks in the chain") + fmt.Println(" getbalance -address ADDRESS - get the balance for an address") + fmt.Println(" createblockchain -address ADDRESS create a blockchain") + fmt.Println(" printchain - Prints the blocks in the chain") + fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send amount to an address") } func (cli *CommandLine) validateArgs() { @@ -27,54 +27,15 @@ func (cli *CommandLine) validateArgs() { } } -func (cli *CommandLine) addBlock(data string) { - cli.blockchain.AddBlock(data) - fmt.Println("Added Block!") -} - -func (cli *CommandLine) run() { - cli.validateArgs() - - addBlockCmd := flag.NewFlagSet("add", flag.ExitOnError) - printChainCmd := flag.NewFlagSet("print", flag.ExitOnError) - addBlockData := addBlockCmd.String("block", "", "Block data") - - switch os.Args[1] { - case "add": - err := addBlockCmd.Parse(os.Args[2:]) - blockchain.Handle(err) - - case "print": - err := printChainCmd.Parse(os.Args[2:]) - blockchain.Handle(err) - - default: - cli.printUsage() - runtime.Goexit() - } - - if addBlockCmd.Parsed() { - if *addBlockData == "" { - addBlockCmd.Usage() - runtime.Goexit() - } - - cli.addBlock(*addBlockData) - } - - if printChainCmd.Parsed() { - cli.printChain() - } -} - func (cli *CommandLine) printChain() { - iter := cli.blockchain.Iterator() + chain := blockchain.ContinueBlockChain("") + defer chain.Database.Close() + iter := chain.Iterator() for { block := iter.Next() fmt.Printf("Prev. Hash: %x\n", block.PrevHash) - fmt.Printf("Data: %s\n", block.Data) fmt.Printf("Hash: %x\n", block.Hash) pow := blockchain.NewProof(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) @@ -86,11 +47,102 @@ func (cli *CommandLine) printChain() { } } -func main() { - defer os.Exit(0) - chain := blockchain.InitBlockChain() +func (cli *CommandLine) createBlockChain(address string) { + chain := blockchain.InitBlockChain(address) + chain.Database.Close() + fmt.Println("Finished!") +} + +func (cli *CommandLine) getBalance(address string) { + chain := blockchain.ContinueBlockChain(address) defer chain.Database.Close() - cli := CommandLine{chain} + balance := 0 + UTXOs := chain.FindUTXO(address) + + 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) { + chain := blockchain.ContinueBlockChain(from) + defer chain.Database.Close() + + tx := blockchain.NewTransaction(from, to, amount, chain) + chain.AddBlock([]*blockchain.Transaction{tx}) + fmt.Println("Success!") +} + +func (cli *CommandLine) 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 of") + createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to create a blockchain 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:]) + blockchain.Handle(err) + + case "createblockchain": + err := createBlockchainCmd.Parse(os.Args[2:]) + blockchain.Handle(err) + + case "printchain": + err := printChainCmd.Parse(os.Args[2:]) + blockchain.Handle(err) + + case "send": + err := sendCmd.Parse(os.Args[4:]) + blockchain.Handle(err) + + default: + cli.printUsage() + runtime.Goexit() + } + + if getBalanceCmd.Parsed() { + if *getBalanceAddress == "" { + getBalanceCmd.Usage() + runtime.Goexit() + } + cli.getBalance(*getBalanceAddress) + } + + if createBlockchainCmd.Parsed() { + if *createBlockchainAddress == "" { + createBlockchainCmd.Usage() + runtime.Goexit() + } + cli.createBlockChain(*createBlockchainAddress) + } + + if printChainCmd.Parsed() { + cli.printChain() + } + + if sendCmd.Parsed() { + if *sendFrom == "" || *sendTo == "" || *sendAmount == 0 { + sendCmd.Usage() + runtime.Goexit() + } + cli.send(*sendFrom, *sendTo, *sendAmount) + } +} + +func main() { + defer os.Exit(0) + cli := CommandLine{} cli.run() }