diff --git a/README.md b/README.md index 94d9b78..94180b9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # go-blockchain +First, set **NODE_ID** to 3000 (**export NODE_ID=3000**) in the first terminal window. I’ll use badges like **NODE 3000** or **NODE 3001** before next paragraphs, for you to know what node to perform actions on. + **创建钱包** ``` @@ -35,12 +37,24 @@ **转账:** ``` -> ./go-blockchain send -from 1321QKXdSdYXLPZiGxC9NS6HjTuDEFR119 -to 1546AtPEFt5uGyvhbRDLUoqLTy5zfUZhsY -amount 1 +> ./go-blockchain send -from 1321QKXdSdYXLPZiGxC9NS6HjTuDEFR119 -to 1546AtPEFt5uGyvhbRDLUoqLTy5zfUZhsY -amount 1 -mine > 00000013eda8154a87b0ee7ad5bf54f63cd6a0a2de89ce4bbc9f08313cf2c62a > > Success! ``` +**启动节点** + +``` +> ./go-blockchain startnode +``` + +**设置挖矿节点** + +``` +> ./go-blockchain startnode -miner 14kiRgXmK5tDq3MzoM33Tz3wLZSTbBK9TK +``` + **打印区块链:** ``` diff --git a/cli/cli.go b/cli/cli.go index e1282a6..317991c 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "log" + "os" ) @@ -18,7 +19,8 @@ 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 -miner ADDRESS - Start a node with ID specified in NODE_ID env. var. -miner enables mining") } func (cli *CLI) validateArgs() { @@ -32,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) @@ -39,12 +47,15 @@ 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") + 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": @@ -72,13 +83,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) } @@ -92,7 +108,7 @@ func (cli *CLI) Run() { getBalanceCmd.Usage() os.Exit(1) } - cli.getBalance(*getBalanceAddress) + cli.getBalance(*getBalanceAddress, nodeID) } if createBlockchainCmd.Parsed() { @@ -100,23 +116,23 @@ func (cli *CLI) Run() { createBlockchainCmd.Usage() os.Exit(1) } - cli.createBlockchain(*createBlockchainAddress) + cli.createBlockchain(*createBlockchainAddress, nodeID) } if createWalletCmd.Parsed() { - cli.createWallet() + cli.createWallet(nodeID) } if listAddressesCmd.Parsed() { - cli.listAddresses() + cli.listAddresses(nodeID) } if printChainCmd.Parsed() { - cli.printChain() + cli.printChain(nodeID) } if reindexUTXOCmd.Parsed() { - cli.reindexUTXO() + cli.reindexUTXO(nodeID) } if sendCmd.Parsed() { @@ -125,6 +141,15 @@ func (cli *CLI) Run() { os.Exit(1) } - cli.send(*sendFrom, *sendTo, *sendAmount) + cli.send(*sendFrom, *sendTo, *sendAmount, nodeID, *sendMine) + } + + if startNodeCmd.Parsed() { + nodeID := os.Getenv("NODE_ID") + if nodeID == "" { + startNodeCmd.Usage() + os.Exit(1) + } + cli.startNode(nodeID, *startNodeMiner) } } diff --git a/cli/createblockchain.go b/cli/createblockchain.go index 076d5c3..772fd2e 100644 --- a/cli/createblockchain.go +++ b/cli/createblockchain.go @@ -6,11 +6,15 @@ import ( "log" ) -func (cli *CLI) createBlockchain(address string) { +func (cli *CLI) createBlockchain(address, nodeID string) { if !core.ValidateAddress(address) { log.Panic("ERROR: Address is not valid") } - bc := core.CreateBlockchain(address) - bc.DB.Close() + bc := core.CreateBlockchain(address, nodeID) + defer bc.DB.Close() + + UTXOSet := core.UTXOSet{bc} + UTXOSet.Reindex() + fmt.Println("Done!") } diff --git a/cli/createwallet.go b/cli/createwallet.go index 2737898..1e75bc9 100644 --- a/cli/createwallet.go +++ b/cli/createwallet.go @@ -5,10 +5,10 @@ import ( "github.com/sasaxie/go-blockchain/core" ) -func (cli *CLI) createWallet() { - wallets, _ := core.NewWallets() +func (cli *CLI) createWallet(nodeID string) { + wallets, _ := core.NewWallets(nodeID) address := wallets.CreateWallet() - wallets.SaveToFile() + wallets.SaveToFile(nodeID) fmt.Printf("Your new address: %s\n", address) } diff --git a/cli/getbalance.go b/cli/getbalance.go index 51992a2..8eff966 100644 --- a/cli/getbalance.go +++ b/cli/getbalance.go @@ -7,11 +7,11 @@ import ( "log" ) -func (cli *CLI) getBalance(address string) { +func (cli *CLI) getBalance(address, nodeID string) { if !core.ValidateAddress(address) { log.Panic("ERROR: Address is not valid") } - bc := core.NewBlockchain() + bc := core.NewBlockchain(nodeID) UTXOSet := core.UTXOSet{bc} defer bc.DB.Close() diff --git a/cli/listaddress.go b/cli/listaddress.go index a1d3d7a..b2de601 100644 --- a/cli/listaddress.go +++ b/cli/listaddress.go @@ -6,8 +6,8 @@ import ( "log" ) -func (cli *CLI) listAddresses() { - wallets, err := core.NewWallets() +func (cli *CLI) listAddresses(nodeID string) { + wallets, err := core.NewWallets(nodeID) if err != nil { log.Panic(err) } diff --git a/cli/printchain.go b/cli/printchain.go index 8948dd4..09f0365 100644 --- a/cli/printchain.go +++ b/cli/printchain.go @@ -6,8 +6,8 @@ import ( "strconv" ) -func (cli *CLI) printChain() { - bc := core.NewBlockchain() +func (cli *CLI) printChain(nodeID string) { + bc := core.NewBlockchain(nodeID) defer bc.DB.Close() bci := bc.Iterator() @@ -16,6 +16,7 @@ func (cli *CLI) printChain() { 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 := core.NewProofOfWork(block) fmt.Printf("PoW: %s\n\n", strconv.FormatBool(pow.Validate())) diff --git a/cli/reindexutxo.go b/cli/reindexutxo.go index 881f69e..f901a03 100644 --- a/cli/reindexutxo.go +++ b/cli/reindexutxo.go @@ -5,8 +5,8 @@ import ( "github.com/sasaxie/go-blockchain/core" ) -func (cli *CLI) reindexUTXO() { - bc := core.NewBlockchain() +func (cli *CLI) reindexUTXO(nodeID string) { + bc := core.NewBlockchain(nodeID) UTXOSet := core.UTXOSet{bc} UTXOSet.Reindex() diff --git a/cli/send.go b/cli/send.go index c2d0384..1d7c806 100644 --- a/cli/send.go +++ b/cli/send.go @@ -6,7 +6,7 @@ import ( "log" ) -func (cli *CLI) send(from, to string, amount int) { +func (cli *CLI) send(from, to string, amount int, nodeID string, mineNow bool) { if !core.ValidateAddress(from) { log.Panic("ERROR: Sender address is not valid") } @@ -14,15 +14,27 @@ func (cli *CLI) send(from, to string, amount int) { log.Panic("ERROR: Recipient address is not valid") } - bc := core.NewBlockchain() + bc := core.NewBlockchain(nodeID) UTXOSet := core.UTXOSet{bc} defer bc.DB.Close() - tx := core.NewUTXOTransaction(from, to, amount, &UTXOSet) - cbTx := core.NewCoinbaseTX(from, "") - txs := []*core.Transaction{cbTx, tx} + wallets, err := core.NewWallets(nodeID) + if err != nil { + log.Panic(err) + } + wallet := wallets.GetWallet(from) + + tx := core.NewUTXOTransaction(&wallet, to, amount, &UTXOSet) + + if mineNow { + cbTx := core.NewCoinbaseTX(from, "") + txs := []*core.Transaction{cbTx, tx} + + newBlock := bc.MineBlock(txs) + UTXOSet.Update(newBlock) + } else { + core.SendTx(core.KnownNodes[0], tx) + } - newBlock := bc.MineBlock(txs) - UTXOSet.Update(newBlock) fmt.Println("Success!") } diff --git a/cli/startnode.go b/cli/startnode.go new file mode 100644 index 0000000..76f86a2 --- /dev/null +++ b/cli/startnode.go @@ -0,0 +1,19 @@ +package cli + +import ( + "fmt" + "github.com/sasaxie/go-blockchain/core" + "log" +) + +func (cli *CLI) startNode(nodeID, minerAddress string) { + fmt.Printf("Starting node %s\n", nodeID) + if len(minerAddress) > 0 { + if core.ValidateAddress(minerAddress) { + fmt.Println("Mining is on. Address to receive rewards: ", minerAddress) + } else { + log.Panic("Wrong miner address!") + } + } + core.StartServer(nodeID, minerAddress) +} diff --git a/core/block.go b/core/block.go index 66b5d56..421262b 100644 --- a/core/block.go +++ b/core/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/core/blockchain.go b/core/blockchain.go index eadbb6d..e140025 100644 --- a/core/blockchain.go +++ b/core/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) } @@ -95,6 +97,41 @@ func NewBlockchain() *Blockchain { return &bc } +// 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 { + 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) + } + bc.Tip = block.Hash + } + + 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() @@ -167,11 +204,74 @@ 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 + + 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 + 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 + 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") } @@ -181,13 +281,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)) @@ -246,7 +351,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/core/proofofwork.go b/core/proofofwork.go index 6a522be..344a4d1 100644 --- a/core/proofofwork.go +++ b/core/proofofwork.go @@ -9,16 +9,21 @@ import ( "math/big" ) -const targetBits = 24 +var ( + maxNonce = math.MaxInt64 +) + +const targetBits = 16 +// 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 { target := big.NewInt(1) - // targetBits 越大,左移的位数越少,开头的零就越多,难度就越大 target.Lsh(target, uint(256-targetBits)) pow := &ProofOfWork{b, target} @@ -26,7 +31,6 @@ func NewProofOfWork(b *Block) *ProofOfWork { return pow } -// 如果增加新字段,需要更新这个方法的内容 func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ @@ -42,20 +46,20 @@ func (pow *ProofOfWork) prepareData(nonce int) []byte { return data } +// 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 a new block") - for nonce < math.MaxInt64 { + for nonce < maxNonce { data := pow.prepareData(nonce) hash = sha256.Sum256(data) fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) - // hashInt < pow.target == -1 if hashInt.Cmp(pow.target) == -1 { break } else { @@ -67,6 +71,7 @@ func (pow *ProofOfWork) Run() (int, []byte) { return nonce, hash[:] } +// Validate validates block's PoW func (pow *ProofOfWork) Validate() bool { var hashInt big.Int diff --git a/core/server.go b/core/server.go new file mode 100644 index 0000000..7ee2fbe --- /dev/null +++ b/core/server.go @@ -0,0 +1,465 @@ +package core + +import ( + "bytes" + "encoding/gob" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "log" + "net" +) + +const protocol = "tcp" +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) + +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 { + AddFrom string + Transaction []byte +} + +type verzion struct { + Version int + BestHeight 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 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(commandToBytes("addr"), payload...) + + 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 { + 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(commandToBytes("inv"), payload...) + + sendData(address, request) +} + +func sendGetBlocks(address string) { + payload := gobEncode(getblocks{nodeAddress}) + request := append(commandToBytes("getblocks"), payload...) + + 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 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}) + + request := append(commandToBytes("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 now!\n", len(KnownNodes)) + requestBlocks() +} + +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!") + bc.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 := UTXOSet{bc} + UTXOSet.Reindex() + } +} + +func handleInv(request []byte, bc *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 mempool[hex.EncodeToString(txID)].ID == nil { + sendGetData(payload.AddrFrom, "tx", txID) + } + } +} + +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, "block", 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) + } + + 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 && node != payload.AddFrom { + sendInv(node, "tx", [][]byte{tx.ID}) + } + } + } else { + if len(mempool) >= 2 && len(miningAddress) > 0 { + MineTransactions: + var txs []*Transaction + + for id := range mempool { + tx := mempool[id] + if bc.VerifyTransaction(&tx) { + txs = append(txs, &tx) + } + } + + if len(txs) == 0 { + fmt.Println("All transactions are invalid! Waiting for new ones...") + return + } + + cbTx := NewCoinbaseTX(miningAddress, "") + txs = append(txs, cbTx) + + newBlock := bc.MineBlock(txs) + UTXOSet := UTXOSet{bc} + UTXOSet.Reindex() + + 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}) + } + } + + if len(mempool) > 0 { + goto MineTransactions + } + } + } +} + +func handleVersion(request []byte, bc *Blockchain) { + 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) + } + + myBestHeight := bc.GetBestHeight() + foreignerBestHeight := payload.BestHeight + + if myBestHeight < foreignerBestHeight { + sendGetBlocks(payload.AddrFrom) + } else if myBestHeight > foreignerBestHeight { + sendVersion(payload.AddrFrom, bc) + } + + // sendAddr(payload.AddrFrom) + if !nodeIsKnown(payload.AddrFrom) { + KnownNodes = append(KnownNodes, payload.AddrFrom) + } +} + +func handleConnection(conn net.Conn, bc *Blockchain) { + request, err := ioutil.ReadAll(conn) + if err != nil { + log.Panic(err) + } + command := bytesToCommand(request[:commandLength]) + fmt.Printf("Received %s command\n", command) + + switch command { + case "addr": + handleAddr(request) + case "block": + handleBlock(request, bc) + case "inv": + handleInv(request, bc) + case "getblocks": + handleGetBlocks(request, bc) + case "getdata": + handleGetData(request, bc) + case "tx": + handleTx(request, bc) + case "version": + handleVersion(request, bc) + default: + fmt.Println("Unknown command!") + } + + conn.Close() +} + +// StartServer starts a node +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) + } + defer ln.Close() + + bc := NewBlockchain(nodeID) + + if nodeAddress != KnownNodes[0] { + sendVersion(KnownNodes[0], bc) + } + + for { + conn, err := ln.Accept() + if err != nil { + log.Panic(err) + } + go handleConnection(conn, bc) + } +} + +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 +} diff --git a/core/transaction.go b/core/transaction.go index cc510a7..d3462df 100644 --- a/core/transaction.go +++ b/core/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 @@ -191,15 +193,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 +218,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 @@ -232,3 +230,16 @@ func NewUTXOTransaction(from, to string, amount int, UTXOSet *UTXOSet) *Transact 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 +} diff --git a/core/wallet.go b/core/wallet.go index e935a1d..877fdab 100644 --- a/core/wallet.go +++ b/core/wallet.go @@ -6,13 +6,13 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/sha256" + "log" + "github.com/sasaxie/go-blockchain/utils" "golang.org/x/crypto/ripemd160" - "log" ) const version = byte(0x00) -const walletFile = "wallet.dat" const addressChecksumLen = 4 // Wallet stores private and public keys diff --git a/core/wallets.go b/core/wallets.go index 7722e47..96d0df5 100644 --- a/core/wallets.go +++ b/core/wallets.go @@ -10,17 +10,19 @@ 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) - err := wallets.LoadFromFile() + err := wallets.LoadFromFile(nodeID) return &wallets, err } @@ -52,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 } @@ -76,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())