package main import ( "bytes" "crypto/ecdsa" "encoding/hex" "errors" "fmt" "os" "github.com/boltdb/bolt" ) const ( dbFile = "blockchain.db" blocksBucket = "blocks" genesisCoinbaseData = "Arbitrary Genesis Data" ) type Blockchain struct { tip []byte db *bolt.DB } // Return whether the database file already exists func dbExists() bool { if _, err := os.Stat(dbFile); os.IsNotExist(err) { return false } return true } // NewBlockchain creates a new Blockchain object from the DB func NewBlockchain() *Blockchain { if dbExists() == false { fmt.Println("No existing blockchain found. Create one first.") os.Exit(1) } var tip []byte db, err := bolt.Open(dbFile, 0600, nil) if err != nil { exitWithError(err) } err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) tip = b.Get([]byte("1")) return nil }) if err != nil { exitWithError(err) } bc := Blockchain{ tip: tip, db: db, } return &bc } // CreateBlockchain creates the blockchain DB and generate a new genesis block func CreateBlockchain(address string) *Blockchain { if dbExists() { fmt.Println("Blockchain already exists.") os.Exit(1) } var tip []byte db, err := bolt.Open(dbFile, 0600, nil) if err != nil { exitWithError(err) } err = db.Update(func(tx *bolt.Tx) error { cbtx := NewCoinbaseTX(address, genesisCoinbaseData) genesis := NewGenesisBlock(cbtx) b, err := tx.CreateBucket([]byte(blocksBucket)) if err != nil { exitWithError(err) } err = b.Put(genesis.Hash, genesis.Serialize()) if err != nil { exitWithError(err) } err = b.Put([]byte("1"), genesis.Hash) if err != nil { exitWithError(err) } tip = genesis.Hash return nil }) if err != nil { exitWithError(err) } bc := Blockchain{ tip: tip, db: db, } return &bc } // NewGenesisBlock creates a new Genesis Block to start the blockchain func NewGenesisBlock(coinbase *Transaction) *Block { return NewBlock([]*Transaction{coinbase}, []byte{}) } // FindUnspentTransactions returns a list of transactions containing unspent outputs func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { var unspentTXs []Transaction spentTXOs := make(map[string][]int) bci := bc.Iterator() for { block := bci.Next() for _, tx := range block.Transactions { txID := hex.EncodeToString(tx.ID) Outputs: for outIdx, out := range tx.Vout { // Was the output spent? if spentTXOs[txID] != nil { for _, spentOut := range spentTXOs[txID] { if spentOut == outIdx { continue Outputs } } } if out.IsLockedWithKey(pubKeyHash) { unspentTXs = append(unspentTXs, *tx) } } if tx.IsCoinbase() == false { for _, in := range tx.Vin { if in.UsesKey(pubKeyHash) { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } } } } if len(block.PrevBlockHash) == 0 { break } } return unspentTXs } // FindUTXO finds and returns all unspent transaction outputs func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { var UTXOs []TXOutput unspentTransactions := bc.FindUnspentTransactions(pubKeyHash) for _, tx := range unspentTransactions { for _, out := range tx.Vout { if out.IsLockedWithKey(pubKeyHash) { UTXOs = append(UTXOs, out) } } } return UTXOs } // FindSpendableOutputs finds and returns unspent outputs to reference in inputs func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) { unspentOutputs := make(map[string][]int) unspentTXs := bc.FindUnspentTransactions(pubKeyHash) accumulated := 0 Work: for _, tx := range unspentTXs { txID := hex.EncodeToString(tx.ID) for outIdx, out := range tx.Vout { if out.IsLockedWithKey(pubKeyHash) && accumulated < amount { accumulated += out.Value unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) if accumulated >= amount { break Work } } } } return accumulated, unspentOutputs } // MineBlock mines a new block with the provided transactions func (bc *Blockchain) MineBlock(transactions []*Transaction) { var lastHash []byte for _, tx := range transactions { if !bc.VerifyTransaction(tx) { exitWithError(errors.New("Invalid Transaction")) } } err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("1")) return nil }) if err != nil { exitWithError(err) } newBlock := NewBlock(transactions, lastHash) err = bc.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { exitWithError(err) } err = b.Put([]byte("1"), newBlock.Hash) if err != nil { exitWithError(err) } bc.tip = newBlock.Hash return nil }) if err != nil { exitWithError(err) } } 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 not found") } 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 { exitWithError(err) } prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } tx.Sign(privKey, prevTXs) } func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) if err != nil { exitWithError(err) } prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } return tx.Verify(prevTXs) } // Iterating the Blockchain type BlockchainIterator struct { currentHash []byte db *bolt.DB } // Iterator returns a blockchain iterator func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} return bci } // next returns the next block (starting from the tip) func (bci *BlockchainIterator) Next() *Block { var block *Block err := bci.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) encodedBlock := b.Get(bci.currentHash) block = DeserializeBlock(encodedBlock) return nil }) if err != nil { exitWithError(err) } bci.currentHash = block.PrevBlockHash return block }