package main import ( "bytes" "crypto/ecdsa" "encoding/hex" "errors" "fmt" "log" "os" "github.com/boltdb/bolt" ) const dbFile = "blockchain_%s.db" const blocksBucket = "blocks" const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" // Blockchain implements interactions with a DB type Blockchain struct { tip []byte db *bolt.DB } // CreateBlockchain creates a new blockchain DB func CreateBlockchain(address, nodeID string) *Blockchain { dbFile := fmt.Sprintf(dbFile, nodeID) if dbExists(dbFile) { fmt.Println("Blockchain already exists.") os.Exit(1) } var tip []byte cbtx := NewCoinbaseTX(address, genesisCoinbaseData) genesis := NewGenesisBlock(cbtx) db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Panic(err) } err = db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte(blocksBucket)) if err != nil { log.Panic(err) } err = b.Put(genesis.Hash, genesis.Serialize()) if err != nil { log.Panic(err) } err = b.Put([]byte("l"), genesis.Hash) if err != nil { log.Panic(err) } tip = genesis.Hash return nil }) if err != nil { log.Panic(err) } bc := Blockchain{tip, db} return &bc } // NewBlockchain creates a new Blockchain with genesis Block 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) } var tip []byte db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Panic(err) } err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) tip = b.Get([]byte("l")) return nil }) if err != nil { log.Panic(err) } bc := Blockchain{tip, db} return &bc } // Reset removes all blockchain data func (bc *Blockchain) Reset() { } // FindTransaction finds a transaction by its ID func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { bci := bc.Iterator() for { block := bci.Next() for _, tx := range block.Transactions { if bytes.Compare(tx.ID, ID) == 0 { return *tx, nil } } if len(block.PrevBlockHash) == 0 { break } } return Transaction{}, errors.New("Transaction is not found") } // FindUTXO finds all unspent transaction outputs and returns transactions with spent outputs removed func (bc *Blockchain) FindUTXO() map[string]TXOutputs { UTXO := make(map[string]TXOutputs) spentTXOs := make(map[string][]int) bci := bc.Iterator() for { block := bci.Next() for _, tx := range block.Transactions { txID := hex.EncodeToString(tx.ID) Outputs: for outIdx, out := range tx.Vout { // Was the output spent? if spentTXOs[txID] != nil { for _, spentOutIdx := range spentTXOs[txID] { if spentOutIdx == outIdx { continue Outputs } } } outs := UTXO[txID] outs.Outputs = append(outs.Outputs, out) UTXO[txID] = outs } if tx.IsCoinbase() == false { for _, in := range tx.Vin { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } } } if len(block.PrevBlockHash) == 0 { break } } return UTXO } // Iterator returns a BlockchainIterat func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} return bci } // GetBlock finds a block by its hash and returns it func (bc *Blockchain) GetBlock(blockHash []byte) (Block, error) { var block Block err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) blockData := b.Get(blockHash) if blockData == nil { return errors.New("Block is not found.") } block = *DeserializeBlock(blockData) return nil }) if err != nil { return block, err } return block, nil } // GetBlockHashes returns a list of hashes of all the blocks in the chain func (bc *Blockchain) GetBlockHashes() [][]byte { var blocks [][]byte 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 { if bc.VerifyTransaction(tx) != true { log.Panic("ERROR: Invalid transaction") } } err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) blockData := b.Get(lastHash) block := DeserializeBlock(blockData) lastHeight = block.Height return nil }) if err != nil { log.Panic(err) } newBlock := NewBlock(transactions, lastHash, lastHeight+1) err = bc.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { log.Panic(err) } err = b.Put([]byte("l"), newBlock.Hash) if err != nil { log.Panic(err) } bc.tip = newBlock.Hash return nil }) if err != nil { log.Panic(err) } return newBlock } // SignTransaction signs inputs of a Transaction func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) if err != nil { log.Panic(err) } prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } tx.Sign(privKey, prevTXs) } // VerifyTransaction verifies transaction input signatures func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { if tx.IsCoinbase() { return true } prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) if err != nil { log.Panic(err) } prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } return tx.Verify(prevTXs) } func dbExists(dbFile string) bool { if _, err := os.Stat(dbFile); os.IsNotExist(err) { return false } return true }