aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Foiani <sotech117@michaels-mbp.devices.brown.edu>2022-02-28 15:33:25 -0500
committerMichael Foiani <sotech117@michaels-mbp.devices.brown.edu>2022-02-28 15:33:25 -0500
commitf095ce07e577d6b90e8c6f9090be56eb46af8487 (patch)
tree14533c52a8fc97cbb711f3930dd60bddf6d3471c
parent1dd0508d5d3c737f1ee9c723f580baf73b1cfd70 (diff)
Solution code inputted.
-rw-r--r--.idea/.gitignore8
-rw-r--r--.idea/1-chain-shady99.iml9
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/vcs.xml6
-rw-r--r--pkg/blockchain/blockchain.go66
-rw-r--r--pkg/blockchain/blockinfodatabase/blockinfodatabase.go49
-rw-r--r--pkg/blockchain/chainwriter/chainwriter.go60
-rw-r--r--pkg/blockchain/coindatabase/coindatabase.go154
8 files changed, 344 insertions, 16 deletions
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/1-chain-shady99.iml b/.idea/1-chain-shady99.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/1-chain-shady99.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+ <component name="Go" enabled="true" />
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module> \ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..070ffcb
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/1-chain-shady99.iml" filepath="$PROJECT_DIR$/.idea/1-chain-shady99.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/pkg/blockchain/blockchain.go b/pkg/blockchain/blockchain.go
index c456be1..7b92b1a 100644
--- a/pkg/blockchain/blockchain.go
+++ b/pkg/blockchain/blockchain.go
@@ -6,6 +6,7 @@ import (
"Chain/pkg/blockchain/chainwriter"
"Chain/pkg/blockchain/coindatabase"
"Chain/pkg/utils"
+ "math"
)
// BlockChain is the main type of this project.
@@ -86,7 +87,70 @@ func GenesisBlock(config *Config) *block.Block {
// (4) Handles a fork, if necessary.
// (5) Updates the BlockChain's fields.
func (bc *BlockChain) HandleBlock(b *block.Block) {
- //TODO
+ appends := bc.appendsToActiveChain(b)
+ blockHash := b.Hash()
+
+ // 1. Validate Block
+ if appends && !bc.CoinDB.ValidateBlock(b.Transactions) {
+ return
+ }
+
+ // 2. Make Undo Block
+ ub := bc.makeUndoBlock(b.Transactions)
+
+ // 3. Store Block in CoinDatabase
+ bc.CoinDB.StoreBlock(b.Transactions, appends)
+
+ // 4. Get BlockRecord for previous Block
+ previousBr := bc.BlockInfoDB.GetBlockRecord(b.Header.PreviousHash)
+
+ // 5. Store UndoBlock and Block to Disk
+ height := previousBr.Height + 1
+ br := bc.ChainWriter.StoreBlock(b, ub, height)
+
+ // 6. Store BlockRecord to BlockInfoDatabase
+ bc.BlockInfoDB.StoreBlockRecord(blockHash, br)
+
+ if appends {
+ // 7. Handle appending Block
+ bc.Length++
+ bc.LastBlock = b
+ bc.LastHash = blockHash
+ if len(bc.UnsafeHashes) >= 6 {
+ bc.UnsafeHashes = bc.UnsafeHashes[1:]
+ }
+ bc.UnsafeHashes = append(bc.UnsafeHashes, blockHash)
+ } else if height > bc.Length {
+ // 8. Handle fork
+ bc.handleFork(b, blockHash, height)
+ }
+}
+
+// handleFork updates the BlockChain when a fork occurs. First, it
+// finds the Blocks the BlockChain must revert. Once found, it uses
+// those Blocks to update the CoinDatabase. Lastly, it updates the
+// BlockChain's fields to reflect the fork.
+func (bc *BlockChain) handleFork(b *block.Block, blockHash string, height uint32) {
+ bc.Length = height
+ forkedBlocks := bc.getForkedBlocks(blockHash)
+ var forkedUnsafeHashes []string
+ min := int(math.Min(float64(6), float64(len(forkedBlocks))))
+ for i := 0; i < min; i++ {
+ forkedHash := forkedBlocks[i].Hash()
+ forkedUnsafeHashes = append(forkedUnsafeHashes, forkedHash)
+ }
+ // likely have to add unsafe hashes
+ bc.UnsafeHashes = forkedUnsafeHashes
+ blocks, undoBlocks := bc.getBlocksAndUndoBlocks(len(forkedBlocks) - 1)
+ bc.CoinDB.UndoCoins(blocks, undoBlocks)
+ for _, bl := range forkedBlocks {
+ if !bc.CoinDB.ValidateBlock(bl.Transactions) {
+ utils.Debug.Printf("Validation failed for forked block {%v}", b.Hash())
+ }
+ bc.CoinDB.StoreBlock(bl.Transactions, true)
+ }
+ bc.LastBlock = b
+ bc.LastHash = b.Hash()
}
// makeUndoBlock returns an UndoBlock given a slice of Transaction.
diff --git a/pkg/blockchain/blockinfodatabase/blockinfodatabase.go b/pkg/blockchain/blockinfodatabase/blockinfodatabase.go
index c49a625..e219da4 100644
--- a/pkg/blockchain/blockinfodatabase/blockinfodatabase.go
+++ b/pkg/blockchain/blockinfodatabase/blockinfodatabase.go
@@ -1,8 +1,10 @@
package blockinfodatabase
import (
+ "Chain/pkg/pro"
"Chain/pkg/utils"
"github.com/syndtr/goleveldb/leveldb"
+ "google.golang.org/protobuf/proto"
)
// BlockInfoDatabase is a wrapper for a levelDB
@@ -20,13 +22,54 @@ func New(config *Config) *BlockInfoDatabase {
}
// StoreBlockRecord stores a BlockRecord in the BlockInfoDatabase.
+// hash is the hash of the block, and the key for the blockRecord.
+// blockRecord is the value we're storing in the database
+//
+// At a high level, here's what this function is doing:
+// (1) converting a blockRecord to a protobuf version (for more effective storage)
+// (2) converting the protobuf to bytes
+// (3) storing the byte version of the blockRecord in our database
func (blockInfoDB *BlockInfoDatabase) StoreBlockRecord(hash string, blockRecord *BlockRecord) {
- //TODO
+ // convert the blockRecord to a proto version
+ protoRecord := EncodeBlockRecord(blockRecord)
+ // marshaling (serializing) the proto record to bytes
+ bytes, err := proto.Marshal(protoRecord)
+ // checking that the marshalling process didn't throw an error
+ if err != nil {
+ utils.Debug.Printf("Failed to marshal protoRecord:", err)
+ }
+ // attempting to store the bytes in our database AND checking to make
+ // sure that the storing process doesn't fail. The Put(key, value, writeOptions)
+ // function is levelDB's.
+ if err = blockInfoDB.db.Put([]byte(hash), bytes, nil); err != nil {
+ utils.Debug.Printf("Unable to store block protoRecord for hash {%v}", hash)
+ }
}
// GetBlockRecord returns a BlockRecord from the BlockInfoDatabase given
// the relevant block's hash.
+// hash is the hash of the block, and the key for the blockRecord.
+//
+// At a high level, here's what this function is doing:
+// (1) retrieving the byte version of the protobuf record.
+// (2) converting the bytes to protobuf
+// (3) converting the protobuf to blockRecord and returning that.
func (blockInfoDB *BlockInfoDatabase) GetBlockRecord(hash string) *BlockRecord {
- //TODO
- return nil
+ // attempting to retrieve the byte-version of the protobuf record
+ // from our database AND checking that the value is retrieved successfully.
+ // The Get(key, writeOptions) function is levelDB's.
+ data, err := blockInfoDB.db.Get([]byte(hash), nil)
+ if err != nil {
+ utils.Debug.Printf("Unable to get block record for hash {%v}", hash)
+ }
+ // creating a protobuf blockRecord object to fill
+ protoRecord := &pro.BlockRecord{}
+ // unmarshalling (deserializing) the bytes stored in the database into the
+ // protobuf object created on line 66. Checking that the conversion process
+ // from bytes to protobuf object succeeds.
+ if err = proto.Unmarshal(data, protoRecord); err != nil {
+ utils.Debug.Printf("Failed to unmarshal record from hash {%v}:", hash, err)
+ }
+ // convert the protobuf record to a normal blockRecord and returning that.
+ return DecodeBlockRecord(protoRecord)
}
diff --git a/pkg/blockchain/chainwriter/chainwriter.go b/pkg/blockchain/chainwriter/chainwriter.go
index 67a7d49..e1dd512 100644
--- a/pkg/blockchain/chainwriter/chainwriter.go
+++ b/pkg/blockchain/chainwriter/chainwriter.go
@@ -8,6 +8,7 @@ import (
"google.golang.org/protobuf/proto"
"log"
"os"
+ "strconv"
)
// ChainWriter handles all I/O for the BlockChain. It stores and retrieves
@@ -95,15 +96,66 @@ func (cw *ChainWriter) StoreBlock(bl *block.Block, undoBlock *UndoBlock, height
// WriteBlock writes a serialized Block to Disk and returns
// a FileInfo for storage information.
func (cw *ChainWriter) WriteBlock(serializedBlock []byte) *FileInfo {
- //TODO
- return nil
+ // need to know the length of the block
+ length := uint32(len(serializedBlock))
+ // if we don't have enough space for this block in the current file,
+ // we have to update our file by changing the current file number
+ // and resetting the start offset to zero (so we write at the beginning
+ // of the file again.
+ // (recall format from above: "data/block_0.txt")
+ if cw.CurrentBlockOffset+length >= cw.MaxBlockFileSize {
+ cw.CurrentBlockOffset = 0
+ cw.CurrentBlockFileNumber++
+ }
+ // create path to correct file, following format
+ // "DataDirectory/BlockFileName_CurrentBlockFileNumber.FileExtension"
+ // Ex: "data/block_0.txt"
+ fileName := cw.DataDirectory + "/" + cw.BlockFileName + "_" + strconv.Itoa(int(cw.CurrentBlockFileNumber)) + cw.FileExtension
+ // write serialized block to disk
+ writeToDisk(fileName, serializedBlock)
+ // create a file info object with the starting and ending offsets of the serialized block
+ fi := &FileInfo{
+ FileName: fileName,
+ StartOffset: cw.CurrentBlockOffset,
+ EndOffset: cw.CurrentBlockOffset + length,
+ }
+ // update offset for next write
+ cw.CurrentBlockOffset += length
+ // return the file info
+ return fi
}
// WriteUndoBlock writes a serialized UndoBlock to Disk and returns
// a FileInfo for storage information.
func (cw *ChainWriter) WriteUndoBlock(serializedUndoBlock []byte) *FileInfo {
- //TODO
- return nil
+ // need to know the length of the block
+ length := uint32(len(serializedUndoBlock))
+ // if we don't have enough space for this undo block in the current undo file,
+ // we have to update our undo file by changing the current undo file number
+ // and resetting the start undo offset to zero (so we write at the beginning
+ // of the undo file again.
+ // (recall format from above: "data/undo_0.txt")
+ if cw.CurrentUndoOffset+length >= cw.MaxUndoFileSize {
+ cw.CurrentUndoOffset = 0
+ cw.CurrentUndoFileNumber++
+ }
+ // create path to correct file, following format
+ // "DataDirectory/BlockFileName_CurrentBlockFileNumber.FileExtension"
+ // Ex: "data/undo_0.txt"
+ fileName := cw.DataDirectory + "/" + cw.UndoFileName + "_" + strconv.Itoa(int(cw.CurrentUndoFileNumber)) + cw.FileExtension
+ // write serialized undo block to disk
+ writeToDisk(fileName, serializedUndoBlock)
+ // create a file info object with the starting and ending undo offsets of the serialized
+ // undo block
+ fi := &FileInfo{
+ FileName: fileName,
+ StartOffset: cw.CurrentUndoOffset,
+ EndOffset: cw.CurrentUndoOffset + length,
+ }
+ // update offset for next write
+ cw.CurrentUndoOffset += length
+ // return the file info
+ return fi
}
// ReadBlock returns a Block given a FileInfo.
diff --git a/pkg/blockchain/coindatabase/coindatabase.go b/pkg/blockchain/coindatabase/coindatabase.go
index 83b3026..f61e178 100644
--- a/pkg/blockchain/coindatabase/coindatabase.go
+++ b/pkg/blockchain/coindatabase/coindatabase.go
@@ -80,15 +80,69 @@ func (coinDB *CoinDatabase) validateTransaction(transaction *block.Transaction)
// (1) erases the Coins created by a Block and
// (2) marks the Coins used to create those Transactions as unspent.
func (coinDB *CoinDatabase) UndoCoins(blocks []*block.Block, undoBlocks []*chainwriter.UndoBlock) {
- //TODO
+ // loop through all the block/undoBlock pairings || len(blocks) = len(undoBlocks)
+ for i := 0; i < len(blocks); i++ {
+ // (1) deal with Blocks
+ for _, tx := range blocks[i].Transactions {
+ txHash := tx.Hash()
+ for j := 0; j < len(tx.Outputs); j++ {
+ // create the coin locator
+ cl := CoinLocator{
+ ReferenceTransactionHash: txHash,
+ OutputIndex: uint32(j),
+ }
+ // Remove the coin from the main cache (if it exists there) and update size
+ if _, ok := coinDB.mainCache[cl]; ok {
+ delete(coinDB.mainCache, cl)
+ coinDB.mainCacheSize--
+ }
+ }
+ // delete the coin record from the db (erases all the coins in the process)
+ if err := coinDB.db.Delete([]byte(txHash), nil); err != nil {
+ utils.Debug.Printf("Error deleting coin record for transaction {%v}", txHash)
+ }
+ }
+ // (2) deal with UndoBlocks
+ for j := 0; j < len(undoBlocks[i].TransactionInputHashes); j++ {
+ txHash := undoBlocks[i].TransactionInputHashes[j]
+ // retrieve coin record from db
+ cr := coinDB.getCoinRecordFromDB(txHash)
+ //
+ if cr != nil {
+ // Add coins to record. This is the reestablishing part.
+ cr = coinDB.addCoinToRecord(cr, undoBlocks[i], j)
+ cl := CoinLocator{
+ ReferenceTransactionHash: txHash,
+ OutputIndex: undoBlocks[i].OutputIndexes[j],
+ }
+ // Mark the coin in the main cache as unspent
+ if coin, ok := coinDB.mainCache[cl]; ok {
+ coin.IsSpent = false
+ coinDB.mainCache[cl] = coin
+ }
+ } else {
+ // if there was no coin record to get from the db, we
+ // need to make a new one with all the coins from
+ // the undoBlock
+ cr = &CoinRecord{
+ Version: 0,
+ OutputIndexes: undoBlocks[i].OutputIndexes,
+ Amounts: undoBlocks[i].Amounts,
+ LockingScripts: undoBlocks[i].LockingScripts,
+ }
+ }
+ // put the updated record back in the db.
+ coinDB.putRecordInDB(txHash, cr)
+ }
+ }
}
-// addCoinsToRecord adds coins to a CoinRecord given an UndoBlock and
-// returns the updated CoinRecord.
-func (coinDB *CoinDatabase) addCoinsToRecord(cr *CoinRecord, ub *chainwriter.UndoBlock) *CoinRecord {
- cr.OutputIndexes = append(cr.OutputIndexes, ub.OutputIndexes...)
- cr.Amounts = append(cr.Amounts, ub.Amounts...)
- cr.LockingScripts = append(cr.LockingScripts, ub.LockingScripts...)
+// addCoinToRecord adds a Coin to a CoinRecord given an UndoBlock and index,
+// returning the updated CoinRecord.
+func (coinDB *CoinDatabase) addCoinToRecord(cr *CoinRecord, ub *chainwriter.UndoBlock, index int) *CoinRecord {
+ cr.OutputIndexes = append(cr.OutputIndexes, ub.OutputIndexes[index])
+ cr.Amounts = append(cr.Amounts, ub.Amounts[index])
+ cr.LockingScripts = append(cr.LockingScripts, ub.LockingScripts[index])
return cr
}
@@ -143,7 +197,91 @@ func (coinDB *CoinDatabase) FlushMainCache() {
// (2) stores new TransactionOutputs as Coins in the mainCache (if active)
// (3) stores CoinRecords for the Transactions in the db.
func (coinDB *CoinDatabase) StoreBlock(transactions []*block.Transaction, active bool) {
- //TODO
+ // do (1) and (2) only if active
+ if active {
+ coinDB.updateSpentCoins(transactions)
+ coinDB.storeTransactionsInMainCache(transactions)
+ }
+ // always do (3)
+ coinDB.storeTransactionsInDB(transactions)
+}
+
+// storeTransactionsInDB generates CoinRecords from a slice of Transactions and
+// stores them in the CoinDatabase's db.
+//
+// At a high level, this function:
+// (1) creates coin records for the block's transactions
+// (2) stores those coin records in the db
+//
+// Note: NOT included in the stencil.
+func (coinDB *CoinDatabase) storeTransactionsInDB(transactions []*block.Transaction) {
+ for _, tx := range transactions {
+ cr := coinDB.createCoinRecord(tx)
+ txHash := tx.Hash()
+ coinDB.putRecordInDB(txHash, cr)
+ }
+}
+
+// storeTransactionsInMainCache generates Coins from a slice of Transactions
+// and stores them in the CoinDatabase's mainCache. It flushes the mainCache
+// if it reaches mainCacheCapacity.
+//
+// At a high level, this function:
+// (1) loops through the newly created transaction outputs from the Block's
+// transactions.
+// (2) flushes our cache if we reach capacity
+// (3) creates a coin (value) and coin locator (key) for each output,
+// adding them to the main cache.
+//
+// Note: NOT included in the stencil.
+func (coinDB *CoinDatabase) storeTransactionsInMainCache(transactions []*block.Transaction) {
+ for _, tx := range transactions {
+ // get hash now, which we will use in creating coin locators
+ // for each output later
+ txHash := tx.Hash()
+ for i, txo := range tx.Outputs {
+ // check whether we're approaching our capacity and flush if we are
+ if coinDB.mainCacheSize+uint32(len(tx.Outputs)) >= coinDB.mainCacheCapacity {
+ coinDB.FlushMainCache()
+ }
+ // actually create the coin
+ coin := &Coin{
+ TransactionOutput: txo,
+ IsSpent: false,
+ }
+ // create the coin locator, which is they key to the coin
+ cl := CoinLocator{
+ ReferenceTransactionHash: txHash,
+ OutputIndex: uint32(i),
+ }
+ // add the coin to main cach and increment the size of the main cache.
+ coinDB.mainCache[cl] = coin
+ coinDB.mainCacheSize++
+ }
+ }
+}
+
+func (coinDB *CoinDatabase) updateSpentCoins(transactions []*block.Transaction) {
+ // loop through all the transactions from the block,
+ // marking the coins used to create the inputs as spent.
+ for _, tx := range transactions {
+ for _, txi := range tx.Inputs {
+ // get the coin locator for the input
+ cl := makeCoinLocator(txi)
+ // mark coins in the main cache as spent
+ if coin, ok := coinDB.mainCache[cl]; ok {
+ coin.IsSpent = true
+ coinDB.mainCache[cl] = coin
+ } else {
+ // if the coin is not in the cache,
+ // we have to remove the coin from the
+ // database.
+ txHash := tx.Hash()
+ // remove the spent coin from the db
+ coinDB.removeCoinFromDB(txHash, cl)
+ }
+ }
+ }
}
// removeCoinFromDB removes a Coin from a CoinRecord, deleting the CoinRecord